@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.
Files changed (77) hide show
  1. package/README.md +96 -12
  2. package/dist/abis/MetricOmmSimpleRouter.d.ts +44 -0
  3. package/dist/abis/MetricOmmSimpleRouter.d.ts.map +1 -1
  4. package/dist/abis/MetricOmmSimpleRouter.js +58 -0
  5. package/dist/abis/MetricOmmSimpleRouter.js.map +1 -1
  6. package/dist/abis/MetricOmmSwapQuoter.d.ts +26 -2
  7. package/dist/abis/MetricOmmSwapQuoter.d.ts.map +1 -1
  8. package/dist/abis/MetricOmmSwapQuoter.js +34 -2
  9. package/dist/abis/MetricOmmSwapQuoter.js.map +1 -1
  10. package/dist/abis/index.d.ts +0 -1
  11. package/dist/abis/index.d.ts.map +1 -1
  12. package/dist/abis/index.js +0 -1
  13. package/dist/abis/index.js.map +1 -1
  14. package/dist/addresses.d.ts +0 -2
  15. package/dist/addresses.d.ts.map +1 -1
  16. package/dist/addresses.js +8 -9
  17. package/dist/addresses.js.map +1 -1
  18. package/dist/dataProvider/index.d.ts +1 -1
  19. package/dist/dataProvider/index.d.ts.map +1 -1
  20. package/dist/dataProvider/index.js +1 -1
  21. package/dist/dataProvider/index.js.map +1 -1
  22. package/dist/dataProvider/read.d.ts +1 -3
  23. package/dist/dataProvider/read.d.ts.map +1 -1
  24. package/dist/dataProvider/read.js +1 -3
  25. package/dist/dataProvider/read.js.map +1 -1
  26. package/dist/index.d.ts +3 -3
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +3 -3
  29. package/dist/index.js.map +1 -1
  30. package/dist/pool/liquidity.d.ts +83 -7
  31. package/dist/pool/liquidity.d.ts.map +1 -1
  32. package/dist/pool/liquidity.js +63 -3
  33. package/dist/pool/liquidity.js.map +1 -1
  34. package/dist/router/fromRoute.d.ts +93 -0
  35. package/dist/router/fromRoute.d.ts.map +1 -0
  36. package/dist/router/fromRoute.js +187 -0
  37. package/dist/router/fromRoute.js.map +1 -0
  38. package/dist/router/index.d.ts +8 -1
  39. package/dist/router/index.d.ts.map +1 -1
  40. package/dist/router/index.js +8 -1
  41. package/dist/router/index.js.map +1 -1
  42. package/dist/router/nativeSwap.d.ts +47 -0
  43. package/dist/router/nativeSwap.d.ts.map +1 -0
  44. package/dist/router/nativeSwap.js +89 -0
  45. package/dist/router/nativeSwap.js.map +1 -0
  46. package/dist/router/swap.d.ts +82 -1
  47. package/dist/router/swap.d.ts.map +1 -1
  48. package/dist/router/swap.js +51 -1
  49. package/dist/router/swap.js.map +1 -1
  50. package/dist/router/swapPath.d.ts +46 -1
  51. package/dist/router/swapPath.d.ts.map +1 -1
  52. package/dist/router/swapPath.js +34 -0
  53. package/dist/router/swapPath.js.map +1 -1
  54. package/dist/router/utils.d.ts +9 -0
  55. package/dist/router/utils.d.ts.map +1 -0
  56. package/dist/router/utils.js +27 -0
  57. package/dist/router/utils.js.map +1 -0
  58. package/package.json +2 -2
  59. package/src/abis/MetricOmmSimpleRouter.ts +58 -0
  60. package/src/abis/MetricOmmSwapQuoter.ts +34 -2
  61. package/src/abis/index.ts +0 -1
  62. package/src/addresses.ts +8 -11
  63. package/src/dataProvider/index.ts +0 -1
  64. package/src/dataProvider/read.ts +1 -4
  65. package/src/index.ts +22 -2
  66. package/src/pool/liquidity.ts +83 -7
  67. package/src/router/fromRoute.ts +318 -0
  68. package/src/router/index.ts +36 -1
  69. package/src/router/nativeSwap.ts +123 -0
  70. package/src/router/swap.ts +82 -1
  71. package/src/router/swapPath.ts +47 -1
  72. package/src/router/utils.ts +31 -0
  73. package/dist/abis/MetricOmmPoolStateView.d.ts +0 -238
  74. package/dist/abis/MetricOmmPoolStateView.d.ts.map +0 -1
  75. package/dist/abis/MetricOmmPoolStateView.js +0 -315
  76. package/dist/abis/MetricOmmPoolStateView.js.map +0 -1
  77. 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
+ }
@@ -1,5 +1,9 @@
1
1
  /**
2
- * MetricAMM SDK - Router Functions
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
+ }
@@ -1,5 +1,10 @@
1
1
  /**
2
- * MetricAMM SDK - SimpleRouter single-hop swap helpers.
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;