@mento-protocol/mento-sdk 3.2.6 → 3.2.7-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -83,6 +83,67 @@ export function splitAmount(amountIn, splitRatio) {
83
83
  const amountB = amountIn - amountA;
84
84
  return { amountA, amountB };
85
85
  }
86
+ /**
87
+ * Off-chain mirror of Router.sol's quoteAddLiquidity / _quoteZapLiquidity logic.
88
+ *
89
+ * Given desired amounts and pool reserves, returns the (amountA, amountB) the
90
+ * router will actually deposit when adding liquidity. Used to predict the exact
91
+ * `amountAMin` / `amountBMin` the contract will check against post-swap reserves.
92
+ *
93
+ * @param amountADesired - Desired amount of tokenA
94
+ * @param amountBDesired - Desired amount of tokenB
95
+ * @param reserveA - Reserve of tokenA at the moment liquidity is added
96
+ * @param reserveB - Reserve of tokenB at the moment liquidity is added
97
+ */
98
+ export function quoteAddLiquidityFromReserves(amountADesired, amountBDesired, reserveA, reserveB) {
99
+ if (reserveA === 0n && reserveB === 0n) {
100
+ return { amountA: amountADesired, amountB: amountBDesired };
101
+ }
102
+ if (reserveA === 0n || reserveB === 0n) {
103
+ // Mirrors Router.sol's InsufficientLiquidity revert. Caller can fall back.
104
+ return { amountA: 0n, amountB: 0n };
105
+ }
106
+ const amountBOptimal = (amountADesired * reserveB) / reserveA;
107
+ if (amountBOptimal <= amountBDesired) {
108
+ return { amountA: amountADesired, amountB: amountBOptimal };
109
+ }
110
+ const amountAOptimal = (amountBDesired * reserveA) / reserveB;
111
+ return { amountA: amountAOptimal, amountB: amountBDesired };
112
+ }
113
+ /**
114
+ * Computes the net delta a single-hop zap swap applies to a target pool's
115
+ * reserves. Returns `{delta0, delta1}` to add to the pool's pre-swap reserves
116
+ * to obtain the reserves the router will see when it runs `_quoteZapLiquidity`.
117
+ *
118
+ * Only single-hop routes whose factory matches the target pool's factory and
119
+ * whose `(from, to)` are `(token0, token1)` (in either direction) are
120
+ * considered. Multi-hop routes and routes through other pools have no effect
121
+ * on the target pool's reserves and return `{0, 0}`.
122
+ *
123
+ * Note: Multi-hop routes that traverse the target pool as an intermediate hop
124
+ * are intentionally not handled here — they are uncommon for single-sided zaps
125
+ * and would require per-hop amounts from `getAmountsOut`.
126
+ */
127
+ export function computeTargetPoolImpact(routes, amountIn, amountOut, token0, token1, factoryAddr) {
128
+ if (routes.length !== 1) {
129
+ return { delta0: 0n, delta1: 0n };
130
+ }
131
+ const route = routes[0];
132
+ if (route.factory.toLowerCase() !== factoryAddr.toLowerCase()) {
133
+ return { delta0: 0n, delta1: 0n };
134
+ }
135
+ const fromLower = route.from.toLowerCase();
136
+ const toLower = route.to.toLowerCase();
137
+ const t0 = token0.toLowerCase();
138
+ const t1 = token1.toLowerCase();
139
+ if (fromLower === t0 && toLower === t1) {
140
+ return { delta0: amountIn, delta1: -amountOut };
141
+ }
142
+ if (fromLower === t1 && toLower === t0) {
143
+ return { delta0: -amountOut, delta1: amountIn };
144
+ }
145
+ return { delta0: 0n, delta1: 0n };
146
+ }
86
147
  /**
87
148
  * Estimates minimum LP tokens from zap in amounts.
88
149
  *
@@ -2,7 +2,7 @@ import { ROUTER_ABI } from '../../core/abis';
2
2
  import { getContractAddress } from '../../core/constants';
3
3
  import { validateAddress } from '../../utils/validation';
4
4
  import { buildApprovalParams, getAllowance, calculateMinAmount, getPoolInfo, getPoolSnapshot } from './liquidityHelpers';
5
- import { encodeZapInCall, findZapInRoutes, splitAmount, estimateLiquidityFromZapIn, } from './zapHelpers';
5
+ import { encodeZapInCall, findZapInRoutes, splitAmount, estimateLiquidityFromZapIn, quoteAddLiquidityFromReserves, computeTargetPoolImpact, } from './zapHelpers';
6
6
  // ========== ZAP IN OPERATIONS ==========
7
7
  /**
8
8
  * Builds a complete zap in transaction including approval if needed
@@ -66,8 +66,27 @@ async function prepareZapInContextInternal(publicClient, chainId, poolService, r
66
66
  functionName: 'generateZapInParams',
67
67
  args: [token0, token1, factoryAddr, amountInA, amountInB, routesA, routesB],
68
68
  }));
69
- const finalAmountAMin = calculateMinAmount(amountAMin, options.slippageTolerance);
70
- const finalAmountBMin = calculateMinAmount(amountBMin, options.slippageTolerance);
69
+ // Re-quote amountAMin / amountBMin against POST-swap reserves.
70
+ //
71
+ // `generateZapInParams` calls `quoteAddLiquidity` with the pool's *current*
72
+ // reserves, but on-chain `_quoteZapLiquidity` runs *after* the zap's internal
73
+ // swap, which moves the same pool when tokenIn is one of the pool tokens.
74
+ // Applying user slippage on top of a pre-swap minimum makes the contract
75
+ // reject any non-trivial amount because the deterministic price impact of
76
+ // the swap alone exceeds the slippage budget. Predict the post-swap reserves
77
+ // and re-derive the minimums so that user slippage only covers real drift
78
+ // between quote-time and execution.
79
+ const impactA = computeTargetPoolImpact(routesA, amountInA, amountOutMinA, token0, token1, factoryAddr);
80
+ const impactB = computeTargetPoolImpact(routesB, amountInB, amountOutMinB, token0, token1, factoryAddr);
81
+ const projectedReserveA = poolSnapshot.reserve0 + impactA.delta0 + impactB.delta0;
82
+ const projectedReserveB = poolSnapshot.reserve1 + impactA.delta1 + impactB.delta1;
83
+ const useReserveA = projectedReserveA > 0n ? projectedReserveA : poolSnapshot.reserve0;
84
+ const useReserveB = projectedReserveB > 0n ? projectedReserveB : poolSnapshot.reserve1;
85
+ const projected = quoteAddLiquidityFromReserves(amountOutMinA, amountOutMinB, useReserveA, useReserveB);
86
+ const baselineAmountAMin = projected.amountA > 0n ? projected.amountA : amountAMin;
87
+ const baselineAmountBMin = projected.amountB > 0n ? projected.amountB : amountBMin;
88
+ const finalAmountAMin = calculateMinAmount(baselineAmountAMin, options.slippageTolerance);
89
+ const finalAmountBMin = calculateMinAmount(baselineAmountBMin, options.slippageTolerance);
71
90
  const finalAmountOutMinA = calculateMinAmount(amountOutMinA, options.slippageTolerance);
72
91
  const finalAmountOutMinB = calculateMinAmount(amountOutMinB, options.slippageTolerance);
73
92
  const expectedLiquidity = estimateLiquidityFromZapIn(finalAmountOutMinA, finalAmountOutMinB, poolSnapshot.reserve0, poolSnapshot.reserve1, poolSnapshot.totalSupply);
@@ -47,6 +47,40 @@ export declare function splitAmount(amountIn: bigint, splitRatio: number): {
47
47
  amountA: bigint;
48
48
  amountB: bigint;
49
49
  };
50
+ /**
51
+ * Off-chain mirror of Router.sol's quoteAddLiquidity / _quoteZapLiquidity logic.
52
+ *
53
+ * Given desired amounts and pool reserves, returns the (amountA, amountB) the
54
+ * router will actually deposit when adding liquidity. Used to predict the exact
55
+ * `amountAMin` / `amountBMin` the contract will check against post-swap reserves.
56
+ *
57
+ * @param amountADesired - Desired amount of tokenA
58
+ * @param amountBDesired - Desired amount of tokenB
59
+ * @param reserveA - Reserve of tokenA at the moment liquidity is added
60
+ * @param reserveB - Reserve of tokenB at the moment liquidity is added
61
+ */
62
+ export declare function quoteAddLiquidityFromReserves(amountADesired: bigint, amountBDesired: bigint, reserveA: bigint, reserveB: bigint): {
63
+ amountA: bigint;
64
+ amountB: bigint;
65
+ };
66
+ /**
67
+ * Computes the net delta a single-hop zap swap applies to a target pool's
68
+ * reserves. Returns `{delta0, delta1}` to add to the pool's pre-swap reserves
69
+ * to obtain the reserves the router will see when it runs `_quoteZapLiquidity`.
70
+ *
71
+ * Only single-hop routes whose factory matches the target pool's factory and
72
+ * whose `(from, to)` are `(token0, token1)` (in either direction) are
73
+ * considered. Multi-hop routes and routes through other pools have no effect
74
+ * on the target pool's reserves and return `{0, 0}`.
75
+ *
76
+ * Note: Multi-hop routes that traverse the target pool as an intermediate hop
77
+ * are intentionally not handled here — they are uncommon for single-sided zaps
78
+ * and would require per-hop amounts from `getAmountsOut`.
79
+ */
80
+ export declare function computeTargetPoolImpact(routes: RouterRoute[], amountIn: bigint, amountOut: bigint, token0: Address, token1: Address, factoryAddr: Address): {
81
+ delta0: bigint;
82
+ delta1: bigint;
83
+ };
50
84
  /**
51
85
  * Estimates minimum LP tokens from zap in amounts.
52
86
  *
@@ -5,6 +5,8 @@ exports.encodeZapOutCall = encodeZapOutCall;
5
5
  exports.findZapInRoutes = findZapInRoutes;
6
6
  exports.findZapOutRoutes = findZapOutRoutes;
7
7
  exports.splitAmount = splitAmount;
8
+ exports.quoteAddLiquidityFromReserves = quoteAddLiquidityFromReserves;
9
+ exports.computeTargetPoolImpact = computeTargetPoolImpact;
8
10
  exports.estimateLiquidityFromZapIn = estimateLiquidityFromZapIn;
9
11
  const viem_1 = require("viem");
10
12
  const abis_1 = require("../../core/abis");
@@ -91,6 +93,67 @@ function splitAmount(amountIn, splitRatio) {
91
93
  const amountB = amountIn - amountA;
92
94
  return { amountA, amountB };
93
95
  }
96
+ /**
97
+ * Off-chain mirror of Router.sol's quoteAddLiquidity / _quoteZapLiquidity logic.
98
+ *
99
+ * Given desired amounts and pool reserves, returns the (amountA, amountB) the
100
+ * router will actually deposit when adding liquidity. Used to predict the exact
101
+ * `amountAMin` / `amountBMin` the contract will check against post-swap reserves.
102
+ *
103
+ * @param amountADesired - Desired amount of tokenA
104
+ * @param amountBDesired - Desired amount of tokenB
105
+ * @param reserveA - Reserve of tokenA at the moment liquidity is added
106
+ * @param reserveB - Reserve of tokenB at the moment liquidity is added
107
+ */
108
+ function quoteAddLiquidityFromReserves(amountADesired, amountBDesired, reserveA, reserveB) {
109
+ if (reserveA === 0n && reserveB === 0n) {
110
+ return { amountA: amountADesired, amountB: amountBDesired };
111
+ }
112
+ if (reserveA === 0n || reserveB === 0n) {
113
+ // Mirrors Router.sol's InsufficientLiquidity revert. Caller can fall back.
114
+ return { amountA: 0n, amountB: 0n };
115
+ }
116
+ const amountBOptimal = (amountADesired * reserveB) / reserveA;
117
+ if (amountBOptimal <= amountBDesired) {
118
+ return { amountA: amountADesired, amountB: amountBOptimal };
119
+ }
120
+ const amountAOptimal = (amountBDesired * reserveA) / reserveB;
121
+ return { amountA: amountAOptimal, amountB: amountBDesired };
122
+ }
123
+ /**
124
+ * Computes the net delta a single-hop zap swap applies to a target pool's
125
+ * reserves. Returns `{delta0, delta1}` to add to the pool's pre-swap reserves
126
+ * to obtain the reserves the router will see when it runs `_quoteZapLiquidity`.
127
+ *
128
+ * Only single-hop routes whose factory matches the target pool's factory and
129
+ * whose `(from, to)` are `(token0, token1)` (in either direction) are
130
+ * considered. Multi-hop routes and routes through other pools have no effect
131
+ * on the target pool's reserves and return `{0, 0}`.
132
+ *
133
+ * Note: Multi-hop routes that traverse the target pool as an intermediate hop
134
+ * are intentionally not handled here — they are uncommon for single-sided zaps
135
+ * and would require per-hop amounts from `getAmountsOut`.
136
+ */
137
+ function computeTargetPoolImpact(routes, amountIn, amountOut, token0, token1, factoryAddr) {
138
+ if (routes.length !== 1) {
139
+ return { delta0: 0n, delta1: 0n };
140
+ }
141
+ const route = routes[0];
142
+ if (route.factory.toLowerCase() !== factoryAddr.toLowerCase()) {
143
+ return { delta0: 0n, delta1: 0n };
144
+ }
145
+ const fromLower = route.from.toLowerCase();
146
+ const toLower = route.to.toLowerCase();
147
+ const t0 = token0.toLowerCase();
148
+ const t1 = token1.toLowerCase();
149
+ if (fromLower === t0 && toLower === t1) {
150
+ return { delta0: amountIn, delta1: -amountOut };
151
+ }
152
+ if (fromLower === t1 && toLower === t0) {
153
+ return { delta0: -amountOut, delta1: amountIn };
154
+ }
155
+ return { delta0: 0n, delta1: 0n };
156
+ }
94
157
  /**
95
158
  * Estimates minimum LP tokens from zap in amounts.
96
159
  *
@@ -72,8 +72,27 @@ async function prepareZapInContextInternal(publicClient, chainId, poolService, r
72
72
  functionName: 'generateZapInParams',
73
73
  args: [token0, token1, factoryAddr, amountInA, amountInB, routesA, routesB],
74
74
  }));
75
- const finalAmountAMin = (0, liquidityHelpers_1.calculateMinAmount)(amountAMin, options.slippageTolerance);
76
- const finalAmountBMin = (0, liquidityHelpers_1.calculateMinAmount)(amountBMin, options.slippageTolerance);
75
+ // Re-quote amountAMin / amountBMin against POST-swap reserves.
76
+ //
77
+ // `generateZapInParams` calls `quoteAddLiquidity` with the pool's *current*
78
+ // reserves, but on-chain `_quoteZapLiquidity` runs *after* the zap's internal
79
+ // swap, which moves the same pool when tokenIn is one of the pool tokens.
80
+ // Applying user slippage on top of a pre-swap minimum makes the contract
81
+ // reject any non-trivial amount because the deterministic price impact of
82
+ // the swap alone exceeds the slippage budget. Predict the post-swap reserves
83
+ // and re-derive the minimums so that user slippage only covers real drift
84
+ // between quote-time and execution.
85
+ const impactA = (0, zapHelpers_1.computeTargetPoolImpact)(routesA, amountInA, amountOutMinA, token0, token1, factoryAddr);
86
+ const impactB = (0, zapHelpers_1.computeTargetPoolImpact)(routesB, amountInB, amountOutMinB, token0, token1, factoryAddr);
87
+ const projectedReserveA = poolSnapshot.reserve0 + impactA.delta0 + impactB.delta0;
88
+ const projectedReserveB = poolSnapshot.reserve1 + impactA.delta1 + impactB.delta1;
89
+ const useReserveA = projectedReserveA > 0n ? projectedReserveA : poolSnapshot.reserve0;
90
+ const useReserveB = projectedReserveB > 0n ? projectedReserveB : poolSnapshot.reserve1;
91
+ const projected = (0, zapHelpers_1.quoteAddLiquidityFromReserves)(amountOutMinA, amountOutMinB, useReserveA, useReserveB);
92
+ const baselineAmountAMin = projected.amountA > 0n ? projected.amountA : amountAMin;
93
+ const baselineAmountBMin = projected.amountB > 0n ? projected.amountB : amountBMin;
94
+ const finalAmountAMin = (0, liquidityHelpers_1.calculateMinAmount)(baselineAmountAMin, options.slippageTolerance);
95
+ const finalAmountBMin = (0, liquidityHelpers_1.calculateMinAmount)(baselineAmountBMin, options.slippageTolerance);
77
96
  const finalAmountOutMinA = (0, liquidityHelpers_1.calculateMinAmount)(amountOutMinA, options.slippageTolerance);
78
97
  const finalAmountOutMinB = (0, liquidityHelpers_1.calculateMinAmount)(amountOutMinB, options.slippageTolerance);
79
98
  const expectedLiquidity = (0, zapHelpers_1.estimateLiquidityFromZapIn)(finalAmountOutMinA, finalAmountOutMinB, poolSnapshot.reserve0, poolSnapshot.reserve1, poolSnapshot.totalSupply);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mento-protocol/mento-sdk",
3
3
  "description": "Official SDK for interacting with the Mento Protocol",
4
- "version": "3.2.6",
4
+ "version": "3.2.7-beta.0",
5
5
  "license": "MIT",
6
6
  "author": "Mento Labs",
7
7
  "keywords": [
@@ -80,5 +80,11 @@
80
80
  },
81
81
  "dependencies": {
82
82
  "viem": "^2.21.44"
83
+ },
84
+ "pnpm": {
85
+ "onlyBuiltDependencies": [
86
+ "esbuild",
87
+ "unrs-resolver"
88
+ ]
83
89
  }
84
90
  }