@nradko/metric-omm-sdk-v1 0.4.2 → 0.4.3
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.
- package/README.md +96 -12
- package/dist/abis/MetricOmmSimpleRouter.d.ts +44 -0
- package/dist/abis/MetricOmmSimpleRouter.d.ts.map +1 -1
- package/dist/abis/MetricOmmSimpleRouter.js +58 -0
- package/dist/abis/MetricOmmSimpleRouter.js.map +1 -1
- package/dist/abis/MetricOmmSwapQuoter.d.ts +26 -2
- package/dist/abis/MetricOmmSwapQuoter.d.ts.map +1 -1
- package/dist/abis/MetricOmmSwapQuoter.js +34 -2
- package/dist/abis/MetricOmmSwapQuoter.js.map +1 -1
- package/dist/abis/index.d.ts +0 -1
- package/dist/abis/index.d.ts.map +1 -1
- package/dist/abis/index.js +0 -1
- package/dist/abis/index.js.map +1 -1
- package/dist/addresses.d.ts +0 -2
- package/dist/addresses.d.ts.map +1 -1
- package/dist/addresses.js +8 -9
- package/dist/addresses.js.map +1 -1
- package/dist/dataProvider/index.d.ts +1 -1
- package/dist/dataProvider/index.d.ts.map +1 -1
- package/dist/dataProvider/index.js +1 -1
- package/dist/dataProvider/index.js.map +1 -1
- package/dist/dataProvider/read.d.ts +1 -3
- package/dist/dataProvider/read.d.ts.map +1 -1
- package/dist/dataProvider/read.js +1 -3
- package/dist/dataProvider/read.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/pool/liquidity.d.ts +83 -7
- package/dist/pool/liquidity.d.ts.map +1 -1
- package/dist/pool/liquidity.js +63 -3
- package/dist/pool/liquidity.js.map +1 -1
- package/dist/router/fromRoute.d.ts +93 -0
- package/dist/router/fromRoute.d.ts.map +1 -0
- package/dist/router/fromRoute.js +187 -0
- package/dist/router/fromRoute.js.map +1 -0
- package/dist/router/index.d.ts +8 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +8 -1
- package/dist/router/index.js.map +1 -1
- package/dist/router/nativeSwap.d.ts +47 -0
- package/dist/router/nativeSwap.d.ts.map +1 -0
- package/dist/router/nativeSwap.js +89 -0
- package/dist/router/nativeSwap.js.map +1 -0
- package/dist/router/swap.d.ts +82 -1
- package/dist/router/swap.d.ts.map +1 -1
- package/dist/router/swap.js +51 -1
- package/dist/router/swap.js.map +1 -1
- package/dist/router/swapPath.d.ts +46 -1
- package/dist/router/swapPath.d.ts.map +1 -1
- package/dist/router/swapPath.js +34 -0
- package/dist/router/swapPath.js.map +1 -1
- package/dist/router/utils.d.ts +9 -0
- package/dist/router/utils.d.ts.map +1 -0
- package/dist/router/utils.js +27 -0
- package/dist/router/utils.js.map +1 -0
- package/package.json +2 -2
- package/src/abis/MetricOmmSimpleRouter.ts +58 -0
- package/src/abis/MetricOmmSwapQuoter.ts +34 -2
- package/src/abis/index.ts +0 -1
- package/src/addresses.ts +8 -11
- package/src/dataProvider/index.ts +0 -1
- package/src/dataProvider/read.ts +1 -4
- package/src/index.ts +22 -2
- package/src/pool/liquidity.ts +83 -7
- package/src/router/fromRoute.ts +318 -0
- package/src/router/index.ts +36 -1
- package/src/router/nativeSwap.ts +123 -0
- package/src/router/swap.ts +82 -1
- package/src/router/swapPath.ts +47 -1
- package/src/router/utils.ts +31 -0
- package/dist/abis/MetricOmmPoolStateView.d.ts +0 -238
- package/dist/abis/MetricOmmPoolStateView.d.ts.map +0 -1
- package/dist/abis/MetricOmmPoolStateView.js +0 -315
- package/dist/abis/MetricOmmPoolStateView.js.map +0 -1
- package/src/abis/MetricOmmPoolStateView.ts +0 -314
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map route API POST /public/v1/evm/:chain_id/route responses to SimpleRouter swap params.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Address, Hex, PublicClient } from "viem";
|
|
6
|
+
import { getAddress } from "viem";
|
|
7
|
+
import { resolveSwapPath } from "../swap/path.js";
|
|
8
|
+
import { ceilDiv, toSlippageBps } from "./swap.js";
|
|
9
|
+
import type { ExactInputParams, ExactOutputParams } from "./swapPath.js";
|
|
10
|
+
import type { ExactInputSingleParams, ExactOutputSingleParams } from "./swap.js";
|
|
11
|
+
|
|
12
|
+
const BPS_BASE = 10_000n;
|
|
13
|
+
|
|
14
|
+
/** Wire tradeType from the route API. */
|
|
15
|
+
export type TradeType = "exact_in" | "exact_out";
|
|
16
|
+
|
|
17
|
+
/** Wire routeType from the route API. */
|
|
18
|
+
export type RouteType = "direct" | "one_hop";
|
|
19
|
+
|
|
20
|
+
/** One hop in a route API response (RouteLegResponse). */
|
|
21
|
+
export type RouteLegResponse = {
|
|
22
|
+
tradeType: TradeType;
|
|
23
|
+
poolAddress: string;
|
|
24
|
+
pair: string;
|
|
25
|
+
zeroForOne: boolean;
|
|
26
|
+
tokenIn: string;
|
|
27
|
+
tokenOut: string;
|
|
28
|
+
amountIn: string;
|
|
29
|
+
amountOut: string;
|
|
30
|
+
effectivePriceX64: string;
|
|
31
|
+
priceImpactE6: string;
|
|
32
|
+
priceLimitX64: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** One execution path in a route API response (RouteOptionResponse). */
|
|
36
|
+
export type RouteOptionResponse = {
|
|
37
|
+
tradeType: TradeType;
|
|
38
|
+
routeType: RouteType;
|
|
39
|
+
amountIn: string;
|
|
40
|
+
amountOut: string;
|
|
41
|
+
effectivePriceX64: string;
|
|
42
|
+
priceImpactE6: string;
|
|
43
|
+
legs: RouteLegResponse[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Route API response (RouteResponse). */
|
|
47
|
+
export type RouteResponse = {
|
|
48
|
+
tokenIn: string;
|
|
49
|
+
tokenOut: string;
|
|
50
|
+
tradeType: TradeType;
|
|
51
|
+
amountIn: string;
|
|
52
|
+
amountOut: string;
|
|
53
|
+
effectivePriceX64: string;
|
|
54
|
+
priceImpactBestRoute: string;
|
|
55
|
+
candidatesConsidered: number;
|
|
56
|
+
routes: RouteOptionResponse[];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type PrepareExactInputFromRouteParams = {
|
|
60
|
+
publicClient: PublicClient;
|
|
61
|
+
route: RouteResponse;
|
|
62
|
+
recipient: Address;
|
|
63
|
+
deadline: bigint;
|
|
64
|
+
/** Human-readable slippage tolerance applied to the selected route's `amountOut`, e.g. `0.5` = 0.5%. */
|
|
65
|
+
slippagePercent: number;
|
|
66
|
+
extensionDatas?: readonly Hex[];
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type PrepareExactOutputFromRouteParams = {
|
|
70
|
+
publicClient: PublicClient;
|
|
71
|
+
route: RouteResponse;
|
|
72
|
+
recipient: Address;
|
|
73
|
+
deadline: bigint;
|
|
74
|
+
/** Human-readable slippage tolerance applied to the selected route's `amountIn`, e.g. `0.5` = 0.5%. */
|
|
75
|
+
slippagePercent: number;
|
|
76
|
+
extensionDatas?: readonly Hex[];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type PreparedExactInputFromRoute =
|
|
80
|
+
| { kind: "single"; params: ExactInputSingleParams }
|
|
81
|
+
| { kind: "multihop"; params: ExactInputParams };
|
|
82
|
+
|
|
83
|
+
export type PreparedExactOutputFromRoute =
|
|
84
|
+
| { kind: "single"; params: ExactOutputSingleParams }
|
|
85
|
+
| { kind: "multihop"; params: ExactOutputParams };
|
|
86
|
+
|
|
87
|
+
function parseAddress(value: string, field: string): Address {
|
|
88
|
+
try {
|
|
89
|
+
return getAddress(value);
|
|
90
|
+
} catch {
|
|
91
|
+
throw new Error(`invalid ${field}: ${value}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseAmount(value: string, field: string): bigint {
|
|
96
|
+
try {
|
|
97
|
+
const amount = BigInt(value);
|
|
98
|
+
if (amount < 0n) {
|
|
99
|
+
throw new Error(`${field} must be non-negative`);
|
|
100
|
+
}
|
|
101
|
+
return amount;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof Error && error.message.includes("must be non-negative")) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`invalid ${field}: ${value}`, { cause: error });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function selectRouteOption(route: RouteResponse): RouteOptionResponse {
|
|
111
|
+
if (route.routes.length === 0) {
|
|
112
|
+
throw new Error("route.routes must not be empty");
|
|
113
|
+
}
|
|
114
|
+
if (route.routes.length > 1) {
|
|
115
|
+
throw new Error("route was split across multiple routes; re-request with maxRoutes=1");
|
|
116
|
+
}
|
|
117
|
+
return route.routes[0]!;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function assertTradeType(
|
|
121
|
+
route: RouteResponse,
|
|
122
|
+
option: RouteOptionResponse,
|
|
123
|
+
tradeType: TradeType,
|
|
124
|
+
): void {
|
|
125
|
+
if (route.tradeType !== tradeType) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`route tradeType must be ${tradeType}; use ${
|
|
128
|
+
tradeType === "exact_in" ? "prepareExactOutputFromRoute" : "prepareExactInputFromRoute"
|
|
129
|
+
} for ${tradeType === "exact_in" ? "exact_out" : "exact_in"}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (option.tradeType !== tradeType) {
|
|
133
|
+
throw new Error(`routes[0].tradeType must be ${tradeType}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function validateRouteLegs(
|
|
138
|
+
legs: RouteLegResponse[],
|
|
139
|
+
tokenIn: Address,
|
|
140
|
+
tokenOut: Address,
|
|
141
|
+
): Address[] {
|
|
142
|
+
if (legs.length === 0) {
|
|
143
|
+
throw new Error("routes[0].legs must not be empty");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const pools: Address[] = [];
|
|
147
|
+
for (let i = 0; i < legs.length; i++) {
|
|
148
|
+
const leg = legs[i]!;
|
|
149
|
+
const legTokenIn = parseAddress(leg.tokenIn, `legs[${i}].tokenIn`);
|
|
150
|
+
const legTokenOut = parseAddress(leg.tokenOut, `legs[${i}].tokenOut`);
|
|
151
|
+
pools.push(parseAddress(leg.poolAddress, `legs[${i}].poolAddress`));
|
|
152
|
+
|
|
153
|
+
if (i === 0 && legTokenIn !== tokenIn) {
|
|
154
|
+
throw new Error(`legs[0].tokenIn (${legTokenIn}) does not match route.tokenIn (${tokenIn})`);
|
|
155
|
+
}
|
|
156
|
+
if (i === legs.length - 1 && legTokenOut !== tokenOut) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`legs[${i}].tokenOut (${legTokenOut}) does not match route.tokenOut (${tokenOut})`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (i > 0) {
|
|
162
|
+
const prevLegTokenOut = parseAddress(legs[i - 1]!.tokenOut, `legs[${i - 1}].tokenOut`);
|
|
163
|
+
if (legTokenIn !== prevLegTokenOut) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`legs[${i}].tokenIn (${legTokenIn}) does not connect to legs[${i - 1}].tokenOut (${prevLegTokenOut})`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return pools;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function amountOutMinimumFromRoute(amountOut: bigint, slippagePercent: number): bigint {
|
|
175
|
+
const slippageBps = toSlippageBps(slippagePercent);
|
|
176
|
+
return (amountOut * (BPS_BASE - slippageBps)) / BPS_BASE;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function amountInMaximumFromRoute(amountIn: bigint, slippagePercent: number): bigint {
|
|
180
|
+
const slippageBps = toSlippageBps(slippagePercent);
|
|
181
|
+
return ceilDiv(amountIn * (BPS_BASE + slippageBps), BPS_BASE);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Convert a route API exact-in response into SimpleRouter exact-input swap params.
|
|
186
|
+
*
|
|
187
|
+
* Resolves swap direction on-chain, applies slippage to the quoted `amountOut`, and
|
|
188
|
+
* returns params ready for `encodeExactInputSingleCalldata` or `encodeExactInputCalldata`.
|
|
189
|
+
*/
|
|
190
|
+
export async function routeToExactInputParams(
|
|
191
|
+
params: PrepareExactInputFromRouteParams,
|
|
192
|
+
): Promise<PreparedExactInputFromRoute> {
|
|
193
|
+
const { route, publicClient, recipient, deadline, slippagePercent, extensionDatas } = params;
|
|
194
|
+
|
|
195
|
+
const tokenIn = parseAddress(route.tokenIn, "tokenIn");
|
|
196
|
+
const tokenOut = parseAddress(route.tokenOut, "tokenOut");
|
|
197
|
+
const option = selectRouteOption(route);
|
|
198
|
+
assertTradeType(route, option, "exact_in");
|
|
199
|
+
|
|
200
|
+
const requestedAmountIn = parseAmount(route.amountIn, "amountIn");
|
|
201
|
+
const amountIn = parseAmount(option.amountIn, "routes[0].amountIn");
|
|
202
|
+
if (amountIn !== requestedAmountIn) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
"routes[0].amountIn does not match request amountIn; partial fill is not supported",
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const quotedAmountOut = parseAmount(option.amountOut, "routes[0].amountOut");
|
|
208
|
+
const amountOutMinimum = amountOutMinimumFromRoute(quotedAmountOut, slippagePercent);
|
|
209
|
+
|
|
210
|
+
const pools = validateRouteLegs(option.legs, tokenIn, tokenOut);
|
|
211
|
+
const {
|
|
212
|
+
tokens,
|
|
213
|
+
zeroForOnes,
|
|
214
|
+
extensionDatas: resolvedExtensionDatas,
|
|
215
|
+
zeroForOneBitMap,
|
|
216
|
+
} = await resolveSwapPath(publicClient, tokenIn, tokenOut, pools, extensionDatas);
|
|
217
|
+
|
|
218
|
+
if (pools.length === 1) {
|
|
219
|
+
const leg = option.legs[0]!;
|
|
220
|
+
return {
|
|
221
|
+
kind: "single",
|
|
222
|
+
params: {
|
|
223
|
+
pool: pools[0]!,
|
|
224
|
+
tokenIn,
|
|
225
|
+
tokenOut,
|
|
226
|
+
zeroForOne: zeroForOnes[0]!,
|
|
227
|
+
amountIn,
|
|
228
|
+
amountOutMinimum,
|
|
229
|
+
recipient,
|
|
230
|
+
deadline,
|
|
231
|
+
priceLimitX64: parseAmount(leg.priceLimitX64, "legs[0].priceLimitX64"),
|
|
232
|
+
extensionData: resolvedExtensionDatas[0],
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
kind: "multihop",
|
|
239
|
+
params: {
|
|
240
|
+
tokens,
|
|
241
|
+
pools,
|
|
242
|
+
extensionDatas: resolvedExtensionDatas,
|
|
243
|
+
zeroForOneBitMap,
|
|
244
|
+
amountIn,
|
|
245
|
+
amountOutMinimum,
|
|
246
|
+
recipient,
|
|
247
|
+
deadline,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Convert a route API exact-out response into SimpleRouter exact-output swap params.
|
|
254
|
+
*
|
|
255
|
+
* Resolves swap direction on-chain, applies slippage to the quoted `amountIn`, and
|
|
256
|
+
* returns params ready for `encodeExactOutputSingleCalldata` or `encodeExactOutputCalldata`.
|
|
257
|
+
*/
|
|
258
|
+
export async function routeToExactOutputParams(
|
|
259
|
+
params: PrepareExactOutputFromRouteParams,
|
|
260
|
+
): Promise<PreparedExactOutputFromRoute> {
|
|
261
|
+
const { route, publicClient, recipient, deadline, slippagePercent, extensionDatas } = params;
|
|
262
|
+
|
|
263
|
+
const tokenIn = parseAddress(route.tokenIn, "tokenIn");
|
|
264
|
+
const tokenOut = parseAddress(route.tokenOut, "tokenOut");
|
|
265
|
+
const option = selectRouteOption(route);
|
|
266
|
+
assertTradeType(route, option, "exact_out");
|
|
267
|
+
|
|
268
|
+
const requestedAmountOut = parseAmount(route.amountOut, "amountOut");
|
|
269
|
+
const amountOut = parseAmount(option.amountOut, "routes[0].amountOut");
|
|
270
|
+
if (amountOut !== requestedAmountOut) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
"routes[0].amountOut does not match request amountOut; partial fill is not supported",
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
const quotedAmountIn = parseAmount(option.amountIn, "routes[0].amountIn");
|
|
276
|
+
const amountInMaximum = amountInMaximumFromRoute(quotedAmountIn, slippagePercent);
|
|
277
|
+
|
|
278
|
+
const pools = validateRouteLegs(option.legs, tokenIn, tokenOut);
|
|
279
|
+
const {
|
|
280
|
+
tokens,
|
|
281
|
+
zeroForOnes,
|
|
282
|
+
extensionDatas: resolvedExtensionDatas,
|
|
283
|
+
zeroForOneBitMap,
|
|
284
|
+
} = await resolveSwapPath(publicClient, tokenIn, tokenOut, pools, extensionDatas);
|
|
285
|
+
|
|
286
|
+
if (pools.length === 1) {
|
|
287
|
+
const leg = option.legs[0]!;
|
|
288
|
+
return {
|
|
289
|
+
kind: "single",
|
|
290
|
+
params: {
|
|
291
|
+
pool: pools[0]!,
|
|
292
|
+
tokenIn,
|
|
293
|
+
tokenOut,
|
|
294
|
+
zeroForOne: zeroForOnes[0]!,
|
|
295
|
+
amountOut,
|
|
296
|
+
amountInMaximum,
|
|
297
|
+
recipient,
|
|
298
|
+
deadline,
|
|
299
|
+
priceLimitX64: parseAmount(leg.priceLimitX64, "legs[0].priceLimitX64"),
|
|
300
|
+
extensionData: resolvedExtensionDatas[0],
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
kind: "multihop",
|
|
307
|
+
params: {
|
|
308
|
+
tokens,
|
|
309
|
+
pools,
|
|
310
|
+
extensionDatas: resolvedExtensionDatas,
|
|
311
|
+
zeroForOneBitMap,
|
|
312
|
+
amountOut,
|
|
313
|
+
amountInMaximum,
|
|
314
|
+
recipient,
|
|
315
|
+
deadline,
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
package/src/router/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* SimpleRouter swap API — quotes, calldata encoding, and native ETH multicall helpers.
|
|
3
|
+
*
|
|
4
|
+
* **ERC-20 swap:** `buildExactInputSingleParams` → `encodeExactInputSingleCalldata` → send to `simpleRouter`.
|
|
5
|
+
|
|
6
|
+
* Approve `tokenIn` on the router for ERC-20 input (not required for ETH input).
|
|
3
7
|
*/
|
|
4
8
|
|
|
5
9
|
export {
|
|
@@ -28,3 +32,34 @@ export {
|
|
|
28
32
|
type BuildExactInputSingleParams,
|
|
29
33
|
type BuildExactOutputSingleParams,
|
|
30
34
|
} from "./swap.js";
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
routeToExactInputParams as prepareExactInputFromRoute,
|
|
38
|
+
routeToExactOutputParams as prepareExactOutputFromRoute,
|
|
39
|
+
type TradeType,
|
|
40
|
+
type RouteType,
|
|
41
|
+
type RouteLegResponse,
|
|
42
|
+
type RouteOptionResponse,
|
|
43
|
+
type RouteResponse,
|
|
44
|
+
type PrepareExactInputFromRouteParams,
|
|
45
|
+
type PrepareExactOutputFromRouteParams,
|
|
46
|
+
type PreparedExactInputFromRoute,
|
|
47
|
+
type PreparedExactOutputFromRoute,
|
|
48
|
+
} from "./fromRoute.js";
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
encodeMulticallCalldata,
|
|
52
|
+
encodeRefundETHCalldata,
|
|
53
|
+
encodeUnwrapWETH9Calldata,
|
|
54
|
+
} from "./utils.js";
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
prepareExactInputSingleAndUnwrapCalldata,
|
|
58
|
+
prepareExactInputAndUnwrapCalldata,
|
|
59
|
+
prepareExactInputSingleSupportingNativePaymentCalldata,
|
|
60
|
+
prepareExactInputSupportingNativePaymentCalldata,
|
|
61
|
+
prepareExactOutputSupportingNativePaymentCalldata,
|
|
62
|
+
prepareExactOutputSingleSupportingNativePaymentCalldata,
|
|
63
|
+
prepareExactOutputSingleAndUnwrapCalldata,
|
|
64
|
+
prepareExactOutputAndUnwrapCalldata,
|
|
65
|
+
} from "./nativeSwap.js";
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Address, Hex } from "viem";
|
|
2
|
+
import type { ExactInputParams, ExactOutputParams } from "./swapPath.js";
|
|
3
|
+
import type { ExactInputSingleParams, ExactOutputSingleParams } from "./swap.js";
|
|
4
|
+
import { encodeExactInputSingleCalldata, encodeExactOutputSingleCalldata } from "./swap.js";
|
|
5
|
+
import { encodeExactInputCalldata, encodeExactOutputCalldata } from "./swapPath.js";
|
|
6
|
+
import {
|
|
7
|
+
encodeMulticallCalldata,
|
|
8
|
+
encodeRefundETHCalldata,
|
|
9
|
+
encodeUnwrapWETH9Calldata,
|
|
10
|
+
} from "./utils.js";
|
|
11
|
+
|
|
12
|
+
function withRouterRecipient<T extends { recipient: Address }>(
|
|
13
|
+
params: T,
|
|
14
|
+
simpleRouter: Address,
|
|
15
|
+
): T {
|
|
16
|
+
return { ...params, recipient: simpleRouter };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Single-hop exact input when `tokenIn` is wrappedNative - `tokenIn` MUST be wrappedNative.
|
|
20
|
+
* Set `msg.value` to a value that you want to pay with native ETH on the transaction.
|
|
21
|
+
* The remaining amount will be paid with wrappedNative (if it is approved, otherwise it will revert).
|
|
22
|
+
* Any unused native ETH will be refunded to the caller.
|
|
23
|
+
*/
|
|
24
|
+
export function prepareExactInputSingleSupportingNativePaymentCalldata(
|
|
25
|
+
params: ExactInputSingleParams,
|
|
26
|
+
): Hex {
|
|
27
|
+
return encodeMulticallCalldata([
|
|
28
|
+
encodeExactInputSingleCalldata(params),
|
|
29
|
+
// Refund is needed because it is possible (vary rare) that the router will not consume whole amountIn while still output the required amountOutMinimum.
|
|
30
|
+
encodeRefundETHCalldata(),
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Multihop exact input when the first token is wrappedNative - `tokenIn` MUST be wrappedNative.
|
|
35
|
+
* Set `msg.value` to a value that you want to pay with native ETH on the transaction.
|
|
36
|
+
* The remaining amount will be paid with wrappedNative (if it is approved, otherwise it will revert).
|
|
37
|
+
* Any unused native ETH will be refunded to the caller.
|
|
38
|
+
*/
|
|
39
|
+
export function prepareExactInputSupportingNativePaymentCalldata(params: ExactInputParams): Hex {
|
|
40
|
+
return encodeMulticallCalldata([
|
|
41
|
+
encodeExactInputCalldata(params),
|
|
42
|
+
// Refund is needed because it is possible (vary rare) that the router will not consume whole amountIn while still output the required amountOutMinimum.
|
|
43
|
+
encodeRefundETHCalldata(),
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Single-hop exact output when `tokenIn` is wrappedNative - `tokenIn` MUST be wrappedNative.
|
|
48
|
+
* Set `msg.value` to a value that you want to pay with native ETH on the transaction.
|
|
49
|
+
* The remaining amount will be paid with wrappedNative (if it is approved, otherwise it will revert)
|
|
50
|
+
* Any unused native ETH will be refunded to the caller.
|
|
51
|
+
* */
|
|
52
|
+
export function prepareExactOutputSingleSupportingNativePaymentCalldata(
|
|
53
|
+
params: ExactOutputSingleParams,
|
|
54
|
+
): Hex {
|
|
55
|
+
return encodeMulticallCalldata([
|
|
56
|
+
encodeExactOutputSingleCalldata(params),
|
|
57
|
+
encodeRefundETHCalldata(),
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Multihop exact output when the first token is wrappedNative - `tokenIn` MUST be wrappedNative.
|
|
62
|
+
* Set `msg.value` to a value that you want to pay with native ETH on the transaction.
|
|
63
|
+
* The remaining amount will be paid with wrappedNative (if it is approved, otherwise it will revert)
|
|
64
|
+
* Any unused native ETH will be refunded to the caller.
|
|
65
|
+
* */
|
|
66
|
+
export function prepareExactOutputSupportingNativePaymentCalldata(params: ExactOutputParams): Hex {
|
|
67
|
+
return encodeMulticallCalldata([encodeExactOutputCalldata(params), encodeRefundETHCalldata()]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Single-hop exact input with ETH output — `tokenOut` MUST be wrappedNative.
|
|
72
|
+
* Unwraps the whole amount of wrappedNative to native ETH and sends it to the recipient.
|
|
73
|
+
*/
|
|
74
|
+
export function prepareExactInputSingleAndUnwrapCalldata(
|
|
75
|
+
params: ExactInputSingleParams,
|
|
76
|
+
simpleRouter: Address,
|
|
77
|
+
): Hex {
|
|
78
|
+
return encodeMulticallCalldata([
|
|
79
|
+
encodeExactInputSingleCalldata(withRouterRecipient(params, simpleRouter)),
|
|
80
|
+
encodeUnwrapWETH9Calldata(params.amountOutMinimum, params.recipient),
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Multihop exact input with ETH output - `tokenOut` MUST be wrappedNative.
|
|
85
|
+
* Unwraps the whole amount of wrappedNative to native ETH and sends it to the recipient.
|
|
86
|
+
* */
|
|
87
|
+
export function prepareExactInputAndUnwrapCalldata(
|
|
88
|
+
params: ExactInputParams,
|
|
89
|
+
simpleRouter: Address,
|
|
90
|
+
): Hex {
|
|
91
|
+
return encodeMulticallCalldata([
|
|
92
|
+
encodeExactInputCalldata(withRouterRecipient(params, simpleRouter)),
|
|
93
|
+
encodeUnwrapWETH9Calldata(params.amountOutMinimum, params.recipient),
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Single-hop exact output with ETH output — `tokenOut` MUST be wrappedNative.
|
|
99
|
+
* Unwraps the whole amount of wrappedNative to native ETH and sends it to the recipient.
|
|
100
|
+
*/
|
|
101
|
+
export function prepareExactOutputSingleAndUnwrapCalldata(
|
|
102
|
+
params: ExactOutputSingleParams,
|
|
103
|
+
simpleRouter: Address,
|
|
104
|
+
): Hex {
|
|
105
|
+
return encodeMulticallCalldata([
|
|
106
|
+
encodeExactOutputSingleCalldata(withRouterRecipient(params, simpleRouter)),
|
|
107
|
+
encodeUnwrapWETH9Calldata(params.amountOut, params.recipient),
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Multihop exact output with ETH output — `tokenOut` MUST be wrappedNative.
|
|
113
|
+
* Unwraps the whole amount of wrappedNative to native ETH and sends it to the recipient.
|
|
114
|
+
*/
|
|
115
|
+
export function prepareExactOutputAndUnwrapCalldata(
|
|
116
|
+
params: ExactOutputParams,
|
|
117
|
+
simpleRouter: Address,
|
|
118
|
+
): Hex {
|
|
119
|
+
return encodeMulticallCalldata([
|
|
120
|
+
encodeExactOutputCalldata(withRouterRecipient(params, simpleRouter)),
|
|
121
|
+
encodeUnwrapWETH9Calldata(params.amountOut, params.recipient),
|
|
122
|
+
]);
|
|
123
|
+
}
|
package/src/router/swap.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* SimpleRouter single-hop swap helpers.
|
|
3
|
+
*
|
|
4
|
+
* Typical ERC-20 flow:
|
|
5
|
+
* 1. `buildExactInputSingleParams` / `buildExactOutputSingleParams` — quote + slippage
|
|
6
|
+
* 2. `encodeExactInputSingleCalldata` — calldata for `walletClient.writeContract` or `sendTransaction`
|
|
7
|
+
* 3. Approve `tokenIn` on the SimpleRouter before sending (not needed for native ETH; see `nativeSwap.ts`)
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
10
|
import type { Address, Hex } from "viem";
|
|
@@ -13,25 +18,44 @@ import type { PublicClient } from "viem";
|
|
|
13
18
|
|
|
14
19
|
const BPS_BASE = 10_000n;
|
|
15
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Arguments for `MetricOmmSimpleRouter.exactInputSingle`.
|
|
23
|
+
* Pass to `encodeExactInputSingleCalldata` or native prepare helpers when `tokenIn` is WETH.
|
|
24
|
+
*/
|
|
16
25
|
export type ExactInputSingleParams = {
|
|
26
|
+
/** Pool to swap through. */
|
|
17
27
|
pool: Address;
|
|
28
|
+
/** Input token (`addresses.wrappedNative` when swapping from native ETH). */
|
|
18
29
|
tokenIn: Address;
|
|
30
|
+
/** Output token (`addresses.wrappedNative` when unwrapping to ETH in a follow-up call). */
|
|
19
31
|
tokenOut: Address;
|
|
32
|
+
/** `true` when swapping token0 → token1 for this pool. */
|
|
20
33
|
zeroForOne: boolean;
|
|
34
|
+
/** Exact input amount in `tokenIn` native decimals. */
|
|
21
35
|
amountIn: bigint;
|
|
36
|
+
/** Minimum acceptable output; reverts if less is received. */
|
|
22
37
|
amountOutMinimum: bigint;
|
|
38
|
+
/** Address that receives output tokens. */
|
|
23
39
|
recipient: Address;
|
|
40
|
+
/** Unix timestamp after which the swap reverts. */
|
|
24
41
|
deadline: bigint;
|
|
42
|
+
/** Q64.64 price bound; use `0` / `MAX_UINT128` defaults from builders when unsure. */
|
|
25
43
|
priceLimitX64: bigint;
|
|
44
|
+
/** Per-pool extension calldata; omit for pools without extensions. */
|
|
26
45
|
extensionData?: Hex;
|
|
27
46
|
};
|
|
28
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Arguments for `MetricOmmSimpleRouter.exactOutputSingle`.
|
|
50
|
+
*/
|
|
29
51
|
export type ExactOutputSingleParams = {
|
|
30
52
|
pool: Address;
|
|
31
53
|
tokenIn: Address;
|
|
32
54
|
tokenOut: Address;
|
|
33
55
|
zeroForOne: boolean;
|
|
56
|
+
/** Exact output amount in `tokenOut` native decimals. */
|
|
34
57
|
amountOut: bigint;
|
|
58
|
+
/** Maximum input willing to spend (slippage cap). */
|
|
35
59
|
amountInMaximum: bigint;
|
|
36
60
|
recipient: Address;
|
|
37
61
|
deadline: bigint;
|
|
@@ -39,20 +63,31 @@ export type ExactOutputSingleParams = {
|
|
|
39
63
|
extensionData?: Hex;
|
|
40
64
|
};
|
|
41
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Inputs for {@link buildExactInputSingleParams}.
|
|
68
|
+
* Quotes live on-chain via the SwapQuoter and applies `slippagePercent` to derive `amountOutMinimum`.
|
|
69
|
+
*/
|
|
42
70
|
export interface BuildExactInputSingleParams {
|
|
43
71
|
publicClient: PublicClient;
|
|
44
72
|
pool: Address;
|
|
45
73
|
tokenIn: Address;
|
|
46
74
|
tokenOut: Address;
|
|
47
75
|
recipient: Address;
|
|
76
|
+
/** Input amount in `tokenIn` decimals (wei for ETH / WETH). */
|
|
48
77
|
amountIn: bigint;
|
|
78
|
+
/** Human-readable slippage tolerance, e.g. `0.5` = 0.5%. */
|
|
49
79
|
slippagePercent: number;
|
|
80
|
+
/** Optional price bound; defaults from quoter direction when omitted. */
|
|
50
81
|
priceLimitX64?: bigint;
|
|
51
82
|
deadline: bigint;
|
|
52
83
|
extensionData?: Hex;
|
|
84
|
+
/** Override when `addresses.quoter` is unset. */
|
|
53
85
|
quoterAddress?: Address;
|
|
86
|
+
/** `addresses.wrappedNative` — required when `tokenIn` or `tokenOut` is native (zero address). */
|
|
87
|
+
wrappedNative?: Address;
|
|
54
88
|
}
|
|
55
89
|
|
|
90
|
+
/** Inputs for {@link buildExactOutputSingleParams}. */
|
|
56
91
|
export interface BuildExactOutputSingleParams {
|
|
57
92
|
publicClient: PublicClient;
|
|
58
93
|
pool: Address;
|
|
@@ -65,8 +100,13 @@ export interface BuildExactOutputSingleParams {
|
|
|
65
100
|
deadline: bigint;
|
|
66
101
|
extensionData?: Hex;
|
|
67
102
|
quoterAddress?: Address;
|
|
103
|
+
wrappedNative?: Address;
|
|
68
104
|
}
|
|
69
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Convert a human slippage percent to basis points (1 bp = 0.01%).
|
|
108
|
+
* @example toSlippageBps(0.5) // 50n
|
|
109
|
+
*/
|
|
70
110
|
export function toSlippageBps(slippagePercent: number): bigint {
|
|
71
111
|
if (!Number.isFinite(slippagePercent) || slippagePercent < 0 || slippagePercent > 100) {
|
|
72
112
|
throw new Error("slippagePercent must be a finite number between 0 and 100");
|
|
@@ -74,6 +114,7 @@ export function toSlippageBps(slippagePercent: number): bigint {
|
|
|
74
114
|
return BigInt(Math.round(slippagePercent * 100));
|
|
75
115
|
}
|
|
76
116
|
|
|
117
|
+
/** Ceiling division for bigint values. Used when rounding slippage up on exact-output swaps. */
|
|
77
118
|
export function ceilDiv(numerator: bigint, denominator: bigint): bigint {
|
|
78
119
|
if (denominator <= 0n) {
|
|
79
120
|
throw new Error("denominator must be > 0");
|
|
@@ -115,6 +156,35 @@ function toExactOutputSingleStruct(params: ExactOutputSingleParams) {
|
|
|
115
156
|
};
|
|
116
157
|
}
|
|
117
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Build single-hop exact-input swap params with live quote and slippage.
|
|
161
|
+
*
|
|
162
|
+
* Resolves `zeroForOne`, quotes `amountOut`, and sets `amountOutMinimum`.
|
|
163
|
+
* For native ETH input, use `prepareExactInputSingleSupportingNativePaymentCalldata` and set `msg.value` on the transaction.
|
|
164
|
+
* (params will contain WETH as `tokenIn`).
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* const params = await buildExactInputSingleParams({
|
|
169
|
+
* publicClient,
|
|
170
|
+
* pool,
|
|
171
|
+
* tokenIn: usdc,
|
|
172
|
+
* tokenOut: weth,
|
|
173
|
+
* recipient: account,
|
|
174
|
+
* amountIn: parseUnits("100", 6),
|
|
175
|
+
* slippagePercent: 0.5,
|
|
176
|
+
* deadline: BigInt(Math.floor(Date.now() / 1000) + 1200),
|
|
177
|
+
* quoterAddress: addresses.quoter,
|
|
178
|
+
* });
|
|
179
|
+
* await walletClient.writeContract({
|
|
180
|
+
* address: addresses.simpleRouter,
|
|
181
|
+
* abi: MetricOmmSimpleRouterAbi,
|
|
182
|
+
* functionName: "exactInputSingle",
|
|
183
|
+
* args: [params],
|
|
184
|
+
* account,
|
|
185
|
+
* });
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
118
188
|
export async function buildExactInputSingleParams(
|
|
119
189
|
params: BuildExactInputSingleParams,
|
|
120
190
|
): Promise<ExactInputSingleParams> {
|
|
@@ -157,6 +227,10 @@ export async function buildExactInputSingleParams(
|
|
|
157
227
|
};
|
|
158
228
|
}
|
|
159
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Build single-hop exact-output swap params with live quote and slippage.
|
|
232
|
+
* Sets `amountInMaximum` from quoted input plus `slippagePercent`.
|
|
233
|
+
*/
|
|
160
234
|
export async function buildExactOutputSingleParams(
|
|
161
235
|
params: BuildExactOutputSingleParams,
|
|
162
236
|
): Promise<ExactOutputSingleParams> {
|
|
@@ -199,6 +273,10 @@ export async function buildExactOutputSingleParams(
|
|
|
199
273
|
};
|
|
200
274
|
}
|
|
201
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Encode `exactInputSingle` calldata for `addresses.simpleRouter`.
|
|
278
|
+
* Approve `tokenIn` on the router before sending (unless using native ETH prepare helpers).
|
|
279
|
+
*/
|
|
202
280
|
export function encodeExactInputSingleCalldata(params: ExactInputSingleParams): Hex {
|
|
203
281
|
return encodeFunctionData({
|
|
204
282
|
abi: SIMPLE_ROUTER_ABI,
|
|
@@ -207,8 +285,10 @@ export function encodeExactInputSingleCalldata(params: ExactInputSingleParams):
|
|
|
207
285
|
});
|
|
208
286
|
}
|
|
209
287
|
|
|
288
|
+
/** Alias for {@link encodeExactInputSingleCalldata}. */
|
|
210
289
|
export const prepareExactInputSingleCalldata = encodeExactInputSingleCalldata;
|
|
211
290
|
|
|
291
|
+
/** Encode `exactOutputSingle` calldata for `addresses.simpleRouter`. */
|
|
212
292
|
export function encodeExactOutputSingleCalldata(params: ExactOutputSingleParams): Hex {
|
|
213
293
|
return encodeFunctionData({
|
|
214
294
|
abi: SIMPLE_ROUTER_ABI,
|
|
@@ -217,4 +297,5 @@ export function encodeExactOutputSingleCalldata(params: ExactOutputSingleParams)
|
|
|
217
297
|
});
|
|
218
298
|
}
|
|
219
299
|
|
|
300
|
+
/** Alias for {@link encodeExactOutputSingleCalldata}. */
|
|
220
301
|
export const prepareExactOutputSingleCalldata = encodeExactOutputSingleCalldata;
|