@continuumdao/ctm-mpc-defi 0.2.30 → 0.2.32

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 (40) hide show
  1. package/dist/agent/catalog.cjs +1933 -56
  2. package/dist/agent/catalog.cjs.map +1 -1
  3. package/dist/agent/catalog.d.ts +3671 -661
  4. package/dist/agent/catalog.js +1896 -58
  5. package/dist/agent/catalog.js.map +1 -1
  6. package/dist/agent/skills/aerodrome/SKILL.md +93 -0
  7. package/dist/agent/skills/compound-v3/SKILL.md +132 -0
  8. package/dist/agent/skills/euler-v2/SKILL.md +1 -1
  9. package/dist/agent/skills/morpho/SKILL.md +16 -8
  10. package/dist/buildBatch-CngvebiD.d.ts +45 -0
  11. package/dist/chains/evm/index.d.ts +6 -44
  12. package/dist/core/index.cjs +2 -1
  13. package/dist/core/index.cjs.map +1 -1
  14. package/dist/core/index.js +2 -1
  15. package/dist/core/index.js.map +1 -1
  16. package/dist/index.cjs +119 -1
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +4 -1
  19. package/dist/index.js +119 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/protocols/evm/aerodrome/index.cjs +3922 -0
  22. package/dist/protocols/evm/aerodrome/index.cjs.map +1 -0
  23. package/dist/protocols/evm/aerodrome/index.d.ts +792 -0
  24. package/dist/protocols/evm/aerodrome/index.js +3843 -0
  25. package/dist/protocols/evm/aerodrome/index.js.map +1 -0
  26. package/dist/protocols/evm/compound-v3/index.cjs +1542 -0
  27. package/dist/protocols/evm/compound-v3/index.cjs.map +1 -0
  28. package/dist/protocols/evm/compound-v3/index.d.ts +683 -0
  29. package/dist/protocols/evm/compound-v3/index.js +1488 -0
  30. package/dist/protocols/evm/compound-v3/index.js.map +1 -0
  31. package/dist/protocols/evm/euler-v2/index.cjs +17 -10
  32. package/dist/protocols/evm/euler-v2/index.cjs.map +1 -1
  33. package/dist/protocols/evm/euler-v2/index.js +17 -10
  34. package/dist/protocols/evm/euler-v2/index.js.map +1 -1
  35. package/dist/protocols/evm/morpho/index.cjs +746 -39
  36. package/dist/protocols/evm/morpho/index.cjs.map +1 -1
  37. package/dist/protocols/evm/morpho/index.d.ts +161 -14
  38. package/dist/protocols/evm/morpho/index.js +736 -42
  39. package/dist/protocols/evm/morpho/index.js.map +1 -1
  40. package/package.json +15 -1
@@ -0,0 +1,3843 @@
1
+ import { zeroAddress, getAddress, encodePacked, parseUnits, formatUnits, encodeFunctionData, defineChain, createPublicClient, http, parseGwei, serializeTransaction, keccak256 } from 'viem';
2
+ import { fetchChainFeeParams, gasLimitFromEstimateAndChainConfig, gweiToDecimalString, proposalTxParamsToFeeSnapshot, alignEip1559FeesWithLatestBase, getClientIdFromKeyGenResult } from '@continuumdao/continuum-node-sdk';
3
+
4
+ // src/core/registry.ts
5
+ var modules = [];
6
+ function registerProtocolModule(mod) {
7
+ const existing = modules.findIndex((m) => m.id === mod.id);
8
+ if (existing >= 0) {
9
+ modules[existing] = mod;
10
+ } else {
11
+ modules.push(mod);
12
+ }
13
+ }
14
+
15
+ // src/core/expiry.ts
16
+ var DEFAULT_DEFI_MULTISIGN_EXPIRY_SECONDS = 30 * 60;
17
+
18
+ // src/protocols/evm/aerodrome/support.ts
19
+ var AERODROME_PROTOCOL_ID = "aerodrome";
20
+ var AERODROME_CHAIN_ID = 8453;
21
+ var AERODROME_NATIVE_PLACEHOLDER = zeroAddress;
22
+ var AERODROME_WETH_BASE = "0x4200000000000000000000000000000000000006";
23
+ var AERODROME_USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
24
+ function isAerodromeChainSupported(chainId) {
25
+ const n = typeof chainId === "number" ? chainId : Number.parseInt(String(chainId), 10);
26
+ return n === AERODROME_CHAIN_ID;
27
+ }
28
+ function aerodromeOnChainDeadlineUnix(expiryDate) {
29
+ if (typeof expiryDate === "number" && Number.isFinite(expiryDate) && expiryDate > 0) {
30
+ return BigInt(Math.floor(expiryDate));
31
+ }
32
+ return BigInt(Math.floor(Date.now() / 1e3) + DEFAULT_DEFI_MULTISIGN_EXPIRY_SECONDS);
33
+ }
34
+ function requireAerodromeRpcUrl(rpcUrl) {
35
+ const url = (rpcUrl ?? "").trim();
36
+ if (!url) {
37
+ throw new Error(
38
+ "rpcUrl is required. The MCP server injects it from get_chain_registry rpcGateway \u2014 pass chainId 8453 only, do not pass a public RPC URL."
39
+ );
40
+ }
41
+ return url;
42
+ }
43
+ function isAerodromeNativeToken(address) {
44
+ const t = (address ?? "").trim().toLowerCase();
45
+ if (!t) return false;
46
+ if (t === "eth" || t === "native" || t === "native_eth") return true;
47
+ if (t === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") return true;
48
+ try {
49
+ return getAddress(address) === zeroAddress;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+ var AERODROME_BASE_ADDRESSES_FALLBACK = {
55
+ chainId: AERODROME_CHAIN_ID,
56
+ aero: "0x940181a94A35A4569E4529A3CDfB74e38FD98631",
57
+ router: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
58
+ poolFactory: "0x420DD381b31aEf6683db6B902084cB0FFECe40Da",
59
+ factoryRegistry: "0x5C3F18F06CC09CA1910767A34a20F771039E37C0",
60
+ voter: "0x16613524e02ad97eDfeF371bC883F2F5d6C480A5",
61
+ votingEscrow: "0xeBf418Fe2512e7E6bd9b87a8F0f294aCDC67e6B4",
62
+ rewardsDistributor: "0x227f65131A261548b057215bB1D5Ab2997964C7d",
63
+ mixedQuoter: "0x0A5aA5D3a4d28014f967Bf0f29EAA3FF9807D5c6",
64
+ /** MixedRouteQuoterV3 — v2 + all three CL factories (bitmask path encoding). */
65
+ mixedQuoterV3: "0xCd2A7D98e82D6107eac1828ce8DeAA6acB65b555",
66
+ nfpm: "0x827922686190790b37229fd06084350E74485b72",
67
+ /** Oldest Slipstream CL factory (Gauges V1). Path bit 1<<20. */
68
+ clFactory: "0x5e7BB104d84c7CB9B682AaC2F3d509f5F406809A",
69
+ /** Slipstream CL factory 2 (gauge caps). Raw tickSpacing in MixedQuoterV3. */
70
+ clFactory2: "0xaDe65c38CD4849aDBA595a4323a8C7DdfE89716a",
71
+ /** Newest Slipstream CL factory (Gauges V3) — Coinbase B20 stock pools. Path bit 1<<19. */
72
+ clFactory3: "0xf8f2eB4940CFE7d13603DDDD87f123820Fc061Ef",
73
+ universalRouter: "0x6Cb442acF35158D5eDa88fe602221b67B400Be3E",
74
+ swapRouter: "0xBE6D8f0d05cC4be24d5167a3eF062215bE6D18a5",
75
+ swapRouter2: "0xcbBb8035cAc7D4B3Ca7aBb74cF7BdF900215Ce0D",
76
+ swapRouter3: "0x698Cb2b6dd822994581fEa6eA4Fc755d1363A92F",
77
+ pricesOracle: "0xee717411f6E44F9feE011835C8E6FAaC5dEfF166",
78
+ slipstreamHelper: "0x0AD09A66af0154a84e86F761313d02d0abB6edd5",
79
+ lpSugar: "0x69dD9db6d8f8E7d83887A704f447b1a584b599A1",
80
+ sugarSlipstreamHelper: "0x9c62ab10577fB3C20A22E231b7703Ed6D456CC7a",
81
+ swapper: "0x91616a7B9CF6D23f8C17845581051EBdC4BcB916",
82
+ weth: AERODROME_WETH_BASE
83
+ };
84
+ var SUGAR_ENV_URL = "https://raw.githubusercontent.com/velodrome-finance/sugar/main/deployments/base.env";
85
+ var SECURITY_MDX_URL = "https://raw.githubusercontent.com/aerodrome-finance/docs/main/content/security.mdx";
86
+ function asAddr(raw) {
87
+ const s = (raw ?? "").trim();
88
+ if (!s) return null;
89
+ try {
90
+ return getAddress(s.startsWith("0x") ? s : `0x${s}`);
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+ function parseSugarBaseEnv(text) {
96
+ const out = {};
97
+ for (const line of text.split(/\r?\n/)) {
98
+ const trimmed = line.trim();
99
+ if (!trimmed || trimmed.startsWith("#")) continue;
100
+ const eq = trimmed.indexOf("=");
101
+ if (eq <= 0) continue;
102
+ const key = trimmed.slice(0, eq).trim();
103
+ const val = trimmed.slice(eq + 1).trim().replace(/^['"]|['"]$/g, "");
104
+ if (key && val) out[key] = val;
105
+ }
106
+ return out;
107
+ }
108
+ function parseSecurityMdxAddresses(text) {
109
+ const out = {};
110
+ const re = /([A-Za-z][A-Za-z0-9 /_-]{1,80})\s+((?:0x)[a-fA-F0-9]{40})/g;
111
+ let m;
112
+ while (m = re.exec(text)) {
113
+ const name = (m[1] ?? "").trim().toLowerCase().replace(/\s+/g, "");
114
+ const addr = m[2];
115
+ if (name && addr && !out[name]) out[name] = addr;
116
+ }
117
+ return out;
118
+ }
119
+ function nthAddr(csv, n) {
120
+ if (!csv) return null;
121
+ const part = csv.split(",")[n];
122
+ return part ? asAddr(part.trim()) : null;
123
+ }
124
+ function firstAddr(csv) {
125
+ return nthAddr(csv, 0);
126
+ }
127
+ function mergeLoaded(sugar, docs) {
128
+ const fb = AERODROME_BASE_ADDRESSES_FALLBACK;
129
+ const pick = (loaded, fallback) => loaded ?? getAddress(fallback);
130
+ return {
131
+ chainId: AERODROME_CHAIN_ID,
132
+ aero: pick(asAddr(docs.aero), fb.aero),
133
+ router: pick(asAddr(docs.router), fb.router),
134
+ poolFactory: pick(firstAddr(sugar.V2_FACTORIES_8453) ?? asAddr(docs.poolfactory), fb.poolFactory),
135
+ factoryRegistry: pick(asAddr(sugar.REGISTRY_8453) ?? asAddr(docs.factoryregistry), fb.factoryRegistry),
136
+ voter: pick(asAddr(sugar.VOTER_8453) ?? asAddr(docs.voter), fb.voter),
137
+ votingEscrow: pick(asAddr(docs.votingescrow), fb.votingEscrow),
138
+ rewardsDistributor: pick(asAddr(sugar.DIST_8453) ?? asAddr(docs.rewardsdistributor), fb.rewardsDistributor),
139
+ mixedQuoter: pick(asAddr(docs.mixedquoter), fb.mixedQuoter),
140
+ mixedQuoterV3: pick(asAddr(docs.mixedquoterv3), fb.mixedQuoterV3),
141
+ nfpm: pick(asAddr(docs.nonfungiblepositionmanager), fb.nfpm),
142
+ clFactory: pick(firstAddr(sugar.CL_FACTORIES_8453) ?? asAddr(docs.poolfactory), fb.clFactory),
143
+ clFactory2: pick(nthAddr(sugar.CL_FACTORIES_8453, 1), fb.clFactory2),
144
+ clFactory3: pick(nthAddr(sugar.CL_FACTORIES_8453, 2), fb.clFactory3),
145
+ universalRouter: pick(asAddr(docs.universalrouter), fb.universalRouter),
146
+ swapRouter: pick(asAddr(docs.swaprouter), fb.swapRouter),
147
+ swapRouter2: pick(asAddr(docs.swaprouter2), fb.swapRouter2),
148
+ swapRouter3: pick(asAddr(docs.swaprouter3), fb.swapRouter3),
149
+ pricesOracle: pick(asAddr(docs.pricesoracle), fb.pricesOracle),
150
+ slipstreamHelper: pick(asAddr(docs.slipstreamhelper), fb.slipstreamHelper),
151
+ lpSugar: pick(asAddr(sugar.LP_SUGAR_ADDRESS_8453), fb.lpSugar),
152
+ sugarSlipstreamHelper: pick(asAddr(sugar.SLIPSTREAM_HELPER_8453), fb.sugarSlipstreamHelper),
153
+ swapper: pick(asAddr(sugar.SWAPPER_8453), fb.swapper),
154
+ weth: pick(asAddr(AERODROME_WETH_BASE), fb.weth)
155
+ };
156
+ }
157
+ var cache = null;
158
+ async function fetchText(url) {
159
+ try {
160
+ const res = await fetch(url, { cache: "force-cache" });
161
+ if (!res.ok) return null;
162
+ return await res.text();
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+ async function loadAerodromeAddresses() {
168
+ if (!cache) {
169
+ cache = (async () => {
170
+ const [sugarText, docsText] = await Promise.all([fetchText(SUGAR_ENV_URL), fetchText(SECURITY_MDX_URL)]);
171
+ const sugar = sugarText ? parseSugarBaseEnv(sugarText) : {};
172
+ const docs = docsText ? parseSecurityMdxAddresses(docsText) : {};
173
+ return mergeLoaded(sugar, docs);
174
+ })();
175
+ }
176
+ return cache;
177
+ }
178
+ function resetAerodromeAddressCache() {
179
+ cache = null;
180
+ }
181
+ function aerodromeFallbackAddresses() {
182
+ return mergeLoaded({}, {});
183
+ }
184
+ function aerodromeClFactoryList(addrs) {
185
+ return [addrs.clFactory, addrs.clFactory2, addrs.clFactory3];
186
+ }
187
+ function aerodromeClSwapRouterForFactory(addrs, factory) {
188
+ const f = getAddress(factory).toLowerCase();
189
+ if (f === addrs.clFactory3.toLowerCase()) return addrs.swapRouter3;
190
+ if (f === addrs.clFactory2.toLowerCase()) return addrs.swapRouter2;
191
+ return addrs.swapRouter;
192
+ }
193
+
194
+ // src/protocols/evm/aerodrome/abi.ts
195
+ var lpSugarAbi = [
196
+ {
197
+ type: "function",
198
+ name: "tokens",
199
+ stateMutability: "view",
200
+ inputs: [
201
+ { name: "_limit", type: "uint256" },
202
+ { name: "_offset", type: "uint256" },
203
+ { name: "_account", type: "address" },
204
+ { name: "_addresses", type: "address[]" }
205
+ ],
206
+ outputs: [
207
+ {
208
+ name: "",
209
+ type: "tuple[]",
210
+ components: [
211
+ { name: "token_address", type: "address" },
212
+ { name: "symbol", type: "string" },
213
+ { name: "decimals", type: "uint8" },
214
+ { name: "account_balance", type: "uint256" },
215
+ { name: "listed", type: "bool" },
216
+ { name: "emerging", type: "bool" }
217
+ ]
218
+ }
219
+ ]
220
+ },
221
+ {
222
+ type: "function",
223
+ name: "forSwaps",
224
+ stateMutability: "view",
225
+ inputs: [
226
+ { name: "_limit", type: "uint256" },
227
+ { name: "_offset", type: "uint256" }
228
+ ],
229
+ outputs: [
230
+ {
231
+ name: "",
232
+ type: "tuple[]",
233
+ components: [
234
+ { name: "lp", type: "address" },
235
+ { name: "type", type: "int24" },
236
+ { name: "token0", type: "address" },
237
+ { name: "token1", type: "address" },
238
+ { name: "factory", type: "address" },
239
+ { name: "pool_fee", type: "uint256" }
240
+ ]
241
+ }
242
+ ]
243
+ },
244
+ {
245
+ type: "function",
246
+ name: "all",
247
+ stateMutability: "view",
248
+ inputs: [
249
+ { name: "_limit", type: "uint256" },
250
+ { name: "_offset", type: "uint256" },
251
+ { name: "_filter", type: "uint256" }
252
+ ],
253
+ outputs: [
254
+ {
255
+ name: "",
256
+ type: "tuple[]",
257
+ components: [
258
+ { name: "lp", type: "address" },
259
+ { name: "symbol", type: "string" },
260
+ { name: "decimals", type: "uint8" },
261
+ { name: "liquidity", type: "uint256" },
262
+ { name: "type", type: "int24" },
263
+ { name: "tick", type: "int24" },
264
+ { name: "sqrt_ratio", type: "uint160" },
265
+ { name: "token0", type: "address" },
266
+ { name: "reserve0", type: "uint256" },
267
+ { name: "staked0", type: "uint256" },
268
+ { name: "token1", type: "address" },
269
+ { name: "reserve1", type: "uint256" },
270
+ { name: "staked1", type: "uint256" },
271
+ { name: "gauge", type: "address" },
272
+ { name: "gauge_liquidity", type: "uint256" },
273
+ { name: "gauge_alive", type: "bool" },
274
+ { name: "fee", type: "address" },
275
+ { name: "bribe", type: "address" },
276
+ { name: "factory", type: "address" },
277
+ { name: "emissions", type: "uint256" },
278
+ { name: "emissions_token", type: "address" },
279
+ { name: "emissions_cap", type: "uint256" },
280
+ { name: "pool_fee", type: "uint256" },
281
+ { name: "unstaked_fee", type: "uint256" },
282
+ { name: "token0_fees", type: "uint256" },
283
+ { name: "token1_fees", type: "uint256" },
284
+ { name: "locked", type: "uint256" },
285
+ { name: "emerging", type: "uint256" },
286
+ { name: "created_at", type: "uint32" },
287
+ { name: "nfpm", type: "address" },
288
+ { name: "alm", type: "address" },
289
+ { name: "root", type: "address" }
290
+ ]
291
+ }
292
+ ]
293
+ },
294
+ {
295
+ type: "function",
296
+ name: "byAddress",
297
+ stateMutability: "view",
298
+ inputs: [{ name: "_address", type: "address" }],
299
+ outputs: [
300
+ {
301
+ name: "",
302
+ type: "tuple",
303
+ components: [
304
+ { name: "lp", type: "address" },
305
+ { name: "symbol", type: "string" },
306
+ { name: "decimals", type: "uint8" },
307
+ { name: "liquidity", type: "uint256" },
308
+ { name: "type", type: "int24" },
309
+ { name: "tick", type: "int24" },
310
+ { name: "sqrt_ratio", type: "uint160" },
311
+ { name: "token0", type: "address" },
312
+ { name: "reserve0", type: "uint256" },
313
+ { name: "staked0", type: "uint256" },
314
+ { name: "token1", type: "address" },
315
+ { name: "reserve1", type: "uint256" },
316
+ { name: "staked1", type: "uint256" },
317
+ { name: "gauge", type: "address" },
318
+ { name: "gauge_liquidity", type: "uint256" },
319
+ { name: "gauge_alive", type: "bool" },
320
+ { name: "fee", type: "address" },
321
+ { name: "bribe", type: "address" },
322
+ { name: "factory", type: "address" },
323
+ { name: "emissions", type: "uint256" },
324
+ { name: "emissions_token", type: "address" },
325
+ { name: "emissions_cap", type: "uint256" },
326
+ { name: "pool_fee", type: "uint256" },
327
+ { name: "unstaked_fee", type: "uint256" },
328
+ { name: "token0_fees", type: "uint256" },
329
+ { name: "token1_fees", type: "uint256" },
330
+ { name: "locked", type: "uint256" },
331
+ { name: "emerging", type: "uint256" },
332
+ { name: "created_at", type: "uint32" },
333
+ { name: "nfpm", type: "address" },
334
+ { name: "alm", type: "address" },
335
+ { name: "root", type: "address" }
336
+ ]
337
+ }
338
+ ]
339
+ },
340
+ {
341
+ type: "function",
342
+ name: "count",
343
+ stateMutability: "view",
344
+ inputs: [],
345
+ outputs: [{ name: "", type: "uint256" }]
346
+ },
347
+ {
348
+ type: "function",
349
+ name: "positions",
350
+ stateMutability: "view",
351
+ inputs: [
352
+ { name: "_limit", type: "uint256" },
353
+ { name: "_offset", type: "uint256" },
354
+ { name: "_account", type: "address" }
355
+ ],
356
+ outputs: [
357
+ {
358
+ name: "",
359
+ type: "tuple[]",
360
+ components: [
361
+ { name: "id", type: "uint256" },
362
+ { name: "lp", type: "address" },
363
+ { name: "liquidity", type: "uint256" },
364
+ { name: "staked", type: "uint256" },
365
+ { name: "amount0", type: "uint256" },
366
+ { name: "amount1", type: "uint256" },
367
+ { name: "staked0", type: "uint256" },
368
+ { name: "staked1", type: "uint256" },
369
+ { name: "unstaked_earned0", type: "uint256" },
370
+ { name: "unstaked_earned1", type: "uint256" },
371
+ { name: "emissions_earned", type: "uint256" },
372
+ { name: "tick_lower", type: "int24" },
373
+ { name: "tick_upper", type: "int24" },
374
+ { name: "sqrt_ratio_lower", type: "uint160" },
375
+ { name: "sqrt_ratio_upper", type: "uint160" },
376
+ { name: "locker", type: "address" },
377
+ { name: "unlocks_at", type: "uint32" },
378
+ { name: "alm", type: "address" }
379
+ ]
380
+ }
381
+ ]
382
+ }
383
+ ];
384
+ var routerAbi = [
385
+ {
386
+ type: "function",
387
+ name: "weth",
388
+ stateMutability: "view",
389
+ inputs: [],
390
+ outputs: [{ name: "", type: "address" }]
391
+ },
392
+ {
393
+ type: "function",
394
+ name: "defaultFactory",
395
+ stateMutability: "view",
396
+ inputs: [],
397
+ outputs: [{ name: "", type: "address" }]
398
+ },
399
+ {
400
+ type: "function",
401
+ name: "poolFor",
402
+ stateMutability: "view",
403
+ inputs: [
404
+ { name: "tokenA", type: "address" },
405
+ { name: "tokenB", type: "address" },
406
+ { name: "stable", type: "bool" },
407
+ { name: "_factory", type: "address" }
408
+ ],
409
+ outputs: [{ name: "pool", type: "address" }]
410
+ },
411
+ {
412
+ type: "function",
413
+ name: "getAmountsOut",
414
+ stateMutability: "view",
415
+ inputs: [
416
+ { name: "amountIn", type: "uint256" },
417
+ {
418
+ name: "routes",
419
+ type: "tuple[]",
420
+ components: [
421
+ { name: "from", type: "address" },
422
+ { name: "to", type: "address" },
423
+ { name: "stable", type: "bool" },
424
+ { name: "factory", type: "address" }
425
+ ]
426
+ }
427
+ ],
428
+ outputs: [{ name: "amounts", type: "uint256[]" }]
429
+ },
430
+ {
431
+ type: "function",
432
+ name: "quoteAddLiquidity",
433
+ stateMutability: "view",
434
+ inputs: [
435
+ { name: "tokenA", type: "address" },
436
+ { name: "tokenB", type: "address" },
437
+ { name: "stable", type: "bool" },
438
+ { name: "_factory", type: "address" },
439
+ { name: "amountADesired", type: "uint256" },
440
+ { name: "amountBDesired", type: "uint256" }
441
+ ],
442
+ outputs: [
443
+ { name: "amountA", type: "uint256" },
444
+ { name: "amountB", type: "uint256" },
445
+ { name: "liquidity", type: "uint256" }
446
+ ]
447
+ },
448
+ {
449
+ type: "function",
450
+ name: "addLiquidity",
451
+ stateMutability: "nonpayable",
452
+ inputs: [
453
+ { name: "tokenA", type: "address" },
454
+ { name: "tokenB", type: "address" },
455
+ { name: "stable", type: "bool" },
456
+ { name: "amountADesired", type: "uint256" },
457
+ { name: "amountBDesired", type: "uint256" },
458
+ { name: "amountAMin", type: "uint256" },
459
+ { name: "amountBMin", type: "uint256" },
460
+ { name: "to", type: "address" },
461
+ { name: "deadline", type: "uint256" }
462
+ ],
463
+ outputs: [
464
+ { name: "amountA", type: "uint256" },
465
+ { name: "amountB", type: "uint256" },
466
+ { name: "liquidity", type: "uint256" }
467
+ ]
468
+ },
469
+ {
470
+ type: "function",
471
+ name: "addLiquidityETH",
472
+ stateMutability: "payable",
473
+ inputs: [
474
+ { name: "token", type: "address" },
475
+ { name: "stable", type: "bool" },
476
+ { name: "amountTokenDesired", type: "uint256" },
477
+ { name: "amountTokenMin", type: "uint256" },
478
+ { name: "amountETHMin", type: "uint256" },
479
+ { name: "to", type: "address" },
480
+ { name: "deadline", type: "uint256" }
481
+ ],
482
+ outputs: [
483
+ { name: "amountToken", type: "uint256" },
484
+ { name: "amountETH", type: "uint256" },
485
+ { name: "liquidity", type: "uint256" }
486
+ ]
487
+ },
488
+ {
489
+ type: "function",
490
+ name: "removeLiquidity",
491
+ stateMutability: "nonpayable",
492
+ inputs: [
493
+ { name: "tokenA", type: "address" },
494
+ { name: "tokenB", type: "address" },
495
+ { name: "stable", type: "bool" },
496
+ { name: "liquidity", type: "uint256" },
497
+ { name: "amountAMin", type: "uint256" },
498
+ { name: "amountBMin", type: "uint256" },
499
+ { name: "to", type: "address" },
500
+ { name: "deadline", type: "uint256" }
501
+ ],
502
+ outputs: [
503
+ { name: "amountA", type: "uint256" },
504
+ { name: "amountB", type: "uint256" }
505
+ ]
506
+ },
507
+ {
508
+ type: "function",
509
+ name: "removeLiquidityETH",
510
+ stateMutability: "nonpayable",
511
+ inputs: [
512
+ { name: "token", type: "address" },
513
+ { name: "stable", type: "bool" },
514
+ { name: "liquidity", type: "uint256" },
515
+ { name: "amountTokenMin", type: "uint256" },
516
+ { name: "amountETHMin", type: "uint256" },
517
+ { name: "to", type: "address" },
518
+ { name: "deadline", type: "uint256" }
519
+ ],
520
+ outputs: [
521
+ { name: "amountToken", type: "uint256" },
522
+ { name: "amountETH", type: "uint256" }
523
+ ]
524
+ },
525
+ {
526
+ type: "function",
527
+ name: "swapExactTokensForTokens",
528
+ stateMutability: "nonpayable",
529
+ inputs: [
530
+ { name: "amountIn", type: "uint256" },
531
+ { name: "amountOutMin", type: "uint256" },
532
+ {
533
+ name: "routes",
534
+ type: "tuple[]",
535
+ components: [
536
+ { name: "from", type: "address" },
537
+ { name: "to", type: "address" },
538
+ { name: "stable", type: "bool" },
539
+ { name: "factory", type: "address" }
540
+ ]
541
+ },
542
+ { name: "to", type: "address" },
543
+ { name: "deadline", type: "uint256" }
544
+ ],
545
+ outputs: [{ name: "amounts", type: "uint256[]" }]
546
+ },
547
+ {
548
+ type: "function",
549
+ name: "swapExactETHForTokens",
550
+ stateMutability: "payable",
551
+ inputs: [
552
+ { name: "amountOutMin", type: "uint256" },
553
+ {
554
+ name: "routes",
555
+ type: "tuple[]",
556
+ components: [
557
+ { name: "from", type: "address" },
558
+ { name: "to", type: "address" },
559
+ { name: "stable", type: "bool" },
560
+ { name: "factory", type: "address" }
561
+ ]
562
+ },
563
+ { name: "to", type: "address" },
564
+ { name: "deadline", type: "uint256" }
565
+ ],
566
+ outputs: [{ name: "amounts", type: "uint256[]" }]
567
+ },
568
+ {
569
+ type: "function",
570
+ name: "swapExactTokensForETH",
571
+ stateMutability: "nonpayable",
572
+ inputs: [
573
+ { name: "amountIn", type: "uint256" },
574
+ { name: "amountOutMin", type: "uint256" },
575
+ {
576
+ name: "routes",
577
+ type: "tuple[]",
578
+ components: [
579
+ { name: "from", type: "address" },
580
+ { name: "to", type: "address" },
581
+ { name: "stable", type: "bool" },
582
+ { name: "factory", type: "address" }
583
+ ]
584
+ },
585
+ { name: "to", type: "address" },
586
+ { name: "deadline", type: "uint256" }
587
+ ],
588
+ outputs: [{ name: "amounts", type: "uint256[]" }]
589
+ }
590
+ ];
591
+ var mixedQuoterAbi = [
592
+ {
593
+ type: "function",
594
+ name: "quoteExactInput",
595
+ stateMutability: "nonpayable",
596
+ inputs: [
597
+ { name: "path", type: "bytes" },
598
+ { name: "amountIn", type: "uint256" }
599
+ ],
600
+ outputs: [
601
+ { name: "amountOut", type: "uint256" },
602
+ { name: "v3RouteStartTickSpacing", type: "uint160[]" },
603
+ { name: "v3RouteStartSqrtPriceX96AfterList", type: "uint160[]" },
604
+ { name: "v3RouteInitializedTicksCrossedList", type: "uint32[]" },
605
+ { name: "v3SwapGasEstimate", type: "uint256" }
606
+ ]
607
+ }
608
+ ];
609
+ var mixedQuoterV3Abi = [
610
+ {
611
+ type: "function",
612
+ name: "quoteExactInput",
613
+ stateMutability: "nonpayable",
614
+ inputs: [
615
+ { name: "path", type: "bytes" },
616
+ { name: "amountIn", type: "uint256" }
617
+ ],
618
+ outputs: [
619
+ { name: "amountOut", type: "uint256" },
620
+ { name: "v3SqrtPriceX96AfterList", type: "uint160[]" },
621
+ { name: "v3InitializedTicksCrossedList", type: "uint32[]" },
622
+ { name: "v3SwapGasEstimate", type: "uint256" }
623
+ ]
624
+ }
625
+ ];
626
+ var v2PoolFactoryAbi = [
627
+ {
628
+ type: "function",
629
+ name: "getPool",
630
+ stateMutability: "view",
631
+ inputs: [
632
+ { name: "tokenA", type: "address" },
633
+ { name: "tokenB", type: "address" },
634
+ { name: "stable", type: "bool" }
635
+ ],
636
+ outputs: [{ name: "", type: "address" }]
637
+ }
638
+ ];
639
+ var clPoolFactoryAbi = [
640
+ {
641
+ type: "function",
642
+ name: "getPool",
643
+ stateMutability: "view",
644
+ inputs: [
645
+ { name: "tokenA", type: "address" },
646
+ { name: "tokenB", type: "address" },
647
+ { name: "tickSpacing", type: "int24" }
648
+ ],
649
+ outputs: [{ name: "", type: "address" }]
650
+ },
651
+ {
652
+ type: "function",
653
+ name: "tickSpacings",
654
+ stateMutability: "view",
655
+ inputs: [],
656
+ outputs: [{ name: "", type: "int24[]" }]
657
+ }
658
+ ];
659
+ var clPoolAbi = [
660
+ {
661
+ type: "function",
662
+ name: "slot0",
663
+ stateMutability: "view",
664
+ inputs: [],
665
+ outputs: [
666
+ { name: "sqrtPriceX96", type: "uint160" },
667
+ { name: "tick", type: "int24" },
668
+ { name: "observationIndex", type: "uint16" },
669
+ { name: "observationCardinality", type: "uint16" },
670
+ { name: "observationCardinalityNext", type: "uint16" },
671
+ { name: "unlocked", type: "bool" }
672
+ ]
673
+ },
674
+ {
675
+ type: "function",
676
+ name: "liquidity",
677
+ stateMutability: "view",
678
+ inputs: [],
679
+ outputs: [{ name: "", type: "uint128" }]
680
+ }
681
+ ];
682
+ var sugarHelperAbi = [
683
+ {
684
+ type: "function",
685
+ name: "estimateAmount0",
686
+ stateMutability: "view",
687
+ inputs: [
688
+ { name: "amount1", type: "uint256" },
689
+ { name: "sqrtRatioX96", type: "uint160" },
690
+ { name: "sqrtRatioAX96", type: "uint160" },
691
+ { name: "sqrtRatioBX96", type: "uint160" }
692
+ ],
693
+ outputs: [{ name: "", type: "uint256" }]
694
+ },
695
+ {
696
+ type: "function",
697
+ name: "estimateAmount1",
698
+ stateMutability: "view",
699
+ inputs: [
700
+ { name: "amount0", type: "uint256" },
701
+ { name: "sqrtRatioX96", type: "uint160" },
702
+ { name: "sqrtRatioAX96", type: "uint160" },
703
+ { name: "sqrtRatioBX96", type: "uint160" }
704
+ ],
705
+ outputs: [{ name: "", type: "uint256" }]
706
+ }
707
+ ];
708
+ var nfpmAbi = [
709
+ {
710
+ type: "function",
711
+ name: "mint",
712
+ stateMutability: "payable",
713
+ inputs: [
714
+ {
715
+ name: "params",
716
+ type: "tuple",
717
+ components: [
718
+ { name: "token0", type: "address" },
719
+ { name: "token1", type: "address" },
720
+ { name: "tickSpacing", type: "int24" },
721
+ { name: "tickLower", type: "int24" },
722
+ { name: "tickUpper", type: "int24" },
723
+ { name: "amount0Desired", type: "uint256" },
724
+ { name: "amount1Desired", type: "uint256" },
725
+ { name: "amount0Min", type: "uint256" },
726
+ { name: "amount1Min", type: "uint256" },
727
+ { name: "recipient", type: "address" },
728
+ { name: "deadline", type: "uint256" },
729
+ { name: "sqrtPriceX96", type: "uint160" }
730
+ ]
731
+ }
732
+ ],
733
+ outputs: [
734
+ { name: "tokenId", type: "uint256" },
735
+ { name: "liquidity", type: "uint128" },
736
+ { name: "amount0", type: "uint256" },
737
+ { name: "amount1", type: "uint256" }
738
+ ]
739
+ },
740
+ {
741
+ type: "function",
742
+ name: "increaseLiquidity",
743
+ stateMutability: "payable",
744
+ inputs: [
745
+ {
746
+ name: "params",
747
+ type: "tuple",
748
+ components: [
749
+ { name: "tokenId", type: "uint256" },
750
+ { name: "amount0Desired", type: "uint256" },
751
+ { name: "amount1Desired", type: "uint256" },
752
+ { name: "amount0Min", type: "uint256" },
753
+ { name: "amount1Min", type: "uint256" },
754
+ { name: "deadline", type: "uint256" }
755
+ ]
756
+ }
757
+ ],
758
+ outputs: [
759
+ { name: "liquidity", type: "uint128" },
760
+ { name: "amount0", type: "uint256" },
761
+ { name: "amount1", type: "uint256" }
762
+ ]
763
+ },
764
+ {
765
+ type: "function",
766
+ name: "decreaseLiquidity",
767
+ stateMutability: "payable",
768
+ inputs: [
769
+ {
770
+ name: "params",
771
+ type: "tuple",
772
+ components: [
773
+ { name: "tokenId", type: "uint256" },
774
+ { name: "liquidity", type: "uint128" },
775
+ { name: "amount0Min", type: "uint256" },
776
+ { name: "amount1Min", type: "uint256" },
777
+ { name: "deadline", type: "uint256" }
778
+ ]
779
+ }
780
+ ],
781
+ outputs: [
782
+ { name: "amount0", type: "uint256" },
783
+ { name: "amount1", type: "uint256" }
784
+ ]
785
+ },
786
+ {
787
+ type: "function",
788
+ name: "collect",
789
+ stateMutability: "payable",
790
+ inputs: [
791
+ {
792
+ name: "params",
793
+ type: "tuple",
794
+ components: [
795
+ { name: "tokenId", type: "uint256" },
796
+ { name: "recipient", type: "address" },
797
+ { name: "amount0Max", type: "uint128" },
798
+ { name: "amount1Max", type: "uint128" }
799
+ ]
800
+ }
801
+ ],
802
+ outputs: [
803
+ { name: "amount0", type: "uint256" },
804
+ { name: "amount1", type: "uint256" }
805
+ ]
806
+ },
807
+ {
808
+ type: "function",
809
+ name: "burn",
810
+ stateMutability: "payable",
811
+ inputs: [{ name: "tokenId", type: "uint256" }],
812
+ outputs: []
813
+ },
814
+ {
815
+ type: "function",
816
+ name: "approve",
817
+ stateMutability: "nonpayable",
818
+ inputs: [
819
+ { name: "to", type: "address" },
820
+ { name: "tokenId", type: "uint256" }
821
+ ],
822
+ outputs: []
823
+ }
824
+ ];
825
+ var clSwapRouterAbi = [
826
+ {
827
+ type: "function",
828
+ name: "exactInputSingle",
829
+ stateMutability: "payable",
830
+ inputs: [
831
+ {
832
+ name: "params",
833
+ type: "tuple",
834
+ components: [
835
+ { name: "tokenIn", type: "address" },
836
+ { name: "tokenOut", type: "address" },
837
+ { name: "tickSpacing", type: "int24" },
838
+ { name: "recipient", type: "address" },
839
+ { name: "deadline", type: "uint256" },
840
+ { name: "amountIn", type: "uint256" },
841
+ { name: "amountOutMinimum", type: "uint256" },
842
+ { name: "sqrtPriceLimitX96", type: "uint160" }
843
+ ]
844
+ }
845
+ ],
846
+ outputs: [{ name: "amountOut", type: "uint256" }]
847
+ }
848
+ ];
849
+ var poolClaimFeesAbi = [
850
+ {
851
+ type: "function",
852
+ name: "claimFees",
853
+ stateMutability: "nonpayable",
854
+ inputs: [],
855
+ outputs: [
856
+ { name: "claimed0", type: "uint256" },
857
+ { name: "claimed1", type: "uint256" }
858
+ ]
859
+ }
860
+ ];
861
+ var gaugeAbi = [
862
+ {
863
+ type: "function",
864
+ name: "deposit",
865
+ stateMutability: "nonpayable",
866
+ inputs: [{ name: "_amount", type: "uint256" }],
867
+ outputs: []
868
+ },
869
+ {
870
+ type: "function",
871
+ name: "withdraw",
872
+ stateMutability: "nonpayable",
873
+ inputs: [{ name: "_amount", type: "uint256" }],
874
+ outputs: []
875
+ },
876
+ {
877
+ type: "function",
878
+ name: "getReward",
879
+ stateMutability: "nonpayable",
880
+ inputs: [{ name: "_account", type: "address" }],
881
+ outputs: []
882
+ }
883
+ ];
884
+ var clGaugeAbi = [
885
+ {
886
+ type: "function",
887
+ name: "deposit",
888
+ stateMutability: "nonpayable",
889
+ inputs: [{ name: "tokenId", type: "uint256" }],
890
+ outputs: []
891
+ },
892
+ {
893
+ type: "function",
894
+ name: "withdraw",
895
+ stateMutability: "nonpayable",
896
+ inputs: [{ name: "tokenId", type: "uint256" }],
897
+ outputs: []
898
+ },
899
+ {
900
+ type: "function",
901
+ name: "getReward",
902
+ stateMutability: "nonpayable",
903
+ inputs: [{ name: "tokenId", type: "uint256" }],
904
+ outputs: []
905
+ }
906
+ ];
907
+ var aerodromePoolMetaAbi = [
908
+ { type: "function", name: "symbol", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
909
+ { type: "function", name: "token0", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
910
+ { type: "function", name: "token1", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
911
+ { type: "function", name: "stable", stateMutability: "view", inputs: [], outputs: [{ type: "bool" }] },
912
+ {
913
+ type: "function",
914
+ name: "getReserves",
915
+ stateMutability: "view",
916
+ inputs: [],
917
+ outputs: [
918
+ { name: "_reserve0", type: "uint256" },
919
+ { name: "_reserve1", type: "uint256" },
920
+ { name: "_blockTimestampLast", type: "uint256" }
921
+ ]
922
+ }
923
+ ];
924
+ var aerodromeVoterAbi = [
925
+ {
926
+ type: "function",
927
+ name: "gauges",
928
+ stateMutability: "view",
929
+ inputs: [{ name: "pool", type: "address" }],
930
+ outputs: [{ type: "address" }]
931
+ }
932
+ ];
933
+ var erc20Abi = [
934
+ { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
935
+ { type: "function", name: "decimals", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] },
936
+ { type: "function", name: "totalSupply", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
937
+ {
938
+ type: "function",
939
+ name: "allowance",
940
+ stateMutability: "view",
941
+ inputs: [
942
+ { name: "owner", type: "address" },
943
+ { name: "spender", type: "address" }
944
+ ],
945
+ outputs: [{ type: "uint256" }]
946
+ },
947
+ {
948
+ type: "function",
949
+ name: "approve",
950
+ stateMutability: "nonpayable",
951
+ inputs: [
952
+ { name: "spender", type: "address" },
953
+ { name: "amount", type: "uint256" }
954
+ ],
955
+ outputs: [{ type: "bool" }]
956
+ },
957
+ {
958
+ type: "function",
959
+ name: "symbol",
960
+ stateMutability: "view",
961
+ inputs: [],
962
+ outputs: [{ type: "string" }]
963
+ }
964
+ ];
965
+ function aerodromePublicClient(rpcUrl) {
966
+ const url = requireAerodromeRpcUrl(rpcUrl);
967
+ const chain = defineChain({
968
+ id: AERODROME_CHAIN_ID,
969
+ name: "Base",
970
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
971
+ rpcUrls: { default: { http: [url] } },
972
+ contracts: {
973
+ multicall3: {
974
+ address: "0xcA11bde05977b3631167028862bE2a173976CA11",
975
+ blockCreated: 5022
976
+ }
977
+ }
978
+ });
979
+ return createPublicClient({ chain, transport: http(url) });
980
+ }
981
+
982
+ // src/protocols/evm/aerodrome/stockTokens.ts
983
+ var AERODROME_COINBASE_STOCK_TOKENS = [
984
+ { symbol: "AAPLc", name: "Apple Inc.", address: "0xb200000000000000000000C2e324d24d7eEcd1fb" },
985
+ { symbol: "NVDAc", name: "NVIDIA Corporation", address: "0xb20000000000000000000078ee7ce2fE4908108C" },
986
+ { symbol: "METAc", name: "Meta Platforms Inc.", address: "0xb2000000000000000000008bC8786B856E61707C" },
987
+ { symbol: "GOOGLc", name: "Alphabet Inc.", address: "0xb2000000000000000000002D0BA3164cc74f58B7" },
988
+ { symbol: "AMZNc", name: "Amazon.com Inc.", address: "0xb200000000000000000000d9192b6B456483C2E8" },
989
+ { symbol: "TSLAc", name: "Tesla Inc.", address: "0xb2000000000000000000001e800a7f5189430cD0" },
990
+ { symbol: "MSFTc", name: "Microsoft Corporation", address: "0xB200000000000000000000Ab99cFa739E253872B" },
991
+ { symbol: "COINc", name: "Coinbase Global Inc.", address: "0xb200000000000000000000c85a31389D71F3ecfb" },
992
+ { symbol: "MSTRc", name: "Strategy Inc.", address: "0xb2000000000000000000004884b426556b92883d" },
993
+ { symbol: "CRCLc", name: "Circle Internet Group Inc.", address: "0xB20000000000000000000019f6E7C675b73C2e4D" },
994
+ { symbol: "INTCc", name: "Intel Corporation", address: "0xB2000000000000000000004AFF16039bA04bdFBc" },
995
+ { symbol: "SNDKc", name: "Sandisk Corporation", address: "0xb200000000000000000000397293Cb8cda9a10c5" },
996
+ { symbol: "SPCXc", name: "Space Exploration Technologies Corp.", address: "0xb2000000000000000000007b9fcbd005511aCBd5" }
997
+ ];
998
+ var AERODROME_COINBASE_STOCK_DECIMALS = 8;
999
+ var AERODROME_B20_ASSET_PREFIX = "0xb200000000000000000000";
1000
+ var BASE_STOCKS_DOCS_URLS = [
1001
+ "https://docs.base.org/base-chain/asset-issuance/tokenized-stocks-on-base.md",
1002
+ "https://docs.base.org/base-chain/asset-issuance/tokenized-stocks-on-base"
1003
+ ];
1004
+ function isAerodromeB20AssetToken(address) {
1005
+ try {
1006
+ return getAddress(address).toLowerCase().startsWith(AERODROME_B20_ASSET_PREFIX);
1007
+ } catch {
1008
+ return false;
1009
+ }
1010
+ }
1011
+ function isAerodromeCoinbaseStockToken(address) {
1012
+ return isAerodromeB20AssetToken(address);
1013
+ }
1014
+ function looksLikeCoinbaseStockSymbol(symbol) {
1015
+ return /^[A-Za-z][A-Za-z0-9]{0,10}c$/.test((symbol ?? "").trim());
1016
+ }
1017
+ function lookupAerodromeKnownToken(token) {
1018
+ const key = (token ?? "").trim();
1019
+ if (!key) return null;
1020
+ const lower = key.toLowerCase();
1021
+ if (lower === "weth") {
1022
+ return { address: getAddress(AERODROME_WETH_BASE), symbol: "WETH", decimals: 18 };
1023
+ }
1024
+ if (lower === "usdc") {
1025
+ return { address: getAddress(AERODROME_USDC_BASE), symbol: "USDC", decimals: 6 };
1026
+ }
1027
+ if (lower === "aero") {
1028
+ return { address: getAddress(AERODROME_BASE_ADDRESSES_FALLBACK.aero), symbol: "AERO", decimals: 18 };
1029
+ }
1030
+ const stock = AERODROME_COINBASE_STOCK_TOKENS.find((t) => t.symbol.toLowerCase() === lower);
1031
+ if (stock) {
1032
+ return {
1033
+ address: getAddress(stock.address),
1034
+ symbol: stock.symbol,
1035
+ decimals: AERODROME_COINBASE_STOCK_DECIMALS
1036
+ };
1037
+ }
1038
+ try {
1039
+ const address = getAddress(key);
1040
+ const want = address.toLowerCase();
1041
+ if (want === getAddress(AERODROME_WETH_BASE).toLowerCase()) {
1042
+ return { address, symbol: "WETH", decimals: 18 };
1043
+ }
1044
+ if (want === getAddress(AERODROME_USDC_BASE).toLowerCase()) {
1045
+ return { address, symbol: "USDC", decimals: 6 };
1046
+ }
1047
+ if (want === getAddress(AERODROME_BASE_ADDRESSES_FALLBACK.aero).toLowerCase()) {
1048
+ return { address, symbol: "AERO", decimals: 18 };
1049
+ }
1050
+ const byAddr = AERODROME_COINBASE_STOCK_TOKENS.find((t) => getAddress(t.address).toLowerCase() === want);
1051
+ if (byAddr) {
1052
+ return { address, symbol: byAddr.symbol, decimals: AERODROME_COINBASE_STOCK_DECIMALS };
1053
+ }
1054
+ } catch {
1055
+ }
1056
+ return null;
1057
+ }
1058
+ function resolveAerodromeErc20OrWeth(token, weth) {
1059
+ const raw = (token ?? "").trim();
1060
+ if (!raw) throw new Error("Token is required.");
1061
+ if (isAerodromeNativeToken(raw)) return getAddress(weth);
1062
+ const known = lookupAerodromeKnownToken(raw);
1063
+ if (known) return known.address;
1064
+ try {
1065
+ return getAddress(raw);
1066
+ } catch {
1067
+ throw new Error(
1068
+ `Unknown Aerodrome token "${raw}". Pass a 0x address or a known ticker (ETH, WETH, USDC, AERO, AAPLc, NVDAc, \u2026). Coinbase B20 stocks are seeded \u2014 do not ask the user for a contract address.`
1069
+ );
1070
+ }
1071
+ }
1072
+ function aerodromeUnissuedStockMessage(symbolOrAddress) {
1073
+ const label = (symbolOrAddress ?? "").trim() || "This token";
1074
+ return `${label} is listed but not issued yet (zero supply). There is no Aerodrome liquidity to quote.`;
1075
+ }
1076
+ async function assertAerodromeStockIssued(args) {
1077
+ if (args.isNative || !isAerodromeCoinbaseStockToken(args.token)) return;
1078
+ try {
1079
+ const client = aerodromePublicClient(args.rpcUrl);
1080
+ const supply = await client.readContract({
1081
+ address: args.token,
1082
+ abi: erc20Abi,
1083
+ functionName: "totalSupply"
1084
+ });
1085
+ if (supply === 0n) throw new Error(aerodromeUnissuedStockMessage(args.label));
1086
+ } catch (e) {
1087
+ if (e instanceof Error && e.message.includes("not issued yet")) throw e;
1088
+ }
1089
+ }
1090
+ function aerodromeCoinbaseStockAddresses() {
1091
+ return AERODROME_COINBASE_STOCK_TOKENS.map((t) => getAddress(t.address));
1092
+ }
1093
+ var MAX_DOCS_CHARS = 4e5;
1094
+ var MAX_OFFICIAL_STOCKS = 40;
1095
+ function parseBaseTokenizedStockDocs(text) {
1096
+ try {
1097
+ if (typeof text !== "string" || text.length < 20) return [];
1098
+ const clipped = text.length > MAX_DOCS_CHARS ? text.slice(0, MAX_DOCS_CHARS) : text;
1099
+ const out = [];
1100
+ const seen = /* @__PURE__ */ new Set();
1101
+ const patterns = [
1102
+ /\|\s*`?([A-Za-z][A-Za-z0-9]{0,10}c)`?\s*\|\s*`?(0x[bB]200[a-fA-F0-9]{36})`?/g,
1103
+ /([A-Za-z][A-Za-z0-9]{0,10}c)[^0]{0,40}(0x[bB]200[a-fA-F0-9]{36})/g
1104
+ ];
1105
+ for (const re of patterns) {
1106
+ let m;
1107
+ while (m = re.exec(clipped)) {
1108
+ const symbol = (m[1] ?? "").trim();
1109
+ if (!looksLikeCoinbaseStockSymbol(symbol)) continue;
1110
+ let address;
1111
+ try {
1112
+ address = getAddress(m[2] ?? "");
1113
+ } catch {
1114
+ continue;
1115
+ }
1116
+ if (!isAerodromeB20AssetToken(address)) continue;
1117
+ const key = address.toLowerCase();
1118
+ if (seen.has(key)) continue;
1119
+ seen.add(key);
1120
+ out.push({ symbol, address });
1121
+ if (out.length > MAX_OFFICIAL_STOCKS) return [];
1122
+ }
1123
+ }
1124
+ return out;
1125
+ } catch {
1126
+ return [];
1127
+ }
1128
+ }
1129
+ async function fetchOfficialBaseTokenizedStocks() {
1130
+ for (const url of BASE_STOCKS_DOCS_URLS) {
1131
+ try {
1132
+ const res = await fetch(url, { cache: "no-store", signal: AbortSignal.timeout(8e3) });
1133
+ if (!res.ok) continue;
1134
+ const parsed = parseBaseTokenizedStockDocs(await res.text());
1135
+ if (parsed.length > 0) return parsed;
1136
+ } catch {
1137
+ }
1138
+ }
1139
+ return [];
1140
+ }
1141
+ function seedName(address) {
1142
+ const want = address.toLowerCase();
1143
+ return AERODROME_COINBASE_STOCK_TOKENS.find((t) => getAddress(t.address).toLowerCase() === want)?.name;
1144
+ }
1145
+ function seedSymbol(address) {
1146
+ const want = address.toLowerCase();
1147
+ return AERODROME_COINBASE_STOCK_TOKENS.find((t) => getAddress(t.address).toLowerCase() === want)?.symbol;
1148
+ }
1149
+ function seedStockRows() {
1150
+ return AERODROME_COINBASE_STOCK_TOKENS.map((t) => ({
1151
+ address: getAddress(t.address),
1152
+ symbol: t.symbol,
1153
+ name: t.name,
1154
+ decimals: AERODROME_COINBASE_STOCK_DECIMALS,
1155
+ issued: false
1156
+ }));
1157
+ }
1158
+ async function aerodromeResolveStockTokens(args) {
1159
+ let official = [];
1160
+ try {
1161
+ official = args.official ?? await fetchOfficialBaseTokenizedStocks();
1162
+ } catch {
1163
+ official = [];
1164
+ }
1165
+ const candidates = /* @__PURE__ */ new Map();
1166
+ const add = (addressRaw, meta) => {
1167
+ if (!isAerodromeB20AssetToken(addressRaw)) return;
1168
+ const address = getAddress(addressRaw);
1169
+ const key = address.toLowerCase();
1170
+ const prev = candidates.get(key);
1171
+ candidates.set(key, {
1172
+ address,
1173
+ symbol: meta?.symbol || prev?.symbol,
1174
+ name: meta?.name || prev?.name,
1175
+ decimals: meta?.decimals ?? prev?.decimals
1176
+ });
1177
+ };
1178
+ for (const t of AERODROME_COINBASE_STOCK_TOKENS) {
1179
+ add(t.address, { symbol: t.symbol, name: t.name, decimals: AERODROME_COINBASE_STOCK_DECIMALS });
1180
+ }
1181
+ for (const t of official) {
1182
+ add(t.address, { symbol: t.symbol, decimals: AERODROME_COINBASE_STOCK_DECIMALS });
1183
+ }
1184
+ for (const t of args.knownTokens ?? []) {
1185
+ if (!isAerodromeB20AssetToken(t.address)) continue;
1186
+ const symbol = (t.symbol ?? "").trim();
1187
+ const seeded = Boolean(seedSymbol(getAddress(t.address)));
1188
+ if (!seeded && symbol && !looksLikeCoinbaseStockSymbol(symbol)) continue;
1189
+ add(t.address, { symbol, decimals: t.decimals });
1190
+ }
1191
+ const rows = [...candidates.values()];
1192
+ if (rows.length === 0) return seedStockRows();
1193
+ try {
1194
+ const client = aerodromePublicClient(args.rpcUrl);
1195
+ const meta = await client.multicall({
1196
+ allowFailure: true,
1197
+ contracts: rows.flatMap((row) => [
1198
+ { address: row.address, abi: erc20Abi, functionName: "symbol" },
1199
+ { address: row.address, abi: erc20Abi, functionName: "name" },
1200
+ { address: row.address, abi: erc20Abi, functionName: "decimals" },
1201
+ { address: row.address, abi: erc20Abi, functionName: "totalSupply" }
1202
+ ])
1203
+ });
1204
+ return rows.map((row, i) => {
1205
+ const base = i * 4;
1206
+ const onchainSymbol = meta[base]?.status === "success" ? String(meta[base].result) : "";
1207
+ const onchainName = meta[base + 1]?.status === "success" ? String(meta[base + 1].result) : "";
1208
+ const onchainDecimals = meta[base + 2]?.status === "success" ? Number(meta[base + 2].result) : NaN;
1209
+ const supply = meta[base + 3]?.status === "success" ? meta[base + 3].result : 0n;
1210
+ return {
1211
+ address: row.address,
1212
+ symbol: onchainSymbol || row.symbol || seedSymbol(row.address) || row.address.slice(0, 8),
1213
+ name: seedName(row.address) || onchainName || onchainSymbol || row.symbol || "",
1214
+ decimals: Number.isFinite(onchainDecimals) ? onchainDecimals : row.decimals ?? AERODROME_COINBASE_STOCK_DECIMALS,
1215
+ issued: supply > 0n
1216
+ };
1217
+ });
1218
+ } catch {
1219
+ return seedStockRows();
1220
+ }
1221
+ }
1222
+ var FALLBACK_TICK_SPACINGS = [1, 10, 50, 100, 200, 500, 2e3];
1223
+ function checksum(addr) {
1224
+ return getAddress(addr);
1225
+ }
1226
+ function pairKey(a, b) {
1227
+ const [x, y] = [a.toLowerCase(), b.toLowerCase()].sort();
1228
+ return `${x}-${y}`;
1229
+ }
1230
+ function connectorPairs(tokenIn, tokenOut, connectors) {
1231
+ const pairs = [];
1232
+ const seen = /* @__PURE__ */ new Set();
1233
+ const add = (a, b) => {
1234
+ if (a.toLowerCase() === b.toLowerCase()) return;
1235
+ const key = pairKey(a, b);
1236
+ if (seen.has(key)) return;
1237
+ seen.add(key);
1238
+ pairs.push([a, b]);
1239
+ };
1240
+ add(tokenIn, tokenOut);
1241
+ for (const c of connectors) {
1242
+ add(tokenIn, c);
1243
+ add(tokenOut, c);
1244
+ }
1245
+ const weth = connectors[0];
1246
+ const usdc = connectors[1];
1247
+ if (weth && usdc) add(weth, usdc);
1248
+ return pairs;
1249
+ }
1250
+ async function tickSpacingsForFactory(rpcUrl, factory) {
1251
+ const client = aerodromePublicClient(rpcUrl);
1252
+ try {
1253
+ const rows = await client.readContract({
1254
+ address: factory,
1255
+ abi: clPoolFactoryAbi,
1256
+ functionName: "tickSpacings"
1257
+ });
1258
+ const nums = rows.map((n) => Number(n)).filter((n) => Number.isFinite(n) && n > 0);
1259
+ return nums.length > 0 ? nums : [...FALLBACK_TICK_SPACINGS];
1260
+ } catch {
1261
+ return [...FALLBACK_TICK_SPACINGS];
1262
+ }
1263
+ }
1264
+ async function aerodromeDiscoverConnectorPools(args) {
1265
+ const client = aerodromePublicClient(args.rpcUrl);
1266
+ const connectors = [args.addrs.weth, getAddress(AERODROME_USDC_BASE), args.addrs.aero];
1267
+ const pairs = connectorPairs(args.tokenIn, args.tokenOut, connectors);
1268
+ const clFactories = aerodromeClFactoryList(args.addrs);
1269
+ const spacingsByFactory = await Promise.all(
1270
+ clFactories.map(async (factory) => ({ factory, spacings: await tickSpacingsForFactory(args.rpcUrl, factory) }))
1271
+ );
1272
+ const jobs = [];
1273
+ for (const [tokenA, tokenB] of pairs) {
1274
+ jobs.push({ kind: "v2", tokenA, tokenB, stable: false });
1275
+ jobs.push({ kind: "v2", tokenA, tokenB, stable: true });
1276
+ for (const { factory, spacings } of spacingsByFactory) {
1277
+ for (const spacing of spacings) {
1278
+ jobs.push({ kind: "cl", factory, tokenA, tokenB, spacing });
1279
+ }
1280
+ }
1281
+ }
1282
+ const results = await client.multicall({
1283
+ allowFailure: true,
1284
+ contracts: jobs.map(
1285
+ (job) => job.kind === "v2" ? {
1286
+ address: args.addrs.poolFactory,
1287
+ abi: v2PoolFactoryAbi,
1288
+ functionName: "getPool",
1289
+ args: [job.tokenA, job.tokenB, job.stable]
1290
+ } : {
1291
+ address: job.factory,
1292
+ abi: clPoolFactoryAbi,
1293
+ functionName: "getPool",
1294
+ args: [job.tokenA, job.tokenB, job.spacing]
1295
+ }
1296
+ )
1297
+ });
1298
+ const seen = /* @__PURE__ */ new Set();
1299
+ const pools = [];
1300
+ for (let i = 0; i < jobs.length; i++) {
1301
+ const job = jobs[i];
1302
+ const row = results[i];
1303
+ if (!row || row.status !== "success") continue;
1304
+ const lp = row.result;
1305
+ if (!lp || lp === zeroAddress) continue;
1306
+ const key = lp.toLowerCase();
1307
+ if (seen.has(key)) continue;
1308
+ seen.add(key);
1309
+ const token0 = job.tokenA.toLowerCase() < job.tokenB.toLowerCase() ? job.tokenA : job.tokenB;
1310
+ const token1 = token0.toLowerCase() === job.tokenA.toLowerCase() ? job.tokenB : job.tokenA;
1311
+ if (job.kind === "v2") {
1312
+ pools.push({
1313
+ lp: checksum(lp),
1314
+ type: job.stable ? 0 : -1,
1315
+ token0: checksum(token0),
1316
+ token1: checksum(token1),
1317
+ factory: args.addrs.poolFactory,
1318
+ poolFee: "0",
1319
+ kind: job.stable ? "stable" : "volatile"
1320
+ });
1321
+ } else {
1322
+ pools.push({
1323
+ lp: checksum(lp),
1324
+ type: job.spacing,
1325
+ token0: checksum(token0),
1326
+ token1: checksum(token1),
1327
+ factory: checksum(job.factory),
1328
+ poolFee: "0",
1329
+ kind: "cl"
1330
+ });
1331
+ }
1332
+ }
1333
+ return pools;
1334
+ }
1335
+ async function aerodromeDiscoverConnectorPoolsLoaded(args) {
1336
+ const addrs = await loadAerodromeAddresses();
1337
+ return aerodromeDiscoverConnectorPools({ ...args, addrs });
1338
+ }
1339
+ async function aerodromeDiscoverDirectPools(args) {
1340
+ const addrs = await loadAerodromeAddresses();
1341
+ const client = aerodromePublicClient(args.rpcUrl);
1342
+ const tokenA = checksum(args.tokenA);
1343
+ const tokenB = checksum(args.tokenB);
1344
+ const clFactories = aerodromeClFactoryList(addrs);
1345
+ const spacingsByFactory = await Promise.all(
1346
+ clFactories.map(async (factory) => ({ factory, spacings: await tickSpacingsForFactory(args.rpcUrl, factory) }))
1347
+ );
1348
+ const jobs = [
1349
+ { kind: "v2", stable: false },
1350
+ { kind: "v2", stable: true }
1351
+ ];
1352
+ for (const { factory, spacings } of spacingsByFactory) {
1353
+ for (const spacing of spacings) jobs.push({ kind: "cl", factory, spacing });
1354
+ }
1355
+ const results = await client.multicall({
1356
+ allowFailure: true,
1357
+ contracts: jobs.map(
1358
+ (job) => job.kind === "v2" ? {
1359
+ address: addrs.poolFactory,
1360
+ abi: v2PoolFactoryAbi,
1361
+ functionName: "getPool",
1362
+ args: [tokenA, tokenB, job.stable]
1363
+ } : {
1364
+ address: job.factory,
1365
+ abi: clPoolFactoryAbi,
1366
+ functionName: "getPool",
1367
+ args: [tokenA, tokenB, job.spacing]
1368
+ }
1369
+ )
1370
+ });
1371
+ const token0 = tokenA.toLowerCase() < tokenB.toLowerCase() ? tokenA : tokenB;
1372
+ const token1 = token0.toLowerCase() === tokenA.toLowerCase() ? tokenB : tokenA;
1373
+ const seen = /* @__PURE__ */ new Set();
1374
+ const pools = [];
1375
+ for (let i = 0; i < jobs.length; i++) {
1376
+ const job = jobs[i];
1377
+ const row = results[i];
1378
+ if (!row || row.status !== "success") continue;
1379
+ const lp = row.result;
1380
+ if (!lp || lp === zeroAddress) continue;
1381
+ const key = lp.toLowerCase();
1382
+ if (seen.has(key)) continue;
1383
+ seen.add(key);
1384
+ if (job.kind === "v2") {
1385
+ pools.push({
1386
+ lp: checksum(lp),
1387
+ type: job.stable ? 0 : -1,
1388
+ token0,
1389
+ token1,
1390
+ factory: addrs.poolFactory,
1391
+ poolFee: "0",
1392
+ kind: job.stable ? "stable" : "volatile"
1393
+ });
1394
+ } else {
1395
+ pools.push({
1396
+ lp: checksum(lp),
1397
+ type: job.spacing,
1398
+ token0,
1399
+ token1,
1400
+ factory: checksum(job.factory),
1401
+ poolFee: "0",
1402
+ kind: "cl"
1403
+ });
1404
+ }
1405
+ }
1406
+ return pools;
1407
+ }
1408
+ function tokenLabel(raw, resolved, isNative) {
1409
+ if (isNative) return "ETH";
1410
+ return lookupAerodromeKnownToken(raw)?.symbol || lookupAerodromeKnownToken(resolved)?.symbol || resolved.slice(0, 8);
1411
+ }
1412
+ async function aerodromeDiscoverPoolsSummary(args) {
1413
+ if (!isAerodromeChainSupported(args.chainId)) {
1414
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
1415
+ }
1416
+ const rpcUrl = requireAerodromeRpcUrl(args.rpcUrl);
1417
+ const addrs = await loadAerodromeAddresses();
1418
+ const isNative = isAerodromeNativeToken(args.token);
1419
+ const token = resolveAerodromeErc20OrWeth(args.token, addrs.weth);
1420
+ await assertAerodromeStockIssued({ rpcUrl, token, isNative, label: args.token });
1421
+ const counterpartRaw = (args.tokenOut ?? "").trim() || "USDC";
1422
+ const isNativeOut = isAerodromeNativeToken(counterpartRaw);
1423
+ const tokenOut = resolveAerodromeErc20OrWeth(counterpartRaw, addrs.weth);
1424
+ await assertAerodromeStockIssued({ rpcUrl, token: tokenOut, isNative: isNativeOut, label: counterpartRaw });
1425
+ if (token.toLowerCase() === tokenOut.toLowerCase()) {
1426
+ throw new Error("token and tokenOut must differ (default tokenOut is USDC).");
1427
+ }
1428
+ const pools = await aerodromeDiscoverConnectorPools({ rpcUrl, tokenIn: token, tokenOut, addrs });
1429
+ const inLabel = tokenLabel(args.token, token, isNative);
1430
+ const outLabel = tokenLabel(counterpartRaw, tokenOut, isNativeOut);
1431
+ return {
1432
+ chainId: AERODROME_CHAIN_ID,
1433
+ token: {
1434
+ address: isNative ? zeroAddress : token,
1435
+ symbol: inLabel,
1436
+ decimals: lookupAerodromeKnownToken(args.token)?.decimals ?? lookupAerodromeKnownToken(token)?.decimals ?? null
1437
+ },
1438
+ tokenOut: {
1439
+ address: isNativeOut ? zeroAddress : tokenOut,
1440
+ symbol: outLabel,
1441
+ decimals: lookupAerodromeKnownToken(counterpartRaw)?.decimals ?? lookupAerodromeKnownToken(tokenOut)?.decimals ?? null
1442
+ },
1443
+ pools: pools.map((p) => ({
1444
+ lp: p.lp,
1445
+ kind: p.kind,
1446
+ tickSpacing: p.kind === "cl" ? p.type : void 0,
1447
+ token0: p.token0,
1448
+ token1: p.token1,
1449
+ factory: p.factory
1450
+ })),
1451
+ notes: pools.length === 0 ? `No factory pool on the ${inLabel}/${outLabel} connector graph (direct + WETH/USDC/AERO hops). B20 stocks LP vs USDC; ETH\u2192stock quotes hop WETH\u2192USDC\u2192stock. Call ctm_aerodrome_quote with tickers for a price.` : `Factory-discovered pools on the ${inLabel}/${outLabel} connector graph (v2 + Slipstream 1/2/3). Not a Sugar dump. Call ctm_aerodrome_quote({ tokenIn: "${inLabel}", tokenOut: "${outLabel}", amountHuman }) for a price.`
1452
+ };
1453
+ }
1454
+ function poolKind(type) {
1455
+ if (type === 0) return "stable";
1456
+ if (type === -1) return "volatile";
1457
+ return "cl";
1458
+ }
1459
+ async function paginate(pageSize, maxPages, readPage) {
1460
+ const out = [];
1461
+ for (let page = 0; page < maxPages; page++) {
1462
+ const rows = await readPage(BigInt(pageSize), BigInt(page * pageSize));
1463
+ out.push(...rows);
1464
+ if (rows.length < pageSize) break;
1465
+ }
1466
+ return out;
1467
+ }
1468
+ async function aerodromeFetchListedTokens(args) {
1469
+ if (!isAerodromeChainSupported(args.chainId)) {
1470
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
1471
+ }
1472
+ const addrs = await loadAerodromeAddresses();
1473
+ const client = aerodromePublicClient(args.rpcUrl);
1474
+ const account = args.account?.trim() ? getAddress(args.account) : zeroAddress;
1475
+ const extra = [...aerodromeCoinbaseStockAddresses(), ...args.extraAddresses ?? []].map((a) => {
1476
+ try {
1477
+ return getAddress(a);
1478
+ } catch {
1479
+ return null;
1480
+ }
1481
+ }).filter((a) => Boolean(a));
1482
+ const rows = await paginate(
1483
+ 200,
1484
+ 15,
1485
+ (limit, offset) => client.readContract({
1486
+ address: addrs.lpSugar,
1487
+ abi: lpSugarAbi,
1488
+ functionName: "tokens",
1489
+ args: [limit, offset, account, extra]
1490
+ })
1491
+ );
1492
+ const seen = /* @__PURE__ */ new Set();
1493
+ const tokens = [];
1494
+ for (const t of rows) {
1495
+ const address = getAddress(t.token_address);
1496
+ const key = address.toLowerCase();
1497
+ if (seen.has(key) || address === zeroAddress) continue;
1498
+ seen.add(key);
1499
+ tokens.push({
1500
+ address,
1501
+ symbol: t.symbol,
1502
+ decimals: Number(t.decimals),
1503
+ listed: Boolean(t.listed),
1504
+ emerging: Boolean(t.emerging),
1505
+ accountBalance: t.account_balance.toString()
1506
+ });
1507
+ }
1508
+ return tokens;
1509
+ }
1510
+ async function aerodromeFetchSwapPools(args) {
1511
+ if (!isAerodromeChainSupported(args.chainId)) {
1512
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
1513
+ }
1514
+ const addrs = await loadAerodromeAddresses();
1515
+ const client = aerodromePublicClient(args.rpcUrl);
1516
+ const rows = await paginate(
1517
+ 200,
1518
+ 20,
1519
+ (limit, offset) => client.readContract({
1520
+ address: addrs.lpSugar,
1521
+ abi: lpSugarAbi,
1522
+ functionName: "forSwaps",
1523
+ args: [limit, offset]
1524
+ })
1525
+ );
1526
+ return rows.map((p) => ({
1527
+ lp: getAddress(p.lp),
1528
+ type: Number(p.type),
1529
+ token0: getAddress(p.token0),
1530
+ token1: getAddress(p.token1),
1531
+ factory: getAddress(p.factory),
1532
+ poolFee: p.pool_fee.toString(),
1533
+ kind: poolKind(Number(p.type))
1534
+ }));
1535
+ }
1536
+ async function aerodromeFetchPools(args) {
1537
+ if (!isAerodromeChainSupported(args.chainId)) {
1538
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
1539
+ }
1540
+ const addrs = await loadAerodromeAddresses();
1541
+ const client = aerodromePublicClient(args.rpcUrl);
1542
+ const limit = BigInt(args.limit ?? 50);
1543
+ const offset = BigInt(args.offset ?? 0);
1544
+ const rows = await client.readContract({
1545
+ address: addrs.lpSugar,
1546
+ abi: lpSugarAbi,
1547
+ functionName: "all",
1548
+ args: [limit, offset, 0n]
1549
+ });
1550
+ return rows.map(mapSugarLp);
1551
+ }
1552
+ function mapSugarLp(p) {
1553
+ return {
1554
+ lp: getAddress(p.lp),
1555
+ symbol: p.symbol,
1556
+ type: Number(p.type),
1557
+ kind: poolKind(Number(p.type)),
1558
+ token0: getAddress(p.token0),
1559
+ token1: getAddress(p.token1),
1560
+ gauge: getAddress(p.gauge),
1561
+ gaugeAlive: Boolean(p.gauge_alive),
1562
+ nfpm: getAddress(p.nfpm),
1563
+ reserve0: p.reserve0.toString(),
1564
+ reserve1: p.reserve1.toString()
1565
+ };
1566
+ }
1567
+ async function aerodromeFetchPoolsByAddress(args) {
1568
+ if (!isAerodromeChainSupported(args.chainId)) {
1569
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
1570
+ }
1571
+ const addrs = await loadAerodromeAddresses();
1572
+ const client = aerodromePublicClient(args.rpcUrl);
1573
+ const unique = [];
1574
+ const seen = /* @__PURE__ */ new Set();
1575
+ for (const raw of args.lps) {
1576
+ try {
1577
+ const lp = getAddress(raw);
1578
+ const key = lp.toLowerCase();
1579
+ if (lp === zeroAddress || seen.has(key)) continue;
1580
+ seen.add(key);
1581
+ unique.push(lp);
1582
+ } catch {
1583
+ }
1584
+ }
1585
+ const out = /* @__PURE__ */ new Map();
1586
+ if (unique.length === 0) return out;
1587
+ const contracts = unique.flatMap((lp) => [
1588
+ { address: lp, abi: aerodromePoolMetaAbi, functionName: "symbol" },
1589
+ { address: lp, abi: aerodromePoolMetaAbi, functionName: "token0" },
1590
+ { address: lp, abi: aerodromePoolMetaAbi, functionName: "token1" },
1591
+ { address: lp, abi: aerodromePoolMetaAbi, functionName: "stable" },
1592
+ { address: addrs.voter, abi: aerodromeVoterAbi, functionName: "gauges", args: [lp] }
1593
+ ]);
1594
+ const results = await client.multicall({ allowFailure: true, contracts });
1595
+ const tokenAddrs = /* @__PURE__ */ new Set();
1596
+ const rawRows = [];
1597
+ for (let i = 0; i < unique.length; i++) {
1598
+ const base = i * 5;
1599
+ const symbolRow = results[base];
1600
+ const token0Row = results[base + 1];
1601
+ const token1Row = results[base + 2];
1602
+ const stableRow = results[base + 3];
1603
+ const gaugeRow = results[base + 4];
1604
+ if (token0Row?.status !== "success" || token1Row?.status !== "success") continue;
1605
+ if (typeof token0Row.result !== "string" || typeof token1Row.result !== "string") continue;
1606
+ const token0 = getAddress(token0Row.result);
1607
+ const token1 = getAddress(token1Row.result);
1608
+ tokenAddrs.add(token0.toLowerCase());
1609
+ tokenAddrs.add(token1.toLowerCase());
1610
+ const stable = stableRow?.status === "success" ? Boolean(stableRow.result) : null;
1611
+ rawRows.push({
1612
+ lp: unique[i],
1613
+ symbol: symbolRow?.status === "success" ? String(symbolRow.result || "") : "",
1614
+ token0,
1615
+ token1,
1616
+ kind: stable == null ? "cl" : stable ? "stable" : "volatile",
1617
+ gauge: gaugeRow?.status === "success" && typeof gaugeRow.result === "string" ? getAddress(gaugeRow.result) : zeroAddress
1618
+ });
1619
+ }
1620
+ const tokens = [...tokenAddrs].map((a) => getAddress(a));
1621
+ const tokenSymbols = /* @__PURE__ */ new Map();
1622
+ if (tokens.length > 0) {
1623
+ const syms = await client.multicall({
1624
+ allowFailure: true,
1625
+ contracts: tokens.map((token) => ({
1626
+ address: token,
1627
+ abi: erc20Abi,
1628
+ functionName: "symbol"
1629
+ }))
1630
+ });
1631
+ for (let i = 0; i < tokens.length; i++) {
1632
+ const row = syms[i];
1633
+ if (row?.status === "success" && row.result) tokenSymbols.set(tokens[i].toLowerCase(), String(row.result));
1634
+ }
1635
+ }
1636
+ for (const row of rawRows) {
1637
+ const kindTag = row.kind === "stable" ? "sAMM" : row.kind === "cl" ? "CL" : "vAMM";
1638
+ const s0 = tokenSymbols.get(row.token0.toLowerCase()) || "";
1639
+ const s1 = tokenSymbols.get(row.token1.toLowerCase()) || "";
1640
+ const pair = s0 && s1 ? `${kindTag}-${s0}/${s1}` : "";
1641
+ out.set(row.lp.toLowerCase(), {
1642
+ lp: row.lp,
1643
+ symbol: row.symbol.trim() || pair,
1644
+ type: row.kind === "stable" ? 0 : row.kind === "volatile" ? -1 : 100,
1645
+ kind: row.kind,
1646
+ token0: row.token0,
1647
+ token1: row.token1,
1648
+ gauge: row.gauge,
1649
+ gaugeAlive: row.gauge !== zeroAddress,
1650
+ nfpm: zeroAddress,
1651
+ reserve0: "0",
1652
+ reserve1: "0"
1653
+ });
1654
+ }
1655
+ return out;
1656
+ }
1657
+ async function aerodromeFetchPositions(args) {
1658
+ if (!isAerodromeChainSupported(args.chainId)) {
1659
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
1660
+ }
1661
+ const addrs = await loadAerodromeAddresses();
1662
+ const client = aerodromePublicClient(args.rpcUrl);
1663
+ const account = getAddress(args.account);
1664
+ const rows = await paginate(
1665
+ 50,
1666
+ 8,
1667
+ (limit, offset) => client.readContract({
1668
+ address: addrs.lpSugar,
1669
+ abi: lpSugarAbi,
1670
+ functionName: "positions",
1671
+ args: [limit, offset, account]
1672
+ })
1673
+ );
1674
+ return rows.map((p) => ({
1675
+ id: p.id.toString(),
1676
+ lp: getAddress(p.lp),
1677
+ liquidity: p.liquidity.toString(),
1678
+ staked: p.staked.toString(),
1679
+ amount0: p.amount0.toString(),
1680
+ amount1: p.amount1.toString(),
1681
+ unstakedEarned0: p.unstaked_earned0.toString(),
1682
+ unstakedEarned1: p.unstaked_earned1.toString(),
1683
+ emissionsEarned: p.emissions_earned.toString(),
1684
+ tickLower: Number(p.tick_lower),
1685
+ tickUpper: Number(p.tick_upper)
1686
+ }));
1687
+ }
1688
+ async function aerodromeFetchPositionsWithPools(args) {
1689
+ const positions = await aerodromeFetchPositions(args);
1690
+ const pools = await aerodromeFetchPoolsByAddress({
1691
+ chainId: args.chainId,
1692
+ rpcUrl: args.rpcUrl,
1693
+ lps: positions.map((p) => p.lp)
1694
+ });
1695
+ return positions.map((p) => {
1696
+ const pool = pools.get(p.lp.toLowerCase());
1697
+ const cl = pool?.kind === "cl" || p.id !== "0" || p.tickLower !== p.tickUpper;
1698
+ return {
1699
+ ...p,
1700
+ symbol: pool?.symbol ?? "",
1701
+ kind: pool?.kind ?? (cl ? "cl" : "volatile"),
1702
+ gauge: pool?.gauge ?? zeroAddress,
1703
+ gaugeAlive: pool?.gaugeAlive ?? false,
1704
+ token0: pool?.token0 ?? zeroAddress,
1705
+ token1: pool?.token1 ?? zeroAddress
1706
+ };
1707
+ }).filter(isMeaningfulAerodromePosition);
1708
+ }
1709
+ function isMeaningfulAerodromePosition(p) {
1710
+ if (p.kind === "cl" || p.id !== "0") return true;
1711
+ if (BigInt(p.staked || "0") > 0n) return true;
1712
+ if (BigInt(p.emissionsEarned || "0") > 0n) return true;
1713
+ return BigInt(p.liquidity || "0") >= 10n ** 12n;
1714
+ }
1715
+
1716
+ // src/protocols/evm/aerodrome/priceImpact.ts
1717
+ function aerodromeProbeAmountIn(amountIn) {
1718
+ if (amountIn <= 1n) return null;
1719
+ let probe = amountIn / 10000n;
1720
+ if (probe < 1n) probe = 1n;
1721
+ if (probe >= amountIn) return null;
1722
+ return probe;
1723
+ }
1724
+ function computeAerodromePriceImpactPercent(args) {
1725
+ const { amountIn, amountOut, probeIn, probeOut } = args;
1726
+ if (amountIn <= 0n || amountOut <= 0n || probeIn <= 0n || probeOut <= 0n) return null;
1727
+ const executed = amountOut * probeIn;
1728
+ const spot = amountIn * probeOut;
1729
+ if (spot === 0n) return null;
1730
+ if (executed >= spot) return 0;
1731
+ const scale = 100000000n;
1732
+ const scaled = (spot - executed) * scale * 100n / spot;
1733
+ const pct = Number(scaled) / Number(scale);
1734
+ if (!Number.isFinite(pct)) return null;
1735
+ return Math.max(0, pct);
1736
+ }
1737
+ function formatAerodromePriceImpact(percent0to100) {
1738
+ if (percent0to100 == null || !Number.isFinite(percent0to100) || percent0to100 < 0) return "\u2014";
1739
+ if (percent0to100 === 0) return "0%";
1740
+ if (percent0to100 < 1e-4) return "<0.0001%";
1741
+ const t = percent0to100 < 0.01 ? percent0to100.toFixed(4) : percent0to100 < 1 ? percent0to100.toFixed(3) : percent0to100 < 10 ? percent0to100.toFixed(2) : percent0to100.toFixed(1);
1742
+ return `${t.replace(/(\.\d*?[1-9])0+$/g, "$1").replace(/\.0$/, "")}%`;
1743
+ }
1744
+
1745
+ // src/protocols/evm/aerodrome/sdkSession.ts
1746
+ async function loadSdkJs() {
1747
+ try {
1748
+ return await import('@dromos-labs/sdk.js');
1749
+ } catch {
1750
+ return null;
1751
+ }
1752
+ }
1753
+ async function tryAerodromeSdkQuote(args) {
1754
+ const sdk = await loadSdkJs();
1755
+ if (!sdk?.getDefaultConfig || !sdk.getQuoteForSwap || !sdk.base) return null;
1756
+ try {
1757
+ const config = sdk.getDefaultConfig({
1758
+ chains: [{ chain: sdk.base, rpcUrl: args.rpcUrl }]
1759
+ });
1760
+ const quote = await sdk.getQuoteForSwap({
1761
+ config,
1762
+ fromToken: { address: args.tokenIn, chainId: AERODROME_CHAIN_ID },
1763
+ toToken: { address: args.tokenOut, chainId: AERODROME_CHAIN_ID },
1764
+ amountIn: args.amountIn
1765
+ });
1766
+ if (!quote) return null;
1767
+ const out = quote.amountOut ?? quote.expectedAmount;
1768
+ if (out == null || out <= 0n) return null;
1769
+ let priceImpactPercent = null;
1770
+ const probeIn = aerodromeProbeAmountIn(args.amountIn);
1771
+ if (probeIn != null) {
1772
+ try {
1773
+ const probeQuote = await sdk.getQuoteForSwap({
1774
+ config,
1775
+ fromToken: { address: args.tokenIn, chainId: AERODROME_CHAIN_ID },
1776
+ toToken: { address: args.tokenOut, chainId: AERODROME_CHAIN_ID },
1777
+ amountIn: probeIn
1778
+ });
1779
+ const probeOut = probeQuote?.amountOut ?? probeQuote?.expectedAmount;
1780
+ if (probeOut != null && probeOut > 0n) {
1781
+ priceImpactPercent = computeAerodromePriceImpactPercent({
1782
+ amountIn: args.amountIn,
1783
+ amountOut: out,
1784
+ probeIn,
1785
+ probeOut
1786
+ });
1787
+ }
1788
+ } catch {
1789
+ priceImpactPercent = null;
1790
+ }
1791
+ }
1792
+ return {
1793
+ chainId: AERODROME_CHAIN_ID,
1794
+ tokenIn: args.isNativeIn ? zeroAddress : args.tokenIn,
1795
+ tokenOut: args.isNativeOut ? zeroAddress : args.tokenOut,
1796
+ amountInWei: args.amountIn.toString(),
1797
+ amountOutWei: out.toString(),
1798
+ amountOutHuman: null,
1799
+ priceImpactPercent,
1800
+ hops: [],
1801
+ pathPacked: null,
1802
+ isNativeIn: args.isNativeIn,
1803
+ isNativeOut: args.isNativeOut,
1804
+ source: "sdk.js"
1805
+ };
1806
+ } catch {
1807
+ return null;
1808
+ }
1809
+ }
1810
+ async function tryAerodromeSdkUnsignedSwap(args) {
1811
+ const sdk = await loadSdkJs();
1812
+ if (!sdk?.getDefaultConfig || !sdk.swap || !sdk.base) return null;
1813
+ try {
1814
+ const config = sdk.getDefaultConfig({
1815
+ chains: [{ chain: sdk.base, rpcUrl: args.rpcUrl }]
1816
+ });
1817
+ const tx = await sdk.swap({
1818
+ config,
1819
+ quote: args.quote,
1820
+ account: args.account,
1821
+ slippage: args.slippage,
1822
+ unsignedTransactionOnly: true
1823
+ });
1824
+ if (!tx?.to || !tx.data) return null;
1825
+ return {
1826
+ to: getAddress(tx.to),
1827
+ data: tx.data.startsWith("0x") ? tx.data : `0x${tx.data}`,
1828
+ value: typeof tx.value === "bigint" ? tx.value : BigInt(tx.value ?? 0)
1829
+ };
1830
+ } catch {
1831
+ return null;
1832
+ }
1833
+ }
1834
+ var AERODROME_STABLE_FILLER = 2097152;
1835
+ var AERODROME_VOLATILE_FILLER = 4194304;
1836
+ var AERODROME_CL_FACTORY_OLDEST_BIT = 1048576;
1837
+ var AERODROME_CL_FACTORY_NEWEST_BIT = 524288;
1838
+ function aerodromeMixedPathFiller(hop) {
1839
+ if (hop.kind === "stable") return AERODROME_STABLE_FILLER;
1840
+ if (hop.kind === "volatile") return AERODROME_VOLATILE_FILLER;
1841
+ const f = hop.factory.toLowerCase();
1842
+ if (f === AERODROME_BASE_ADDRESSES_FALLBACK.clFactory3.toLowerCase()) {
1843
+ return hop.type | AERODROME_CL_FACTORY_NEWEST_BIT;
1844
+ }
1845
+ if (f === AERODROME_BASE_ADDRESSES_FALLBACK.clFactory.toLowerCase()) {
1846
+ return hop.type | AERODROME_CL_FACTORY_OLDEST_BIT;
1847
+ }
1848
+ return hop.type;
1849
+ }
1850
+ function encodeAerodromeMixedPath(hops) {
1851
+ if (hops.length === 0) throw new Error("Aerodrome path requires at least one hop.");
1852
+ const types = ["address"];
1853
+ const values = [hops[0].from];
1854
+ for (const hop of hops) {
1855
+ types.push("int24", "address");
1856
+ values.push(aerodromeMixedPathFiller(hop), hop.to);
1857
+ }
1858
+ return encodePacked(types, values);
1859
+ }
1860
+ function neighbors(pools, token) {
1861
+ const t = token.toLowerCase();
1862
+ return pools.filter(
1863
+ (p) => p.token0.toLowerCase() === t || p.token1.toLowerCase() === t
1864
+ );
1865
+ }
1866
+ function hopFromPool(pool, from) {
1867
+ const fromL = from.toLowerCase();
1868
+ const to = pool.token0.toLowerCase() === fromL ? pool.token1 : pool.token0;
1869
+ return {
1870
+ from: getAddress(from),
1871
+ to: getAddress(to),
1872
+ stable: pool.kind === "stable",
1873
+ factory: pool.factory,
1874
+ type: pool.type,
1875
+ kind: pool.kind,
1876
+ lp: pool.lp
1877
+ };
1878
+ }
1879
+ function findAerodromePaths(pools, tokenIn, tokenOut, maxDepth = 3) {
1880
+ const goal = tokenOut.toLowerCase();
1881
+ const start = getAddress(tokenIn);
1882
+ const out = [];
1883
+ const walk = (current, path, usedLp) => {
1884
+ if (path.length >= maxDepth) return;
1885
+ for (const pool of neighbors(pools, current)) {
1886
+ const lpKey = pool.lp.toLowerCase();
1887
+ if (usedLp.has(lpKey)) continue;
1888
+ const hop = hopFromPool(pool, current);
1889
+ const nextPath = [...path, hop];
1890
+ if (hop.to.toLowerCase() === goal) {
1891
+ out.push(nextPath);
1892
+ continue;
1893
+ }
1894
+ usedLp.add(lpKey);
1895
+ walk(hop.to, nextPath, usedLp);
1896
+ usedLp.delete(lpKey);
1897
+ }
1898
+ };
1899
+ walk(start, [], /* @__PURE__ */ new Set());
1900
+ return out.slice(0, 40);
1901
+ }
1902
+ function mergePools(into, extra) {
1903
+ const seen = new Set(into.map((p) => p.lp.toLowerCase()));
1904
+ const out = [...into];
1905
+ for (const p of extra) {
1906
+ const key = p.lp.toLowerCase();
1907
+ if (seen.has(key)) continue;
1908
+ seen.add(key);
1909
+ out.push(p);
1910
+ }
1911
+ return out;
1912
+ }
1913
+ async function quoteViaRouter(rpcUrl, router, amountIn, hops) {
1914
+ if (hops.some((h) => h.kind === "cl")) return null;
1915
+ const client = aerodromePublicClient(rpcUrl);
1916
+ try {
1917
+ const amounts = await client.readContract({
1918
+ address: router,
1919
+ abi: routerAbi,
1920
+ functionName: "getAmountsOut",
1921
+ args: [
1922
+ amountIn,
1923
+ hops.map((h) => ({ from: h.from, to: h.to, stable: h.stable, factory: h.factory }))
1924
+ ]
1925
+ });
1926
+ const last = amounts[amounts.length - 1];
1927
+ return last ?? null;
1928
+ } catch {
1929
+ return null;
1930
+ }
1931
+ }
1932
+ async function quoteViaMixedQuoter(rpcUrl, quoter, amountIn, hops, abi) {
1933
+ const client = aerodromePublicClient(rpcUrl);
1934
+ const path = encodeAerodromeMixedPath(hops);
1935
+ try {
1936
+ const res = await client.simulateContract({
1937
+ address: quoter,
1938
+ abi,
1939
+ functionName: "quoteExactInput",
1940
+ args: [path, amountIn]
1941
+ });
1942
+ return res.result[0];
1943
+ } catch {
1944
+ return null;
1945
+ }
1946
+ }
1947
+ async function quoteAerodromeHopsAmountOut(args) {
1948
+ const viaRouter = await quoteViaRouter(args.rpcUrl, args.router, args.amountIn, args.hops);
1949
+ if (viaRouter != null && viaRouter > 0n) return { out: viaRouter, source: "router" };
1950
+ const viaV3 = await quoteViaMixedQuoter(
1951
+ args.rpcUrl,
1952
+ args.mixedQuoterV3,
1953
+ args.amountIn,
1954
+ args.hops,
1955
+ mixedQuoterV3Abi
1956
+ );
1957
+ if (viaV3 != null && viaV3 > 0n) return { out: viaV3, source: "mixed_quoter" };
1958
+ const viaV1 = await quoteViaMixedQuoter(
1959
+ args.rpcUrl,
1960
+ args.mixedQuoter,
1961
+ args.amountIn,
1962
+ args.hops,
1963
+ mixedQuoterAbi
1964
+ );
1965
+ if (viaV1 != null && viaV1 > 0n) return { out: viaV1, source: "mixed_quoter" };
1966
+ return null;
1967
+ }
1968
+ async function readTokenDecimals(rpcUrl, token, isNative) {
1969
+ if (isNative) return 18;
1970
+ if (isAerodromeCoinbaseStockToken(token)) return AERODROME_COINBASE_STOCK_DECIMALS;
1971
+ if (token.toLowerCase() === AERODROME_USDC_BASE.toLowerCase()) return 6;
1972
+ const client = aerodromePublicClient(rpcUrl);
1973
+ return Number(
1974
+ await client.readContract({
1975
+ address: token,
1976
+ abi: erc20Abi,
1977
+ functionName: "decimals"
1978
+ })
1979
+ );
1980
+ }
1981
+ async function throwIfUnissuedStock(args) {
1982
+ await assertAerodromeStockIssued(args);
1983
+ }
1984
+ async function aerodromeQuote(args) {
1985
+ if (!isAerodromeChainSupported(args.chainId)) {
1986
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
1987
+ }
1988
+ const rpcUrl = requireAerodromeRpcUrl(args.rpcUrl);
1989
+ const addrs = await loadAerodromeAddresses();
1990
+ const isNativeIn = isAerodromeNativeToken(args.tokenIn);
1991
+ const isNativeOut = isAerodromeNativeToken(args.tokenOut);
1992
+ const tokenIn = resolveAerodromeErc20OrWeth(args.tokenIn, addrs.weth);
1993
+ const tokenOut = resolveAerodromeErc20OrWeth(args.tokenOut, addrs.weth);
1994
+ if (tokenIn.toLowerCase() === tokenOut.toLowerCase()) {
1995
+ throw new Error("tokenIn and tokenOut must differ.");
1996
+ }
1997
+ let decimals = args.tokenInDecimals;
1998
+ if (isAerodromeCoinbaseStockToken(tokenIn)) {
1999
+ decimals = AERODROME_COINBASE_STOCK_DECIMALS;
2000
+ } else if (decimals == null) {
2001
+ decimals = await readTokenDecimals(rpcUrl, tokenIn, isNativeIn);
2002
+ }
2003
+ const amountIn = parseUnits(args.amountHuman.trim(), decimals);
2004
+ if (amountIn <= 0n) throw new Error("amountHuman must be a positive decimal string.");
2005
+ const sdkQuote = await tryAerodromeSdkQuote({
2006
+ rpcUrl,
2007
+ tokenIn,
2008
+ tokenOut,
2009
+ amountIn,
2010
+ isNativeIn,
2011
+ isNativeOut
2012
+ });
2013
+ if (sdkQuote) return sdkQuote;
2014
+ let pools = await aerodromeDiscoverConnectorPools({ rpcUrl, tokenIn, tokenOut, addrs });
2015
+ let paths = findAerodromePaths(pools, tokenIn, tokenOut);
2016
+ if (paths.length === 0) {
2017
+ const sugarPools = await aerodromeFetchSwapPools({ chainId: AERODROME_CHAIN_ID, rpcUrl });
2018
+ pools = mergePools(pools, sugarPools);
2019
+ const connectors = [addrs.weth, getAddress(AERODROME_USDC_BASE), addrs.aero];
2020
+ const relevant = pools.filter((p) => {
2021
+ const a = p.token0.toLowerCase();
2022
+ const b = p.token1.toLowerCase();
2023
+ const tin = tokenIn.toLowerCase();
2024
+ const tout = tokenOut.toLowerCase();
2025
+ const conn = new Set(connectors.map((c) => c.toLowerCase()));
2026
+ return a === tin || b === tin || a === tout || b === tout || conn.has(a) || conn.has(b);
2027
+ });
2028
+ paths = findAerodromePaths(relevant, tokenIn, tokenOut);
2029
+ }
2030
+ if (paths.length === 0) {
2031
+ await throwIfUnissuedStock({ rpcUrl, token: tokenOut, isNative: isNativeOut, label: args.tokenOut });
2032
+ await throwIfUnissuedStock({ rpcUrl, token: tokenIn, isNative: isNativeIn, label: args.tokenIn });
2033
+ const tout = tokenOut.toLowerCase();
2034
+ const outHasPool = pools.some(
2035
+ (p) => p.token0.toLowerCase() === tout || p.token1.toLowerCase() === tout
2036
+ );
2037
+ if (!outHasPool) {
2038
+ throw new Error(
2039
+ `No Aerodrome pool for ${args.tokenOut}. This token may not be issued yet or has no liquidity on Aerodrome.`
2040
+ );
2041
+ }
2042
+ throw new Error("No Aerodrome route found for this pair.");
2043
+ }
2044
+ let best = null;
2045
+ for (const hops of paths) {
2046
+ const quoted = await quoteAerodromeHopsAmountOut({
2047
+ rpcUrl,
2048
+ router: addrs.router,
2049
+ mixedQuoter: addrs.mixedQuoter,
2050
+ mixedQuoterV3: addrs.mixedQuoterV3,
2051
+ amountIn,
2052
+ hops
2053
+ });
2054
+ if (!quoted) continue;
2055
+ if (!best || quoted.out > best.out) {
2056
+ best = { hops, out: quoted.out, source: quoted.source };
2057
+ }
2058
+ }
2059
+ if (!best) {
2060
+ await throwIfUnissuedStock({ rpcUrl, token: tokenOut, isNative: isNativeOut, label: args.tokenOut });
2061
+ await throwIfUnissuedStock({ rpcUrl, token: tokenIn, isNative: isNativeIn, label: args.tokenIn });
2062
+ throw new Error("Aerodrome quote returned zero for all candidate routes.");
2063
+ }
2064
+ let amountOutHuman = null;
2065
+ try {
2066
+ const outDecimals = await readTokenDecimals(rpcUrl, tokenOut, isNativeOut);
2067
+ amountOutHuman = formatUnits(best.out, outDecimals);
2068
+ } catch {
2069
+ amountOutHuman = null;
2070
+ }
2071
+ let priceImpactPercent = null;
2072
+ const probeIn = aerodromeProbeAmountIn(amountIn);
2073
+ if (probeIn != null) {
2074
+ const probeQuoted = await quoteAerodromeHopsAmountOut({
2075
+ rpcUrl,
2076
+ router: addrs.router,
2077
+ mixedQuoter: addrs.mixedQuoter,
2078
+ mixedQuoterV3: addrs.mixedQuoterV3,
2079
+ amountIn: probeIn,
2080
+ hops: best.hops
2081
+ });
2082
+ if (probeQuoted && probeQuoted.out > 0n) {
2083
+ priceImpactPercent = computeAerodromePriceImpactPercent({
2084
+ amountIn,
2085
+ amountOut: best.out,
2086
+ probeIn,
2087
+ probeOut: probeQuoted.out
2088
+ });
2089
+ }
2090
+ }
2091
+ return {
2092
+ chainId: AERODROME_CHAIN_ID,
2093
+ tokenIn: isNativeIn ? zeroAddress : tokenIn,
2094
+ tokenOut: isNativeOut ? zeroAddress : tokenOut,
2095
+ amountInWei: amountIn.toString(),
2096
+ amountOutWei: best.out.toString(),
2097
+ amountOutHuman,
2098
+ priceImpactPercent,
2099
+ hops: best.hops,
2100
+ pathPacked: encodeAerodromeMixedPath(best.hops),
2101
+ isNativeIn,
2102
+ isNativeOut,
2103
+ source: best.source
2104
+ };
2105
+ }
2106
+
2107
+ // src/core/purpose.ts
2108
+ function mergePurposeText(purposeText, purposeSuffix) {
2109
+ const t = (purposeText ?? "").trim();
2110
+ const suffix = (purposeSuffix ?? "").trim();
2111
+ if (!suffix) return t;
2112
+ return t ? `${t}
2113
+
2114
+ ${suffix}` : suffix;
2115
+ }
2116
+
2117
+ // src/core/envelope.ts
2118
+ function finalizeMultisign(input) {
2119
+ const { keyGen, destinationChainID, legs } = input;
2120
+ if (legs.length === 0) {
2121
+ throw new Error("finalizeMultisign requires at least one leg");
2122
+ }
2123
+ const ph = (keyGen.pubkeyhex ?? "").trim();
2124
+ if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
2125
+ const keyList = keyGen.keylist ?? [];
2126
+ const clientId = getClientIdFromKeyGenResult(keyGen);
2127
+ const first = legs[0];
2128
+ const messageHashes = legs.map((l) => l.msgHash);
2129
+ const messageRawBatch = legs.map((l) => l.msgRaw);
2130
+ const batchMeta = legs.map((l) => ({
2131
+ destinationAddress: l.destinationAddress,
2132
+ signatureText: l.signatureText,
2133
+ ...l.audit
2134
+ }));
2135
+ const proposalTxParams = legs.map((l) => l.proposalTxParams).filter((p) => p != null && typeof p === "object");
2136
+ const extraPayload = {
2137
+ batchMeta,
2138
+ ...input.extraJSON ?? {}
2139
+ };
2140
+ const extraJSON = JSON.stringify(extraPayload, (_, v) => typeof v === "bigint" ? v.toString() : v);
2141
+ const bodyForSign = {
2142
+ keyList,
2143
+ pubKey: ph,
2144
+ msgHash: messageHashes[0],
2145
+ msgRaw: first.msgRaw,
2146
+ destinationChainID,
2147
+ destinationAddress: input.destinationAddress ?? first.destinationAddress,
2148
+ extraJSON,
2149
+ signatureText: first.signatureText,
2150
+ purpose: mergePurposeText(input.purposeText, input.purposeSuffix),
2151
+ ...first.feeSnapshot
2152
+ };
2153
+ if (legs.length > 1) {
2154
+ bodyForSign.messageHashes = messageHashes;
2155
+ bodyForSign.messageRawBatch = messageRawBatch;
2156
+ }
2157
+ if (proposalTxParams.length > 0) {
2158
+ bodyForSign.proposalTxParams = proposalTxParams;
2159
+ }
2160
+ const valueWei = first.valueWei;
2161
+ if (valueWei != null && valueWei > 0n) {
2162
+ bodyForSign.value = valueWei.toString();
2163
+ }
2164
+ if (clientId) bodyForSign.clientId = clientId;
2165
+ if (input.expiryDate != null && input.expiryDate > 0) {
2166
+ bodyForSign.expiryDate = Math.floor(input.expiryDate);
2167
+ }
2168
+ return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
2169
+ }
2170
+ function routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimit) {
2171
+ if (chainGasLimit != null && Number.isFinite(chainGasLimit) && chainGasLimit > 0) {
2172
+ return gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit);
2173
+ }
2174
+ return (estimatedGas * 12n + 9n) / 10n;
2175
+ }
2176
+
2177
+ // src/chains/evm/buildBatch.ts
2178
+ async function buildEvmMultisignBatch(args) {
2179
+ const { context, steps } = args;
2180
+ const {
2181
+ chainId,
2182
+ rpcUrl,
2183
+ executorAddress,
2184
+ chainDetail,
2185
+ useCustomGas,
2186
+ customGasChainDetails,
2187
+ keyGen,
2188
+ purposeText
2189
+ } = context;
2190
+ if (steps.length === 0) throw new Error("buildEvmMultisignBatch requires at least one step");
2191
+ const ch = defineChain({
2192
+ id: chainId,
2193
+ name: "Destination",
2194
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
2195
+ rpcUrls: { default: { http: [rpcUrl] } }
2196
+ });
2197
+ const publicClient = createPublicClient({ chain: ch, transport: http(rpcUrl) });
2198
+ const feeParams = await fetchChainFeeParams(rpcUrl, chainId);
2199
+ const legacy = Boolean(chainDetail?.legacy) || !feeParams.isEip1559;
2200
+ const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
2201
+ const gasLimitConfig = useCustomGas && chainDetail?.gasLimit != null ? Number(chainDetail.gasLimit) : void 0;
2202
+ const chainGasLimitRouter = chainDetail?.gasLimit != null && Number.isFinite(Number(chainDetail.gasLimit)) && Number(chainDetail.gasLimit) > 0 ? Number(chainDetail.gasLimit) : void 0;
2203
+ const gasFeeMultiplier = useCustomGas && chainDetail?.gasMultiplier != null ? Number(chainDetail.gasMultiplier) : void 0;
2204
+ const executor = getAddress(executorAddress);
2205
+ const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
2206
+ const legs = [];
2207
+ for (let i = 0; i < steps.length; i++) {
2208
+ const step = steps[i];
2209
+ const currentNonce = baseNonce + i;
2210
+ let estimatedGas;
2211
+ if (args.estimateGasForStep) {
2212
+ estimatedGas = await args.estimateGasForStep({ step, index: i, publicClient, executor });
2213
+ } else {
2214
+ try {
2215
+ estimatedGas = await publicClient.estimateGas({
2216
+ to: step.to,
2217
+ data: step.data,
2218
+ value: step.value,
2219
+ account: executor
2220
+ });
2221
+ } catch {
2222
+ estimatedGas = step.fallbackGas ?? 100000n;
2223
+ }
2224
+ }
2225
+ let gasLimitI;
2226
+ if (args.resolveGasLimit) {
2227
+ gasLimitI = await args.resolveGasLimit({ step, index: i, estimatedGas, publicClient });
2228
+ } else if (step.routerSwap) {
2229
+ gasLimitI = routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimitRouter);
2230
+ } else {
2231
+ gasLimitI = useCustomGas ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
2232
+ }
2233
+ let proposalTxParams;
2234
+ let feeSnapshot;
2235
+ let serialized;
2236
+ if (legacy) {
2237
+ let gasPriceWei = await publicClient.getGasPrice();
2238
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
2239
+ gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
2240
+ }
2241
+ if (useCustomGas && chainDetail?.gasPrice != null && chainDetail.gasPrice > 0) {
2242
+ const configured = parseGwei(gweiToDecimalString(Number(chainDetail.gasPrice)));
2243
+ if (configured > gasPriceWei) gasPriceWei = configured;
2244
+ }
2245
+ serialized = serializeTransaction({
2246
+ type: "legacy",
2247
+ to: step.to,
2248
+ data: step.data,
2249
+ value: step.value,
2250
+ gas: gasLimitI,
2251
+ gasPrice: gasPriceWei,
2252
+ nonce: currentNonce,
2253
+ chainId
2254
+ });
2255
+ proposalTxParams = {
2256
+ nonce: currentNonce,
2257
+ gasLimit: gasLimitI.toString(),
2258
+ txType: "legacy",
2259
+ gasPrice: gasPriceWei.toString()
2260
+ };
2261
+ feeSnapshot = proposalTxParamsToFeeSnapshot(proposalTxParams);
2262
+ } else {
2263
+ const fetchedBase = feeParams.baseFeeGwei ?? 0;
2264
+ const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
2265
+ const configuredBase = useCustomGas && chainDetail?.baseFee != null ? Number(chainDetail.baseFee) : 0;
2266
+ const configuredPriority = useCustomGas && chainDetail?.priorityFee != null ? Number(chainDetail.priorityFee) : 0;
2267
+ const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
2268
+ const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
2269
+ const baseFeeMultiplierPct = useCustomGas && chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(chainDetail.baseFeeMultiplier)) : 100;
2270
+ const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
2271
+ const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
2272
+ let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : parseGwei("1");
2273
+ let maxFeePerGas = parseGwei(gweiToDecimalString(maxFeePerGasGwei));
2274
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
2275
+ maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
2276
+ maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
2277
+ }
2278
+ ({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
2279
+ maxFeePerGas,
2280
+ maxPriorityFeePerGas,
2281
+ latestBaseFeeWei
2282
+ ));
2283
+ serialized = serializeTransaction({
2284
+ type: "eip1559",
2285
+ to: step.to,
2286
+ data: step.data,
2287
+ value: step.value,
2288
+ gas: gasLimitI,
2289
+ maxFeePerGas,
2290
+ maxPriorityFeePerGas,
2291
+ nonce: currentNonce,
2292
+ chainId
2293
+ });
2294
+ proposalTxParams = {
2295
+ nonce: currentNonce,
2296
+ gasLimit: gasLimitI.toString(),
2297
+ txType: "eip1559",
2298
+ maxFeePerGas: maxFeePerGas.toString(),
2299
+ maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
2300
+ };
2301
+ feeSnapshot = i === 0 ? proposalTxParamsToFeeSnapshot(proposalTxParams) : {};
2302
+ }
2303
+ const h = keccak256(serialized);
2304
+ const msgHash = h.startsWith("0x") ? h.slice(2) : h;
2305
+ const batchMetaExtra = args.buildBatchMeta({ step, index: i, gasLimit: gasLimitI });
2306
+ legs.push({
2307
+ msgHash,
2308
+ msgRaw: i === 0 && args.firstMsgRawNo0x != null ? args.firstMsgRawNo0x : serialized,
2309
+ destinationAddress: step.to,
2310
+ signatureText: typeof batchMetaExtra.signatureText === "string" ? batchMetaExtra.signatureText : JSON.stringify(batchMetaExtra.signatureText ?? {}),
2311
+ audit: batchMetaExtra,
2312
+ feeSnapshot: i === 0 ? feeSnapshot : {},
2313
+ proposalTxParams,
2314
+ valueWei: i === 0 ? step.value : void 0
2315
+ });
2316
+ if (i === 0 && args.firstMsgRawNo0x != null) {
2317
+ legs[0].msgRaw = args.firstMsgRawNo0x;
2318
+ }
2319
+ }
2320
+ const extraJSON = {};
2321
+ if (useCustomGas && customGasChainDetails && Object.keys(customGasChainDetails).length > 0) {
2322
+ extraJSON.customGasChainDetails = customGasChainDetails;
2323
+ }
2324
+ const result = finalizeMultisign({
2325
+ keyGen,
2326
+ purposeText,
2327
+ purposeSuffix: args.purposeSuffix,
2328
+ destinationChainID: String(chainId),
2329
+ destinationAddress: args.destinationAddress ?? steps[0].to,
2330
+ legs,
2331
+ extraJSON: Object.keys(extraJSON).length > 0 ? extraJSON : void 0,
2332
+ expiryDate: context.expiryDate
2333
+ });
2334
+ const pv = args.payableValueWei;
2335
+ if (pv != null && pv > 0n) {
2336
+ result.bodyForSign.value = pv.toString();
2337
+ }
2338
+ return result;
2339
+ }
2340
+
2341
+ // src/protocols/evm/aerodrome/batch.ts
2342
+ async function pushErc20ApproveIfNeeded(args) {
2343
+ const client = aerodromePublicClient(args.rpcUrl);
2344
+ const allowance = await client.readContract({
2345
+ address: args.token,
2346
+ abi: erc20Abi,
2347
+ functionName: "allowance",
2348
+ args: [args.owner, args.spender]
2349
+ });
2350
+ if (allowance >= args.amountWei) return;
2351
+ if (allowance > 0n) {
2352
+ args.steps.push({
2353
+ to: args.token,
2354
+ data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [args.spender, 0n] }),
2355
+ value: 0n,
2356
+ kind: "approve",
2357
+ fallbackGas: 80000n
2358
+ });
2359
+ }
2360
+ args.steps.push({
2361
+ to: args.token,
2362
+ data: encodeFunctionData({
2363
+ abi: erc20Abi,
2364
+ functionName: "approve",
2365
+ args: [args.spender, args.amountWei]
2366
+ }),
2367
+ value: 0n,
2368
+ kind: "approve",
2369
+ fallbackGas: 80000n
2370
+ });
2371
+ }
2372
+ async function buildAerodromeMultisignBatch(args) {
2373
+ if (args.steps.length === 0) throw new Error("Aerodrome batch has no steps.");
2374
+ const executor = getAddress(args.executorAddress);
2375
+ const first = args.steps[0];
2376
+ const firstDataNo0x = first.data.startsWith("0x") ? first.data.slice(2) : first.data;
2377
+ const payableValueWei = args.steps.reduce((a, s) => a + s.value, 0n);
2378
+ const chainGasLimitRouter = args.chainDetail?.gasLimit != null && Number.isFinite(Number(args.chainDetail.gasLimit)) && Number(args.chainDetail.gasLimit) > 0 ? Number(args.chainDetail.gasLimit) : void 0;
2379
+ return buildEvmMultisignBatch({
2380
+ context: {
2381
+ chainCategory: "evm",
2382
+ keyGen: args.keyGen,
2383
+ purposeText: args.purposeText,
2384
+ chainId: AERODROME_CHAIN_ID,
2385
+ rpcUrl: args.rpcUrl,
2386
+ executorAddress: executor,
2387
+ chainDetail: args.chainDetail,
2388
+ useCustomGas: args.useCustomGas,
2389
+ customGasChainDetails: args.customGasChainDetails,
2390
+ expiryDate: args.expiryDate
2391
+ },
2392
+ steps: args.steps,
2393
+ purposeSuffix: args.purposeSuffix,
2394
+ firstMsgRawNo0x: firstDataNo0x,
2395
+ destinationAddress: first.to,
2396
+ payableValueWei: payableValueWei > 0n ? payableValueWei : void 0,
2397
+ resolveGasLimit: async ({ step, estimatedGas }) => {
2398
+ const s = step;
2399
+ if (s.routerSwap) return routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimitRouter);
2400
+ return estimatedGas;
2401
+ },
2402
+ buildBatchMeta: ({ step }) => args.meta(step)
2403
+ });
2404
+ }
2405
+
2406
+ // src/protocols/evm/aerodrome/swapMultisign.ts
2407
+ function applySlippage(amountOut, slippagePercent) {
2408
+ if (!Number.isFinite(slippagePercent) || slippagePercent <= 0 || slippagePercent >= 100) {
2409
+ throw new Error("Slippage must be between 0 and 100 (exclusive).");
2410
+ }
2411
+ const bps = BigInt(Math.round(slippagePercent * 100));
2412
+ return amountOut - amountOut * bps / 10000n;
2413
+ }
2414
+ function routerRoutes(hops) {
2415
+ return hops.map((h) => ({ from: h.from, to: h.to, stable: h.stable, factory: h.factory }));
2416
+ }
2417
+ async function appendSequentialMixedHops(args) {
2418
+ let remainingIn = args.amountIn;
2419
+ for (let i = 0; i < args.hops.length; i++) {
2420
+ const hop = args.hops[i];
2421
+ const isFirst = i === 0;
2422
+ const isLast = i === args.hops.length - 1;
2423
+ const quoted = await quoteAerodromeHopsAmountOut({
2424
+ rpcUrl: args.rpcUrl,
2425
+ router: args.addrs.router,
2426
+ mixedQuoter: args.addrs.mixedQuoter,
2427
+ mixedQuoterV3: args.addrs.mixedQuoterV3,
2428
+ amountIn: remainingIn,
2429
+ hops: [hop]
2430
+ });
2431
+ if (!quoted) {
2432
+ throw new Error(`Aerodrome hop ${i + 1} of ${args.hops.length} returned no quote.`);
2433
+ }
2434
+ const hopOutMin = isLast ? args.amountOutMin : applySlippage(quoted.out, args.slippagePercent);
2435
+ const payNative = isFirst && args.isNativeIn;
2436
+ if (hop.kind === "cl") {
2437
+ const swapRouter = aerodromeClSwapRouterForFactory(args.addrs, hop.factory);
2438
+ if (!payNative) {
2439
+ await pushErc20ApproveIfNeeded({
2440
+ steps: args.steps,
2441
+ rpcUrl: args.rpcUrl,
2442
+ token: hop.from,
2443
+ owner: args.executor,
2444
+ spender: swapRouter,
2445
+ amountWei: remainingIn
2446
+ });
2447
+ }
2448
+ args.steps.push({
2449
+ to: swapRouter,
2450
+ data: encodeFunctionData({
2451
+ abi: clSwapRouterAbi,
2452
+ functionName: "exactInputSingle",
2453
+ args: [
2454
+ {
2455
+ tokenIn: hop.from,
2456
+ tokenOut: hop.to,
2457
+ tickSpacing: hop.type,
2458
+ recipient: args.executor,
2459
+ deadline: args.deadline,
2460
+ amountIn: remainingIn,
2461
+ amountOutMinimum: hopOutMin,
2462
+ sqrtPriceLimitX96: 0n
2463
+ }
2464
+ ]
2465
+ }),
2466
+ value: payNative ? remainingIn : 0n,
2467
+ kind: "swap",
2468
+ routerSwap: true,
2469
+ fallbackGas: 400000n
2470
+ });
2471
+ } else {
2472
+ if (!payNative) {
2473
+ await pushErc20ApproveIfNeeded({
2474
+ steps: args.steps,
2475
+ rpcUrl: args.rpcUrl,
2476
+ token: hop.from,
2477
+ owner: args.executor,
2478
+ spender: args.addrs.router,
2479
+ amountWei: remainingIn
2480
+ });
2481
+ }
2482
+ const routes = routerRoutes([hop]);
2483
+ if (payNative) {
2484
+ args.steps.push({
2485
+ to: args.addrs.router,
2486
+ data: encodeFunctionData({
2487
+ abi: routerAbi,
2488
+ functionName: "swapExactETHForTokens",
2489
+ args: [hopOutMin, routes, args.executor, args.deadline]
2490
+ }),
2491
+ value: remainingIn,
2492
+ kind: "swap",
2493
+ routerSwap: true,
2494
+ fallbackGas: 350000n
2495
+ });
2496
+ } else if (isLast && args.isNativeOut) {
2497
+ args.steps.push({
2498
+ to: args.addrs.router,
2499
+ data: encodeFunctionData({
2500
+ abi: routerAbi,
2501
+ functionName: "swapExactTokensForETH",
2502
+ args: [remainingIn, hopOutMin, routes, args.executor, args.deadline]
2503
+ }),
2504
+ value: 0n,
2505
+ kind: "swap",
2506
+ routerSwap: true,
2507
+ fallbackGas: 350000n
2508
+ });
2509
+ } else {
2510
+ args.steps.push({
2511
+ to: args.addrs.router,
2512
+ data: encodeFunctionData({
2513
+ abi: routerAbi,
2514
+ functionName: "swapExactTokensForTokens",
2515
+ args: [remainingIn, hopOutMin, routes, args.executor, args.deadline]
2516
+ }),
2517
+ value: 0n,
2518
+ kind: "swap",
2519
+ routerSwap: true,
2520
+ fallbackGas: 350000n
2521
+ });
2522
+ }
2523
+ }
2524
+ remainingIn = hopOutMin;
2525
+ }
2526
+ }
2527
+ async function buildEvmMultisignBodyAerodromeSwapBatch(args) {
2528
+ if (args.chainId !== AERODROME_CHAIN_ID) {
2529
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
2530
+ }
2531
+ const executor = getAddress(args.executorAddress);
2532
+ const addrs = await loadAerodromeAddresses();
2533
+ const quote = args.quoteSnapshot?.hops?.length && args.quoteSnapshot.amountOutWei ? args.quoteSnapshot : await aerodromeQuote({
2534
+ chainId: AERODROME_CHAIN_ID,
2535
+ rpcUrl: args.rpcUrl,
2536
+ tokenIn: args.tokenIn,
2537
+ tokenOut: args.tokenOut,
2538
+ amountHuman: args.amountHuman
2539
+ });
2540
+ const amountIn = BigInt(quote.amountInWei);
2541
+ const amountOutMin = applySlippage(BigInt(quote.amountOutWei), args.slippagePercent);
2542
+ const isNativeIn = quote.isNativeIn || isAerodromeNativeToken(args.tokenIn);
2543
+ const isNativeOut = quote.isNativeOut || isAerodromeNativeToken(args.tokenOut);
2544
+ const hops = quote.hops;
2545
+ const deadline = aerodromeOnChainDeadlineUnix(args.expiryDate);
2546
+ const steps = [];
2547
+ const sdkTx = await tryAerodromeSdkUnsignedSwap({
2548
+ rpcUrl: args.rpcUrl,
2549
+ account: executor,
2550
+ quote,
2551
+ slippage: args.slippagePercent / 100
2552
+ });
2553
+ if (sdkTx) {
2554
+ const tokenInAddr = getAddress(isNativeIn ? addrs.weth : quote.tokenIn);
2555
+ if (!isNativeIn) {
2556
+ await pushErc20ApproveIfNeeded({
2557
+ steps,
2558
+ rpcUrl: args.rpcUrl,
2559
+ token: tokenInAddr,
2560
+ owner: executor,
2561
+ spender: sdkTx.to,
2562
+ amountWei: amountIn
2563
+ });
2564
+ }
2565
+ steps.push({
2566
+ to: sdkTx.to,
2567
+ data: sdkTx.data,
2568
+ value: sdkTx.value,
2569
+ kind: "swap",
2570
+ routerSwap: true,
2571
+ fallbackGas: 400000n
2572
+ });
2573
+ } else if (hops.length > 0 && hops.every((h) => h.kind !== "cl")) {
2574
+ const routes = routerRoutes(hops);
2575
+ if (!isNativeIn) {
2576
+ await pushErc20ApproveIfNeeded({
2577
+ steps,
2578
+ rpcUrl: args.rpcUrl,
2579
+ token: hops[0].from,
2580
+ owner: executor,
2581
+ spender: addrs.router,
2582
+ amountWei: amountIn
2583
+ });
2584
+ }
2585
+ if (isNativeIn) {
2586
+ steps.push({
2587
+ to: addrs.router,
2588
+ data: encodeFunctionData({
2589
+ abi: routerAbi,
2590
+ functionName: "swapExactETHForTokens",
2591
+ args: [amountOutMin, routes, executor, deadline]
2592
+ }),
2593
+ value: amountIn,
2594
+ kind: "swap",
2595
+ routerSwap: true,
2596
+ fallbackGas: 350000n
2597
+ });
2598
+ } else if (isNativeOut) {
2599
+ steps.push({
2600
+ to: addrs.router,
2601
+ data: encodeFunctionData({
2602
+ abi: routerAbi,
2603
+ functionName: "swapExactTokensForETH",
2604
+ args: [amountIn, amountOutMin, routes, executor, deadline]
2605
+ }),
2606
+ value: 0n,
2607
+ kind: "swap",
2608
+ routerSwap: true,
2609
+ fallbackGas: 350000n
2610
+ });
2611
+ } else {
2612
+ steps.push({
2613
+ to: addrs.router,
2614
+ data: encodeFunctionData({
2615
+ abi: routerAbi,
2616
+ functionName: "swapExactTokensForTokens",
2617
+ args: [amountIn, amountOutMin, routes, executor, deadline]
2618
+ }),
2619
+ value: 0n,
2620
+ kind: "swap",
2621
+ routerSwap: true,
2622
+ fallbackGas: 350000n
2623
+ });
2624
+ }
2625
+ } else if (hops.length === 1 && hops[0].kind === "cl") {
2626
+ const hop = hops[0];
2627
+ const swapRouter = aerodromeClSwapRouterForFactory(addrs, hop.factory);
2628
+ if (!isNativeIn) {
2629
+ await pushErc20ApproveIfNeeded({
2630
+ steps,
2631
+ rpcUrl: args.rpcUrl,
2632
+ token: hop.from,
2633
+ owner: executor,
2634
+ spender: swapRouter,
2635
+ amountWei: amountIn
2636
+ });
2637
+ }
2638
+ steps.push({
2639
+ to: swapRouter,
2640
+ data: encodeFunctionData({
2641
+ abi: clSwapRouterAbi,
2642
+ functionName: "exactInputSingle",
2643
+ args: [
2644
+ {
2645
+ tokenIn: hop.from,
2646
+ tokenOut: hop.to,
2647
+ tickSpacing: hop.type,
2648
+ recipient: executor,
2649
+ deadline,
2650
+ amountIn,
2651
+ amountOutMinimum: amountOutMin,
2652
+ sqrtPriceLimitX96: 0n
2653
+ }
2654
+ ]
2655
+ }),
2656
+ value: isNativeIn ? amountIn : 0n,
2657
+ kind: "swap",
2658
+ routerSwap: true,
2659
+ fallbackGas: 400000n
2660
+ });
2661
+ } else if (hops.length > 1) {
2662
+ await appendSequentialMixedHops({
2663
+ steps,
2664
+ addrs,
2665
+ rpcUrl: args.rpcUrl,
2666
+ executor,
2667
+ hops,
2668
+ amountIn,
2669
+ amountOutMin,
2670
+ slippagePercent: args.slippagePercent,
2671
+ deadline,
2672
+ isNativeIn,
2673
+ isNativeOut
2674
+ });
2675
+ } else {
2676
+ throw new Error(
2677
+ "No unsigned Aerodrome swap path: install @dromos-labs/sdk.js for mixed routes, or use a v2 / single-hop CL quote."
2678
+ );
2679
+ }
2680
+ return buildAerodromeMultisignBatch({
2681
+ keyGen: args.keyGen,
2682
+ rpcUrl: args.rpcUrl,
2683
+ chainDetail: args.chainDetail,
2684
+ useCustomGas: args.useCustomGas,
2685
+ customGasChainDetails: args.customGasChainDetails,
2686
+ executorAddress: executor,
2687
+ purposeText: args.purposeText,
2688
+ expiryDate: args.expiryDate,
2689
+ steps,
2690
+ purposeSuffix: steps.length === 1 ? "Aerodrome: 1-tx swap." : `Aerodrome: ${steps.length}-tx batch \u2014 ERC-20 approve then swap.`,
2691
+ meta: (step) => step.kind === "approve" ? {
2692
+ signatureText: JSON.stringify({
2693
+ kind: "Aerodrome",
2694
+ name: "ERC20.approve",
2695
+ note: "Allowance for this swap amount only."
2696
+ }),
2697
+ evm: { type: "aerodrome_erc20_approve", version: 1, chainId: String(AERODROME_CHAIN_ID) }
2698
+ } : {
2699
+ signatureText: JSON.stringify({
2700
+ kind: "Aerodrome",
2701
+ name: "swap",
2702
+ hops: hops.map((h) => ({ from: h.from, to: h.to, kind: h.kind }))
2703
+ }),
2704
+ evm: { type: "aerodrome_swap", version: 1, chainId: String(AERODROME_CHAIN_ID) }
2705
+ }
2706
+ });
2707
+ }
2708
+ var OTHER_SIDE_DESIRED = 10n ** 36n;
2709
+ var Q96 = 1n << 96n;
2710
+ function pairTokenLabel(address, isNative) {
2711
+ if (isNative) return "ETH";
2712
+ if (address.toLowerCase() === AERODROME_WETH_BASE.toLowerCase()) return "WETH";
2713
+ if (address.toLowerCase() === AERODROME_USDC_BASE.toLowerCase()) return "USDC";
2714
+ const stock = AERODROME_COINBASE_STOCK_TOKENS.find((t) => t.address.toLowerCase() === address.toLowerCase());
2715
+ return stock?.symbol || `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
2716
+ }
2717
+ function noDirectPoolHint(args) {
2718
+ const a = pairTokenLabel(args.tokenA, args.isNativeA);
2719
+ const b = pairTokenLabel(args.tokenB, args.isNativeB);
2720
+ const stockAddr = isAerodromeB20AssetToken(args.tokenB) ? args.tokenB : isAerodromeB20AssetToken(args.tokenA) ? args.tokenA : null;
2721
+ if (stockAddr) {
2722
+ const sym = pairTokenLabel(stockAddr, false);
2723
+ return `No ${a}/${b} pool on Aerodrome. ${sym} liquidity is on Slipstream vs USDC (swaps hop through USDC). Pick USDC as the pair token to add ${sym}/USDC liquidity.`;
2724
+ }
2725
+ return `No vAMM, sAMM, or Slipstream pool for ${a}/${b}. This pair cannot be deposited as a single LP.`;
2726
+ }
2727
+ function formatLpHuman(wei, decimals) {
2728
+ const maxFrac = Math.min(Math.max(decimals, 0), 8);
2729
+ const dust = decimals > maxFrac ? 10n ** BigInt(decimals - maxFrac) : 1n;
2730
+ const rounded = dust === 1n ? wei : (wei + dust / 2n) / dust * dust;
2731
+ const s = formatUnits(rounded, decimals);
2732
+ if (!s.includes(".")) return s;
2733
+ const [i, f] = s.split(".");
2734
+ const trimmed = (f ?? "").replace(/0+$/, "");
2735
+ return trimmed ? `${i}.${trimmed}` : i ?? s;
2736
+ }
2737
+ async function aerodromeQuoteAddLiquidity(args) {
2738
+ if (!isAerodromeChainSupported(args.chainId)) {
2739
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
2740
+ }
2741
+ const addrs = await loadAerodromeAddresses();
2742
+ const client = aerodromePublicClient(args.rpcUrl);
2743
+ const isNativeA = isAerodromeNativeToken(args.tokenA);
2744
+ const isNativeB = isAerodromeNativeToken(args.tokenB);
2745
+ const tokenA = resolveAerodromeErc20OrWeth(args.tokenA, addrs.weth);
2746
+ const tokenB = resolveAerodromeErc20OrWeth(args.tokenB, addrs.weth);
2747
+ await assertAerodromeStockIssued({
2748
+ rpcUrl: args.rpcUrl,
2749
+ token: tokenA,
2750
+ isNative: isNativeA,
2751
+ label: args.tokenA
2752
+ });
2753
+ await assertAerodromeStockIssued({
2754
+ rpcUrl: args.rpcUrl,
2755
+ token: tokenB,
2756
+ isNative: isNativeB,
2757
+ label: args.tokenB
2758
+ });
2759
+ const rawA = (args.amountAHuman ?? "").trim().replace(/,/g, "");
2760
+ const rawB = (args.amountBHuman ?? "").trim().replace(/,/g, "");
2761
+ if (!rawA && !rawB) throw new Error("Enter amount A or amount B.");
2762
+ const decA = isAerodromeCoinbaseStockToken(tokenA) ? AERODROME_COINBASE_STOCK_DECIMALS : args.tokenADecimals ?? (isNativeA ? 18 : Number(await client.readContract({ address: tokenA, abi: erc20Abi, functionName: "decimals" })));
2763
+ const decB = isAerodromeCoinbaseStockToken(tokenB) ? AERODROME_COINBASE_STOCK_DECIMALS : args.tokenBDecimals ?? (isNativeB ? 18 : Number(await client.readContract({ address: tokenB, abi: erc20Abi, functionName: "decimals" })));
2764
+ const pool = await client.readContract({
2765
+ address: addrs.router,
2766
+ abi: routerAbi,
2767
+ functionName: "poolFor",
2768
+ args: [tokenA, tokenB, args.stable, addrs.poolFactory]
2769
+ });
2770
+ let emptyPool = pool === zeroAddress;
2771
+ if (!emptyPool) {
2772
+ try {
2773
+ const reserves = await client.readContract({
2774
+ address: pool,
2775
+ abi: aerodromePoolMetaAbi,
2776
+ functionName: "getReserves"
2777
+ });
2778
+ emptyPool = reserves[0] === 0n || reserves[1] === 0n;
2779
+ } catch {
2780
+ emptyPool = true;
2781
+ }
2782
+ }
2783
+ const amountAParsed = rawA ? parseUnits(rawA, decA) : 0n;
2784
+ const amountBParsed = rawB ? parseUnits(rawB, decB) : 0n;
2785
+ if (emptyPool) {
2786
+ const cl = await quoteClPairRatio({
2787
+ rpcUrl: args.rpcUrl,
2788
+ tokenA,
2789
+ tokenB,
2790
+ rawA,
2791
+ rawB,
2792
+ amountAParsed,
2793
+ amountBParsed});
2794
+ if (cl) {
2795
+ return {
2796
+ tokenA,
2797
+ tokenB,
2798
+ stable: args.stable,
2799
+ amountA: cl.amountA.toString(),
2800
+ amountB: cl.amountB.toString(),
2801
+ amountAHuman: formatLpHuman(cl.amountA, decA),
2802
+ amountBHuman: formatLpHuman(cl.amountB, decB),
2803
+ liquidity: "0",
2804
+ isNativeA,
2805
+ isNativeB,
2806
+ pool: cl.pool,
2807
+ emptyPool: false,
2808
+ kind: "cl",
2809
+ tickSpacing: cl.tickSpacing
2810
+ };
2811
+ }
2812
+ return {
2813
+ tokenA,
2814
+ tokenB,
2815
+ stable: args.stable,
2816
+ amountA: amountAParsed.toString(),
2817
+ amountB: amountBParsed.toString(),
2818
+ amountAHuman: rawA,
2819
+ amountBHuman: rawB,
2820
+ liquidity: "0",
2821
+ isNativeA,
2822
+ isNativeB,
2823
+ pool,
2824
+ emptyPool: true,
2825
+ seedable: false,
2826
+ hint: noDirectPoolHint({ tokenA, tokenB, isNativeA, isNativeB })
2827
+ };
2828
+ }
2829
+ const amountADesired = rawA ? amountAParsed : OTHER_SIDE_DESIRED;
2830
+ const amountBDesired = rawB ? amountBParsed : OTHER_SIDE_DESIRED;
2831
+ const [amountA, amountB, liquidity] = await client.readContract({
2832
+ address: addrs.router,
2833
+ abi: routerAbi,
2834
+ functionName: "quoteAddLiquidity",
2835
+ args: [tokenA, tokenB, args.stable, addrs.poolFactory, amountADesired, amountBDesired]
2836
+ });
2837
+ return {
2838
+ tokenA,
2839
+ tokenB,
2840
+ stable: args.stable,
2841
+ amountA: amountA.toString(),
2842
+ amountB: amountB.toString(),
2843
+ amountAHuman: formatLpHuman(amountA, decA),
2844
+ amountBHuman: formatLpHuman(amountB, decB),
2845
+ liquidity: liquidity.toString(),
2846
+ isNativeA,
2847
+ isNativeB,
2848
+ pool,
2849
+ emptyPool: false,
2850
+ kind: args.stable ? "stable" : "volatile",
2851
+ seedable: true
2852
+ };
2853
+ }
2854
+ async function quoteClPairRatio(args) {
2855
+ const directs = await aerodromeDiscoverDirectPools({
2856
+ rpcUrl: args.rpcUrl,
2857
+ tokenA: args.tokenA,
2858
+ tokenB: args.tokenB
2859
+ });
2860
+ const cls = directs.filter((p) => p.kind === "cl");
2861
+ if (cls.length === 0) return null;
2862
+ const client = aerodromePublicClient(args.rpcUrl);
2863
+ const meta = await client.multicall({
2864
+ allowFailure: true,
2865
+ contracts: cls.flatMap((p) => [
2866
+ { address: p.lp, abi: clPoolAbi, functionName: "liquidity" },
2867
+ { address: p.lp, abi: clPoolAbi, functionName: "slot0" }
2868
+ ])
2869
+ });
2870
+ let best = null;
2871
+ for (let i = 0; i < cls.length; i++) {
2872
+ const liqRow = meta[i * 2];
2873
+ const slotRow = meta[i * 2 + 1];
2874
+ if (slotRow?.status !== "success" || !slotRow.result) continue;
2875
+ const slot = slotRow.result;
2876
+ if (!Array.isArray(slot)) continue;
2877
+ const sqrtP = BigInt(slot[0]);
2878
+ if (sqrtP === 0n) continue;
2879
+ const liquidity = liqRow?.status === "success" && (typeof liqRow.result === "bigint" || typeof liqRow.result === "string" || typeof liqRow.result === "number") ? BigInt(liqRow.result) : 0n;
2880
+ if (!best || liquidity > best.liquidity) {
2881
+ best = { pool: cls[i].lp, tickSpacing: cls[i].type, sqrtP, liquidity };
2882
+ }
2883
+ }
2884
+ if (!best) return null;
2885
+ const token0 = args.tokenA.toLowerCase() < args.tokenB.toLowerCase() ? args.tokenA : args.tokenB;
2886
+ const aIs0 = args.tokenA.toLowerCase() === token0.toLowerCase();
2887
+ const addrs = await loadAerodromeAddresses();
2888
+ let amount0 = aIs0 ? args.amountAParsed : args.amountBParsed;
2889
+ let amount1 = aIs0 ? args.amountBParsed : args.amountAParsed;
2890
+ if (args.rawA && !args.rawB) {
2891
+ if (aIs0) {
2892
+ amount0 = args.amountAParsed;
2893
+ amount1 = await estimateClOtherSide(client, addrs.sugarSlipstreamHelper, amount0, best.sqrtP, true);
2894
+ } else {
2895
+ amount1 = args.amountAParsed;
2896
+ amount0 = await estimateClOtherSide(client, addrs.sugarSlipstreamHelper, amount1, best.sqrtP, false);
2897
+ }
2898
+ } else if (args.rawB && !args.rawA) {
2899
+ if (aIs0) {
2900
+ amount1 = args.amountBParsed;
2901
+ amount0 = await estimateClOtherSide(client, addrs.sugarSlipstreamHelper, amount1, best.sqrtP, false);
2902
+ } else {
2903
+ amount0 = args.amountBParsed;
2904
+ amount1 = await estimateClOtherSide(client, addrs.sugarSlipstreamHelper, amount0, best.sqrtP, true);
2905
+ }
2906
+ }
2907
+ return {
2908
+ pool: best.pool,
2909
+ tickSpacing: best.tickSpacing,
2910
+ amountA: aIs0 ? amount0 : amount1,
2911
+ amountB: aIs0 ? amount1 : amount0
2912
+ };
2913
+ }
2914
+ async function estimateClOtherSide(client, helper, amount, sqrtP, amountIs0) {
2915
+ try {
2916
+ const out = await client.readContract({
2917
+ address: helper,
2918
+ abi: sugarHelperAbi,
2919
+ functionName: amountIs0 ? "estimateAmount1" : "estimateAmount0",
2920
+ args: [amount, sqrtP, sqrtP, sqrtP]
2921
+ });
2922
+ if (out > 0n) return out;
2923
+ } catch {
2924
+ }
2925
+ if (sqrtP === 0n) return 0n;
2926
+ return amountIs0 ? amount * sqrtP / Q96 * sqrtP / Q96 : amount * Q96 / sqrtP * Q96 / sqrtP;
2927
+ }
2928
+ function minAfterSlippage(amount, slippagePercent) {
2929
+ if (!Number.isFinite(slippagePercent) || slippagePercent <= 0 || slippagePercent >= 100) {
2930
+ throw new Error("Slippage must be between 0 and 100 (exclusive).");
2931
+ }
2932
+ const bps = BigInt(Math.round(slippagePercent * 100));
2933
+ return amount - amount * bps / 10000n;
2934
+ }
2935
+ async function buildEvmMultisignBodyAerodromeAddLiquidityBatch(args) {
2936
+ const executor = getAddress(args.executorAddress);
2937
+ const addrs = await loadAerodromeAddresses();
2938
+ const quote = await aerodromeQuoteAddLiquidity({
2939
+ chainId: args.chainId,
2940
+ rpcUrl: args.rpcUrl,
2941
+ tokenA: args.tokenA,
2942
+ tokenB: args.tokenB,
2943
+ stable: args.stable,
2944
+ amountAHuman: args.amountAHuman,
2945
+ amountBHuman: args.amountBHuman
2946
+ });
2947
+ if (quote.emptyPool || quote.seedable === false) {
2948
+ throw new Error(
2949
+ quote.hint || "No Aerodrome pool to deposit into. Coinbase stocks such as AAPLc have no ETH pool \u2014 pair vs USDC."
2950
+ );
2951
+ }
2952
+ if (quote.kind === "cl") {
2953
+ throw new Error(
2954
+ `This pair\u2019s live liquidity is Slipstream (tick spacing ${quote.tickSpacing ?? "?"}). Use ctm_aerodrome_quote_cl_deposit + ctm_aerodrome_build_cl_mint_multisign. Basic addLiquidity would seed a new empty vAMM.`
2955
+ );
2956
+ }
2957
+ const amountA = BigInt(quote.amountA);
2958
+ const amountB = BigInt(quote.amountB);
2959
+ const minA = minAfterSlippage(amountA, args.slippagePercent);
2960
+ const minB = minAfterSlippage(amountB, args.slippagePercent);
2961
+ const deadline = aerodromeOnChainDeadlineUnix(args.expiryDate);
2962
+ const steps = [];
2963
+ if (!quote.isNativeA) {
2964
+ await pushErc20ApproveIfNeeded({
2965
+ steps,
2966
+ rpcUrl: args.rpcUrl,
2967
+ token: quote.tokenA,
2968
+ owner: executor,
2969
+ spender: addrs.router,
2970
+ amountWei: amountA
2971
+ });
2972
+ }
2973
+ if (!quote.isNativeB) {
2974
+ await pushErc20ApproveIfNeeded({
2975
+ steps,
2976
+ rpcUrl: args.rpcUrl,
2977
+ token: quote.tokenB,
2978
+ owner: executor,
2979
+ spender: addrs.router,
2980
+ amountWei: amountB
2981
+ });
2982
+ }
2983
+ if (quote.isNativeA || quote.isNativeB) {
2984
+ const token = quote.isNativeA ? quote.tokenB : quote.tokenA;
2985
+ const amountToken = quote.isNativeA ? amountB : amountA;
2986
+ const amountEth = quote.isNativeA ? amountA : amountB;
2987
+ const minToken = quote.isNativeA ? minB : minA;
2988
+ const minEth = quote.isNativeA ? minA : minB;
2989
+ steps.push({
2990
+ to: addrs.router,
2991
+ data: encodeFunctionData({
2992
+ abi: routerAbi,
2993
+ functionName: "addLiquidityETH",
2994
+ args: [token, args.stable, amountToken, minToken, minEth, executor, deadline]
2995
+ }),
2996
+ value: amountEth,
2997
+ kind: "add_liquidity",
2998
+ fallbackGas: 400000n
2999
+ });
3000
+ } else {
3001
+ steps.push({
3002
+ to: addrs.router,
3003
+ data: encodeFunctionData({
3004
+ abi: routerAbi,
3005
+ functionName: "addLiquidity",
3006
+ args: [
3007
+ quote.tokenA,
3008
+ quote.tokenB,
3009
+ args.stable,
3010
+ amountA,
3011
+ amountB,
3012
+ minA,
3013
+ minB,
3014
+ executor,
3015
+ deadline
3016
+ ]
3017
+ }),
3018
+ value: 0n,
3019
+ kind: "add_liquidity",
3020
+ fallbackGas: 400000n
3021
+ });
3022
+ }
3023
+ return buildAerodromeMultisignBatch({
3024
+ keyGen: args.keyGen,
3025
+ rpcUrl: args.rpcUrl,
3026
+ chainDetail: args.chainDetail,
3027
+ useCustomGas: args.useCustomGas,
3028
+ customGasChainDetails: args.customGasChainDetails,
3029
+ executorAddress: executor,
3030
+ purposeText: args.purposeText,
3031
+ expiryDate: args.expiryDate,
3032
+ steps,
3033
+ purposeSuffix: "Aerodrome: add basic-pool liquidity.",
3034
+ meta: () => ({
3035
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "addLiquidity", stable: args.stable }),
3036
+ evm: { type: "aerodrome_add_liquidity", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3037
+ })
3038
+ });
3039
+ }
3040
+ async function buildEvmMultisignBodyAerodromeRemoveLiquidityBatch(args) {
3041
+ const executor = getAddress(args.executorAddress);
3042
+ const addrs = await loadAerodromeAddresses();
3043
+ const client = aerodromePublicClient(args.rpcUrl);
3044
+ const lp = getAddress(args.lpToken);
3045
+ const dec = args.lpDecimals ?? Number(await client.readContract({ address: lp, abi: erc20Abi, functionName: "decimals" }));
3046
+ const liquidity = parseUnits(args.liquidityHuman.trim(), dec);
3047
+ const tokenA = resolveAerodromeErc20OrWeth(args.tokenA, addrs.weth);
3048
+ const tokenB = resolveAerodromeErc20OrWeth(args.tokenB, addrs.weth);
3049
+ const isNativeA = isAerodromeNativeToken(args.tokenA);
3050
+ const isNativeB = isAerodromeNativeToken(args.tokenB);
3051
+ const minA = parseUnits(args.amountAMinHuman?.trim() || "0", 18);
3052
+ const minB = parseUnits(args.amountBMinHuman?.trim() || "0", 18);
3053
+ const deadline = aerodromeOnChainDeadlineUnix(args.expiryDate);
3054
+ const steps = [];
3055
+ await pushErc20ApproveIfNeeded({
3056
+ steps,
3057
+ rpcUrl: args.rpcUrl,
3058
+ token: lp,
3059
+ owner: executor,
3060
+ spender: addrs.router,
3061
+ amountWei: liquidity
3062
+ });
3063
+ if (isNativeA || isNativeB) {
3064
+ const token = isNativeA ? tokenB : tokenA;
3065
+ steps.push({
3066
+ to: addrs.router,
3067
+ data: encodeFunctionData({
3068
+ abi: routerAbi,
3069
+ functionName: "removeLiquidityETH",
3070
+ args: [token, args.stable, liquidity, isNativeA ? minB : minA, isNativeA ? minA : minB, executor, deadline]
3071
+ }),
3072
+ value: 0n,
3073
+ kind: "remove_liquidity",
3074
+ fallbackGas: 350000n
3075
+ });
3076
+ } else {
3077
+ steps.push({
3078
+ to: addrs.router,
3079
+ data: encodeFunctionData({
3080
+ abi: routerAbi,
3081
+ functionName: "removeLiquidity",
3082
+ args: [tokenA, tokenB, args.stable, liquidity, minA, minB, executor, deadline]
3083
+ }),
3084
+ value: 0n,
3085
+ kind: "remove_liquidity",
3086
+ fallbackGas: 350000n
3087
+ });
3088
+ }
3089
+ return buildAerodromeMultisignBatch({
3090
+ keyGen: args.keyGen,
3091
+ rpcUrl: args.rpcUrl,
3092
+ chainDetail: args.chainDetail,
3093
+ useCustomGas: args.useCustomGas,
3094
+ customGasChainDetails: args.customGasChainDetails,
3095
+ executorAddress: executor,
3096
+ purposeText: args.purposeText,
3097
+ expiryDate: args.expiryDate,
3098
+ steps,
3099
+ purposeSuffix: "Aerodrome: remove basic-pool liquidity.",
3100
+ meta: () => ({
3101
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "removeLiquidity", lp }),
3102
+ evm: { type: "aerodrome_remove_liquidity", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3103
+ })
3104
+ });
3105
+ }
3106
+ async function aerodromeQuoteClDeposit(args) {
3107
+ if (!isAerodromeChainSupported(args.chainId)) {
3108
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
3109
+ }
3110
+ const addrs = await loadAerodromeAddresses();
3111
+ const client = aerodromePublicClient(args.rpcUrl);
3112
+ const token0 = resolveAerodromeErc20OrWeth(args.token0, addrs.weth);
3113
+ const token1 = resolveAerodromeErc20OrWeth(args.token1, addrs.weth);
3114
+ const dec0 = args.token0Decimals ?? (isAerodromeNativeToken(args.token0) ? 18 : Number(await client.readContract({ address: token0, abi: erc20Abi, functionName: "decimals" })));
3115
+ const dec1 = args.token1Decimals ?? (isAerodromeNativeToken(args.token1) ? 18 : Number(await client.readContract({ address: token1, abi: erc20Abi, functionName: "decimals" })));
3116
+ const helper = addrs.sugarSlipstreamHelper;
3117
+ const sqrt = BigInt(args.sqrtPriceX96 || "0");
3118
+ let amount0 = args.amount0Human?.trim() ? parseUnits(args.amount0Human.trim(), dec0) : 0n;
3119
+ let amount1 = args.amount1Human?.trim() ? parseUnits(args.amount1Human.trim(), dec1) : 0n;
3120
+ if (sqrt > 0n && amount0 > 0n && amount1 === 0n) {
3121
+ try {
3122
+ amount1 = await client.readContract({
3123
+ address: helper,
3124
+ abi: sugarHelperAbi,
3125
+ functionName: "estimateAmount1",
3126
+ args: [amount0, sqrt, sqrt, sqrt]
3127
+ });
3128
+ } catch {
3129
+ }
3130
+ } else if (sqrt > 0n && amount1 > 0n && amount0 === 0n) {
3131
+ try {
3132
+ amount0 = await client.readContract({
3133
+ address: helper,
3134
+ abi: sugarHelperAbi,
3135
+ functionName: "estimateAmount0",
3136
+ args: [amount1, sqrt, sqrt, sqrt]
3137
+ });
3138
+ } catch {
3139
+ }
3140
+ }
3141
+ return {
3142
+ token0,
3143
+ token1,
3144
+ amount0: amount0.toString(),
3145
+ amount1: amount1.toString(),
3146
+ tickSpacing: args.tickSpacing,
3147
+ tickLower: args.tickLower,
3148
+ tickUpper: args.tickUpper,
3149
+ sqrtPriceX96: args.sqrtPriceX96
3150
+ };
3151
+ }
3152
+ function minAfterSlippage2(amount, slippagePercent) {
3153
+ if (!Number.isFinite(slippagePercent) || slippagePercent <= 0 || slippagePercent >= 100) {
3154
+ throw new Error("Slippage must be between 0 and 100 (exclusive).");
3155
+ }
3156
+ const bps = BigInt(Math.round(slippagePercent * 100));
3157
+ return amount - amount * bps / 10000n;
3158
+ }
3159
+ async function buildEvmMultisignBodyAerodromeClMintBatch(args) {
3160
+ const executor = getAddress(args.executorAddress);
3161
+ const addrs = await loadAerodromeAddresses();
3162
+ const quote = await aerodromeQuoteClDeposit({
3163
+ chainId: args.chainId,
3164
+ rpcUrl: args.rpcUrl,
3165
+ token0: args.token0,
3166
+ token1: args.token1,
3167
+ amount0Human: args.amount0Human,
3168
+ amount1Human: args.amount1Human,
3169
+ tickSpacing: args.tickSpacing,
3170
+ tickLower: args.tickLower,
3171
+ tickUpper: args.tickUpper,
3172
+ sqrtPriceX96: args.sqrtPriceX96
3173
+ });
3174
+ const amount0 = BigInt(quote.amount0);
3175
+ const amount1 = BigInt(quote.amount1);
3176
+ const steps = [];
3177
+ let value = 0n;
3178
+ if (!isAerodromeNativeToken(args.token0)) {
3179
+ await pushErc20ApproveIfNeeded({
3180
+ steps,
3181
+ rpcUrl: args.rpcUrl,
3182
+ token: quote.token0,
3183
+ owner: executor,
3184
+ spender: addrs.nfpm,
3185
+ amountWei: amount0
3186
+ });
3187
+ } else {
3188
+ value += amount0;
3189
+ }
3190
+ if (!isAerodromeNativeToken(args.token1)) {
3191
+ await pushErc20ApproveIfNeeded({
3192
+ steps,
3193
+ rpcUrl: args.rpcUrl,
3194
+ token: quote.token1,
3195
+ owner: executor,
3196
+ spender: addrs.nfpm,
3197
+ amountWei: amount1
3198
+ });
3199
+ } else {
3200
+ value += amount1;
3201
+ }
3202
+ const poolExists = BigInt(args.sqrtPriceX96) === 0n;
3203
+ steps.push({
3204
+ to: addrs.nfpm,
3205
+ data: encodeFunctionData({
3206
+ abi: nfpmAbi,
3207
+ functionName: "mint",
3208
+ args: [
3209
+ {
3210
+ token0: quote.token0,
3211
+ token1: quote.token1,
3212
+ tickSpacing: args.tickSpacing,
3213
+ tickLower: args.tickLower,
3214
+ tickUpper: args.tickUpper,
3215
+ amount0Desired: amount0,
3216
+ amount1Desired: amount1,
3217
+ amount0Min: minAfterSlippage2(amount0, args.slippagePercent),
3218
+ amount1Min: minAfterSlippage2(amount1, args.slippagePercent),
3219
+ recipient: executor,
3220
+ deadline: aerodromeOnChainDeadlineUnix(args.expiryDate),
3221
+ sqrtPriceX96: poolExists ? 0n : BigInt(args.sqrtPriceX96)
3222
+ }
3223
+ ]
3224
+ }),
3225
+ value,
3226
+ kind: "cl_mint",
3227
+ fallbackGas: 600000n
3228
+ });
3229
+ return buildAerodromeMultisignBatch({
3230
+ keyGen: args.keyGen,
3231
+ rpcUrl: args.rpcUrl,
3232
+ chainDetail: args.chainDetail,
3233
+ useCustomGas: args.useCustomGas,
3234
+ customGasChainDetails: args.customGasChainDetails,
3235
+ executorAddress: executor,
3236
+ purposeText: args.purposeText,
3237
+ expiryDate: args.expiryDate,
3238
+ steps,
3239
+ purposeSuffix: "Aerodrome Slipstream: mint concentrated position NFT.",
3240
+ meta: () => ({
3241
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "nfpm.mint" }),
3242
+ evm: { type: "aerodrome_cl_mint", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3243
+ })
3244
+ });
3245
+ }
3246
+ async function buildEvmMultisignBodyAerodromeClIncreaseBatch(args) {
3247
+ const executor = getAddress(args.executorAddress);
3248
+ const addrs = await loadAerodromeAddresses();
3249
+ const amount0 = BigInt(args.amount0Wei);
3250
+ const amount1 = BigInt(args.amount1Wei);
3251
+ const token0 = resolveAerodromeErc20OrWeth(args.token0, addrs.weth);
3252
+ const token1 = resolveAerodromeErc20OrWeth(args.token1, addrs.weth);
3253
+ const steps = [];
3254
+ let value = 0n;
3255
+ if (!isAerodromeNativeToken(args.token0)) {
3256
+ await pushErc20ApproveIfNeeded({
3257
+ steps,
3258
+ rpcUrl: args.rpcUrl,
3259
+ token: token0,
3260
+ owner: executor,
3261
+ spender: addrs.nfpm,
3262
+ amountWei: amount0
3263
+ });
3264
+ } else value += amount0;
3265
+ if (!isAerodromeNativeToken(args.token1)) {
3266
+ await pushErc20ApproveIfNeeded({
3267
+ steps,
3268
+ rpcUrl: args.rpcUrl,
3269
+ token: token1,
3270
+ owner: executor,
3271
+ spender: addrs.nfpm,
3272
+ amountWei: amount1
3273
+ });
3274
+ } else value += amount1;
3275
+ steps.push({
3276
+ to: addrs.nfpm,
3277
+ data: encodeFunctionData({
3278
+ abi: nfpmAbi,
3279
+ functionName: "increaseLiquidity",
3280
+ args: [
3281
+ {
3282
+ tokenId: BigInt(args.tokenId),
3283
+ amount0Desired: amount0,
3284
+ amount1Desired: amount1,
3285
+ amount0Min: minAfterSlippage2(amount0, args.slippagePercent),
3286
+ amount1Min: minAfterSlippage2(amount1, args.slippagePercent),
3287
+ deadline: aerodromeOnChainDeadlineUnix(args.expiryDate)
3288
+ }
3289
+ ]
3290
+ }),
3291
+ value,
3292
+ kind: "cl_increase",
3293
+ fallbackGas: 400000n
3294
+ });
3295
+ return buildAerodromeMultisignBatch({
3296
+ keyGen: args.keyGen,
3297
+ rpcUrl: args.rpcUrl,
3298
+ chainDetail: args.chainDetail,
3299
+ useCustomGas: args.useCustomGas,
3300
+ customGasChainDetails: args.customGasChainDetails,
3301
+ executorAddress: executor,
3302
+ purposeText: args.purposeText,
3303
+ expiryDate: args.expiryDate,
3304
+ steps,
3305
+ purposeSuffix: "Aerodrome Slipstream: increase liquidity.",
3306
+ meta: () => ({
3307
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "nfpm.increaseLiquidity", tokenId: args.tokenId }),
3308
+ evm: { type: "aerodrome_cl_increase", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3309
+ })
3310
+ });
3311
+ }
3312
+ async function buildEvmMultisignBodyAerodromeClDecreaseBatch(args) {
3313
+ const executor = getAddress(args.executorAddress);
3314
+ const addrs = await loadAerodromeAddresses();
3315
+ const steps = [
3316
+ {
3317
+ to: addrs.nfpm,
3318
+ data: encodeFunctionData({
3319
+ abi: nfpmAbi,
3320
+ functionName: "decreaseLiquidity",
3321
+ args: [
3322
+ {
3323
+ tokenId: BigInt(args.tokenId),
3324
+ liquidity: BigInt(args.liquidity),
3325
+ amount0Min: BigInt(args.amount0MinWei ?? "0"),
3326
+ amount1Min: BigInt(args.amount1MinWei ?? "0"),
3327
+ deadline: aerodromeOnChainDeadlineUnix(args.expiryDate)
3328
+ }
3329
+ ]
3330
+ }),
3331
+ value: 0n,
3332
+ kind: "cl_decrease",
3333
+ fallbackGas: 300000n
3334
+ }
3335
+ ];
3336
+ return buildAerodromeMultisignBatch({
3337
+ keyGen: args.keyGen,
3338
+ rpcUrl: args.rpcUrl,
3339
+ chainDetail: args.chainDetail,
3340
+ useCustomGas: args.useCustomGas,
3341
+ customGasChainDetails: args.customGasChainDetails,
3342
+ executorAddress: executor,
3343
+ purposeText: args.purposeText,
3344
+ expiryDate: args.expiryDate,
3345
+ steps,
3346
+ purposeSuffix: "Aerodrome Slipstream: decrease liquidity.",
3347
+ meta: () => ({
3348
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "nfpm.decreaseLiquidity", tokenId: args.tokenId }),
3349
+ evm: { type: "aerodrome_cl_decrease", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3350
+ })
3351
+ });
3352
+ }
3353
+ async function buildEvmMultisignBodyAerodromeClCollectBatch(args) {
3354
+ const executor = getAddress(args.executorAddress);
3355
+ const addrs = await loadAerodromeAddresses();
3356
+ const max = 2n ** 128n - 1n;
3357
+ const steps = [
3358
+ {
3359
+ to: addrs.nfpm,
3360
+ data: encodeFunctionData({
3361
+ abi: nfpmAbi,
3362
+ functionName: "collect",
3363
+ args: [{ tokenId: BigInt(args.tokenId), recipient: executor, amount0Max: max, amount1Max: max }]
3364
+ }),
3365
+ value: 0n,
3366
+ kind: "cl_collect",
3367
+ fallbackGas: 200000n
3368
+ }
3369
+ ];
3370
+ return buildAerodromeMultisignBatch({
3371
+ keyGen: args.keyGen,
3372
+ rpcUrl: args.rpcUrl,
3373
+ chainDetail: args.chainDetail,
3374
+ useCustomGas: args.useCustomGas,
3375
+ customGasChainDetails: args.customGasChainDetails,
3376
+ executorAddress: executor,
3377
+ purposeText: args.purposeText,
3378
+ expiryDate: args.expiryDate,
3379
+ steps,
3380
+ purposeSuffix: "Aerodrome Slipstream: collect fees.",
3381
+ meta: () => ({
3382
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "nfpm.collect", tokenId: args.tokenId }),
3383
+ evm: { type: "aerodrome_cl_collect", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3384
+ })
3385
+ });
3386
+ }
3387
+ async function buildEvmMultisignBodyAerodromeClBurnBatch(args) {
3388
+ const executor = getAddress(args.executorAddress);
3389
+ const addrs = await loadAerodromeAddresses();
3390
+ const steps = [
3391
+ {
3392
+ to: addrs.nfpm,
3393
+ data: encodeFunctionData({
3394
+ abi: nfpmAbi,
3395
+ functionName: "burn",
3396
+ args: [BigInt(args.tokenId)]
3397
+ }),
3398
+ value: 0n,
3399
+ kind: "cl_burn",
3400
+ fallbackGas: 120000n
3401
+ }
3402
+ ];
3403
+ return buildAerodromeMultisignBatch({
3404
+ keyGen: args.keyGen,
3405
+ rpcUrl: args.rpcUrl,
3406
+ chainDetail: args.chainDetail,
3407
+ useCustomGas: args.useCustomGas,
3408
+ customGasChainDetails: args.customGasChainDetails,
3409
+ executorAddress: executor,
3410
+ purposeText: args.purposeText,
3411
+ expiryDate: args.expiryDate,
3412
+ steps,
3413
+ purposeSuffix: "Aerodrome Slipstream: burn empty position NFT.",
3414
+ meta: () => ({
3415
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "nfpm.burn", tokenId: args.tokenId }),
3416
+ evm: { type: "aerodrome_cl_burn", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3417
+ })
3418
+ });
3419
+ }
3420
+ async function buildEvmMultisignBodyAerodromeGaugeStakeBatch(args) {
3421
+ const executor = getAddress(args.executorAddress);
3422
+ const gauge = getAddress(args.gauge);
3423
+ const steps = [];
3424
+ if (args.cl) {
3425
+ if (!args.tokenId) throw new Error("tokenId is required to stake a Slipstream NFT.");
3426
+ if (!args.lpToken) throw new Error("nfpm / lpToken address is required to approve a Slipstream NFT.");
3427
+ const tokenId = BigInt(args.tokenId);
3428
+ const nfpm = getAddress(args.lpToken);
3429
+ steps.push({
3430
+ to: nfpm,
3431
+ data: encodeFunctionData({
3432
+ abi: nfpmAbi,
3433
+ functionName: "approve",
3434
+ args: [gauge, tokenId]
3435
+ }),
3436
+ value: 0n,
3437
+ kind: "nft_approve",
3438
+ fallbackGas: 80000n
3439
+ });
3440
+ steps.push({
3441
+ to: gauge,
3442
+ data: encodeFunctionData({ abi: clGaugeAbi, functionName: "deposit", args: [tokenId] }),
3443
+ value: 0n,
3444
+ kind: "gauge_stake",
3445
+ fallbackGas: 200000n
3446
+ });
3447
+ } else {
3448
+ if (!args.lpToken || !args.amountHuman) throw new Error("lpToken and amountHuman are required to stake basic LP.");
3449
+ const amount = parseUnits(args.amountHuman.trim(), args.lpDecimals ?? 18);
3450
+ await pushErc20ApproveIfNeeded({
3451
+ steps,
3452
+ rpcUrl: args.rpcUrl,
3453
+ token: getAddress(args.lpToken),
3454
+ owner: executor,
3455
+ spender: gauge,
3456
+ amountWei: amount
3457
+ });
3458
+ steps.push({
3459
+ to: gauge,
3460
+ data: encodeFunctionData({ abi: gaugeAbi, functionName: "deposit", args: [amount] }),
3461
+ value: 0n,
3462
+ kind: "gauge_stake",
3463
+ fallbackGas: 180000n
3464
+ });
3465
+ }
3466
+ return buildAerodromeMultisignBatch({
3467
+ keyGen: args.keyGen,
3468
+ rpcUrl: args.rpcUrl,
3469
+ chainDetail: args.chainDetail,
3470
+ useCustomGas: args.useCustomGas,
3471
+ customGasChainDetails: args.customGasChainDetails,
3472
+ executorAddress: executor,
3473
+ purposeText: args.purposeText,
3474
+ expiryDate: args.expiryDate,
3475
+ steps,
3476
+ purposeSuffix: "Aerodrome: stake LP in gauge.",
3477
+ meta: () => ({
3478
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "gauge.deposit" }),
3479
+ evm: { type: "aerodrome_gauge_stake", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3480
+ })
3481
+ });
3482
+ }
3483
+ async function buildEvmMultisignBodyAerodromeGaugeUnstakeBatch(args) {
3484
+ const executor = getAddress(args.executorAddress);
3485
+ const gauge = getAddress(args.gauge);
3486
+ const steps = [];
3487
+ if (args.cl) {
3488
+ if (!args.tokenId) throw new Error("tokenId is required to unstake a Slipstream NFT.");
3489
+ steps.push({
3490
+ to: gauge,
3491
+ data: encodeFunctionData({
3492
+ abi: clGaugeAbi,
3493
+ functionName: "withdraw",
3494
+ args: [BigInt(args.tokenId)]
3495
+ }),
3496
+ value: 0n,
3497
+ kind: "gauge_unstake",
3498
+ fallbackGas: 200000n
3499
+ });
3500
+ } else {
3501
+ if (!args.amountHuman) throw new Error("amountHuman is required to unstake basic LP.");
3502
+ const amount = parseUnits(args.amountHuman.trim(), args.lpDecimals ?? 18);
3503
+ steps.push({
3504
+ to: gauge,
3505
+ data: encodeFunctionData({ abi: gaugeAbi, functionName: "withdraw", args: [amount] }),
3506
+ value: 0n,
3507
+ kind: "gauge_unstake",
3508
+ fallbackGas: 180000n
3509
+ });
3510
+ }
3511
+ return buildAerodromeMultisignBatch({
3512
+ keyGen: args.keyGen,
3513
+ rpcUrl: args.rpcUrl,
3514
+ chainDetail: args.chainDetail,
3515
+ useCustomGas: args.useCustomGas,
3516
+ customGasChainDetails: args.customGasChainDetails,
3517
+ executorAddress: executor,
3518
+ purposeText: args.purposeText,
3519
+ expiryDate: args.expiryDate,
3520
+ steps,
3521
+ purposeSuffix: "Aerodrome: unstake LP from gauge.",
3522
+ meta: () => ({
3523
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "gauge.withdraw" }),
3524
+ evm: { type: "aerodrome_gauge_unstake", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3525
+ })
3526
+ });
3527
+ }
3528
+ async function buildEvmMultisignBodyAerodromeClaimFeesBatch(args) {
3529
+ const executor = getAddress(args.executorAddress);
3530
+ const pool = getAddress(args.pool);
3531
+ const steps = [
3532
+ {
3533
+ to: pool,
3534
+ data: encodeFunctionData({ abi: poolClaimFeesAbi, functionName: "claimFees" }),
3535
+ value: 0n,
3536
+ kind: "claim_fees",
3537
+ fallbackGas: 150000n
3538
+ }
3539
+ ];
3540
+ return buildAerodromeMultisignBatch({
3541
+ keyGen: args.keyGen,
3542
+ rpcUrl: args.rpcUrl,
3543
+ chainDetail: args.chainDetail,
3544
+ useCustomGas: args.useCustomGas,
3545
+ customGasChainDetails: args.customGasChainDetails,
3546
+ executorAddress: executor,
3547
+ purposeText: args.purposeText,
3548
+ expiryDate: args.expiryDate,
3549
+ steps,
3550
+ purposeSuffix: "Aerodrome: claim basic-pool fees.",
3551
+ meta: () => ({
3552
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "pool.claimFees", pool }),
3553
+ evm: { type: "aerodrome_claim_fees", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3554
+ })
3555
+ });
3556
+ }
3557
+ async function buildEvmMultisignBodyAerodromeClaimEmissionsBatch(args) {
3558
+ const executor = getAddress(args.executorAddress);
3559
+ const gauge = getAddress(args.gauge);
3560
+ const steps = [
3561
+ args.cl ? {
3562
+ to: gauge,
3563
+ data: encodeFunctionData({
3564
+ abi: clGaugeAbi,
3565
+ functionName: "getReward",
3566
+ args: [BigInt(args.tokenId ?? "0")]
3567
+ }),
3568
+ value: 0n,
3569
+ kind: "claim_emissions",
3570
+ fallbackGas: 150000n
3571
+ } : {
3572
+ to: gauge,
3573
+ data: encodeFunctionData({
3574
+ abi: gaugeAbi,
3575
+ functionName: "getReward",
3576
+ args: [executor]
3577
+ }),
3578
+ value: 0n,
3579
+ kind: "claim_emissions",
3580
+ fallbackGas: 150000n
3581
+ }
3582
+ ];
3583
+ return buildAerodromeMultisignBatch({
3584
+ keyGen: args.keyGen,
3585
+ rpcUrl: args.rpcUrl,
3586
+ chainDetail: args.chainDetail,
3587
+ useCustomGas: args.useCustomGas,
3588
+ customGasChainDetails: args.customGasChainDetails,
3589
+ executorAddress: executor,
3590
+ purposeText: args.purposeText,
3591
+ expiryDate: args.expiryDate,
3592
+ steps,
3593
+ purposeSuffix: "Aerodrome: claim gauge emissions.",
3594
+ meta: () => ({
3595
+ signatureText: JSON.stringify({ kind: "Aerodrome", name: "gauge.getReward" }),
3596
+ evm: { type: "aerodrome_claim_emissions", version: 1, chainId: String(AERODROME_CHAIN_ID) }
3597
+ })
3598
+ });
3599
+ }
3600
+
3601
+ // src/protocols/evm/aerodrome/purpose.ts
3602
+ function buildAerodromeSwapPurposePrefill(args) {
3603
+ const symIn = (args.tokenInSymbol || "").trim() || "token in";
3604
+ const symOut = (args.tokenOutSymbol || "").trim() || "token out";
3605
+ const slip = Number.parseFloat(String(args.slippageInput || "0.5").replace(/,/g, ""));
3606
+ const slipText = Number.isFinite(slip) ? String(Math.round(slip * 100) / 100) : "0.5";
3607
+ const amt = (args.amountIn || "").trim();
3608
+ const qo = (args.quoteOutAmount || "").trim();
3609
+ const mid = amt && qo ? `${amt} ${symIn} for an estimated ${qo} ${symOut}` : `${amt || "\u2026"} ${symIn} \u2192 ${symOut}`;
3610
+ return `Aerodrome: swap ${mid} (max slippage ${slipText}%) on Base (chainId 8453)`;
3611
+ }
3612
+ function buildAerodromeLiquidityPurposePrefill(args) {
3613
+ return `Aerodrome: ${args.action} ${args.tokenASymbol}/${args.tokenBSymbol} on Base (chainId 8453)`;
3614
+ }
3615
+ function buildAerodromeRewardsPurposePrefill(args) {
3616
+ const action = args?.action ?? "claim fees/emissions";
3617
+ const label = (args?.poolLabel || "").trim();
3618
+ const mid = label ? `${action} on ${label}` : action;
3619
+ return `Aerodrome: ${mid} on Base (chainId 8453)`;
3620
+ }
3621
+ function aerodromePositionLabel(row) {
3622
+ const symbol = (row.symbol || "").trim();
3623
+ if (symbol) {
3624
+ if (row.kind === "cl" && row.id && row.id !== "0") return `${symbol} #${row.id}`;
3625
+ return symbol;
3626
+ }
3627
+ const kind = row.kind === "stable" ? "sAMM" : row.kind === "cl" ? "CL" : "vAMM";
3628
+ const short = row.lp.length > 12 ? `${row.lp.slice(0, 6)}\u2026${row.lp.slice(-4)}` : row.lp;
3629
+ return row.id && row.id !== "0" ? `${kind} ${short} #${row.id}` : `${kind} ${short}`;
3630
+ }
3631
+ function aerodromePositionKey(row) {
3632
+ return `${row.lp.toLowerCase()}:${row.id || "0"}`;
3633
+ }
3634
+
3635
+ // src/protocols/evm/aerodrome/reads.ts
3636
+ function mergeStocksIntoListed(tokens, stocks) {
3637
+ const byAddr = new Map(tokens.map((t) => [t.address.toLowerCase(), { ...t }]));
3638
+ for (const stock of stocks) {
3639
+ const key = stock.address.toLowerCase();
3640
+ const prev = byAddr.get(key);
3641
+ byAddr.set(key, {
3642
+ address: stock.address,
3643
+ symbol: stock.symbol || prev?.symbol || "",
3644
+ decimals: stock.decimals || prev?.decimals || AERODROME_COINBASE_STOCK_DECIMALS,
3645
+ listed: prev?.listed ?? true,
3646
+ emerging: prev?.emerging ?? false,
3647
+ accountBalance: prev?.accountBalance ?? "0",
3648
+ name: stock.name,
3649
+ issued: stock.issued
3650
+ });
3651
+ }
3652
+ const stocksFirst = stocks.map((s) => byAddr.get(s.address.toLowerCase())).filter(Boolean);
3653
+ const rest = [...byAddr.values()].filter((t) => !isAerodromeB20AssetToken(t.address));
3654
+ return [...stocksFirst, ...rest];
3655
+ }
3656
+ async function aerodromeFetchListedTokensSummary(args) {
3657
+ const includeSugar = args.includeSugarListed !== false;
3658
+ const officialPromise = fetchOfficialBaseTokenizedStocks().catch(() => []);
3659
+ const tokens = includeSugar ? await aerodromeFetchListedTokens(args) : [];
3660
+ const official = await officialPromise;
3661
+ const stocks = await aerodromeResolveStockTokens({
3662
+ rpcUrl: args.rpcUrl,
3663
+ official,
3664
+ knownTokens: tokens
3665
+ });
3666
+ const addrs = await loadAerodromeAddresses();
3667
+ const core = [
3668
+ { address: zeroAddress, symbol: "ETH", decimals: 18, listed: true, emerging: false, accountBalance: "0" },
3669
+ {
3670
+ address: AERODROME_WETH_BASE,
3671
+ symbol: "WETH",
3672
+ decimals: 18,
3673
+ listed: true,
3674
+ emerging: false,
3675
+ accountBalance: "0"
3676
+ },
3677
+ {
3678
+ address: AERODROME_USDC_BASE,
3679
+ symbol: "USDC",
3680
+ decimals: 6,
3681
+ listed: true,
3682
+ emerging: false,
3683
+ accountBalance: "0"
3684
+ },
3685
+ { address: addrs.aero, symbol: "AERO", decimals: 18, listed: true, emerging: false, accountBalance: "0" }
3686
+ ];
3687
+ return {
3688
+ chainId: AERODROME_CHAIN_ID,
3689
+ native: { address: zeroAddress, symbol: "ETH", decimals: 18 },
3690
+ aero: { address: addrs.aero, symbol: "AERO", decimals: 18 },
3691
+ stocks,
3692
+ includeSugarListed: includeSugar,
3693
+ tokens: mergeStocksIntoListed([...core, ...tokens], stocks),
3694
+ notes: includeSugar ? "Full LpSugar.tokens plus Coinbase B20 stocks. issued is totalSupply > 0. For a price, call ctm_aerodrome_quote with the ticker \u2014 do not grep this list." : "Slim list: ETH, WETH, USDC, AERO, and Coinbase B20 stocks (AAPLc, \u2026). issued is totalSupply > 0. Do not use this for a price \u2014 call ctm_aerodrome_quote({ tokenIn, tokenOut, amountHuman }) with tickers. Pass includeSugarListed:true only if you need the full Sugar dump."
3695
+ };
3696
+ }
3697
+ async function aerodromeFetchPoolsSummary(args) {
3698
+ const pools = await aerodromeFetchPools(args);
3699
+ return { chainId: AERODROME_CHAIN_ID, pools };
3700
+ }
3701
+ async function aerodromeFetchPositionsSummary(args) {
3702
+ const account = (args.account ?? args.executorAddress ?? "").trim();
3703
+ if (!account) throw new Error("account or executorAddress is required.");
3704
+ if (!isAerodromeChainSupported(args.chainId)) {
3705
+ throw new Error(`Aerodrome is Base-only (chainId ${AERODROME_CHAIN_ID}).`);
3706
+ }
3707
+ const positions = await aerodromeFetchPositionsWithPools({
3708
+ chainId: args.chainId,
3709
+ rpcUrl: args.rpcUrl,
3710
+ account
3711
+ });
3712
+ return {
3713
+ chainId: AERODROME_CHAIN_ID,
3714
+ account,
3715
+ notes: "Dust v2 rows hidden (liquidity < 1e12 and no stake/emissions). Claim fees from the pool (claimFees.pool = lp). Claim emissions from the gauge in AERO (claimEmissions); CL staked also needs tokenId + cl. Skip emissions when claimEmissions is null.",
3716
+ positions: positions.map((p) => {
3717
+ const gaugeOk = Boolean(p.gauge) && p.gauge.toLowerCase() !== zeroAddress.toLowerCase();
3718
+ return {
3719
+ ...p,
3720
+ label: aerodromePositionLabel(p),
3721
+ stakedInGauge: BigInt(p.staked || "0") > 0n,
3722
+ claimFees: { pool: p.lp },
3723
+ claimEmissions: gaugeOk ? {
3724
+ gauge: p.gauge,
3725
+ ...p.kind === "cl" && p.id !== "0" ? { tokenId: p.id, cl: true } : { cl: false }
3726
+ } : null
3727
+ };
3728
+ })
3729
+ };
3730
+ }
3731
+
3732
+ // src/protocols/evm/aerodrome/index.ts
3733
+ var aerodromeProtocolModule = {
3734
+ id: AERODROME_PROTOCOL_ID,
3735
+ chainCategory: "evm",
3736
+ isChainSupported(ctx) {
3737
+ if (ctx.chainCategory !== "evm") return false;
3738
+ return isAerodromeChainSupported(ctx.chainId);
3739
+ },
3740
+ isTokenSupported(token) {
3741
+ if (token.category !== "evm") return false;
3742
+ return token.kind === "native" || token.kind === "erc20";
3743
+ },
3744
+ actions: [
3745
+ {
3746
+ id: "aerodrome.quote",
3747
+ protocolId: AERODROME_PROTOCOL_ID,
3748
+ chainCategory: "evm",
3749
+ description: "Quote an Aerodrome swap on Base via factory discovery + MixedRouteQuoterV3 / Router. Tickers (AAPLc, USDC, ETH) accepted.",
3750
+ commonParams: [],
3751
+ params: {
3752
+ chainId: { type: "number", required: true, description: `Must be ${AERODROME_CHAIN_ID}` },
3753
+ tokenIn: { type: "address", required: true, description: "Ticker (AAPLc, USDC, ETH, WETH, AERO) or 0x / 0x0 native" },
3754
+ tokenOut: { type: "address", required: true, description: "Ticker (AAPLc, USDC, ETH, WETH, AERO) or 0x / 0x0 native" },
3755
+ amountHuman: { type: "string", required: true, description: "Human-readable input amount (B20 stocks are 8 decimals)" }
3756
+ }
3757
+ },
3758
+ {
3759
+ id: "aerodrome.swap",
3760
+ protocolId: AERODROME_PROTOCOL_ID,
3761
+ chainCategory: "evm",
3762
+ description: "Swap via Aerodrome Router / Slipstream SwapRouter (optional ERC-20 approve batch)",
3763
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
3764
+ params: {
3765
+ tokenIn: { type: "address", required: true, description: "Ticker (AAPLc, USDC, ETH) or 0x / 0x0 native" },
3766
+ tokenOut: { type: "address", required: true, description: "Ticker (AAPLc, USDC, ETH) or 0x / 0x0 native" },
3767
+ amountHuman: { type: "string", required: true, description: "Human-readable input amount" },
3768
+ slippagePercent: { type: "number", required: true, description: "Slippage percent (0\u2013100 exclusive)" }
3769
+ }
3770
+ },
3771
+ {
3772
+ id: "aerodrome.add-liquidity",
3773
+ protocolId: AERODROME_PROTOCOL_ID,
3774
+ chainCategory: "evm",
3775
+ description: "Deposit into a basic vAMM/sAMM pool (one-sided amounts; tickers OK)",
3776
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
3777
+ params: {
3778
+ tokenA: { type: "address", required: true, description: "Ticker (AAPLc, USDC, ETH) or 0x / 0x0 native" },
3779
+ tokenB: { type: "address", required: true, description: "Ticker (AAPLc, USDC, ETH) or 0x / 0x0 native" },
3780
+ stable: { type: "boolean", required: true, description: "true = sAMM, false = vAMM" },
3781
+ amountAHuman: { type: "string", required: false, description: "Amount A; omit to solve from B" },
3782
+ amountBHuman: { type: "string", required: false, description: "Amount B; omit to solve from A" },
3783
+ slippagePercent: { type: "number", required: true, description: "Slippage percent" }
3784
+ }
3785
+ },
3786
+ {
3787
+ id: "aerodrome.remove-liquidity",
3788
+ protocolId: AERODROME_PROTOCOL_ID,
3789
+ chainCategory: "evm",
3790
+ description: "Withdraw from a basic vAMM/sAMM pool",
3791
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
3792
+ params: {
3793
+ lpToken: { type: "address", required: true, description: "LP token" },
3794
+ liquidityHuman: { type: "string", required: true, description: "LP amount" }
3795
+ }
3796
+ },
3797
+ {
3798
+ id: "aerodrome.cl-mint",
3799
+ protocolId: AERODROME_PROTOCOL_ID,
3800
+ chainCategory: "evm",
3801
+ description: "Mint a Slipstream concentrated liquidity NFT",
3802
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
3803
+ params: {
3804
+ token0: { type: "address", required: true, description: "Token0" },
3805
+ token1: { type: "address", required: true, description: "Token1" },
3806
+ tickSpacing: { type: "number", required: true, description: "Pool tick spacing" },
3807
+ tickLower: { type: "number", required: true, description: "Lower tick" },
3808
+ tickUpper: { type: "number", required: true, description: "Upper tick" }
3809
+ }
3810
+ },
3811
+ {
3812
+ id: "aerodrome.gauge-stake",
3813
+ protocolId: AERODROME_PROTOCOL_ID,
3814
+ chainCategory: "evm",
3815
+ description: "Stake basic LP tokens or a Slipstream NFT in a gauge",
3816
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
3817
+ params: {
3818
+ gauge: { type: "address", required: true, description: "Gauge address" }
3819
+ }
3820
+ },
3821
+ {
3822
+ id: "aerodrome.claim-fees",
3823
+ protocolId: AERODROME_PROTOCOL_ID,
3824
+ chainCategory: "evm",
3825
+ description: "Claim unstaked fees from the pool (not the gauge)",
3826
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
3827
+ params: { pool: { type: "address", required: true, description: "Pool lp from fetch_positions claimFees.pool" } }
3828
+ },
3829
+ {
3830
+ id: "aerodrome.claim-emissions",
3831
+ protocolId: AERODROME_PROTOCOL_ID,
3832
+ chainCategory: "evm",
3833
+ description: "Claim gauge emissions in AERO (not the pool)",
3834
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
3835
+ params: { gauge: { type: "address", required: true, description: "Gauge from fetch_positions claimEmissions" } }
3836
+ }
3837
+ ]
3838
+ };
3839
+ registerProtocolModule(aerodromeProtocolModule);
3840
+
3841
+ export { AERODROME_B20_ASSET_PREFIX, AERODROME_BASE_ADDRESSES_FALLBACK, AERODROME_CHAIN_ID, AERODROME_CL_FACTORY_NEWEST_BIT, AERODROME_CL_FACTORY_OLDEST_BIT, AERODROME_COINBASE_STOCK_DECIMALS, AERODROME_COINBASE_STOCK_TOKENS, AERODROME_NATIVE_PLACEHOLDER, AERODROME_PROTOCOL_ID, AERODROME_STABLE_FILLER, AERODROME_USDC_BASE, AERODROME_VOLATILE_FILLER, AERODROME_WETH_BASE, aerodromeClFactoryList, aerodromeClSwapRouterForFactory, aerodromeCoinbaseStockAddresses, aerodromeDiscoverConnectorPools, aerodromeDiscoverConnectorPoolsLoaded, aerodromeDiscoverDirectPools, aerodromeDiscoverPoolsSummary, aerodromeFallbackAddresses, aerodromeFetchListedTokens, aerodromeFetchListedTokensSummary, aerodromeFetchPools, aerodromeFetchPoolsByAddress, aerodromeFetchPoolsSummary, aerodromeFetchPositions, aerodromeFetchPositionsSummary, aerodromeFetchPositionsWithPools, aerodromeFetchSwapPools, aerodromeMixedPathFiller, aerodromeOnChainDeadlineUnix, aerodromePositionKey, aerodromePositionLabel, aerodromeProbeAmountIn, aerodromeProtocolModule, aerodromeQuote, aerodromeQuoteAddLiquidity, aerodromeQuoteClDeposit, aerodromeResolveStockTokens, aerodromeUnissuedStockMessage, assertAerodromeStockIssued, buildAerodromeLiquidityPurposePrefill, buildAerodromeRewardsPurposePrefill, buildAerodromeSwapPurposePrefill, buildEvmMultisignBodyAerodromeAddLiquidityBatch, buildEvmMultisignBodyAerodromeClBurnBatch, buildEvmMultisignBodyAerodromeClCollectBatch, buildEvmMultisignBodyAerodromeClDecreaseBatch, buildEvmMultisignBodyAerodromeClIncreaseBatch, buildEvmMultisignBodyAerodromeClMintBatch, buildEvmMultisignBodyAerodromeClaimEmissionsBatch, buildEvmMultisignBodyAerodromeClaimFeesBatch, buildEvmMultisignBodyAerodromeGaugeStakeBatch, buildEvmMultisignBodyAerodromeGaugeUnstakeBatch, buildEvmMultisignBodyAerodromeRemoveLiquidityBatch, buildEvmMultisignBodyAerodromeSwapBatch, computeAerodromePriceImpactPercent, encodeAerodromeMixedPath, fetchOfficialBaseTokenizedStocks, findAerodromePaths, formatAerodromePriceImpact, isAerodromeB20AssetToken, isAerodromeChainSupported, isAerodromeCoinbaseStockToken, isAerodromeNativeToken, loadAerodromeAddresses, looksLikeCoinbaseStockSymbol, lookupAerodromeKnownToken, parseBaseTokenizedStockDocs, parseSecurityMdxAddresses, parseSugarBaseEnv, quoteAerodromeHopsAmountOut, requireAerodromeRpcUrl, resetAerodromeAddressCache, resolveAerodromeErc20OrWeth, tryAerodromeSdkQuote, tryAerodromeSdkUnsignedSwap };
3842
+ //# sourceMappingURL=index.js.map
3843
+ //# sourceMappingURL=index.js.map