@spectratools/aborean-cli 0.7.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +2364 -723
  2. package/package.json +3 -2
package/dist/cli.js CHANGED
@@ -5,12 +5,12 @@ import { readFileSync } from "fs";
5
5
  import { dirname, resolve } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { checksumAddress as checksumAddress6 } from "@spectratools/cli-shared";
8
- import { Cli as Cli8, z as z8 } from "incur";
8
+ import { Cli as Cli8, z as z9 } from "incur";
9
9
  import { formatUnits as formatUnits4 } from "viem";
10
10
 
11
11
  // src/commands/cl.ts
12
- import { checksumAddress, isAddress } from "@spectratools/cli-shared";
13
- import { Cli, z } from "incur";
12
+ import { checksumAddress as checksumAddress2, isAddress } from "@spectratools/cli-shared";
13
+ import { Cli, z as z2 } from "incur";
14
14
  import { formatUnits, parseUnits } from "viem";
15
15
 
16
16
  // src/contracts/abis/CLFactory.abi.json
@@ -560,6 +560,47 @@ var RewardsDistributor_abi_default = [
560
560
  }
561
561
  ];
562
562
 
563
+ // src/contracts/abis/SwapRouter.abi.json
564
+ var SwapRouter_abi_default = [
565
+ {
566
+ type: "function",
567
+ name: "exactInputSingle",
568
+ inputs: [
569
+ {
570
+ name: "params",
571
+ type: "tuple",
572
+ internalType: "struct ISwapRouter.ExactInputSingleParams",
573
+ components: [
574
+ { name: "tokenIn", type: "address", internalType: "address" },
575
+ { name: "tokenOut", type: "address", internalType: "address" },
576
+ { name: "tickSpacing", type: "int24", internalType: "int24" },
577
+ { name: "recipient", type: "address", internalType: "address" },
578
+ { name: "deadline", type: "uint256", internalType: "uint256" },
579
+ { name: "amountIn", type: "uint256", internalType: "uint256" },
580
+ { name: "amountOutMinimum", type: "uint256", internalType: "uint256" },
581
+ { name: "sqrtPriceLimitX96", type: "uint160", internalType: "uint160" }
582
+ ]
583
+ }
584
+ ],
585
+ outputs: [{ name: "amountOut", type: "uint256", internalType: "uint256" }],
586
+ stateMutability: "payable"
587
+ },
588
+ {
589
+ type: "function",
590
+ name: "factory",
591
+ inputs: [],
592
+ outputs: [{ name: "", type: "address", internalType: "address" }],
593
+ stateMutability: "view"
594
+ },
595
+ {
596
+ type: "function",
597
+ name: "WETH9",
598
+ inputs: [],
599
+ outputs: [{ name: "", type: "address", internalType: "address" }],
600
+ stateMutability: "view"
601
+ }
602
+ ];
603
+
563
604
  // src/contracts/abis/V2Pool.abi.json
564
605
  var V2Pool_abi_default = [
565
606
  {
@@ -947,6 +988,7 @@ var nonfungiblePositionManagerAbi = NonfungiblePositionManager_abi_default;
947
988
  var poolFactoryAbi = PoolFactory_abi_default;
948
989
  var quoterV2Abi = QuoterV2_abi_default;
949
990
  var rewardsDistributorAbi = RewardsDistributor_abi_default;
991
+ var swapRouterAbi = SwapRouter_abi_default;
950
992
  var v2PoolAbi = V2Pool_abi_default;
951
993
  var v2RouterAbi = V2Router_abi_default;
952
994
  var voterAbi = Voter_abi_default;
@@ -1032,7 +1074,12 @@ var ABOREAN_ADDRESSES = {
1032
1074
  };
1033
1075
 
1034
1076
  // src/contracts/client.ts
1035
- import { http, createPublicClient, defineChain } from "viem";
1077
+ import {
1078
+ http,
1079
+ createPublicClient,
1080
+ createWalletClient,
1081
+ defineChain
1082
+ } from "viem";
1036
1083
  var abstractMainnet = defineChain({
1037
1084
  id: 2741,
1038
1085
  name: "Abstract Mainnet",
@@ -1053,63 +1100,174 @@ function createAboreanPublicClient(rpcUrl) {
1053
1100
  transport: http(rpcUrl ?? process.env.ABSTRACT_RPC_URL ?? "https://api.mainnet.abs.xyz")
1054
1101
  });
1055
1102
  }
1103
+ function createAboreanWalletClient(account, rpcUrl) {
1104
+ return createWalletClient({
1105
+ account,
1106
+ chain: abstractMainnet,
1107
+ transport: http(rpcUrl ?? process.env.ABSTRACT_RPC_URL ?? "https://api.mainnet.abs.xyz")
1108
+ });
1109
+ }
1110
+
1111
+ // src/commands/_write-utils.ts
1112
+ import {
1113
+ createPrivateKeySigner,
1114
+ executeTx
1115
+ } from "@spectratools/tx-shared";
1116
+ import { z } from "incur";
1117
+
1118
+ // src/commands/_common.ts
1119
+ import { checksumAddress, weiToEth } from "@spectratools/cli-shared";
1120
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
1121
+ function toChecksum(address) {
1122
+ try {
1123
+ return checksumAddress(address);
1124
+ } catch {
1125
+ return address;
1126
+ }
1127
+ }
1128
+ function asNum(value) {
1129
+ return Number(value);
1130
+ }
1131
+ function relTime(unixSeconds) {
1132
+ const ts = typeof unixSeconds === "bigint" ? Number(unixSeconds) : unixSeconds;
1133
+ if (!Number.isFinite(ts) || ts <= 0) return "n/a";
1134
+ const delta = ts - Math.floor(Date.now() / 1e3);
1135
+ const abs = Math.abs(delta);
1136
+ const hours = Math.floor(abs / 3600);
1137
+ const minutes = Math.floor(abs % 3600 / 60);
1138
+ const label = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
1139
+ return delta >= 0 ? `in ${label}` : `${label} ago`;
1140
+ }
1141
+ function clampPositive(seconds) {
1142
+ return seconds > 0 ? seconds : 0;
1143
+ }
1144
+ function jsonSafe(value) {
1145
+ if (typeof value === "bigint") return value.toString();
1146
+ if (Array.isArray(value)) return value.map((item) => jsonSafe(item));
1147
+ if (value && typeof value === "object") {
1148
+ return Object.fromEntries(
1149
+ Object.entries(value).map(([key, entry]) => [
1150
+ key,
1151
+ jsonSafe(entry)
1152
+ ])
1153
+ );
1154
+ }
1155
+ return value;
1156
+ }
1157
+
1158
+ // src/commands/_write-utils.ts
1159
+ var PRIVATE_KEY_MISSING_MESSAGE = "Missing PRIVATE_KEY env var. Set PRIVATE_KEY to a 0x-prefixed 32-byte hex private key.";
1160
+ var writeEnv = z.object({
1161
+ ABSTRACT_RPC_URL: z.string().optional().describe("Abstract RPC URL override"),
1162
+ PRIVATE_KEY: z.string().min(1, PRIVATE_KEY_MISSING_MESSAGE).describe("Private key (0x-prefixed 32-byte hex) for signing transactions")
1163
+ });
1164
+ var writeOptions = z.object({
1165
+ "dry-run": z.boolean().default(false).describe("Simulate the transaction without broadcasting"),
1166
+ "gas-limit": z.string().optional().describe("Gas limit override (in gas units)"),
1167
+ "max-fee": z.string().optional().describe("Max fee per gas override in wei (EIP-1559)"),
1168
+ nonce: z.number().optional().describe("Nonce override")
1169
+ });
1170
+ function resolveAccount(env8) {
1171
+ const privateKey = env8.PRIVATE_KEY?.trim();
1172
+ if (!privateKey) {
1173
+ throw new Error(PRIVATE_KEY_MISSING_MESSAGE);
1174
+ }
1175
+ const signer = createPrivateKeySigner(privateKey);
1176
+ return signer.account;
1177
+ }
1178
+ function formatTxResult(result) {
1179
+ if (result.status === "dry-run") {
1180
+ return {
1181
+ dryRun: true,
1182
+ estimatedGas: result.estimatedGas.toString(),
1183
+ simulationResult: jsonSafe(result.simulationResult),
1184
+ ...result.privyPolicy !== void 0 ? { privyPolicy: jsonSafe(result.privyPolicy) } : {}
1185
+ };
1186
+ }
1187
+ return {
1188
+ txHash: result.hash,
1189
+ blockNumber: Number(result.blockNumber),
1190
+ gasUsed: result.gasUsed.toString()
1191
+ };
1192
+ }
1193
+ async function aboreanWriteTx(opts) {
1194
+ const { env: env8, options, address, abi, functionName, args, value } = opts;
1195
+ const account = resolveAccount(env8);
1196
+ const publicClient = createAboreanPublicClient(env8.ABSTRACT_RPC_URL);
1197
+ const walletClient = createAboreanWalletClient(account, env8.ABSTRACT_RPC_URL);
1198
+ const result = await executeTx({
1199
+ publicClient,
1200
+ walletClient,
1201
+ account,
1202
+ address,
1203
+ abi,
1204
+ functionName,
1205
+ ...args !== void 0 ? { args } : {},
1206
+ ...value !== void 0 ? { value } : {},
1207
+ dryRun: options["dry-run"],
1208
+ ...options["gas-limit"] ? { gasLimit: BigInt(options["gas-limit"]) } : {},
1209
+ ...options["max-fee"] ? { maxFeePerGas: BigInt(options["max-fee"]) } : {},
1210
+ ...options.nonce !== void 0 ? { nonce: options.nonce } : {}
1211
+ });
1212
+ return formatTxResult(result);
1213
+ }
1056
1214
 
1057
1215
  // src/commands/cl.ts
1058
1216
  var Q96 = 2n ** 96n;
1059
1217
  var MULTICALL_BATCH_SIZE = 100;
1060
- var env = z.object({
1061
- ABSTRACT_RPC_URL: z.string().optional().describe("Abstract RPC URL override")
1218
+ var env = z2.object({
1219
+ ABSTRACT_RPC_URL: z2.string().optional().describe("Abstract RPC URL override")
1062
1220
  });
1063
- var tokenSchema = z.object({
1064
- address: z.string(),
1065
- symbol: z.string(),
1066
- decimals: z.number()
1221
+ var tokenSchema = z2.object({
1222
+ address: z2.string(),
1223
+ symbol: z2.string(),
1224
+ decimals: z2.number()
1067
1225
  });
1068
- var poolRowSchema = z.object({
1069
- pool: z.string(),
1070
- pair: z.string(),
1226
+ var poolRowSchema = z2.object({
1227
+ pool: z2.string(),
1228
+ pair: z2.string(),
1071
1229
  token0: tokenSchema,
1072
1230
  token1: tokenSchema,
1073
- fee: z.number(),
1074
- feePercent: z.number(),
1075
- tickSpacing: z.number(),
1076
- liquidity: z.string(),
1077
- currentTick: z.number(),
1078
- sqrtPriceX96: z.string(),
1079
- activeLiquidityEstimate: z.object({
1080
- token0: z.string(),
1081
- token1: z.string(),
1082
- totalInToken0: z.number().nullable(),
1083
- totalInToken1: z.number().nullable()
1231
+ fee: z2.number(),
1232
+ feePercent: z2.number(),
1233
+ tickSpacing: z2.number(),
1234
+ liquidity: z2.string(),
1235
+ currentTick: z2.number(),
1236
+ sqrtPriceX96: z2.string(),
1237
+ activeLiquidityEstimate: z2.object({
1238
+ token0: z2.string(),
1239
+ token1: z2.string(),
1240
+ totalInToken0: z2.number().nullable(),
1241
+ totalInToken1: z2.number().nullable()
1084
1242
  }),
1085
- price: z.object({
1086
- token1PerToken0: z.number().nullable(),
1087
- token0PerToken1: z.number().nullable()
1243
+ price: z2.object({
1244
+ token1PerToken0: z2.number().nullable(),
1245
+ token0PerToken1: z2.number().nullable()
1088
1246
  })
1089
1247
  });
1090
- var quoteOutputSchema = z.object({
1091
- pool: z.string(),
1092
- selectedFee: z.number(),
1093
- selectedTickSpacing: z.number(),
1248
+ var quoteOutputSchema = z2.object({
1249
+ pool: z2.string(),
1250
+ selectedFee: z2.number(),
1251
+ selectedTickSpacing: z2.number(),
1094
1252
  tokenIn: tokenSchema,
1095
1253
  tokenOut: tokenSchema,
1096
- amountIn: z.object({
1097
- raw: z.string(),
1098
- decimal: z.string()
1254
+ amountIn: z2.object({
1255
+ raw: z2.string(),
1256
+ decimal: z2.string()
1099
1257
  }),
1100
- amountOut: z.object({
1101
- raw: z.string(),
1102
- decimal: z.string()
1258
+ amountOut: z2.object({
1259
+ raw: z2.string(),
1260
+ decimal: z2.string()
1103
1261
  }),
1104
- execution: z.object({
1105
- sqrtPriceX96After: z.string(),
1106
- initializedTicksCrossed: z.number(),
1107
- gasEstimate: z.string()
1262
+ execution: z2.object({
1263
+ sqrtPriceX96After: z2.string(),
1264
+ initializedTicksCrossed: z2.number(),
1265
+ gasEstimate: z2.string()
1108
1266
  }),
1109
- prices: z.object({
1110
- poolMidPriceOutPerIn: z.number().nullable(),
1111
- quotePriceOutPerIn: z.number().nullable(),
1112
- priceImpactPct: z.number().nullable()
1267
+ prices: z2.object({
1268
+ poolMidPriceOutPerIn: z2.number().nullable(),
1269
+ quotePriceOutPerIn: z2.number().nullable(),
1270
+ priceImpactPct: z2.number().nullable()
1113
1271
  })
1114
1272
  });
1115
1273
  var erc20MetadataAbi = [
@@ -1137,7 +1295,7 @@ function finiteOrNull(value) {
1137
1295
  function toTokenMetaFallback(address) {
1138
1296
  return {
1139
1297
  address,
1140
- symbol: shortAddress(checksumAddress(address)),
1298
+ symbol: shortAddress(checksumAddress2(address)),
1141
1299
  decimals: 18
1142
1300
  };
1143
1301
  }
@@ -1232,7 +1390,7 @@ async function readTokenMetadata(client, tokenAddresses) {
1232
1390
  const symbol = symbolResult && symbolResult.status === "success" && typeof symbolResult.result === "string" ? symbolResult.result : fallback.symbol;
1233
1391
  const decimals = decimalsResult && decimalsResult.status === "success" && typeof decimalsResult.result === "number" ? decimalsResult.result : fallback.decimals;
1234
1392
  out.set(address, {
1235
- address: checksumAddress(address),
1393
+ address: checksumAddress2(address),
1236
1394
  symbol,
1237
1395
  decimals
1238
1396
  });
@@ -1316,7 +1474,7 @@ function toPoolRow(pool, tokenMeta) {
1316
1474
  prices
1317
1475
  );
1318
1476
  return {
1319
- pool: checksumAddress(pool.pool),
1477
+ pool: checksumAddress2(pool.pool),
1320
1478
  pair: `${token0.symbol}/${token1.symbol}`,
1321
1479
  token0,
1322
1480
  token1,
@@ -1339,9 +1497,9 @@ var cl = Cli.create("cl", {
1339
1497
  cl.command("pools", {
1340
1498
  description: "List Slipstream pools with current state, prices, and active liquidity estimate.",
1341
1499
  env,
1342
- output: z.object({
1343
- count: z.number(),
1344
- pools: z.array(poolRowSchema)
1500
+ output: z2.object({
1501
+ count: z2.number(),
1502
+ pools: z2.array(poolRowSchema)
1345
1503
  }),
1346
1504
  async run(c) {
1347
1505
  const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
@@ -1382,11 +1540,11 @@ cl.command("pools", {
1382
1540
  });
1383
1541
  cl.command("pool", {
1384
1542
  description: "Get detailed state for a Slipstream pool address.",
1385
- args: z.object({
1386
- pool: z.string().describe("Pool address")
1543
+ args: z2.object({
1544
+ pool: z2.string().describe("Pool address")
1387
1545
  }),
1388
1546
  env,
1389
- output: z.object({
1547
+ output: z2.object({
1390
1548
  pool: poolRowSchema
1391
1549
  }),
1392
1550
  async run(c) {
@@ -1397,7 +1555,7 @@ cl.command("pool", {
1397
1555
  });
1398
1556
  }
1399
1557
  const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1400
- const checksummedPool = checksumAddress(c.args.pool);
1558
+ const checksummedPool = checksumAddress2(c.args.pool);
1401
1559
  const [poolState] = await readPoolStates(client, [checksummedPool]);
1402
1560
  const tokenMeta = await readTokenMetadata(client, [poolState.token0, poolState.token1]);
1403
1561
  const row = toPoolRow(poolState, tokenMeta);
@@ -1431,25 +1589,25 @@ cl.command("pool", {
1431
1589
  });
1432
1590
  cl.command("positions", {
1433
1591
  description: "List concentrated liquidity NFT positions for an owner.",
1434
- args: z.object({
1435
- owner: z.string().describe("Owner wallet address")
1592
+ args: z2.object({
1593
+ owner: z2.string().describe("Owner wallet address")
1436
1594
  }),
1437
1595
  env,
1438
- output: z.object({
1439
- owner: z.string(),
1440
- count: z.number(),
1441
- positions: z.array(
1442
- z.object({
1443
- tokenId: z.string(),
1444
- pair: z.string(),
1596
+ output: z2.object({
1597
+ owner: z2.string(),
1598
+ count: z2.number(),
1599
+ positions: z2.array(
1600
+ z2.object({
1601
+ tokenId: z2.string(),
1602
+ pair: z2.string(),
1445
1603
  token0: tokenSchema,
1446
1604
  token1: tokenSchema,
1447
- tickSpacing: z.number(),
1448
- tickLower: z.number(),
1449
- tickUpper: z.number(),
1450
- liquidity: z.string(),
1451
- tokensOwed0: z.object({ raw: z.string(), decimal: z.string() }),
1452
- tokensOwed1: z.object({ raw: z.string(), decimal: z.string() })
1605
+ tickSpacing: z2.number(),
1606
+ tickLower: z2.number(),
1607
+ tickUpper: z2.number(),
1608
+ liquidity: z2.string(),
1609
+ tokensOwed0: z2.object({ raw: z2.string(), decimal: z2.string() }),
1610
+ tokensOwed1: z2.object({ raw: z2.string(), decimal: z2.string() })
1453
1611
  })
1454
1612
  )
1455
1613
  }),
@@ -1461,7 +1619,7 @@ cl.command("positions", {
1461
1619
  });
1462
1620
  }
1463
1621
  const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1464
- const owner = checksumAddress(c.args.owner);
1622
+ const owner = checksumAddress2(c.args.owner);
1465
1623
  const balance = await client.readContract({
1466
1624
  abi: nonfungiblePositionManagerAbi,
1467
1625
  address: ABOREAN_CL_ADDRESSES.nonfungiblePositionManager,
@@ -1545,13 +1703,13 @@ cl.command("positions", {
1545
1703
  });
1546
1704
  cl.command("quote", {
1547
1705
  description: "Quote a single-hop Slipstream swap via QuoterV2.",
1548
- args: z.object({
1549
- tokenIn: z.string().describe("Input token address"),
1550
- tokenOut: z.string().describe("Output token address"),
1551
- amountIn: z.string().describe("Input amount in human-readable decimal units")
1706
+ args: z2.object({
1707
+ tokenIn: z2.string().describe("Input token address"),
1708
+ tokenOut: z2.string().describe("Output token address"),
1709
+ amountIn: z2.string().describe("Input amount in human-readable decimal units")
1552
1710
  }),
1553
- options: z.object({
1554
- fee: z.coerce.number().int().positive().optional().describe("Optional fee tier filter")
1711
+ options: z2.object({
1712
+ fee: z2.coerce.number().int().positive().optional().describe("Optional fee tier filter")
1555
1713
  }),
1556
1714
  env,
1557
1715
  output: quoteOutputSchema,
@@ -1564,8 +1722,8 @@ cl.command("quote", {
1564
1722
  });
1565
1723
  }
1566
1724
  const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1567
- const inAddress = checksumAddress(tokenIn);
1568
- const outAddress = checksumAddress(tokenOut);
1725
+ const inAddress = checksumAddress2(tokenIn);
1726
+ const outAddress = checksumAddress2(tokenOut);
1569
1727
  const allPools = await listPoolAddresses(client);
1570
1728
  const poolStates = await readPoolStates(client, allPools);
1571
1729
  const pairPools = poolStates.filter((pool) => {
@@ -1632,7 +1790,7 @@ cl.command("quote", {
1632
1790
  const priceImpactPct = quotePriceOutPerIn === null || poolMidPriceOutPerIn === null || poolMidPriceOutPerIn === 0 ? null : finiteOrNull((poolMidPriceOutPerIn - quotePriceOutPerIn) / poolMidPriceOutPerIn * 100);
1633
1791
  return c.ok(
1634
1792
  {
1635
- pool: checksumAddress(selectedPool.pool),
1793
+ pool: checksumAddress2(selectedPool.pool),
1636
1794
  selectedFee: selectedPool.fee,
1637
1795
  selectedTickSpacing: selectedPool.tickSpacing,
1638
1796
  tokenIn: inMeta,
@@ -1662,7 +1820,7 @@ cl.command("quote", {
1662
1820
  commands: [
1663
1821
  {
1664
1822
  command: "cl pool",
1665
- args: { pool: checksumAddress(selectedPool.pool) },
1823
+ args: { pool: checksumAddress2(selectedPool.pool) },
1666
1824
  description: "Inspect the pool used for this quote"
1667
1825
  },
1668
1826
  {
@@ -1680,147 +1838,755 @@ cl.command("quote", {
1680
1838
  );
1681
1839
  }
1682
1840
  });
1683
-
1684
- // src/commands/gauges.ts
1685
- import { Cli as Cli2, z as z2 } from "incur";
1686
-
1687
- // src/commands/_common.ts
1688
- import { checksumAddress as checksumAddress2, weiToEth } from "@spectratools/cli-shared";
1689
- var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
1690
- function toChecksum(address) {
1691
- try {
1692
- return checksumAddress2(address);
1693
- } catch {
1694
- return address;
1695
- }
1696
- }
1697
- function asNum(value) {
1698
- return Number(value);
1699
- }
1700
- function relTime(unixSeconds) {
1701
- const ts = typeof unixSeconds === "bigint" ? Number(unixSeconds) : unixSeconds;
1702
- if (!Number.isFinite(ts) || ts <= 0) return "n/a";
1703
- const delta = ts - Math.floor(Date.now() / 1e3);
1704
- const abs = Math.abs(delta);
1705
- const hours = Math.floor(abs / 3600);
1706
- const minutes = Math.floor(abs % 3600 / 60);
1707
- const label = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
1708
- return delta >= 0 ? `in ${label}` : `${label} ago`;
1709
- }
1710
- function clampPositive(seconds) {
1711
- return seconds > 0 ? seconds : 0;
1712
- }
1713
-
1714
- // src/commands/gauges.ts
1715
- var env2 = z2.object({
1716
- ABSTRACT_RPC_URL: z2.string().optional().describe("Abstract RPC URL override")
1717
- });
1718
- async function discoverGaugePools(client) {
1719
- const [v2PoolCount, clPoolCount] = await Promise.all([
1720
- client.readContract({
1721
- abi: poolFactoryAbi,
1722
- address: ABOREAN_V2_ADDRESSES.poolFactory,
1723
- functionName: "allPoolsLength"
1841
+ var DEFAULT_SLIPPAGE_PERCENT = 0.5;
1842
+ var DEFAULT_DEADLINE_SECONDS = 300;
1843
+ var swapOutputSchema = z2.object({
1844
+ pool: z2.string(),
1845
+ tokenIn: tokenSchema,
1846
+ tokenOut: tokenSchema,
1847
+ amountIn: z2.object({ raw: z2.string(), decimal: z2.string() }),
1848
+ quotedAmountOut: z2.object({ raw: z2.string(), decimal: z2.string() }),
1849
+ amountOutMinimum: z2.object({ raw: z2.string(), decimal: z2.string() }),
1850
+ slippagePercent: z2.number(),
1851
+ deadlineSeconds: z2.number(),
1852
+ tx: z2.union([
1853
+ z2.object({
1854
+ txHash: z2.string(),
1855
+ blockNumber: z2.number(),
1856
+ gasUsed: z2.string()
1724
1857
  }),
1725
- client.readContract({
1726
- abi: clFactoryAbi,
1727
- address: ABOREAN_CL_ADDRESSES.clFactory,
1728
- functionName: "allPoolsLength"
1858
+ z2.object({
1859
+ dryRun: z2.literal(true),
1860
+ estimatedGas: z2.string(),
1861
+ simulationResult: z2.unknown()
1729
1862
  })
1730
- ]);
1731
- const v2Indices = Array.from({ length: asNum(v2PoolCount) }, (_, i) => BigInt(i));
1732
- const clIndices = Array.from({ length: asNum(clPoolCount) }, (_, i) => BigInt(i));
1733
- const [v2Pools, clPools] = await Promise.all([
1734
- v2Indices.length ? client.multicall({
1735
- allowFailure: false,
1736
- contracts: v2Indices.map((index) => ({
1737
- abi: poolFactoryAbi,
1738
- address: ABOREAN_V2_ADDRESSES.poolFactory,
1739
- functionName: "allPools",
1740
- args: [index]
1741
- }))
1742
- }) : Promise.resolve([]),
1743
- clIndices.length ? client.multicall({
1744
- allowFailure: false,
1745
- contracts: clIndices.map((index) => ({
1746
- abi: clFactoryAbi,
1747
- address: ABOREAN_CL_ADDRESSES.clFactory,
1748
- functionName: "allPools",
1749
- args: [index]
1750
- }))
1751
- }) : Promise.resolve([])
1752
- ]);
1753
- const pools2 = [...v2Pools, ...clPools];
1754
- if (!pools2.length) return [];
1755
- const gauges2 = await client.multicall({
1756
- allowFailure: false,
1757
- contracts: pools2.map((pool) => ({
1758
- abi: voterAbi,
1759
- address: ABOREAN_V2_ADDRESSES.voter,
1760
- functionName: "gauges",
1761
- args: [pool]
1762
- }))
1763
- });
1764
- return pools2.map((pool, index) => ({ pool, gauge: gauges2[index] })).filter(({ gauge }) => gauge.toLowerCase() !== ZERO_ADDRESS.toLowerCase());
1765
- }
1766
- var gauges = Cli2.create("gauges", {
1767
- description: "Inspect Aborean gauge emissions, staking, and user positions."
1863
+ ])
1768
1864
  });
1769
- gauges.command("list", {
1770
- description: "List active gauges with pool, emissions, and staking stats.",
1771
- env: env2,
1772
- output: z2.object({
1773
- gauges: z2.array(
1774
- z2.object({
1775
- pool: z2.string(),
1776
- gauge: z2.string(),
1777
- rewardToken: z2.string(),
1778
- rewardRate: z2.string(),
1779
- totalStaked: z2.string(),
1780
- claimableEmissions: z2.string(),
1781
- periodFinish: z2.number(),
1782
- periodFinishRelative: z2.string()
1783
- })
1784
- ),
1785
- count: z2.number()
1865
+ cl.command("swap", {
1866
+ description: "Execute a single-hop Slipstream swap via the CL SwapRouter.",
1867
+ options: z2.object({
1868
+ "token-in": z2.string().describe("Input token address"),
1869
+ "token-out": z2.string().describe("Output token address"),
1870
+ "amount-in": z2.string().describe("Input amount in wei"),
1871
+ slippage: z2.coerce.number().default(DEFAULT_SLIPPAGE_PERCENT).describe("Slippage tolerance in percent (default: 0.5)"),
1872
+ deadline: z2.coerce.number().int().default(DEFAULT_DEADLINE_SECONDS).describe("Transaction deadline in seconds from now (default: 300)"),
1873
+ ...writeOptions.shape
1786
1874
  }),
1787
- examples: [{ description: "List all active gauges and current emissions state" }],
1875
+ env: writeEnv,
1876
+ output: swapOutputSchema,
1788
1877
  async run(c) {
1878
+ const tokenIn = c.options["token-in"];
1879
+ const tokenOut = c.options["token-out"];
1880
+ const amountInWei = c.options["amount-in"];
1881
+ const slippage = c.options.slippage;
1882
+ const deadlineSeconds = c.options.deadline;
1883
+ if (!isAddress(tokenIn) || !isAddress(tokenOut)) {
1884
+ return c.error({
1885
+ code: "INVALID_ADDRESS",
1886
+ message: "token-in and token-out must both be valid 0x-prefixed 20-byte addresses."
1887
+ });
1888
+ }
1889
+ const inAddress = checksumAddress2(tokenIn);
1890
+ const outAddress = checksumAddress2(tokenOut);
1891
+ let amountInRaw;
1892
+ try {
1893
+ amountInRaw = BigInt(amountInWei);
1894
+ } catch {
1895
+ return c.error({
1896
+ code: "INVALID_AMOUNT",
1897
+ message: `Invalid amount-in: "${amountInWei}". Provide a valid integer in wei.`
1898
+ });
1899
+ }
1900
+ if (amountInRaw <= 0n) {
1901
+ return c.error({
1902
+ code: "INVALID_AMOUNT",
1903
+ message: "amount-in must be a positive integer."
1904
+ });
1905
+ }
1906
+ if (slippage < 0 || slippage > 100) {
1907
+ return c.error({
1908
+ code: "INVALID_SLIPPAGE",
1909
+ message: `Slippage must be between 0 and 100. Got: ${slippage}`
1910
+ });
1911
+ }
1789
1912
  const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1790
- const gaugePools = await discoverGaugePools(client);
1791
- if (!gaugePools.length) {
1792
- return c.ok({ gauges: [], count: 0 });
1913
+ const allPools = await listPoolAddresses(client);
1914
+ const poolStates = await readPoolStates(client, allPools);
1915
+ const pairPools = poolStates.filter((pool) => {
1916
+ const a = normalizeAddress(pool.token0);
1917
+ const b = normalizeAddress(pool.token1);
1918
+ const tokenInNorm = normalizeAddress(inAddress);
1919
+ const tokenOutNorm = normalizeAddress(outAddress);
1920
+ return a === tokenInNorm && b === tokenOutNorm || a === tokenOutNorm && b === tokenInNorm;
1921
+ });
1922
+ if (pairPools.length === 0) {
1923
+ return c.error({
1924
+ code: "POOL_NOT_FOUND",
1925
+ message: `No Slipstream pool found for pair ${inAddress}/${outAddress}.`
1926
+ });
1793
1927
  }
1794
- const details = await client.multicall({
1795
- allowFailure: false,
1796
- contracts: gaugePools.flatMap(({ gauge }) => [
1797
- {
1798
- abi: gaugeAbi,
1799
- address: gauge,
1800
- functionName: "rewardToken"
1801
- },
1802
- {
1803
- abi: gaugeAbi,
1804
- address: gauge,
1805
- functionName: "rewardRate"
1806
- },
1807
- {
1808
- abi: gaugeAbi,
1809
- address: gauge,
1810
- functionName: "totalSupply"
1811
- },
1812
- {
1813
- abi: gaugeAbi,
1814
- address: gauge,
1815
- functionName: "periodFinish"
1816
- },
1928
+ const selectedPool = [...pairPools].sort((a, b) => {
1929
+ if (a.liquidity === b.liquidity) return 0;
1930
+ return a.liquidity > b.liquidity ? -1 : 1;
1931
+ })[0];
1932
+ const tokenMeta = await readTokenMetadata(client, [inAddress, outAddress]);
1933
+ const inMeta = tokenMeta.get(inAddress) ?? toTokenMetaFallback(inAddress);
1934
+ const outMeta = tokenMeta.get(outAddress) ?? toTokenMetaFallback(outAddress);
1935
+ const quote = await client.readContract({
1936
+ abi: quoterV2Abi,
1937
+ address: ABOREAN_CL_ADDRESSES.quoterV2,
1938
+ functionName: "quoteExactInputSingle",
1939
+ args: [
1817
1940
  {
1818
- abi: voterAbi,
1819
- address: ABOREAN_V2_ADDRESSES.voter,
1820
- functionName: "claimable",
1821
- args: [gauge]
1941
+ tokenIn: inAddress,
1942
+ tokenOut: outAddress,
1943
+ amountIn: amountInRaw,
1944
+ tickSpacing: selectedPool.tickSpacing,
1945
+ sqrtPriceLimitX96: 0n
1822
1946
  }
1823
- ])
1947
+ ]
1948
+ });
1949
+ const quotedAmountOut = quote[0];
1950
+ const slippageBps = BigInt(Math.round(slippage * 100));
1951
+ const amountOutMinimum = quotedAmountOut - quotedAmountOut * slippageBps / 10000n;
1952
+ const account = resolveAccount(c.env);
1953
+ const deadlineTimestamp = BigInt(Math.floor(Date.now() / 1e3) + deadlineSeconds);
1954
+ const txResult = await aboreanWriteTx({
1955
+ env: c.env,
1956
+ options: {
1957
+ "dry-run": c.options["dry-run"],
1958
+ "gas-limit": c.options["gas-limit"],
1959
+ "max-fee": c.options["max-fee"],
1960
+ nonce: c.options.nonce
1961
+ },
1962
+ address: ABOREAN_CL_ADDRESSES.swapRouter,
1963
+ abi: swapRouterAbi,
1964
+ functionName: "exactInputSingle",
1965
+ args: [
1966
+ {
1967
+ tokenIn: inAddress,
1968
+ tokenOut: outAddress,
1969
+ tickSpacing: selectedPool.tickSpacing,
1970
+ recipient: account.address,
1971
+ deadline: deadlineTimestamp,
1972
+ amountIn: amountInRaw,
1973
+ amountOutMinimum,
1974
+ sqrtPriceLimitX96: 0n
1975
+ }
1976
+ ]
1977
+ });
1978
+ const amountInDecimal = formatUnits(amountInRaw, inMeta.decimals);
1979
+ const quotedOutDecimal = formatUnits(quotedAmountOut, outMeta.decimals);
1980
+ const minOutDecimal = formatUnits(amountOutMinimum, outMeta.decimals);
1981
+ return c.ok({
1982
+ pool: checksumAddress2(selectedPool.pool),
1983
+ tokenIn: inMeta,
1984
+ tokenOut: outMeta,
1985
+ amountIn: { raw: amountInRaw.toString(), decimal: amountInDecimal },
1986
+ quotedAmountOut: { raw: quotedAmountOut.toString(), decimal: quotedOutDecimal },
1987
+ amountOutMinimum: { raw: amountOutMinimum.toString(), decimal: minOutDecimal },
1988
+ slippagePercent: slippage,
1989
+ deadlineSeconds,
1990
+ tx: txResult
1991
+ });
1992
+ }
1993
+ });
1994
+ var nfpmMintAbi = [
1995
+ {
1996
+ type: "function",
1997
+ name: "mint",
1998
+ stateMutability: "payable",
1999
+ inputs: [
2000
+ {
2001
+ name: "params",
2002
+ type: "tuple",
2003
+ components: [
2004
+ { name: "token0", type: "address" },
2005
+ { name: "token1", type: "address" },
2006
+ { name: "tickSpacing", type: "int24" },
2007
+ { name: "tickLower", type: "int24" },
2008
+ { name: "tickUpper", type: "int24" },
2009
+ { name: "amount0Desired", type: "uint256" },
2010
+ { name: "amount1Desired", type: "uint256" },
2011
+ { name: "amount0Min", type: "uint256" },
2012
+ { name: "amount1Min", type: "uint256" },
2013
+ { name: "recipient", type: "address" },
2014
+ { name: "deadline", type: "uint256" },
2015
+ { name: "sqrtPriceX96", type: "uint160" }
2016
+ ]
2017
+ }
2018
+ ],
2019
+ outputs: [
2020
+ { name: "tokenId", type: "uint256" },
2021
+ { name: "liquidity", type: "uint128" },
2022
+ { name: "amount0", type: "uint256" },
2023
+ { name: "amount1", type: "uint256" }
2024
+ ]
2025
+ }
2026
+ ];
2027
+ var nfpmDecreaseLiquidityAbi = [
2028
+ {
2029
+ type: "function",
2030
+ name: "decreaseLiquidity",
2031
+ stateMutability: "payable",
2032
+ inputs: [
2033
+ {
2034
+ name: "params",
2035
+ type: "tuple",
2036
+ components: [
2037
+ { name: "tokenId", type: "uint256" },
2038
+ { name: "liquidity", type: "uint128" },
2039
+ { name: "amount0Min", type: "uint256" },
2040
+ { name: "amount1Min", type: "uint256" },
2041
+ { name: "deadline", type: "uint256" }
2042
+ ]
2043
+ }
2044
+ ],
2045
+ outputs: [
2046
+ { name: "amount0", type: "uint256" },
2047
+ { name: "amount1", type: "uint256" }
2048
+ ]
2049
+ }
2050
+ ];
2051
+ var nfpmCollectAbi = [
2052
+ {
2053
+ type: "function",
2054
+ name: "collect",
2055
+ stateMutability: "payable",
2056
+ inputs: [
2057
+ {
2058
+ name: "params",
2059
+ type: "tuple",
2060
+ components: [
2061
+ { name: "tokenId", type: "uint256" },
2062
+ { name: "recipient", type: "address" },
2063
+ { name: "amount0Max", type: "uint128" },
2064
+ { name: "amount1Max", type: "uint128" }
2065
+ ]
2066
+ }
2067
+ ],
2068
+ outputs: [
2069
+ { name: "amount0", type: "uint256" },
2070
+ { name: "amount1", type: "uint256" }
2071
+ ]
2072
+ }
2073
+ ];
2074
+ var nfpmBurnAbi = [
2075
+ {
2076
+ type: "function",
2077
+ name: "burn",
2078
+ stateMutability: "payable",
2079
+ inputs: [{ name: "tokenId", type: "uint256" }],
2080
+ outputs: []
2081
+ }
2082
+ ];
2083
+ var erc20ApproveAbi = [
2084
+ {
2085
+ type: "function",
2086
+ name: "approve",
2087
+ stateMutability: "nonpayable",
2088
+ inputs: [
2089
+ { name: "spender", type: "address" },
2090
+ { name: "amount", type: "uint256" }
2091
+ ],
2092
+ outputs: [{ name: "", type: "bool" }]
2093
+ },
2094
+ {
2095
+ type: "function",
2096
+ name: "allowance",
2097
+ stateMutability: "view",
2098
+ inputs: [
2099
+ { name: "owner", type: "address" },
2100
+ { name: "account", type: "address" }
2101
+ ],
2102
+ outputs: [{ name: "", type: "uint256" }]
2103
+ }
2104
+ ];
2105
+ var amountSchema = z2.object({
2106
+ raw: z2.string(),
2107
+ decimal: z2.string()
2108
+ });
2109
+ cl.command("add-position", {
2110
+ description: "Mint a new concentrated liquidity position via the NonfungiblePositionManager. Approves both tokens if needed. Supports --dry-run.",
2111
+ options: z2.object({
2112
+ "token-a": z2.string().describe("First token address"),
2113
+ "token-b": z2.string().describe("Second token address"),
2114
+ "tick-spacing": z2.coerce.number().int().describe("Pool tick spacing"),
2115
+ "tick-lower": z2.coerce.number().int().describe("Lower tick boundary"),
2116
+ "tick-upper": z2.coerce.number().int().describe("Upper tick boundary"),
2117
+ "amount-0": z2.string().describe("Desired amount of token0 in wei"),
2118
+ "amount-1": z2.string().describe("Desired amount of token1 in wei"),
2119
+ slippage: z2.coerce.number().default(0.5).describe("Slippage tolerance in percent (default: 0.5)"),
2120
+ deadline: z2.coerce.number().int().default(300).describe("Transaction deadline in seconds from now (default: 300)"),
2121
+ ...writeOptions.shape
2122
+ }),
2123
+ env: writeEnv,
2124
+ output: z2.object({
2125
+ pool: z2.string(),
2126
+ token0: tokenSchema,
2127
+ token1: tokenSchema,
2128
+ tickSpacing: z2.number(),
2129
+ tickLower: z2.number(),
2130
+ tickUpper: z2.number(),
2131
+ amount0Desired: amountSchema,
2132
+ amount1Desired: amountSchema,
2133
+ amount0Min: amountSchema,
2134
+ amount1Min: amountSchema,
2135
+ slippagePercent: z2.number(),
2136
+ tx: z2.union([
2137
+ z2.object({
2138
+ txHash: z2.string(),
2139
+ blockNumber: z2.number(),
2140
+ gasUsed: z2.string()
2141
+ }),
2142
+ z2.object({
2143
+ dryRun: z2.literal(true),
2144
+ estimatedGas: z2.string(),
2145
+ simulationResult: z2.unknown()
2146
+ })
2147
+ ])
2148
+ }),
2149
+ async run(c) {
2150
+ const tokenARaw = c.options["token-a"];
2151
+ const tokenBRaw = c.options["token-b"];
2152
+ if (!isAddress(tokenARaw) || !isAddress(tokenBRaw)) {
2153
+ return c.error({
2154
+ code: "INVALID_ADDRESS",
2155
+ message: "token-a and token-b must both be valid 0x-prefixed 20-byte addresses."
2156
+ });
2157
+ }
2158
+ const addrA = checksumAddress2(tokenARaw);
2159
+ const addrB = checksumAddress2(tokenBRaw);
2160
+ const [token0, token1] = addrA.toLowerCase() < addrB.toLowerCase() ? [addrA, addrB] : [addrB, addrA];
2161
+ let amount0Desired;
2162
+ let amount1Desired;
2163
+ try {
2164
+ const amountForA = BigInt(c.options["amount-0"]);
2165
+ const amountForB = BigInt(c.options["amount-1"]);
2166
+ if (addrA.toLowerCase() < addrB.toLowerCase()) {
2167
+ amount0Desired = amountForA;
2168
+ amount1Desired = amountForB;
2169
+ } else {
2170
+ amount0Desired = amountForB;
2171
+ amount1Desired = amountForA;
2172
+ }
2173
+ } catch {
2174
+ return c.error({
2175
+ code: "INVALID_AMOUNT",
2176
+ message: "amount-0 and amount-1 must be valid integers in wei."
2177
+ });
2178
+ }
2179
+ if (amount0Desired <= 0n && amount1Desired <= 0n) {
2180
+ return c.error({
2181
+ code: "INVALID_AMOUNT",
2182
+ message: "At least one of amount-0 or amount-1 must be positive."
2183
+ });
2184
+ }
2185
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
2186
+ const allPools = await listPoolAddresses(client);
2187
+ const poolStates = await readPoolStates(client, allPools);
2188
+ const matchingPool = poolStates.find(
2189
+ (pool) => normalizeAddress(pool.token0) === normalizeAddress(token0) && normalizeAddress(pool.token1) === normalizeAddress(token1) && pool.tickSpacing === c.options["tick-spacing"]
2190
+ );
2191
+ if (!matchingPool) {
2192
+ return c.error({
2193
+ code: "POOL_NOT_FOUND",
2194
+ message: `No Slipstream pool found for ${checksumAddress2(token0)}/${checksumAddress2(token1)} with tick spacing ${c.options["tick-spacing"]}.`
2195
+ });
2196
+ }
2197
+ const tokenMeta = await readTokenMetadata(client, [token0, token1]);
2198
+ const meta0 = tokenMeta.get(token0) ?? toTokenMetaFallback(token0);
2199
+ const meta1 = tokenMeta.get(token1) ?? toTokenMetaFallback(token1);
2200
+ const slippageBps = BigInt(Math.round(c.options.slippage * 100));
2201
+ const amount0Min = amount0Desired - amount0Desired * slippageBps / 10000n;
2202
+ const amount1Min = amount1Desired - amount1Desired * slippageBps / 10000n;
2203
+ const deadlineTimestamp = BigInt(Math.floor(Date.now() / 1e3) + c.options.deadline);
2204
+ const account = resolveAccount(c.env);
2205
+ const nfpmAddress = ABOREAN_CL_ADDRESSES.nonfungiblePositionManager;
2206
+ if (!c.options["dry-run"]) {
2207
+ for (const [token, amount] of [
2208
+ [token0, amount0Desired],
2209
+ [token1, amount1Desired]
2210
+ ]) {
2211
+ if (amount <= 0n) continue;
2212
+ const currentAllowance = await client.readContract({
2213
+ abi: erc20ApproveAbi,
2214
+ address: token,
2215
+ functionName: "allowance",
2216
+ args: [account.address, nfpmAddress]
2217
+ });
2218
+ if (currentAllowance < amount) {
2219
+ await aboreanWriteTx({
2220
+ env: c.env,
2221
+ options: { ...c.options, "dry-run": false },
2222
+ address: token,
2223
+ abi: erc20ApproveAbi,
2224
+ functionName: "approve",
2225
+ args: [nfpmAddress, amount]
2226
+ });
2227
+ }
2228
+ }
2229
+ }
2230
+ const txResult = await aboreanWriteTx({
2231
+ env: c.env,
2232
+ options: {
2233
+ "dry-run": c.options["dry-run"],
2234
+ "gas-limit": c.options["gas-limit"],
2235
+ "max-fee": c.options["max-fee"],
2236
+ nonce: c.options.nonce
2237
+ },
2238
+ address: nfpmAddress,
2239
+ abi: nfpmMintAbi,
2240
+ functionName: "mint",
2241
+ args: [
2242
+ {
2243
+ token0,
2244
+ token1,
2245
+ tickSpacing: c.options["tick-spacing"],
2246
+ tickLower: c.options["tick-lower"],
2247
+ tickUpper: c.options["tick-upper"],
2248
+ amount0Desired,
2249
+ amount1Desired,
2250
+ amount0Min,
2251
+ amount1Min,
2252
+ recipient: account.address,
2253
+ deadline: deadlineTimestamp,
2254
+ sqrtPriceX96: 0n
2255
+ }
2256
+ ]
2257
+ });
2258
+ return c.ok({
2259
+ pool: checksumAddress2(matchingPool.pool),
2260
+ token0: meta0,
2261
+ token1: meta1,
2262
+ tickSpacing: c.options["tick-spacing"],
2263
+ tickLower: c.options["tick-lower"],
2264
+ tickUpper: c.options["tick-upper"],
2265
+ amount0Desired: {
2266
+ raw: amount0Desired.toString(),
2267
+ decimal: formatUnits(amount0Desired, meta0.decimals)
2268
+ },
2269
+ amount1Desired: {
2270
+ raw: amount1Desired.toString(),
2271
+ decimal: formatUnits(amount1Desired, meta1.decimals)
2272
+ },
2273
+ amount0Min: {
2274
+ raw: amount0Min.toString(),
2275
+ decimal: formatUnits(amount0Min, meta0.decimals)
2276
+ },
2277
+ amount1Min: {
2278
+ raw: amount1Min.toString(),
2279
+ decimal: formatUnits(amount1Min, meta1.decimals)
2280
+ },
2281
+ slippagePercent: c.options.slippage,
2282
+ tx: txResult
2283
+ });
2284
+ }
2285
+ });
2286
+ cl.command("remove-position", {
2287
+ description: "Remove (close) a concentrated liquidity position. Decreases liquidity to zero, collects all tokens, and burns the NFT. Supports --dry-run.",
2288
+ options: z2.object({
2289
+ "token-id": z2.string().describe("Position NFT token ID"),
2290
+ slippage: z2.coerce.number().default(0.5).describe("Slippage tolerance in percent (default: 0.5)"),
2291
+ deadline: z2.coerce.number().int().default(300).describe("Transaction deadline in seconds from now (default: 300)"),
2292
+ ...writeOptions.shape
2293
+ }),
2294
+ env: writeEnv,
2295
+ output: z2.object({
2296
+ tokenId: z2.string(),
2297
+ pair: z2.string(),
2298
+ token0: tokenSchema,
2299
+ token1: tokenSchema,
2300
+ tickLower: z2.number(),
2301
+ tickUpper: z2.number(),
2302
+ liquidity: z2.string(),
2303
+ slippagePercent: z2.number(),
2304
+ tx: z2.union([
2305
+ z2.object({
2306
+ txHash: z2.string(),
2307
+ blockNumber: z2.number(),
2308
+ gasUsed: z2.string()
2309
+ }),
2310
+ z2.object({
2311
+ dryRun: z2.literal(true),
2312
+ estimatedGas: z2.string(),
2313
+ simulationResult: z2.unknown()
2314
+ })
2315
+ ])
2316
+ }),
2317
+ async run(c) {
2318
+ let tokenId;
2319
+ try {
2320
+ tokenId = BigInt(c.options["token-id"]);
2321
+ } catch {
2322
+ return c.error({
2323
+ code: "INVALID_TOKEN_ID",
2324
+ message: `Invalid token-id: "${c.options["token-id"]}". Provide a valid integer.`
2325
+ });
2326
+ }
2327
+ if (tokenId <= 0n) {
2328
+ return c.error({
2329
+ code: "INVALID_TOKEN_ID",
2330
+ message: "token-id must be a positive integer."
2331
+ });
2332
+ }
2333
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
2334
+ const nfpmAddress = ABOREAN_CL_ADDRESSES.nonfungiblePositionManager;
2335
+ let positionData;
2336
+ try {
2337
+ positionData = await client.readContract({
2338
+ abi: nonfungiblePositionManagerAbi,
2339
+ address: nfpmAddress,
2340
+ functionName: "positions",
2341
+ args: [tokenId]
2342
+ });
2343
+ } catch {
2344
+ return c.error({
2345
+ code: "POSITION_NOT_FOUND",
2346
+ message: `Position with tokenId ${tokenId.toString()} not found.`
2347
+ });
2348
+ }
2349
+ const token0 = positionData[2];
2350
+ const token1 = positionData[3];
2351
+ const tickLower = positionData[5];
2352
+ const tickUpper = positionData[6];
2353
+ const liquidity = positionData[7];
2354
+ if (liquidity === 0n) {
2355
+ return c.error({
2356
+ code: "ZERO_LIQUIDITY",
2357
+ message: `Position ${tokenId.toString()} has zero liquidity. Nothing to remove.`
2358
+ });
2359
+ }
2360
+ const tokenMeta = await readTokenMetadata(client, [token0, token1]);
2361
+ const meta0 = tokenMeta.get(token0) ?? toTokenMetaFallback(token0);
2362
+ const meta1 = tokenMeta.get(token1) ?? toTokenMetaFallback(token1);
2363
+ const account = resolveAccount(c.env);
2364
+ const deadlineTimestamp = BigInt(Math.floor(Date.now() / 1e3) + c.options.deadline);
2365
+ const txResult = await aboreanWriteTx({
2366
+ env: c.env,
2367
+ options: {
2368
+ "dry-run": c.options["dry-run"],
2369
+ "gas-limit": c.options["gas-limit"],
2370
+ "max-fee": c.options["max-fee"],
2371
+ nonce: c.options.nonce
2372
+ },
2373
+ address: nfpmAddress,
2374
+ abi: nfpmDecreaseLiquidityAbi,
2375
+ functionName: "decreaseLiquidity",
2376
+ args: [
2377
+ {
2378
+ tokenId,
2379
+ liquidity,
2380
+ amount0Min: 0n,
2381
+ amount1Min: 0n,
2382
+ deadline: deadlineTimestamp
2383
+ }
2384
+ ]
2385
+ });
2386
+ if (!c.options["dry-run"]) {
2387
+ const maxUint128 = (1n << 128n) - 1n;
2388
+ await aboreanWriteTx({
2389
+ env: c.env,
2390
+ options: { ...c.options, "dry-run": false },
2391
+ address: nfpmAddress,
2392
+ abi: nfpmCollectAbi,
2393
+ functionName: "collect",
2394
+ args: [
2395
+ {
2396
+ tokenId,
2397
+ recipient: account.address,
2398
+ amount0Max: maxUint128,
2399
+ amount1Max: maxUint128
2400
+ }
2401
+ ]
2402
+ });
2403
+ await aboreanWriteTx({
2404
+ env: c.env,
2405
+ options: { ...c.options, "dry-run": false },
2406
+ address: nfpmAddress,
2407
+ abi: nfpmBurnAbi,
2408
+ functionName: "burn",
2409
+ args: [tokenId]
2410
+ });
2411
+ }
2412
+ return c.ok({
2413
+ tokenId: tokenId.toString(),
2414
+ pair: `${meta0.symbol}/${meta1.symbol}`,
2415
+ token0: meta0,
2416
+ token1: meta1,
2417
+ tickLower,
2418
+ tickUpper,
2419
+ liquidity: liquidity.toString(),
2420
+ slippagePercent: c.options.slippage,
2421
+ tx: txResult
2422
+ });
2423
+ }
2424
+ });
2425
+
2426
+ // src/commands/gauges.ts
2427
+ import { isAddress as isAddress2 } from "@spectratools/cli-shared";
2428
+ import { Cli as Cli2, z as z3 } from "incur";
2429
+ var env2 = z3.object({
2430
+ ABSTRACT_RPC_URL: z3.string().optional().describe("Abstract RPC URL override")
2431
+ });
2432
+ var erc20ApproveAbi2 = [
2433
+ {
2434
+ type: "function",
2435
+ name: "approve",
2436
+ stateMutability: "nonpayable",
2437
+ inputs: [
2438
+ { name: "spender", type: "address" },
2439
+ { name: "amount", type: "uint256" }
2440
+ ],
2441
+ outputs: [{ name: "", type: "bool" }]
2442
+ },
2443
+ {
2444
+ type: "function",
2445
+ name: "allowance",
2446
+ stateMutability: "view",
2447
+ inputs: [
2448
+ { name: "owner", type: "address" },
2449
+ { name: "account", type: "address" }
2450
+ ],
2451
+ outputs: [{ name: "", type: "uint256" }]
2452
+ }
2453
+ ];
2454
+ var gaugeDepositAbi = [
2455
+ {
2456
+ type: "function",
2457
+ name: "deposit",
2458
+ stateMutability: "nonpayable",
2459
+ inputs: [{ name: "amount", type: "uint256" }],
2460
+ outputs: []
2461
+ }
2462
+ ];
2463
+ var gaugeDepositWithTokenIdAbi = [
2464
+ {
2465
+ type: "function",
2466
+ name: "deposit",
2467
+ stateMutability: "nonpayable",
2468
+ inputs: [
2469
+ { name: "amount", type: "uint256" },
2470
+ { name: "tokenId", type: "uint256" }
2471
+ ],
2472
+ outputs: []
2473
+ }
2474
+ ];
2475
+ var gaugeWithdrawAbi = [
2476
+ {
2477
+ type: "function",
2478
+ name: "withdraw",
2479
+ stateMutability: "nonpayable",
2480
+ inputs: [{ name: "amount", type: "uint256" }],
2481
+ outputs: []
2482
+ }
2483
+ ];
2484
+ async function discoverGaugePools(client) {
2485
+ const [v2PoolCount, clPoolCount] = await Promise.all([
2486
+ client.readContract({
2487
+ abi: poolFactoryAbi,
2488
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
2489
+ functionName: "allPoolsLength"
2490
+ }),
2491
+ client.readContract({
2492
+ abi: clFactoryAbi,
2493
+ address: ABOREAN_CL_ADDRESSES.clFactory,
2494
+ functionName: "allPoolsLength"
2495
+ })
2496
+ ]);
2497
+ const v2Indices = Array.from({ length: asNum(v2PoolCount) }, (_, i) => BigInt(i));
2498
+ const clIndices = Array.from({ length: asNum(clPoolCount) }, (_, i) => BigInt(i));
2499
+ const [v2Pools, clPools] = await Promise.all([
2500
+ v2Indices.length ? client.multicall({
2501
+ allowFailure: false,
2502
+ contracts: v2Indices.map((index) => ({
2503
+ abi: poolFactoryAbi,
2504
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
2505
+ functionName: "allPools",
2506
+ args: [index]
2507
+ }))
2508
+ }) : Promise.resolve([]),
2509
+ clIndices.length ? client.multicall({
2510
+ allowFailure: false,
2511
+ contracts: clIndices.map((index) => ({
2512
+ abi: clFactoryAbi,
2513
+ address: ABOREAN_CL_ADDRESSES.clFactory,
2514
+ functionName: "allPools",
2515
+ args: [index]
2516
+ }))
2517
+ }) : Promise.resolve([])
2518
+ ]);
2519
+ const pools2 = [...v2Pools, ...clPools];
2520
+ if (!pools2.length) return [];
2521
+ const gauges2 = await client.multicall({
2522
+ allowFailure: false,
2523
+ contracts: pools2.map((pool) => ({
2524
+ abi: voterAbi,
2525
+ address: ABOREAN_V2_ADDRESSES.voter,
2526
+ functionName: "gauges",
2527
+ args: [pool]
2528
+ }))
2529
+ });
2530
+ return pools2.map((pool, index) => ({ pool, gauge: gauges2[index] })).filter(({ gauge }) => gauge.toLowerCase() !== ZERO_ADDRESS.toLowerCase());
2531
+ }
2532
+ var gauges = Cli2.create("gauges", {
2533
+ description: "Inspect Aborean gauge emissions, staking, and user positions."
2534
+ });
2535
+ gauges.command("list", {
2536
+ description: "List active gauges with pool, emissions, and staking stats.",
2537
+ env: env2,
2538
+ output: z3.object({
2539
+ gauges: z3.array(
2540
+ z3.object({
2541
+ pool: z3.string(),
2542
+ gauge: z3.string(),
2543
+ rewardToken: z3.string(),
2544
+ rewardRate: z3.string(),
2545
+ totalStaked: z3.string(),
2546
+ claimableEmissions: z3.string(),
2547
+ periodFinish: z3.number(),
2548
+ periodFinishRelative: z3.string()
2549
+ })
2550
+ ),
2551
+ count: z3.number()
2552
+ }),
2553
+ examples: [{ description: "List all active gauges and current emissions state" }],
2554
+ async run(c) {
2555
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
2556
+ const gaugePools = await discoverGaugePools(client);
2557
+ if (!gaugePools.length) {
2558
+ return c.ok({ gauges: [], count: 0 });
2559
+ }
2560
+ const details = await client.multicall({
2561
+ allowFailure: false,
2562
+ contracts: gaugePools.flatMap(({ gauge }) => [
2563
+ {
2564
+ abi: gaugeAbi,
2565
+ address: gauge,
2566
+ functionName: "rewardToken"
2567
+ },
2568
+ {
2569
+ abi: gaugeAbi,
2570
+ address: gauge,
2571
+ functionName: "rewardRate"
2572
+ },
2573
+ {
2574
+ abi: gaugeAbi,
2575
+ address: gauge,
2576
+ functionName: "totalSupply"
2577
+ },
2578
+ {
2579
+ abi: gaugeAbi,
2580
+ address: gauge,
2581
+ functionName: "periodFinish"
2582
+ },
2583
+ {
2584
+ abi: voterAbi,
2585
+ address: ABOREAN_V2_ADDRESSES.voter,
2586
+ functionName: "claimable",
2587
+ args: [gauge]
2588
+ }
2589
+ ])
1824
2590
  });
1825
2591
  const items = gaugePools.map(({ pool, gauge }, index) => {
1826
2592
  const offset = index * 5;
@@ -1863,27 +2629,27 @@ gauges.command("list", {
1863
2629
  });
1864
2630
  gauges.command("info", {
1865
2631
  description: "Get detailed state for one gauge address.",
1866
- args: z2.object({
1867
- gauge: z2.string().describe("Gauge contract address")
2632
+ args: z3.object({
2633
+ gauge: z3.string().describe("Gauge contract address")
1868
2634
  }),
1869
2635
  env: env2,
1870
- output: z2.object({
1871
- gauge: z2.string(),
1872
- pool: z2.string(),
1873
- isAlive: z2.boolean(),
1874
- stakingToken: z2.string(),
1875
- rewardToken: z2.string(),
1876
- totalStaked: z2.string(),
1877
- rewardRate: z2.string(),
1878
- rewardPerTokenStored: z2.string(),
1879
- fees0: z2.string(),
1880
- fees1: z2.string(),
1881
- left: z2.string(),
1882
- periodFinish: z2.number(),
1883
- periodFinishRelative: z2.string(),
1884
- lastUpdateTime: z2.number(),
1885
- bribeContract: z2.string(),
1886
- feeContract: z2.string()
2636
+ output: z3.object({
2637
+ gauge: z3.string(),
2638
+ pool: z3.string(),
2639
+ isAlive: z3.boolean(),
2640
+ stakingToken: z3.string(),
2641
+ rewardToken: z3.string(),
2642
+ totalStaked: z3.string(),
2643
+ rewardRate: z3.string(),
2644
+ rewardPerTokenStored: z3.string(),
2645
+ fees0: z3.string(),
2646
+ fees1: z3.string(),
2647
+ left: z3.string(),
2648
+ periodFinish: z3.number(),
2649
+ periodFinishRelative: z3.string(),
2650
+ lastUpdateTime: z3.number(),
2651
+ bribeContract: z3.string(),
2652
+ feeContract: z3.string()
1887
2653
  }),
1888
2654
  examples: [
1889
2655
  {
@@ -2029,22 +2795,22 @@ gauges.command("info", {
2029
2795
  });
2030
2796
  gauges.command("staked", {
2031
2797
  description: "Show one address staking positions across all gauges.",
2032
- args: z2.object({
2033
- address: z2.string().describe("Wallet address to inspect")
2798
+ args: z3.object({
2799
+ address: z3.string().describe("Wallet address to inspect")
2034
2800
  }),
2035
2801
  env: env2,
2036
- output: z2.object({
2037
- address: z2.string(),
2038
- positions: z2.array(
2039
- z2.object({
2040
- pool: z2.string(),
2041
- gauge: z2.string(),
2042
- rewardToken: z2.string(),
2043
- staked: z2.string(),
2044
- earned: z2.string()
2802
+ output: z3.object({
2803
+ address: z3.string(),
2804
+ positions: z3.array(
2805
+ z3.object({
2806
+ pool: z3.string(),
2807
+ gauge: z3.string(),
2808
+ rewardToken: z3.string(),
2809
+ staked: z3.string(),
2810
+ earned: z3.string()
2045
2811
  })
2046
2812
  ),
2047
- count: z2.number()
2813
+ count: z3.number()
2048
2814
  }),
2049
2815
  examples: [
2050
2816
  {
@@ -2132,14 +2898,194 @@ gauges.command("staked", {
2132
2898
  );
2133
2899
  }
2134
2900
  });
2901
+ gauges.command("deposit", {
2902
+ description: "Deposit LP tokens into a gauge for staking rewards. Optionally attach a veNFT tokenId for boosted emissions. Approves the gauge to spend LP tokens if needed.",
2903
+ options: z3.object({
2904
+ gauge: z3.string().describe("Gauge contract address"),
2905
+ amount: z3.string().describe("Amount of LP tokens to deposit (in wei)"),
2906
+ "token-id": z3.coerce.number().int().nonnegative().optional().describe("veNFT token id for boosted emissions")
2907
+ }).merge(writeOptions),
2908
+ env: writeEnv,
2909
+ output: z3.object({
2910
+ gauge: z3.string(),
2911
+ stakingToken: z3.string(),
2912
+ amount: z3.string(),
2913
+ tokenId: z3.number().nullable(),
2914
+ tx: z3.union([
2915
+ z3.object({
2916
+ txHash: z3.string(),
2917
+ blockNumber: z3.number(),
2918
+ gasUsed: z3.string()
2919
+ }),
2920
+ z3.object({
2921
+ dryRun: z3.literal(true),
2922
+ estimatedGas: z3.string(),
2923
+ simulationResult: z3.unknown()
2924
+ })
2925
+ ])
2926
+ }),
2927
+ examples: [
2928
+ {
2929
+ options: {
2930
+ gauge: "0x0000000000000000000000000000000000000001",
2931
+ amount: "1000000000000000000",
2932
+ "dry-run": true
2933
+ },
2934
+ description: "Dry-run deposit 1e18 LP tokens into a gauge"
2935
+ },
2936
+ {
2937
+ options: {
2938
+ gauge: "0x0000000000000000000000000000000000000001",
2939
+ amount: "1000000000000000000",
2940
+ "token-id": 42
2941
+ },
2942
+ description: "Deposit with veNFT boost"
2943
+ }
2944
+ ],
2945
+ async run(c) {
2946
+ const gaugeAddress = c.options.gauge;
2947
+ if (!isAddress2(gaugeAddress)) {
2948
+ return c.error({
2949
+ code: "INVALID_ADDRESS",
2950
+ message: `Invalid gauge address: "${gaugeAddress}". Use a valid 0x-prefixed 20-byte hex address.`
2951
+ });
2952
+ }
2953
+ const amount = BigInt(c.options.amount);
2954
+ if (amount <= 0n) {
2955
+ return c.error({
2956
+ code: "INVALID_AMOUNT",
2957
+ message: "amount must be a positive integer in wei."
2958
+ });
2959
+ }
2960
+ const gauge = gaugeAddress;
2961
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
2962
+ const isAlive = await client.readContract({
2963
+ abi: voterAbi,
2964
+ address: ABOREAN_V2_ADDRESSES.voter,
2965
+ functionName: "isAlive",
2966
+ args: [gauge]
2967
+ });
2968
+ if (!isAlive) {
2969
+ return c.error({
2970
+ code: "GAUGE_NOT_ALIVE",
2971
+ message: `Gauge ${toChecksum(gauge)} is not alive.`,
2972
+ retryable: false
2973
+ });
2974
+ }
2975
+ const stakingToken = await client.readContract({
2976
+ abi: gaugeAbi,
2977
+ address: gauge,
2978
+ functionName: "stakingToken"
2979
+ });
2980
+ const account = resolveAccount(c.env);
2981
+ if (!c.options["dry-run"]) {
2982
+ const currentAllowance = await client.readContract({
2983
+ abi: erc20ApproveAbi2,
2984
+ address: stakingToken,
2985
+ functionName: "allowance",
2986
+ args: [account.address, gauge]
2987
+ });
2988
+ if (currentAllowance < amount) {
2989
+ await aboreanWriteTx({
2990
+ env: c.env,
2991
+ options: { ...c.options, "dry-run": false },
2992
+ address: stakingToken,
2993
+ abi: erc20ApproveAbi2,
2994
+ functionName: "approve",
2995
+ args: [gauge, amount]
2996
+ });
2997
+ }
2998
+ }
2999
+ const tokenId = c.options["token-id"];
3000
+ const tx = await aboreanWriteTx({
3001
+ env: c.env,
3002
+ options: c.options,
3003
+ address: gauge,
3004
+ abi: tokenId !== void 0 ? gaugeDepositWithTokenIdAbi : gaugeDepositAbi,
3005
+ functionName: "deposit",
3006
+ args: tokenId !== void 0 ? [amount, BigInt(tokenId)] : [amount]
3007
+ });
3008
+ return c.ok({
3009
+ gauge: toChecksum(gauge),
3010
+ stakingToken: toChecksum(stakingToken),
3011
+ amount: amount.toString(),
3012
+ tokenId: tokenId !== void 0 ? tokenId : null,
3013
+ tx
3014
+ });
3015
+ }
3016
+ });
3017
+ gauges.command("withdraw", {
3018
+ description: "Withdraw LP tokens from a gauge.",
3019
+ options: z3.object({
3020
+ gauge: z3.string().describe("Gauge contract address"),
3021
+ amount: z3.string().describe("Amount of LP tokens to withdraw (in wei)")
3022
+ }).merge(writeOptions),
3023
+ env: writeEnv,
3024
+ output: z3.object({
3025
+ gauge: z3.string(),
3026
+ amount: z3.string(),
3027
+ tx: z3.union([
3028
+ z3.object({
3029
+ txHash: z3.string(),
3030
+ blockNumber: z3.number(),
3031
+ gasUsed: z3.string()
3032
+ }),
3033
+ z3.object({
3034
+ dryRun: z3.literal(true),
3035
+ estimatedGas: z3.string(),
3036
+ simulationResult: z3.unknown()
3037
+ })
3038
+ ])
3039
+ }),
3040
+ examples: [
3041
+ {
3042
+ options: {
3043
+ gauge: "0x0000000000000000000000000000000000000001",
3044
+ amount: "1000000000000000000",
3045
+ "dry-run": true
3046
+ },
3047
+ description: "Dry-run withdraw 1e18 LP tokens from a gauge"
3048
+ }
3049
+ ],
3050
+ async run(c) {
3051
+ const gaugeAddress = c.options.gauge;
3052
+ if (!isAddress2(gaugeAddress)) {
3053
+ return c.error({
3054
+ code: "INVALID_ADDRESS",
3055
+ message: `Invalid gauge address: "${gaugeAddress}". Use a valid 0x-prefixed 20-byte hex address.`
3056
+ });
3057
+ }
3058
+ const amount = BigInt(c.options.amount);
3059
+ if (amount <= 0n) {
3060
+ return c.error({
3061
+ code: "INVALID_AMOUNT",
3062
+ message: "amount must be a positive integer in wei."
3063
+ });
3064
+ }
3065
+ const gauge = gaugeAddress;
3066
+ const tx = await aboreanWriteTx({
3067
+ env: c.env,
3068
+ options: c.options,
3069
+ address: gauge,
3070
+ abi: gaugeWithdrawAbi,
3071
+ functionName: "withdraw",
3072
+ args: [amount]
3073
+ });
3074
+ return c.ok({
3075
+ gauge: toChecksum(gauge),
3076
+ amount: amount.toString(),
3077
+ tx
3078
+ });
3079
+ }
3080
+ });
2135
3081
 
2136
3082
  // src/commands/lending.ts
2137
- import { checksumAddress as checksumAddress3, isAddress as isAddress2 } from "@spectratools/cli-shared";
2138
- import { Cli as Cli3, z as z3 } from "incur";
3083
+ import { checksumAddress as checksumAddress3, isAddress as isAddress3 } from "@spectratools/cli-shared";
3084
+ import { Cli as Cli3, z as z4 } from "incur";
2139
3085
  import { formatUnits as formatUnits2 } from "viem";
2140
3086
  var MORPHO_DEPLOY_BLOCK = 13947713n;
2141
- var env3 = z3.object({
2142
- ABSTRACT_RPC_URL: z3.string().optional().describe("Abstract RPC URL override")
3087
+ var env3 = z4.object({
3088
+ ABSTRACT_RPC_URL: z4.string().optional().describe("Abstract RPC URL override")
2143
3089
  });
2144
3090
  var morphoAbi = [
2145
3091
  {
@@ -2220,27 +3166,27 @@ var erc20MetadataAbi2 = [
2220
3166
  outputs: [{ type: "uint8" }]
2221
3167
  }
2222
3168
  ];
2223
- var tokenMetaSchema = z3.object({
2224
- address: z3.string(),
2225
- symbol: z3.string(),
2226
- decimals: z3.number()
3169
+ var tokenMetaSchema = z4.object({
3170
+ address: z4.string(),
3171
+ symbol: z4.string(),
3172
+ decimals: z4.number()
2227
3173
  });
2228
- var lendingMarketRowSchema = z3.object({
2229
- marketId: z3.string(),
3174
+ var lendingMarketRowSchema = z4.object({
3175
+ marketId: z4.string(),
2230
3176
  loanToken: tokenMetaSchema,
2231
3177
  collateralToken: tokenMetaSchema,
2232
- oracle: z3.string(),
2233
- irm: z3.string(),
2234
- lltvBps: z3.number(),
2235
- lltvPercent: z3.number(),
2236
- totalSupplyAssets: z3.string(),
2237
- totalBorrowAssets: z3.string(),
2238
- totalSupplyShares: z3.string(),
2239
- totalBorrowShares: z3.string(),
2240
- availableLiquidityAssets: z3.string(),
2241
- utilization: z3.number().nullable(),
2242
- feeWad: z3.string(),
2243
- lastUpdate: z3.number()
3178
+ oracle: z4.string(),
3179
+ irm: z4.string(),
3180
+ lltvBps: z4.number(),
3181
+ lltvPercent: z4.number(),
3182
+ totalSupplyAssets: z4.string(),
3183
+ totalBorrowAssets: z4.string(),
3184
+ totalSupplyShares: z4.string(),
3185
+ totalBorrowShares: z4.string(),
3186
+ availableLiquidityAssets: z4.string(),
3187
+ utilization: z4.number().nullable(),
3188
+ feeWad: z4.string(),
3189
+ lastUpdate: z4.number()
2244
3190
  });
2245
3191
  function isMarketId(value) {
2246
3192
  return /^0x[0-9a-fA-F]{64}$/.test(value);
@@ -2476,21 +3422,21 @@ var lending = Cli3.create("lending", {
2476
3422
  });
2477
3423
  lending.command("markets", {
2478
3424
  description: "List Morpho markets discovered from CreateMarket events.",
2479
- args: z3.object({
2480
- limit: z3.coerce.number().int().positive().max(200).default(25).describe("Max markets to return")
3425
+ args: z4.object({
3426
+ limit: z4.coerce.number().int().positive().max(200).default(25).describe("Max markets to return")
2481
3427
  }),
2482
3428
  env: env3,
2483
- output: z3.object({
2484
- morpho: z3.string(),
2485
- marketCount: z3.number(),
2486
- markets: z3.array(lendingMarketRowSchema),
2487
- totalsByLoanToken: z3.array(
2488
- z3.object({
2489
- token: z3.string(),
2490
- symbol: z3.string(),
2491
- decimals: z3.number(),
2492
- totalSupplyAssets: z3.string(),
2493
- totalBorrowAssets: z3.string()
3429
+ output: z4.object({
3430
+ morpho: z4.string(),
3431
+ marketCount: z4.number(),
3432
+ markets: z4.array(lendingMarketRowSchema),
3433
+ totalsByLoanToken: z4.array(
3434
+ z4.object({
3435
+ token: z4.string(),
3436
+ symbol: z4.string(),
3437
+ decimals: z4.number(),
3438
+ totalSupplyAssets: z4.string(),
3439
+ totalBorrowAssets: z4.string()
2494
3440
  })
2495
3441
  )
2496
3442
  }),
@@ -2537,8 +3483,8 @@ lending.command("markets", {
2537
3483
  });
2538
3484
  lending.command("market", {
2539
3485
  description: "Get details for one Morpho market id (bytes32).",
2540
- args: z3.object({
2541
- marketId: z3.string().describe("Morpho market id (bytes32 hex)")
3486
+ args: z4.object({
3487
+ marketId: z4.string().describe("Morpho market id (bytes32 hex)")
2542
3488
  }),
2543
3489
  env: env3,
2544
3490
  output: lendingMarketRowSchema,
@@ -2598,29 +3544,29 @@ lending.command("market", {
2598
3544
  });
2599
3545
  lending.command("position", {
2600
3546
  description: "Inspect one user position in a Morpho market.",
2601
- args: z3.object({
2602
- marketId: z3.string().describe("Morpho market id (bytes32 hex)"),
2603
- user: z3.string().describe("Position owner address")
3547
+ args: z4.object({
3548
+ marketId: z4.string().describe("Morpho market id (bytes32 hex)"),
3549
+ user: z4.string().describe("Position owner address")
2604
3550
  }),
2605
3551
  env: env3,
2606
- output: z3.object({
2607
- marketId: z3.string(),
2608
- user: z3.string(),
3552
+ output: z4.object({
3553
+ marketId: z4.string(),
3554
+ user: z4.string(),
2609
3555
  loanToken: tokenMetaSchema,
2610
3556
  collateralToken: tokenMetaSchema,
2611
- supplyShares: z3.string(),
2612
- supplyAssetsEstimate: z3.object({
2613
- raw: z3.string(),
2614
- decimal: z3.string()
3557
+ supplyShares: z4.string(),
3558
+ supplyAssetsEstimate: z4.object({
3559
+ raw: z4.string(),
3560
+ decimal: z4.string()
2615
3561
  }),
2616
- borrowShares: z3.string(),
2617
- borrowAssetsEstimate: z3.object({
2618
- raw: z3.string(),
2619
- decimal: z3.string()
3562
+ borrowShares: z4.string(),
3563
+ borrowAssetsEstimate: z4.object({
3564
+ raw: z4.string(),
3565
+ decimal: z4.string()
2620
3566
  }),
2621
- collateralAssets: z3.object({
2622
- raw: z3.string(),
2623
- decimal: z3.string()
3567
+ collateralAssets: z4.object({
3568
+ raw: z4.string(),
3569
+ decimal: z4.string()
2624
3570
  })
2625
3571
  }),
2626
3572
  examples: [
@@ -2639,7 +3585,7 @@ lending.command("position", {
2639
3585
  message: "marketId must be a 32-byte hex string (0x + 64 hex chars)"
2640
3586
  });
2641
3587
  }
2642
- if (!isAddress2(c.args.user)) {
3588
+ if (!isAddress3(c.args.user)) {
2643
3589
  return c.error({
2644
3590
  code: "INVALID_ARGUMENT",
2645
3591
  message: "user must be a valid address"
@@ -2750,39 +3696,87 @@ lending.command("position", {
2750
3696
  });
2751
3697
 
2752
3698
  // src/commands/pools.ts
2753
- import { checksumAddress as checksumAddress4, isAddress as isAddress3 } from "@spectratools/cli-shared";
2754
- import { Cli as Cli4, z as z4 } from "incur";
3699
+ import { checksumAddress as checksumAddress4, isAddress as isAddress4 } from "@spectratools/cli-shared";
3700
+ import { Cli as Cli4, z as z5 } from "incur";
2755
3701
  import { formatUnits as formatUnits3, parseUnits as parseUnits2 } from "viem";
2756
3702
  var MULTICALL_BATCH_SIZE2 = 100;
2757
- var env4 = z4.object({
2758
- ABSTRACT_RPC_URL: z4.string().optional().describe("Abstract RPC URL override")
3703
+ var DEFAULT_SLIPPAGE_PERCENT2 = 0.5;
3704
+ var DEFAULT_DEADLINE_SECONDS2 = 300;
3705
+ var erc20ApproveAbi3 = [
3706
+ {
3707
+ type: "function",
3708
+ name: "approve",
3709
+ stateMutability: "nonpayable",
3710
+ inputs: [
3711
+ { name: "spender", type: "address" },
3712
+ { name: "amount", type: "uint256" }
3713
+ ],
3714
+ outputs: [{ name: "", type: "bool" }]
3715
+ },
3716
+ {
3717
+ type: "function",
3718
+ name: "allowance",
3719
+ stateMutability: "view",
3720
+ inputs: [
3721
+ { name: "owner", type: "address" },
3722
+ { name: "account", type: "address" }
3723
+ ],
3724
+ outputs: [{ name: "", type: "uint256" }]
3725
+ }
3726
+ ];
3727
+ var v2RouterSwapAbi = [
3728
+ {
3729
+ type: "function",
3730
+ name: "swapExactTokensForTokens",
3731
+ stateMutability: "nonpayable",
3732
+ inputs: [
3733
+ { name: "amountIn", type: "uint256" },
3734
+ { name: "amountOutMin", type: "uint256" },
3735
+ {
3736
+ name: "routes",
3737
+ type: "tuple[]",
3738
+ components: [
3739
+ { name: "from", type: "address" },
3740
+ { name: "to", type: "address" },
3741
+ { name: "stable", type: "bool" },
3742
+ { name: "factory", type: "address" }
3743
+ ]
3744
+ },
3745
+ { name: "to", type: "address" },
3746
+ { name: "deadline", type: "uint256" }
3747
+ ],
3748
+ outputs: [{ name: "amounts", type: "uint256[]" }]
3749
+ }
3750
+ ];
3751
+ var env4 = z5.object({
3752
+ ABSTRACT_RPC_URL: z5.string().optional().describe("Abstract RPC URL override")
2759
3753
  });
2760
- var tokenSchema2 = z4.object({
2761
- address: z4.string(),
2762
- symbol: z4.string(),
2763
- decimals: z4.number()
3754
+ var tokenSchema2 = z5.object({
3755
+ address: z5.string(),
3756
+ symbol: z5.string(),
3757
+ decimals: z5.number()
2764
3758
  });
2765
- var amountSchema = z4.object({
2766
- raw: z4.string(),
2767
- decimal: z4.string()
3759
+ var amountSchema2 = z5.object({
3760
+ raw: z5.string(),
3761
+ decimal: z5.string()
2768
3762
  });
2769
- var feeSchema = z4.object({
2770
- feeBps: z4.number(),
2771
- feePercent: z4.number()
3763
+ var feeSchema = z5.object({
3764
+ feeBps: z5.number(),
3765
+ feePercent: z5.number()
2772
3766
  }).nullable();
2773
- var poolSummarySchema = z4.object({
2774
- pool: z4.string(),
2775
- pair: z4.string(),
2776
- stable: z4.boolean(),
2777
- poolType: z4.enum(["stable", "volatile"]),
3767
+ var poolSummarySchema = z5.object({
3768
+ pool: z5.string(),
3769
+ pair: z5.string(),
3770
+ stable: z5.boolean(),
3771
+ poolType: z5.enum(["stable", "volatile"]),
2778
3772
  token0: tokenSchema2,
2779
3773
  token1: tokenSchema2,
2780
- reserves: z4.object({
2781
- token0: amountSchema,
2782
- token1: amountSchema,
2783
- blockTimestampLast: z4.number()
3774
+ reserves: z5.object({
3775
+ token0: amountSchema2,
3776
+ token1: amountSchema2,
3777
+ blockTimestampLast: z5.number()
2784
3778
  }),
2785
- totalSupply: z4.string(),
3779
+ totalSupply: z5.string(),
2786
3780
  fee: feeSchema
2787
3781
  });
2788
3782
  var erc20MetadataAbi3 = [
@@ -2987,17 +3981,17 @@ var pools = Cli4.create("pools", {
2987
3981
  });
2988
3982
  pools.command("list", {
2989
3983
  description: "List V2 pools with token pairs, reserves, and stable/volatile type.",
2990
- options: z4.object({
2991
- offset: z4.coerce.number().int().nonnegative().default(0).describe("Pool index offset"),
2992
- limit: z4.coerce.number().int().positive().max(500).default(50).describe("Maximum pools to return (max 500)")
3984
+ options: z5.object({
3985
+ offset: z5.coerce.number().int().nonnegative().default(0).describe("Pool index offset"),
3986
+ limit: z5.coerce.number().int().positive().max(500).default(50).describe("Maximum pools to return (max 500)")
2993
3987
  }),
2994
3988
  env: env4,
2995
- output: z4.object({
2996
- total: z4.number(),
2997
- offset: z4.number(),
2998
- limit: z4.number(),
2999
- count: z4.number(),
3000
- pools: z4.array(poolSummarySchema)
3989
+ output: z5.object({
3990
+ total: z5.number(),
3991
+ offset: z5.number(),
3992
+ limit: z5.number(),
3993
+ count: z5.number(),
3994
+ pools: z5.array(poolSummarySchema)
3001
3995
  }),
3002
3996
  async run(c) {
3003
3997
  const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
@@ -3073,105 +4067,303 @@ pools.command("list", {
3073
4067
  );
3074
4068
  }
3075
4069
  });
3076
- pools.command("pool", {
3077
- description: "Get detailed state for one V2 pool.",
3078
- args: z4.object({
3079
- address: z4.string().describe("Pool address")
4070
+ pools.command("pool", {
4071
+ description: "Get detailed state for one V2 pool.",
4072
+ args: z5.object({
4073
+ address: z5.string().describe("Pool address")
4074
+ }),
4075
+ env: env4,
4076
+ output: z5.object({
4077
+ pool: poolSummarySchema.extend({
4078
+ poolFees: z5.string(),
4079
+ factory: z5.string()
4080
+ })
4081
+ }),
4082
+ async run(c) {
4083
+ if (!isAddress4(c.args.address)) {
4084
+ return c.error({
4085
+ code: "INVALID_ADDRESS",
4086
+ message: `Invalid pool address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
4087
+ });
4088
+ }
4089
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
4090
+ const poolAddress = toAddress(c.args.address);
4091
+ const [token0, token1, stable, reserves, totalSupply, poolFees, factory] = await multicallStrict2(client, [
4092
+ {
4093
+ abi: v2PoolAbi,
4094
+ address: poolAddress,
4095
+ functionName: "token0"
4096
+ },
4097
+ {
4098
+ abi: v2PoolAbi,
4099
+ address: poolAddress,
4100
+ functionName: "token1"
4101
+ },
4102
+ {
4103
+ abi: v2PoolAbi,
4104
+ address: poolAddress,
4105
+ functionName: "stable"
4106
+ },
4107
+ {
4108
+ abi: v2PoolAbi,
4109
+ address: poolAddress,
4110
+ functionName: "getReserves"
4111
+ },
4112
+ {
4113
+ abi: v2PoolAbi,
4114
+ address: poolAddress,
4115
+ functionName: "totalSupply"
4116
+ },
4117
+ {
4118
+ abi: v2PoolAbi,
4119
+ address: poolAddress,
4120
+ functionName: "poolFees"
4121
+ },
4122
+ {
4123
+ abi: v2PoolAbi,
4124
+ address: poolAddress,
4125
+ functionName: "factory"
4126
+ }
4127
+ ]);
4128
+ const tokenMeta = await readTokenMetadata3(client, [token0, token1]);
4129
+ const [fee] = await readPoolFees(client, [{ pool: poolAddress, stable }]);
4130
+ const summary = toPoolSummary(
4131
+ {
4132
+ pool: poolAddress,
4133
+ token0,
4134
+ token1,
4135
+ stable,
4136
+ reserve0: reserves[0],
4137
+ reserve1: reserves[1],
4138
+ blockTimestampLast: reserves[2],
4139
+ totalSupply
4140
+ },
4141
+ tokenMeta,
4142
+ fee ?? null
4143
+ );
4144
+ return c.ok(
4145
+ {
4146
+ pool: {
4147
+ ...summary,
4148
+ poolFees: checksumAddress4(poolFees),
4149
+ factory: checksumAddress4(factory)
4150
+ }
4151
+ },
4152
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
4153
+ cta: {
4154
+ description: "Next steps:",
4155
+ commands: [
4156
+ {
4157
+ command: "pools quote",
4158
+ args: {
4159
+ tokenIn: summary.token0.address,
4160
+ tokenOut: summary.token1.address,
4161
+ amountIn: "1"
4162
+ },
4163
+ description: `Quote a ${summary.token0.symbol} \u2192 ${summary.token1.symbol} swap`
4164
+ },
4165
+ {
4166
+ command: "pools fees",
4167
+ args: { pool: checksumAddress4(poolAddress) },
4168
+ description: "Check fee configuration"
4169
+ }
4170
+ ]
4171
+ }
4172
+ }
4173
+ );
4174
+ }
4175
+ });
4176
+ pools.command("quote", {
4177
+ description: "Quote a single-hop V2 swap between tokenIn and tokenOut.",
4178
+ args: z5.object({
4179
+ tokenIn: z5.string().describe("Input token address"),
4180
+ tokenOut: z5.string().describe("Output token address"),
4181
+ amountIn: z5.string().describe("Input amount in human-readable decimal units")
4182
+ }),
4183
+ options: z5.object({
4184
+ stable: z5.boolean().default(false).describe("Use stable pool route (default: volatile)")
4185
+ }),
4186
+ env: env4,
4187
+ output: z5.object({
4188
+ pool: z5.string(),
4189
+ stable: z5.boolean(),
4190
+ tokenIn: tokenSchema2,
4191
+ tokenOut: tokenSchema2,
4192
+ amountIn: amountSchema2,
4193
+ amountOut: amountSchema2,
4194
+ priceOutPerIn: z5.number().nullable()
4195
+ }),
4196
+ async run(c) {
4197
+ if (!isAddress4(c.args.tokenIn) || !isAddress4(c.args.tokenOut)) {
4198
+ return c.error({
4199
+ code: "INVALID_ADDRESS",
4200
+ message: "tokenIn and tokenOut must both be valid 0x-prefixed 20-byte addresses."
4201
+ });
4202
+ }
4203
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
4204
+ const tokenIn = toAddress(c.args.tokenIn);
4205
+ const tokenOut = toAddress(c.args.tokenOut);
4206
+ const tokenMeta = await readTokenMetadata3(client, [tokenIn, tokenOut]);
4207
+ const inMeta = tokenMeta.get(tokenIn.toLowerCase()) ?? fallbackTokenMeta(tokenIn);
4208
+ const outMeta = tokenMeta.get(tokenOut.toLowerCase()) ?? fallbackTokenMeta(tokenOut);
4209
+ let amountInRaw;
4210
+ try {
4211
+ amountInRaw = parseUnits2(c.args.amountIn, inMeta.decimals);
4212
+ } catch {
4213
+ return c.error({
4214
+ code: "INVALID_AMOUNT",
4215
+ message: `Invalid amountIn: "${c.args.amountIn}" for token ${inMeta.symbol} (${inMeta.decimals} decimals).`
4216
+ });
4217
+ }
4218
+ const poolAddress = await client.readContract({
4219
+ abi: poolFactoryAbi,
4220
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
4221
+ functionName: "getPool",
4222
+ args: [tokenIn, tokenOut, c.options.stable]
4223
+ });
4224
+ if (poolAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase()) {
4225
+ return c.error({
4226
+ code: "POOL_NOT_FOUND",
4227
+ message: `No ${c.options.stable ? "stable" : "volatile"} V2 pool found for pair ${checksumAddress4(
4228
+ tokenIn
4229
+ )}/${checksumAddress4(tokenOut)}.`
4230
+ });
4231
+ }
4232
+ const amounts = await client.readContract({
4233
+ abi: v2RouterAbi,
4234
+ address: ABOREAN_V2_ADDRESSES.router,
4235
+ functionName: "getAmountsOut",
4236
+ args: [
4237
+ amountInRaw,
4238
+ [
4239
+ {
4240
+ from: tokenIn,
4241
+ to: tokenOut,
4242
+ stable: c.options.stable,
4243
+ factory: ABOREAN_V2_ADDRESSES.poolFactory
4244
+ }
4245
+ ]
4246
+ ]
4247
+ });
4248
+ const amountOutRaw = amounts[amounts.length - 1] ?? 0n;
4249
+ const amountInDecimal = formatUnits3(amountInRaw, inMeta.decimals);
4250
+ const amountOutDecimal = formatUnits3(amountOutRaw, outMeta.decimals);
4251
+ const ratio = Number(amountOutDecimal) / Number(amountInDecimal);
4252
+ return c.ok(
4253
+ {
4254
+ pool: checksumAddress4(poolAddress),
4255
+ stable: c.options.stable,
4256
+ tokenIn: inMeta,
4257
+ tokenOut: outMeta,
4258
+ amountIn: {
4259
+ raw: amountInRaw.toString(),
4260
+ decimal: amountInDecimal
4261
+ },
4262
+ amountOut: {
4263
+ raw: amountOutRaw.toString(),
4264
+ decimal: amountOutDecimal
4265
+ },
4266
+ priceOutPerIn: Number(amountInDecimal) === 0 ? null : finiteOrNull3(ratio)
4267
+ },
4268
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
4269
+ cta: {
4270
+ description: "Related commands:",
4271
+ commands: [
4272
+ {
4273
+ command: "pools pool",
4274
+ args: { address: checksumAddress4(poolAddress) },
4275
+ description: "Inspect the pool used for this quote"
4276
+ },
4277
+ {
4278
+ command: "pools quote",
4279
+ args: {
4280
+ tokenIn: outMeta.address,
4281
+ tokenOut: inMeta.address,
4282
+ amountIn: amountOutDecimal
4283
+ },
4284
+ description: `Reverse quote ${outMeta.symbol} \u2192 ${inMeta.symbol}`
4285
+ }
4286
+ ]
4287
+ }
4288
+ }
4289
+ );
4290
+ }
4291
+ });
4292
+ pools.command("fees", {
4293
+ description: "Read V2 fee configuration for a pool address.",
4294
+ args: z5.object({
4295
+ pool: z5.string().describe("Pool address")
3080
4296
  }),
3081
4297
  env: env4,
3082
- output: z4.object({
3083
- pool: poolSummarySchema.extend({
3084
- poolFees: z4.string(),
3085
- factory: z4.string()
3086
- })
4298
+ output: z5.object({
4299
+ pool: z5.string(),
4300
+ pair: z5.string(),
4301
+ stable: z5.boolean(),
4302
+ activeFee: feeSchema,
4303
+ stableFee: feeSchema,
4304
+ volatileFee: feeSchema
3087
4305
  }),
3088
4306
  async run(c) {
3089
- if (!isAddress3(c.args.address)) {
4307
+ if (!isAddress4(c.args.pool)) {
3090
4308
  return c.error({
3091
4309
  code: "INVALID_ADDRESS",
3092
- message: `Invalid pool address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
4310
+ message: `Invalid pool address: "${c.args.pool}". Use a valid 0x-prefixed 20-byte hex address.`
3093
4311
  });
3094
4312
  }
3095
4313
  const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
3096
- const poolAddress = toAddress(c.args.address);
3097
- const [token0, token1, stable, reserves, totalSupply, poolFees, factory] = await multicallStrict2(client, [
4314
+ const pool = toAddress(c.args.pool);
4315
+ const [token0, token1, stable] = await multicallStrict2(client, [
3098
4316
  {
3099
4317
  abi: v2PoolAbi,
3100
- address: poolAddress,
4318
+ address: pool,
3101
4319
  functionName: "token0"
3102
4320
  },
3103
4321
  {
3104
4322
  abi: v2PoolAbi,
3105
- address: poolAddress,
4323
+ address: pool,
3106
4324
  functionName: "token1"
3107
4325
  },
3108
4326
  {
3109
4327
  abi: v2PoolAbi,
3110
- address: poolAddress,
4328
+ address: pool,
3111
4329
  functionName: "stable"
3112
- },
3113
- {
3114
- abi: v2PoolAbi,
3115
- address: poolAddress,
3116
- functionName: "getReserves"
3117
- },
3118
- {
3119
- abi: v2PoolAbi,
3120
- address: poolAddress,
3121
- functionName: "totalSupply"
3122
- },
3123
- {
3124
- abi: v2PoolAbi,
3125
- address: poolAddress,
3126
- functionName: "poolFees"
3127
- },
3128
- {
3129
- abi: v2PoolAbi,
3130
- address: poolAddress,
3131
- functionName: "factory"
3132
4330
  }
3133
4331
  ]);
3134
4332
  const tokenMeta = await readTokenMetadata3(client, [token0, token1]);
3135
- const [fee] = await readPoolFees(client, [{ pool: poolAddress, stable }]);
3136
- const summary = toPoolSummary(
3137
- {
3138
- pool: poolAddress,
3139
- token0,
3140
- token1,
3141
- stable,
3142
- reserve0: reserves[0],
3143
- reserve1: reserves[1],
3144
- blockTimestampLast: reserves[2],
3145
- totalSupply
3146
- },
3147
- tokenMeta,
3148
- fee ?? null
3149
- );
4333
+ const token0Meta = tokenMeta.get(token0.toLowerCase()) ?? fallbackTokenMeta(token0);
4334
+ const token1Meta = tokenMeta.get(token1.toLowerCase()) ?? fallbackTokenMeta(token1);
4335
+ const [stableFeeRaw, volatileFeeRaw] = await readPoolFees(client, [
4336
+ { pool, stable: true },
4337
+ { pool, stable: false }
4338
+ ]);
4339
+ const stableFee = toFeeInfo(stableFeeRaw ?? null);
4340
+ const volatileFee = toFeeInfo(volatileFeeRaw ?? null);
3150
4341
  return c.ok(
3151
4342
  {
3152
- pool: {
3153
- ...summary,
3154
- poolFees: checksumAddress4(poolFees),
3155
- factory: checksumAddress4(factory)
3156
- }
4343
+ pool: checksumAddress4(pool),
4344
+ pair: `${token0Meta.symbol}/${token1Meta.symbol}`,
4345
+ stable,
4346
+ activeFee: stable ? stableFee : volatileFee,
4347
+ stableFee,
4348
+ volatileFee
3157
4349
  },
3158
4350
  c.format === "json" || c.format === "jsonl" ? void 0 : {
3159
4351
  cta: {
3160
- description: "Next steps:",
4352
+ description: "Related commands:",
3161
4353
  commands: [
4354
+ {
4355
+ command: "pools pool",
4356
+ args: { address: checksumAddress4(pool) },
4357
+ description: "View full pool state"
4358
+ },
3162
4359
  {
3163
4360
  command: "pools quote",
3164
4361
  args: {
3165
- tokenIn: summary.token0.address,
3166
- tokenOut: summary.token1.address,
4362
+ tokenIn: token0Meta.address,
4363
+ tokenOut: token1Meta.address,
3167
4364
  amountIn: "1"
3168
4365
  },
3169
- description: `Quote a ${summary.token0.symbol} \u2192 ${summary.token1.symbol} swap`
3170
- },
3171
- {
3172
- command: "pools fees",
3173
- args: { pool: checksumAddress4(poolAddress) },
3174
- description: "Check fee configuration"
4366
+ description: `Quote a ${token0Meta.symbol} \u2192 ${token1Meta.symbol} swap`
3175
4367
  }
3176
4368
  ]
3177
4369
  }
@@ -3179,210 +4371,512 @@ pools.command("pool", {
3179
4371
  );
3180
4372
  }
3181
4373
  });
3182
- pools.command("quote", {
3183
- description: "Quote a single-hop V2 swap between tokenIn and tokenOut.",
3184
- args: z4.object({
3185
- tokenIn: z4.string().describe("Input token address"),
3186
- tokenOut: z4.string().describe("Output token address"),
3187
- amountIn: z4.string().describe("Input amount in human-readable decimal units")
3188
- }),
3189
- options: z4.object({
3190
- stable: z4.boolean().default(false).describe("Use stable pool route (default: volatile)")
3191
- }),
3192
- env: env4,
3193
- output: z4.object({
3194
- pool: z4.string(),
3195
- stable: z4.boolean(),
4374
+ pools.command("swap", {
4375
+ description: "Execute a single-hop V2 AMM swap. Quotes the expected output, applies slippage tolerance, approves the router if needed, and broadcasts the swap transaction.",
4376
+ options: z5.object({
4377
+ "token-in": z5.string().describe("Input token address"),
4378
+ "token-out": z5.string().describe("Output token address"),
4379
+ "amount-in": z5.string().describe("Input amount in wei"),
4380
+ slippage: z5.coerce.number().min(0).max(100).default(DEFAULT_SLIPPAGE_PERCENT2).describe("Slippage tolerance in percent (default: 0.5)"),
4381
+ deadline: z5.coerce.number().int().positive().default(DEFAULT_DEADLINE_SECONDS2).describe("Transaction deadline in seconds from now (default: 300)"),
4382
+ stable: z5.boolean().default(false).describe("Use stable pool route (default: volatile)")
4383
+ }).merge(writeOptions),
4384
+ env: writeEnv,
4385
+ output: z5.object({
4386
+ pool: z5.string(),
4387
+ stable: z5.boolean(),
3196
4388
  tokenIn: tokenSchema2,
3197
4389
  tokenOut: tokenSchema2,
3198
- amountIn: amountSchema,
3199
- amountOut: amountSchema,
3200
- priceOutPerIn: z4.number().nullable()
4390
+ amountIn: amountSchema2,
4391
+ expectedAmountOut: amountSchema2,
4392
+ minAmountOut: amountSchema2,
4393
+ slippagePercent: z5.number(),
4394
+ effectivePrice: z5.number().nullable(),
4395
+ tx: z5.union([
4396
+ z5.object({
4397
+ txHash: z5.string(),
4398
+ blockNumber: z5.number(),
4399
+ gasUsed: z5.string()
4400
+ }),
4401
+ z5.object({
4402
+ dryRun: z5.literal(true),
4403
+ estimatedGas: z5.string(),
4404
+ simulationResult: z5.unknown()
4405
+ })
4406
+ ])
3201
4407
  }),
3202
4408
  async run(c) {
3203
- if (!isAddress3(c.args.tokenIn) || !isAddress3(c.args.tokenOut)) {
4409
+ const tokenInRaw = c.options["token-in"];
4410
+ const tokenOutRaw = c.options["token-out"];
4411
+ if (!isAddress4(tokenInRaw) || !isAddress4(tokenOutRaw)) {
3204
4412
  return c.error({
3205
4413
  code: "INVALID_ADDRESS",
3206
- message: "tokenIn and tokenOut must both be valid 0x-prefixed 20-byte addresses."
4414
+ message: "token-in and token-out must both be valid 0x-prefixed 20-byte addresses."
4415
+ });
4416
+ }
4417
+ const tokenIn = toAddress(tokenInRaw);
4418
+ const tokenOut = toAddress(tokenOutRaw);
4419
+ const amountInRaw = BigInt(c.options["amount-in"]);
4420
+ if (amountInRaw <= 0n) {
4421
+ return c.error({
4422
+ code: "INVALID_AMOUNT",
4423
+ message: "amount-in must be a positive integer in wei."
3207
4424
  });
3208
4425
  }
3209
4426
  const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
3210
- const tokenIn = toAddress(c.args.tokenIn);
3211
- const tokenOut = toAddress(c.args.tokenOut);
3212
4427
  const tokenMeta = await readTokenMetadata3(client, [tokenIn, tokenOut]);
3213
4428
  const inMeta = tokenMeta.get(tokenIn.toLowerCase()) ?? fallbackTokenMeta(tokenIn);
3214
4429
  const outMeta = tokenMeta.get(tokenOut.toLowerCase()) ?? fallbackTokenMeta(tokenOut);
3215
- let amountInRaw;
4430
+ const poolAddress = await client.readContract({
4431
+ abi: poolFactoryAbi,
4432
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
4433
+ functionName: "getPool",
4434
+ args: [tokenIn, tokenOut, c.options.stable]
4435
+ });
4436
+ if (poolAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase()) {
4437
+ return c.error({
4438
+ code: "POOL_NOT_FOUND",
4439
+ message: `No ${c.options.stable ? "stable" : "volatile"} V2 pool found for pair ${checksumAddress4(tokenIn)}/${checksumAddress4(tokenOut)}.`
4440
+ });
4441
+ }
4442
+ const amounts = await client.readContract({
4443
+ abi: v2RouterAbi,
4444
+ address: ABOREAN_V2_ADDRESSES.router,
4445
+ functionName: "getAmountsOut",
4446
+ args: [
4447
+ amountInRaw,
4448
+ [
4449
+ {
4450
+ from: tokenIn,
4451
+ to: tokenOut,
4452
+ stable: c.options.stable,
4453
+ factory: ABOREAN_V2_ADDRESSES.poolFactory
4454
+ }
4455
+ ]
4456
+ ]
4457
+ });
4458
+ const expectedAmountOut = amounts[amounts.length - 1] ?? 0n;
4459
+ if (expectedAmountOut === 0n) {
4460
+ return c.error({
4461
+ code: "ZERO_QUOTE",
4462
+ message: "Router returned zero output amount. The pool may have insufficient liquidity."
4463
+ });
4464
+ }
4465
+ const slippageBps = Math.round(c.options.slippage * 100);
4466
+ const minAmountOut = expectedAmountOut * BigInt(1e4 - slippageBps) / 10000n;
4467
+ const deadlineTimestamp = BigInt(Math.floor(Date.now() / 1e3) + c.options.deadline);
4468
+ const account = resolveAccount(c.env);
4469
+ if (!c.options["dry-run"]) {
4470
+ const currentAllowance = await client.readContract({
4471
+ abi: erc20ApproveAbi3,
4472
+ address: tokenIn,
4473
+ functionName: "allowance",
4474
+ args: [account.address, ABOREAN_V2_ADDRESSES.router]
4475
+ });
4476
+ if (currentAllowance < amountInRaw) {
4477
+ await aboreanWriteTx({
4478
+ env: c.env,
4479
+ options: { ...c.options, "dry-run": false },
4480
+ address: tokenIn,
4481
+ abi: erc20ApproveAbi3,
4482
+ functionName: "approve",
4483
+ args: [ABOREAN_V2_ADDRESSES.router, amountInRaw]
4484
+ });
4485
+ }
4486
+ }
4487
+ const tx = await aboreanWriteTx({
4488
+ env: c.env,
4489
+ options: c.options,
4490
+ address: ABOREAN_V2_ADDRESSES.router,
4491
+ abi: v2RouterSwapAbi,
4492
+ functionName: "swapExactTokensForTokens",
4493
+ args: [
4494
+ amountInRaw,
4495
+ minAmountOut,
4496
+ [
4497
+ {
4498
+ from: tokenIn,
4499
+ to: tokenOut,
4500
+ stable: c.options.stable,
4501
+ factory: ABOREAN_V2_ADDRESSES.poolFactory
4502
+ }
4503
+ ],
4504
+ account.address,
4505
+ deadlineTimestamp
4506
+ ]
4507
+ });
4508
+ const amountInDecimal = formatUnits3(amountInRaw, inMeta.decimals);
4509
+ const expectedOutDecimal = formatUnits3(expectedAmountOut, outMeta.decimals);
4510
+ const minOutDecimal = formatUnits3(minAmountOut, outMeta.decimals);
4511
+ const ratio = Number(expectedOutDecimal) / Number(amountInDecimal);
4512
+ return c.ok({
4513
+ pool: checksumAddress4(poolAddress),
4514
+ stable: c.options.stable,
4515
+ tokenIn: inMeta,
4516
+ tokenOut: outMeta,
4517
+ amountIn: {
4518
+ raw: amountInRaw.toString(),
4519
+ decimal: amountInDecimal
4520
+ },
4521
+ expectedAmountOut: {
4522
+ raw: expectedAmountOut.toString(),
4523
+ decimal: expectedOutDecimal
4524
+ },
4525
+ minAmountOut: {
4526
+ raw: minAmountOut.toString(),
4527
+ decimal: minOutDecimal
4528
+ },
4529
+ slippagePercent: c.options.slippage,
4530
+ effectivePrice: Number(amountInDecimal) === 0 ? null : finiteOrNull3(ratio),
4531
+ tx
4532
+ });
4533
+ }
4534
+ });
4535
+ var v2RouterAddLiquidityAbi = [
4536
+ {
4537
+ type: "function",
4538
+ name: "addLiquidity",
4539
+ stateMutability: "nonpayable",
4540
+ inputs: [
4541
+ { name: "tokenA", type: "address" },
4542
+ { name: "tokenB", type: "address" },
4543
+ { name: "stable", type: "bool" },
4544
+ { name: "amountADesired", type: "uint256" },
4545
+ { name: "amountBDesired", type: "uint256" },
4546
+ { name: "amountAMin", type: "uint256" },
4547
+ { name: "amountBMin", type: "uint256" },
4548
+ { name: "to", type: "address" },
4549
+ { name: "deadline", type: "uint256" }
4550
+ ],
4551
+ outputs: [
4552
+ { name: "amountA", type: "uint256" },
4553
+ { name: "amountB", type: "uint256" },
4554
+ { name: "liquidity", type: "uint256" }
4555
+ ]
4556
+ }
4557
+ ];
4558
+ var v2RouterRemoveLiquidityAbi = [
4559
+ {
4560
+ type: "function",
4561
+ name: "removeLiquidity",
4562
+ stateMutability: "nonpayable",
4563
+ inputs: [
4564
+ { name: "tokenA", type: "address" },
4565
+ { name: "tokenB", type: "address" },
4566
+ { name: "stable", type: "bool" },
4567
+ { name: "liquidity", type: "uint256" },
4568
+ { name: "amountAMin", type: "uint256" },
4569
+ { name: "amountBMin", type: "uint256" },
4570
+ { name: "to", type: "address" },
4571
+ { name: "deadline", type: "uint256" }
4572
+ ],
4573
+ outputs: [
4574
+ { name: "amountA", type: "uint256" },
4575
+ { name: "amountB", type: "uint256" }
4576
+ ]
4577
+ }
4578
+ ];
4579
+ pools.command("add-liquidity", {
4580
+ description: "Add liquidity to a V2 pool. Approves both tokens to the router if needed, then calls addLiquidity. Supports --dry-run.",
4581
+ options: z5.object({
4582
+ "token-a": z5.string().describe("First token address"),
4583
+ "token-b": z5.string().describe("Second token address"),
4584
+ "amount-a": z5.string().describe("Desired amount of token A in wei"),
4585
+ "amount-b": z5.string().describe("Desired amount of token B in wei"),
4586
+ slippage: z5.coerce.number().min(0).max(100).default(DEFAULT_SLIPPAGE_PERCENT2).describe("Slippage tolerance in percent (default: 0.5)"),
4587
+ deadline: z5.coerce.number().int().positive().default(DEFAULT_DEADLINE_SECONDS2).describe("Transaction deadline in seconds from now (default: 300)"),
4588
+ stable: z5.boolean().default(false).describe("Target stable pool (default: volatile)")
4589
+ }).merge(writeOptions),
4590
+ env: writeEnv,
4591
+ output: z5.object({
4592
+ pool: z5.string(),
4593
+ stable: z5.boolean(),
4594
+ tokenA: tokenSchema2,
4595
+ tokenB: tokenSchema2,
4596
+ amountADesired: amountSchema2,
4597
+ amountBDesired: amountSchema2,
4598
+ amountAMin: amountSchema2,
4599
+ amountBMin: amountSchema2,
4600
+ slippagePercent: z5.number(),
4601
+ tx: z5.union([
4602
+ z5.object({
4603
+ txHash: z5.string(),
4604
+ blockNumber: z5.number(),
4605
+ gasUsed: z5.string()
4606
+ }),
4607
+ z5.object({
4608
+ dryRun: z5.literal(true),
4609
+ estimatedGas: z5.string(),
4610
+ simulationResult: z5.unknown()
4611
+ })
4612
+ ])
4613
+ }),
4614
+ async run(c) {
4615
+ const tokenARaw = c.options["token-a"];
4616
+ const tokenBRaw = c.options["token-b"];
4617
+ if (!isAddress4(tokenARaw) || !isAddress4(tokenBRaw)) {
4618
+ return c.error({
4619
+ code: "INVALID_ADDRESS",
4620
+ message: "token-a and token-b must both be valid 0x-prefixed 20-byte addresses."
4621
+ });
4622
+ }
4623
+ const tokenA = toAddress(tokenARaw);
4624
+ const tokenB = toAddress(tokenBRaw);
4625
+ let amountADesired;
4626
+ let amountBDesired;
4627
+ try {
4628
+ amountADesired = BigInt(c.options["amount-a"]);
4629
+ amountBDesired = BigInt(c.options["amount-b"]);
4630
+ } catch {
4631
+ return c.error({
4632
+ code: "INVALID_AMOUNT",
4633
+ message: "amount-a and amount-b must be valid integers in wei."
4634
+ });
4635
+ }
4636
+ if (amountADesired <= 0n || amountBDesired <= 0n) {
4637
+ return c.error({
4638
+ code: "INVALID_AMOUNT",
4639
+ message: "amount-a and amount-b must be positive integers."
4640
+ });
4641
+ }
4642
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
4643
+ const poolAddress = await client.readContract({
4644
+ abi: poolFactoryAbi,
4645
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
4646
+ functionName: "getPool",
4647
+ args: [tokenA, tokenB, c.options.stable]
4648
+ });
4649
+ if (poolAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase()) {
4650
+ return c.error({
4651
+ code: "POOL_NOT_FOUND",
4652
+ message: `No ${c.options.stable ? "stable" : "volatile"} V2 pool found for pair ${checksumAddress4(tokenA)}/${checksumAddress4(tokenB)}.`
4653
+ });
4654
+ }
4655
+ const tokenMeta = await readTokenMetadata3(client, [tokenA, tokenB]);
4656
+ const aMeta = tokenMeta.get(tokenA.toLowerCase()) ?? fallbackTokenMeta(tokenA);
4657
+ const bMeta = tokenMeta.get(tokenB.toLowerCase()) ?? fallbackTokenMeta(tokenB);
4658
+ const slippageBps = Math.round(c.options.slippage * 100);
4659
+ const amountAMin = amountADesired * BigInt(1e4 - slippageBps) / 10000n;
4660
+ const amountBMin = amountBDesired * BigInt(1e4 - slippageBps) / 10000n;
4661
+ const deadlineTimestamp = BigInt(Math.floor(Date.now() / 1e3) + c.options.deadline);
4662
+ const account = resolveAccount(c.env);
4663
+ if (!c.options["dry-run"]) {
4664
+ for (const [token, amount] of [
4665
+ [tokenA, amountADesired],
4666
+ [tokenB, amountBDesired]
4667
+ ]) {
4668
+ const currentAllowance = await client.readContract({
4669
+ abi: erc20ApproveAbi3,
4670
+ address: token,
4671
+ functionName: "allowance",
4672
+ args: [account.address, ABOREAN_V2_ADDRESSES.router]
4673
+ });
4674
+ if (currentAllowance < amount) {
4675
+ await aboreanWriteTx({
4676
+ env: c.env,
4677
+ options: { ...c.options, "dry-run": false },
4678
+ address: token,
4679
+ abi: erc20ApproveAbi3,
4680
+ functionName: "approve",
4681
+ args: [ABOREAN_V2_ADDRESSES.router, amount]
4682
+ });
4683
+ }
4684
+ }
4685
+ }
4686
+ const tx = await aboreanWriteTx({
4687
+ env: c.env,
4688
+ options: c.options,
4689
+ address: ABOREAN_V2_ADDRESSES.router,
4690
+ abi: v2RouterAddLiquidityAbi,
4691
+ functionName: "addLiquidity",
4692
+ args: [
4693
+ tokenA,
4694
+ tokenB,
4695
+ c.options.stable,
4696
+ amountADesired,
4697
+ amountBDesired,
4698
+ amountAMin,
4699
+ amountBMin,
4700
+ account.address,
4701
+ deadlineTimestamp
4702
+ ]
4703
+ });
4704
+ return c.ok({
4705
+ pool: checksumAddress4(poolAddress),
4706
+ stable: c.options.stable,
4707
+ tokenA: aMeta,
4708
+ tokenB: bMeta,
4709
+ amountADesired: {
4710
+ raw: amountADesired.toString(),
4711
+ decimal: formatUnits3(amountADesired, aMeta.decimals)
4712
+ },
4713
+ amountBDesired: {
4714
+ raw: amountBDesired.toString(),
4715
+ decimal: formatUnits3(amountBDesired, bMeta.decimals)
4716
+ },
4717
+ amountAMin: {
4718
+ raw: amountAMin.toString(),
4719
+ decimal: formatUnits3(amountAMin, aMeta.decimals)
4720
+ },
4721
+ amountBMin: {
4722
+ raw: amountBMin.toString(),
4723
+ decimal: formatUnits3(amountBMin, bMeta.decimals)
4724
+ },
4725
+ slippagePercent: c.options.slippage,
4726
+ tx
4727
+ });
4728
+ }
4729
+ });
4730
+ pools.command("remove-liquidity", {
4731
+ description: "Remove liquidity from a V2 pool. Approves the LP token to the router if needed, then calls removeLiquidity. Supports --dry-run.",
4732
+ options: z5.object({
4733
+ "token-a": z5.string().describe("First token address of the pair"),
4734
+ "token-b": z5.string().describe("Second token address of the pair"),
4735
+ liquidity: z5.string().describe("Amount of LP tokens to burn (in wei)"),
4736
+ slippage: z5.coerce.number().min(0).max(100).default(DEFAULT_SLIPPAGE_PERCENT2).describe("Slippage tolerance in percent (default: 0.5)"),
4737
+ deadline: z5.coerce.number().int().positive().default(DEFAULT_DEADLINE_SECONDS2).describe("Transaction deadline in seconds from now (default: 300)"),
4738
+ stable: z5.boolean().default(false).describe("Target stable pool (default: volatile)")
4739
+ }).merge(writeOptions),
4740
+ env: writeEnv,
4741
+ output: z5.object({
4742
+ pool: z5.string(),
4743
+ stable: z5.boolean(),
4744
+ tokenA: tokenSchema2,
4745
+ tokenB: tokenSchema2,
4746
+ liquidity: amountSchema2,
4747
+ amountAMin: amountSchema2,
4748
+ amountBMin: amountSchema2,
4749
+ slippagePercent: z5.number(),
4750
+ tx: z5.union([
4751
+ z5.object({
4752
+ txHash: z5.string(),
4753
+ blockNumber: z5.number(),
4754
+ gasUsed: z5.string()
4755
+ }),
4756
+ z5.object({
4757
+ dryRun: z5.literal(true),
4758
+ estimatedGas: z5.string(),
4759
+ simulationResult: z5.unknown()
4760
+ })
4761
+ ])
4762
+ }),
4763
+ async run(c) {
4764
+ const tokenARaw = c.options["token-a"];
4765
+ const tokenBRaw = c.options["token-b"];
4766
+ if (!isAddress4(tokenARaw) || !isAddress4(tokenBRaw)) {
4767
+ return c.error({
4768
+ code: "INVALID_ADDRESS",
4769
+ message: "token-a and token-b must both be valid 0x-prefixed 20-byte addresses."
4770
+ });
4771
+ }
4772
+ const tokenA = toAddress(tokenARaw);
4773
+ const tokenB = toAddress(tokenBRaw);
4774
+ let liquidityAmount;
3216
4775
  try {
3217
- amountInRaw = parseUnits2(c.args.amountIn, inMeta.decimals);
4776
+ liquidityAmount = BigInt(c.options.liquidity);
3218
4777
  } catch {
3219
4778
  return c.error({
3220
4779
  code: "INVALID_AMOUNT",
3221
- message: `Invalid amountIn: "${c.args.amountIn}" for token ${inMeta.symbol} (${inMeta.decimals} decimals).`
4780
+ message: "liquidity must be a valid integer in wei."
4781
+ });
4782
+ }
4783
+ if (liquidityAmount <= 0n) {
4784
+ return c.error({
4785
+ code: "INVALID_AMOUNT",
4786
+ message: "liquidity must be a positive integer."
3222
4787
  });
3223
4788
  }
4789
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
3224
4790
  const poolAddress = await client.readContract({
3225
4791
  abi: poolFactoryAbi,
3226
4792
  address: ABOREAN_V2_ADDRESSES.poolFactory,
3227
4793
  functionName: "getPool",
3228
- args: [tokenIn, tokenOut, c.options.stable]
4794
+ args: [tokenA, tokenB, c.options.stable]
3229
4795
  });
3230
4796
  if (poolAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase()) {
3231
4797
  return c.error({
3232
4798
  code: "POOL_NOT_FOUND",
3233
- message: `No ${c.options.stable ? "stable" : "volatile"} V2 pool found for pair ${checksumAddress4(
3234
- tokenIn
3235
- )}/${checksumAddress4(tokenOut)}.`
4799
+ message: `No ${c.options.stable ? "stable" : "volatile"} V2 pool found for pair ${checksumAddress4(tokenA)}/${checksumAddress4(tokenB)}.`
3236
4800
  });
3237
4801
  }
3238
- const amounts = await client.readContract({
3239
- abi: v2RouterAbi,
4802
+ const [poolStates] = await readPoolStates2(client, [poolAddress]);
4803
+ const tokenMeta = await readTokenMetadata3(client, [tokenA, tokenB]);
4804
+ const aMeta = tokenMeta.get(tokenA.toLowerCase()) ?? fallbackTokenMeta(tokenA);
4805
+ const bMeta = tokenMeta.get(tokenB.toLowerCase()) ?? fallbackTokenMeta(tokenB);
4806
+ const totalSupply = poolStates.totalSupply;
4807
+ const isTokenAToken0 = tokenA.toLowerCase() === poolStates.token0.toLowerCase();
4808
+ const reserveA = isTokenAToken0 ? poolStates.reserve0 : poolStates.reserve1;
4809
+ const reserveB = isTokenAToken0 ? poolStates.reserve1 : poolStates.reserve0;
4810
+ const expectedAmountA = totalSupply > 0n ? liquidityAmount * reserveA / totalSupply : 0n;
4811
+ const expectedAmountB = totalSupply > 0n ? liquidityAmount * reserveB / totalSupply : 0n;
4812
+ const slippageBps = Math.round(c.options.slippage * 100);
4813
+ const amountAMin = expectedAmountA * BigInt(1e4 - slippageBps) / 10000n;
4814
+ const amountBMin = expectedAmountB * BigInt(1e4 - slippageBps) / 10000n;
4815
+ const deadlineTimestamp = BigInt(Math.floor(Date.now() / 1e3) + c.options.deadline);
4816
+ const account = resolveAccount(c.env);
4817
+ if (!c.options["dry-run"]) {
4818
+ const currentAllowance = await client.readContract({
4819
+ abi: erc20ApproveAbi3,
4820
+ address: poolAddress,
4821
+ functionName: "allowance",
4822
+ args: [account.address, ABOREAN_V2_ADDRESSES.router]
4823
+ });
4824
+ if (currentAllowance < liquidityAmount) {
4825
+ await aboreanWriteTx({
4826
+ env: c.env,
4827
+ options: { ...c.options, "dry-run": false },
4828
+ address: poolAddress,
4829
+ abi: erc20ApproveAbi3,
4830
+ functionName: "approve",
4831
+ args: [ABOREAN_V2_ADDRESSES.router, liquidityAmount]
4832
+ });
4833
+ }
4834
+ }
4835
+ const tx = await aboreanWriteTx({
4836
+ env: c.env,
4837
+ options: c.options,
3240
4838
  address: ABOREAN_V2_ADDRESSES.router,
3241
- functionName: "getAmountsOut",
4839
+ abi: v2RouterRemoveLiquidityAbi,
4840
+ functionName: "removeLiquidity",
3242
4841
  args: [
3243
- amountInRaw,
3244
- [
3245
- {
3246
- from: tokenIn,
3247
- to: tokenOut,
3248
- stable: c.options.stable,
3249
- factory: ABOREAN_V2_ADDRESSES.poolFactory
3250
- }
3251
- ]
4842
+ tokenA,
4843
+ tokenB,
4844
+ c.options.stable,
4845
+ liquidityAmount,
4846
+ amountAMin,
4847
+ amountBMin,
4848
+ account.address,
4849
+ deadlineTimestamp
3252
4850
  ]
3253
4851
  });
3254
- const amountOutRaw = amounts[amounts.length - 1] ?? 0n;
3255
- const amountInDecimal = formatUnits3(amountInRaw, inMeta.decimals);
3256
- const amountOutDecimal = formatUnits3(amountOutRaw, outMeta.decimals);
3257
- const ratio = Number(amountOutDecimal) / Number(amountInDecimal);
3258
- return c.ok(
3259
- {
3260
- pool: checksumAddress4(poolAddress),
3261
- stable: c.options.stable,
3262
- tokenIn: inMeta,
3263
- tokenOut: outMeta,
3264
- amountIn: {
3265
- raw: amountInRaw.toString(),
3266
- decimal: amountInDecimal
3267
- },
3268
- amountOut: {
3269
- raw: amountOutRaw.toString(),
3270
- decimal: amountOutDecimal
3271
- },
3272
- priceOutPerIn: Number(amountInDecimal) === 0 ? null : finiteOrNull3(ratio)
3273
- },
3274
- c.format === "json" || c.format === "jsonl" ? void 0 : {
3275
- cta: {
3276
- description: "Related commands:",
3277
- commands: [
3278
- {
3279
- command: "pools pool",
3280
- args: { address: checksumAddress4(poolAddress) },
3281
- description: "Inspect the pool used for this quote"
3282
- },
3283
- {
3284
- command: "pools quote",
3285
- args: {
3286
- tokenIn: outMeta.address,
3287
- tokenOut: inMeta.address,
3288
- amountIn: amountOutDecimal
3289
- },
3290
- description: `Reverse quote ${outMeta.symbol} \u2192 ${inMeta.symbol}`
3291
- }
3292
- ]
3293
- }
3294
- }
3295
- );
3296
- }
3297
- });
3298
- pools.command("fees", {
3299
- description: "Read V2 fee configuration for a pool address.",
3300
- args: z4.object({
3301
- pool: z4.string().describe("Pool address")
3302
- }),
3303
- env: env4,
3304
- output: z4.object({
3305
- pool: z4.string(),
3306
- pair: z4.string(),
3307
- stable: z4.boolean(),
3308
- activeFee: feeSchema,
3309
- stableFee: feeSchema,
3310
- volatileFee: feeSchema
3311
- }),
3312
- async run(c) {
3313
- if (!isAddress3(c.args.pool)) {
3314
- return c.error({
3315
- code: "INVALID_ADDRESS",
3316
- message: `Invalid pool address: "${c.args.pool}". Use a valid 0x-prefixed 20-byte hex address.`
3317
- });
3318
- }
3319
- const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
3320
- const pool = toAddress(c.args.pool);
3321
- const [token0, token1, stable] = await multicallStrict2(client, [
3322
- {
3323
- abi: v2PoolAbi,
3324
- address: pool,
3325
- functionName: "token0"
4852
+ return c.ok({
4853
+ pool: checksumAddress4(poolAddress),
4854
+ stable: c.options.stable,
4855
+ tokenA: aMeta,
4856
+ tokenB: bMeta,
4857
+ liquidity: {
4858
+ raw: liquidityAmount.toString(),
4859
+ decimal: formatUnits3(liquidityAmount, 18)
3326
4860
  },
3327
- {
3328
- abi: v2PoolAbi,
3329
- address: pool,
3330
- functionName: "token1"
4861
+ amountAMin: {
4862
+ raw: amountAMin.toString(),
4863
+ decimal: formatUnits3(amountAMin, aMeta.decimals)
3331
4864
  },
3332
- {
3333
- abi: v2PoolAbi,
3334
- address: pool,
3335
- functionName: "stable"
3336
- }
3337
- ]);
3338
- const tokenMeta = await readTokenMetadata3(client, [token0, token1]);
3339
- const token0Meta = tokenMeta.get(token0.toLowerCase()) ?? fallbackTokenMeta(token0);
3340
- const token1Meta = tokenMeta.get(token1.toLowerCase()) ?? fallbackTokenMeta(token1);
3341
- const [stableFeeRaw, volatileFeeRaw] = await readPoolFees(client, [
3342
- { pool, stable: true },
3343
- { pool, stable: false }
3344
- ]);
3345
- const stableFee = toFeeInfo(stableFeeRaw ?? null);
3346
- const volatileFee = toFeeInfo(volatileFeeRaw ?? null);
3347
- return c.ok(
3348
- {
3349
- pool: checksumAddress4(pool),
3350
- pair: `${token0Meta.symbol}/${token1Meta.symbol}`,
3351
- stable,
3352
- activeFee: stable ? stableFee : volatileFee,
3353
- stableFee,
3354
- volatileFee
4865
+ amountBMin: {
4866
+ raw: amountBMin.toString(),
4867
+ decimal: formatUnits3(amountBMin, bMeta.decimals)
3355
4868
  },
3356
- c.format === "json" || c.format === "jsonl" ? void 0 : {
3357
- cta: {
3358
- description: "Related commands:",
3359
- commands: [
3360
- {
3361
- command: "pools pool",
3362
- args: { address: checksumAddress4(pool) },
3363
- description: "View full pool state"
3364
- },
3365
- {
3366
- command: "pools quote",
3367
- args: {
3368
- tokenIn: token0Meta.address,
3369
- tokenOut: token1Meta.address,
3370
- amountIn: "1"
3371
- },
3372
- description: `Quote a ${token0Meta.symbol} \u2192 ${token1Meta.symbol} swap`
3373
- }
3374
- ]
3375
- }
3376
- }
3377
- );
4869
+ slippagePercent: c.options.slippage,
4870
+ tx
4871
+ });
3378
4872
  }
3379
4873
  });
3380
4874
 
3381
4875
  // src/commands/vaults.ts
3382
- import { checksumAddress as checksumAddress5, isAddress as isAddress4 } from "@spectratools/cli-shared";
3383
- import { Cli as Cli5, z as z5 } from "incur";
3384
- var env5 = z5.object({
3385
- ABSTRACT_RPC_URL: z5.string().optional().describe("Abstract RPC URL override")
4876
+ import { checksumAddress as checksumAddress5, isAddress as isAddress5 } from "@spectratools/cli-shared";
4877
+ import { Cli as Cli5, z as z6 } from "incur";
4878
+ var env5 = z6.object({
4879
+ ABSTRACT_RPC_URL: z6.string().optional().describe("Abstract RPC URL override")
3386
4880
  });
3387
4881
  var relayAbi = [
3388
4882
  {
@@ -3446,22 +4940,22 @@ var votingEscrowLiteAbi = [
3446
4940
  outputs: [{ type: "uint256" }]
3447
4941
  }
3448
4942
  ];
3449
- var relayRowSchema = z5.object({
3450
- label: z5.string(),
3451
- relay: z5.string(),
3452
- name: z5.string(),
3453
- factoryType: z5.enum(["autoCompounder", "autoConverter"]),
3454
- managedTokenId: z5.string(),
3455
- managedVotingPower: z5.string(),
3456
- relayToken: z5.object({
3457
- address: z5.string(),
3458
- symbol: z5.string(),
3459
- decimals: z5.number()
4943
+ var relayRowSchema = z6.object({
4944
+ label: z6.string(),
4945
+ relay: z6.string(),
4946
+ name: z6.string(),
4947
+ factoryType: z6.enum(["autoCompounder", "autoConverter"]),
4948
+ managedTokenId: z6.string(),
4949
+ managedVotingPower: z6.string(),
4950
+ relayToken: z6.object({
4951
+ address: z6.string(),
4952
+ symbol: z6.string(),
4953
+ decimals: z6.number()
3460
4954
  }),
3461
- relayTokenBalance: z5.string(),
3462
- keeperLastRun: z5.number(),
3463
- keeperLastRunRelative: z5.string(),
3464
- secondsSinceKeeperRun: z5.number()
4955
+ relayTokenBalance: z6.string(),
4956
+ keeperLastRun: z6.number(),
4957
+ keeperLastRunRelative: z6.string(),
4958
+ secondsSinceKeeperRun: z6.number()
3465
4959
  });
3466
4960
  var KNOWN_RELAYS = [
3467
4961
  {
@@ -3587,17 +5081,17 @@ var vaults = Cli5.create("vaults", {
3587
5081
  vaults.command("list", {
3588
5082
  description: "List known Aborean relay vaults with keeper and veNFT state.",
3589
5083
  env: env5,
3590
- output: z5.object({
3591
- relayCount: z5.number(),
3592
- relays: z5.array(relayRowSchema),
3593
- totals: z5.object({
3594
- managedVotingPower: z5.string(),
3595
- relayTokenBalances: z5.array(
3596
- z5.object({
3597
- token: z5.string(),
3598
- symbol: z5.string(),
3599
- decimals: z5.number(),
3600
- balance: z5.string()
5084
+ output: z6.object({
5085
+ relayCount: z6.number(),
5086
+ relays: z6.array(relayRowSchema),
5087
+ totals: z6.object({
5088
+ managedVotingPower: z6.string(),
5089
+ relayTokenBalances: z6.array(
5090
+ z6.object({
5091
+ token: z6.string(),
5092
+ symbol: z6.string(),
5093
+ decimals: z6.number(),
5094
+ balance: z6.string()
3601
5095
  })
3602
5096
  )
3603
5097
  })
@@ -3636,8 +5130,8 @@ vaults.command("list", {
3636
5130
  });
3637
5131
  vaults.command("relay", {
3638
5132
  description: "Inspect one relay vault by address.",
3639
- args: z5.object({
3640
- relay: z5.string().describe("Relay vault contract address")
5133
+ args: z6.object({
5134
+ relay: z6.string().describe("Relay vault contract address")
3641
5135
  }),
3642
5136
  env: env5,
3643
5137
  output: relayRowSchema,
@@ -3648,7 +5142,7 @@ vaults.command("relay", {
3648
5142
  }
3649
5143
  ],
3650
5144
  async run(c) {
3651
- if (!isAddress4(c.args.relay)) {
5145
+ if (!isAddress5(c.args.relay)) {
3652
5146
  return c.error({
3653
5147
  code: "INVALID_ARGUMENT",
3654
5148
  message: "relay must be a valid address"
@@ -3689,9 +5183,9 @@ vaults.command("relay", {
3689
5183
  });
3690
5184
 
3691
5185
  // src/commands/ve.ts
3692
- import { Cli as Cli6, z as z6 } from "incur";
3693
- var env6 = z6.object({
3694
- ABSTRACT_RPC_URL: z6.string().optional().describe("Abstract RPC URL override")
5186
+ import { Cli as Cli6, z as z7 } from "incur";
5187
+ var env6 = z7.object({
5188
+ ABSTRACT_RPC_URL: z7.string().optional().describe("Abstract RPC URL override")
3695
5189
  });
3696
5190
  var ve = Cli6.create("ve", {
3697
5191
  description: "Inspect Aborean VotingEscrow (veABX) global and per-NFT lock state."
@@ -3699,16 +5193,16 @@ var ve = Cli6.create("ve", {
3699
5193
  ve.command("stats", {
3700
5194
  description: "Get global VotingEscrow supply, locks, and decay checkpoint data.",
3701
5195
  env: env6,
3702
- output: z6.object({
3703
- token: z6.string(),
3704
- totalVotingPower: z6.string(),
3705
- totalLocked: z6.string(),
3706
- permanentLocked: z6.string(),
3707
- epoch: z6.number(),
3708
- decayBias: z6.string(),
3709
- decaySlope: z6.string(),
3710
- lastCheckpointTimestamp: z6.number(),
3711
- lastCheckpointBlock: z6.number()
5196
+ output: z7.object({
5197
+ token: z7.string(),
5198
+ totalVotingPower: z7.string(),
5199
+ totalLocked: z7.string(),
5200
+ permanentLocked: z7.string(),
5201
+ epoch: z7.number(),
5202
+ decayBias: z7.string(),
5203
+ decaySlope: z7.string(),
5204
+ lastCheckpointTimestamp: z7.number(),
5205
+ lastCheckpointBlock: z7.number()
3712
5206
  }),
3713
5207
  examples: [{ description: "Show global veABX state and decay metrics" }],
3714
5208
  async run(c) {
@@ -3779,17 +5273,17 @@ ve.command("stats", {
3779
5273
  });
3780
5274
  ve.command("lock", {
3781
5275
  description: "Get lock details and voting power for one veNFT token id.",
3782
- args: z6.object({
3783
- tokenId: z6.coerce.number().int().nonnegative().describe("veNFT token id")
5276
+ args: z7.object({
5277
+ tokenId: z7.coerce.number().int().nonnegative().describe("veNFT token id")
3784
5278
  }),
3785
5279
  env: env6,
3786
- output: z6.object({
3787
- tokenId: z6.number(),
3788
- owner: z6.string(),
3789
- amount: z6.string(),
3790
- unlockTime: z6.number(),
3791
- isPermanent: z6.boolean(),
3792
- votingPower: z6.string()
5280
+ output: z7.object({
5281
+ tokenId: z7.number(),
5282
+ owner: z7.string(),
5283
+ amount: z7.string(),
5284
+ unlockTime: z7.number(),
5285
+ isPermanent: z7.boolean(),
5286
+ votingPower: z7.string()
3793
5287
  }),
3794
5288
  examples: [{ args: { tokenId: 1 }, description: "Inspect lock details for veNFT #1" }],
3795
5289
  async run(c) {
@@ -3846,22 +5340,22 @@ ve.command("lock", {
3846
5340
  });
3847
5341
  ve.command("locks", {
3848
5342
  description: "List all veNFT locks owned by an address.",
3849
- args: z6.object({
3850
- address: z6.string().describe("Owner address")
5343
+ args: z7.object({
5344
+ address: z7.string().describe("Owner address")
3851
5345
  }),
3852
5346
  env: env6,
3853
- output: z6.object({
3854
- address: z6.string(),
3855
- locks: z6.array(
3856
- z6.object({
3857
- tokenId: z6.string(),
3858
- amount: z6.string(),
3859
- unlockTime: z6.number(),
3860
- isPermanent: z6.boolean(),
3861
- votingPower: z6.string()
5347
+ output: z7.object({
5348
+ address: z7.string(),
5349
+ locks: z7.array(
5350
+ z7.object({
5351
+ tokenId: z7.string(),
5352
+ amount: z7.string(),
5353
+ unlockTime: z7.number(),
5354
+ isPermanent: z7.boolean(),
5355
+ votingPower: z7.string()
3862
5356
  })
3863
5357
  ),
3864
- count: z6.number()
5358
+ count: z7.number()
3865
5359
  }),
3866
5360
  examples: [
3867
5361
  {
@@ -3953,13 +5447,13 @@ ve.command("locks", {
3953
5447
  });
3954
5448
  ve.command("voting-power", {
3955
5449
  description: "Get current voting power for one veNFT token id.",
3956
- args: z6.object({
3957
- tokenId: z6.coerce.number().int().nonnegative().describe("veNFT token id")
5450
+ args: z7.object({
5451
+ tokenId: z7.coerce.number().int().nonnegative().describe("veNFT token id")
3958
5452
  }),
3959
5453
  env: env6,
3960
- output: z6.object({
3961
- tokenId: z6.number(),
3962
- votingPower: z6.string()
5454
+ output: z7.object({
5455
+ tokenId: z7.number(),
5456
+ votingPower: z7.string()
3963
5457
  }),
3964
5458
  examples: [{ args: { tokenId: 1 }, description: "Get current voting power for veNFT #1" }],
3965
5459
  async run(c) {
@@ -3997,9 +5491,23 @@ ve.command("voting-power", {
3997
5491
  });
3998
5492
 
3999
5493
  // src/commands/voter.ts
4000
- import { Cli as Cli7, z as z7 } from "incur";
4001
- var env7 = z7.object({
4002
- ABSTRACT_RPC_URL: z7.string().optional().describe("Abstract RPC URL override")
5494
+ import { isAddress as isAddress6 } from "@spectratools/cli-shared";
5495
+ import { Cli as Cli7, z as z8 } from "incur";
5496
+ var voterVoteAbi = [
5497
+ {
5498
+ type: "function",
5499
+ name: "vote",
5500
+ stateMutability: "nonpayable",
5501
+ inputs: [
5502
+ { name: "tokenId", type: "uint256" },
5503
+ { name: "_poolVote", type: "address[]" },
5504
+ { name: "_weights", type: "uint256[]" }
5505
+ ],
5506
+ outputs: []
5507
+ }
5508
+ ];
5509
+ var env7 = z8.object({
5510
+ ABSTRACT_RPC_URL: z8.string().optional().describe("Abstract RPC URL override")
4003
5511
  });
4004
5512
  async function discoverPools(client) {
4005
5513
  const [v2PoolCount, clPoolCount] = await Promise.all([
@@ -4044,14 +5552,14 @@ var voter = Cli7.create("voter", {
4044
5552
  voter.command("epoch", {
4045
5553
  description: "Show current emissions epoch timing from Minter.",
4046
5554
  env: env7,
4047
- output: z7.object({
4048
- activePeriod: z7.number(),
4049
- epochEnd: z7.number(),
4050
- secondsRemaining: z7.number(),
4051
- timeRemaining: z7.string(),
4052
- weekSeconds: z7.number(),
4053
- epochCount: z7.number(),
4054
- weeklyEmission: z7.string()
5555
+ output: z8.object({
5556
+ activePeriod: z8.number(),
5557
+ epochEnd: z8.number(),
5558
+ secondsRemaining: z8.number(),
5559
+ timeRemaining: z8.string(),
5560
+ weekSeconds: z8.number(),
5561
+ epochCount: z8.number(),
5562
+ weeklyEmission: z8.string()
4055
5563
  }),
4056
5564
  examples: [{ description: "Inspect current voter epoch boundaries" }],
4057
5565
  async run(c) {
@@ -4111,16 +5619,16 @@ voter.command("epoch", {
4111
5619
  voter.command("weights", {
4112
5620
  description: "Show current pool voting weight distribution.",
4113
5621
  env: env7,
4114
- output: z7.object({
4115
- totalWeight: z7.string(),
4116
- pools: z7.array(
4117
- z7.object({
4118
- pool: z7.string(),
4119
- gauge: z7.string(),
4120
- weight: z7.string()
5622
+ output: z8.object({
5623
+ totalWeight: z8.string(),
5624
+ pools: z8.array(
5625
+ z8.object({
5626
+ pool: z8.string(),
5627
+ gauge: z8.string(),
5628
+ weight: z8.string()
4121
5629
  })
4122
5630
  ),
4123
- count: z7.number()
5631
+ count: z8.number()
4124
5632
  }),
4125
5633
  examples: [{ description: "List all pools with non-zero voting weight" }],
4126
5634
  async run(c) {
@@ -4199,19 +5707,19 @@ voter.command("weights", {
4199
5707
  });
4200
5708
  voter.command("rewards", {
4201
5709
  description: "Show claimable rebase rewards and voting context for a veNFT.",
4202
- args: z7.object({
4203
- tokenId: z7.coerce.number().int().nonnegative().describe("veNFT token id")
5710
+ args: z8.object({
5711
+ tokenId: z8.coerce.number().int().nonnegative().describe("veNFT token id")
4204
5712
  }),
4205
5713
  env: env7,
4206
- output: z7.object({
4207
- tokenId: z7.number(),
4208
- rewardToken: z7.string(),
4209
- claimableRebase: z7.string(),
4210
- timeCursor: z7.number(),
4211
- lastTokenTime: z7.number(),
4212
- distributorStartTime: z7.number(),
4213
- usedWeight: z7.string(),
4214
- lastVoted: z7.number()
5714
+ output: z8.object({
5715
+ tokenId: z8.number(),
5716
+ rewardToken: z8.string(),
5717
+ claimableRebase: z8.string(),
5718
+ timeCursor: z8.number(),
5719
+ lastTokenTime: z8.number(),
5720
+ distributorStartTime: z8.number(),
5721
+ usedWeight: z8.string(),
5722
+ lastVoted: z8.number()
4215
5723
  }),
4216
5724
  examples: [{ args: { tokenId: 1 }, description: "Check claimable voter/distributor rewards" }],
4217
5725
  async run(c) {
@@ -4298,22 +5806,22 @@ voter.command("rewards", {
4298
5806
  });
4299
5807
  voter.command("bribes", {
4300
5808
  description: "Show active bribe reward tokens and current-epoch amounts for a pool.",
4301
- args: z7.object({
4302
- pool: z7.string().describe("Pool address")
5809
+ args: z8.object({
5810
+ pool: z8.string().describe("Pool address")
4303
5811
  }),
4304
5812
  env: env7,
4305
- output: z7.object({
4306
- pool: z7.string(),
4307
- gauge: z7.string(),
4308
- bribeContract: z7.string(),
4309
- epochStart: z7.number(),
4310
- rewardTokens: z7.array(
4311
- z7.object({
4312
- token: z7.string(),
4313
- epochAmount: z7.string()
5813
+ output: z8.object({
5814
+ pool: z8.string(),
5815
+ gauge: z8.string(),
5816
+ bribeContract: z8.string(),
5817
+ epochStart: z8.number(),
5818
+ rewardTokens: z8.array(
5819
+ z8.object({
5820
+ token: z8.string(),
5821
+ epochAmount: z8.string()
4314
5822
  })
4315
5823
  ),
4316
- count: z7.number()
5824
+ count: z8.number()
4317
5825
  }),
4318
5826
  examples: [
4319
5827
  {
@@ -4426,6 +5934,139 @@ voter.command("bribes", {
4426
5934
  );
4427
5935
  }
4428
5936
  });
5937
+ voter.command("vote", {
5938
+ description: "Cast votes for gauge(s) using a veNFT. Weights are relative and will be normalized by the Voter contract. Each pool address must have an active gauge.",
5939
+ options: z8.object({
5940
+ "token-id": z8.coerce.number().int().nonnegative().describe("veNFT token id to vote with"),
5941
+ pools: z8.string().describe("Comma-separated pool addresses to vote for"),
5942
+ weights: z8.string().describe("Comma-separated relative weights (matching pool order)")
5943
+ }).merge(writeOptions),
5944
+ env: writeEnv,
5945
+ output: z8.object({
5946
+ tokenId: z8.number(),
5947
+ pools: z8.array(z8.string()),
5948
+ weights: z8.array(z8.string()),
5949
+ tx: z8.union([
5950
+ z8.object({
5951
+ txHash: z8.string(),
5952
+ blockNumber: z8.number(),
5953
+ gasUsed: z8.string()
5954
+ }),
5955
+ z8.object({
5956
+ dryRun: z8.literal(true),
5957
+ estimatedGas: z8.string(),
5958
+ simulationResult: z8.unknown()
5959
+ })
5960
+ ])
5961
+ }),
5962
+ examples: [
5963
+ {
5964
+ options: {
5965
+ "token-id": 1,
5966
+ pools: "0x0000000000000000000000000000000000000001",
5967
+ weights: "100",
5968
+ "dry-run": true
5969
+ },
5970
+ description: "Dry-run vote for a single pool with weight 100"
5971
+ },
5972
+ {
5973
+ options: {
5974
+ "token-id": 1,
5975
+ pools: "0x0000000000000000000000000000000000000001,0x0000000000000000000000000000000000000002",
5976
+ weights: "60,40",
5977
+ "dry-run": true
5978
+ },
5979
+ description: "Dry-run vote for two pools with relative weights"
5980
+ }
5981
+ ],
5982
+ async run(c) {
5983
+ const poolAddresses = c.options.pools.split(",").map((s) => s.trim());
5984
+ const weightValues = c.options.weights.split(",").map((s) => s.trim());
5985
+ if (poolAddresses.length === 0 || poolAddresses[0] === "") {
5986
+ return c.error({
5987
+ code: "INVALID_INPUT",
5988
+ message: "At least one pool address is required."
5989
+ });
5990
+ }
5991
+ if (poolAddresses.length !== weightValues.length) {
5992
+ return c.error({
5993
+ code: "INVALID_INPUT",
5994
+ message: `Pool count (${poolAddresses.length}) and weight count (${weightValues.length}) must match.`
5995
+ });
5996
+ }
5997
+ for (const addr of poolAddresses) {
5998
+ if (!isAddress6(addr)) {
5999
+ return c.error({
6000
+ code: "INVALID_ADDRESS",
6001
+ message: `Invalid pool address: "${addr}". Use valid 0x-prefixed 20-byte hex addresses.`
6002
+ });
6003
+ }
6004
+ }
6005
+ const parsedWeights = [];
6006
+ for (const w of weightValues) {
6007
+ const parsed = BigInt(w);
6008
+ if (parsed <= 0n) {
6009
+ return c.error({
6010
+ code: "INVALID_INPUT",
6011
+ message: `Weight must be a positive integer, got: "${w}".`
6012
+ });
6013
+ }
6014
+ parsedWeights.push(parsed);
6015
+ }
6016
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
6017
+ const gaugeResults = await client.multicall({
6018
+ allowFailure: false,
6019
+ contracts: poolAddresses.map((pool) => ({
6020
+ abi: voterAbi,
6021
+ address: ABOREAN_V2_ADDRESSES.voter,
6022
+ functionName: "gauges",
6023
+ args: [pool]
6024
+ }))
6025
+ });
6026
+ for (let i = 0; i < gaugeResults.length; i++) {
6027
+ if (gaugeResults[i].toLowerCase() === ZERO_ADDRESS.toLowerCase()) {
6028
+ return c.error({
6029
+ code: "GAUGE_NOT_FOUND",
6030
+ message: `No gauge exists for pool ${poolAddresses[i]}.`,
6031
+ retryable: false
6032
+ });
6033
+ }
6034
+ }
6035
+ const aliveResults = await client.multicall({
6036
+ allowFailure: false,
6037
+ contracts: gaugeResults.map((gauge) => ({
6038
+ abi: voterAbi,
6039
+ address: ABOREAN_V2_ADDRESSES.voter,
6040
+ functionName: "isAlive",
6041
+ args: [gauge]
6042
+ }))
6043
+ });
6044
+ for (let i = 0; i < aliveResults.length; i++) {
6045
+ if (!aliveResults[i]) {
6046
+ return c.error({
6047
+ code: "GAUGE_NOT_ALIVE",
6048
+ message: `Gauge for pool ${poolAddresses[i]} is not alive.`,
6049
+ retryable: false
6050
+ });
6051
+ }
6052
+ }
6053
+ const tokenId = BigInt(c.options["token-id"]);
6054
+ const tx = await aboreanWriteTx({
6055
+ env: c.env,
6056
+ options: c.options,
6057
+ address: ABOREAN_V2_ADDRESSES.voter,
6058
+ abi: voterVoteAbi,
6059
+ functionName: "vote",
6060
+ args: [tokenId, poolAddresses, parsedWeights]
6061
+ });
6062
+ return c.ok({
6063
+ tokenId: c.options["token-id"],
6064
+ pools: poolAddresses.map((p) => toChecksum(p)),
6065
+ weights: parsedWeights.map((w) => w.toString()),
6066
+ tx
6067
+ });
6068
+ }
6069
+ });
4429
6070
 
4430
6071
  // src/error-handling.ts
4431
6072
  import { AsyncLocalStorage } from "async_hooks";
@@ -4568,8 +6209,8 @@ cli.command(voter);
4568
6209
  cli.command(cl);
4569
6210
  cli.command(vaults);
4570
6211
  cli.command(lending);
4571
- var rootEnv = z8.object({
4572
- ABSTRACT_RPC_URL: z8.string().optional().describe("Abstract RPC URL override")
6212
+ var rootEnv = z9.object({
6213
+ ABSTRACT_RPC_URL: z9.string().optional().describe("Abstract RPC URL override")
4573
6214
  });
4574
6215
  var erc20MetadataAbi4 = [
4575
6216
  {
@@ -4774,65 +6415,65 @@ async function readTopV2PoolsSnapshot(client, limit) {
4774
6415
  cli.command("status", {
4775
6416
  description: "Cross-protocol Aborean snapshot (TVL estimates, epoch, top pools, ve lock, vaults, Morpho lending).",
4776
6417
  env: rootEnv,
4777
- output: z8.object({
4778
- v2PoolCount: z8.number().describe("Number of V2 AMM pools"),
4779
- clPoolCount: z8.number().describe("Number of Slipstream (CL) pools"),
4780
- gaugeCount: z8.number().describe("Number of pools with gauges"),
4781
- totalVotingWeight: z8.string().describe("Total voting weight (wei)"),
4782
- veABXTotalSupply: z8.string().describe("Total veABX supply (wei)"),
4783
- veABXLockedSupply: z8.string().describe("Total ABX locked in VotingEscrow (wei)"),
4784
- epoch: z8.object({
4785
- activePeriod: z8.number(),
4786
- epochEnd: z8.number(),
4787
- secondsRemaining: z8.number(),
4788
- epochCount: z8.number(),
4789
- weeklyEmission: z8.string()
6418
+ output: z9.object({
6419
+ v2PoolCount: z9.number().describe("Number of V2 AMM pools"),
6420
+ clPoolCount: z9.number().describe("Number of Slipstream (CL) pools"),
6421
+ gaugeCount: z9.number().describe("Number of pools with gauges"),
6422
+ totalVotingWeight: z9.string().describe("Total voting weight (wei)"),
6423
+ veABXTotalSupply: z9.string().describe("Total veABX supply (wei)"),
6424
+ veABXLockedSupply: z9.string().describe("Total ABX locked in VotingEscrow (wei)"),
6425
+ epoch: z9.object({
6426
+ activePeriod: z9.number(),
6427
+ epochEnd: z9.number(),
6428
+ secondsRemaining: z9.number(),
6429
+ epochCount: z9.number(),
6430
+ weeklyEmission: z9.string()
4790
6431
  }),
4791
- topPools: z8.array(
4792
- z8.object({
4793
- pool: z8.string(),
4794
- pair: z8.string(),
4795
- poolType: z8.enum(["stable", "volatile"]),
4796
- token0: z8.object({
4797
- address: z8.string(),
4798
- symbol: z8.string(),
4799
- decimals: z8.number()
6432
+ topPools: z9.array(
6433
+ z9.object({
6434
+ pool: z9.string(),
6435
+ pair: z9.string(),
6436
+ poolType: z9.enum(["stable", "volatile"]),
6437
+ token0: z9.object({
6438
+ address: z9.string(),
6439
+ symbol: z9.string(),
6440
+ decimals: z9.number()
4800
6441
  }),
4801
- token1: z8.object({
4802
- address: z8.string(),
4803
- symbol: z8.string(),
4804
- decimals: z8.number()
6442
+ token1: z9.object({
6443
+ address: z9.string(),
6444
+ symbol: z9.string(),
6445
+ decimals: z9.number()
4805
6446
  }),
4806
- reserves: z8.object({
4807
- token0: z8.string(),
4808
- token1: z8.string()
6447
+ reserves: z9.object({
6448
+ token0: z9.string(),
6449
+ token1: z9.string()
4809
6450
  }),
4810
- tvlEstimateUnits: z8.number()
6451
+ tvlEstimateUnits: z9.number()
4811
6452
  })
4812
6453
  ),
4813
- tvl: z8.object({
4814
- v2ReserveUnitEstimate: z8.number(),
4815
- vaultManagedVotingPower: z8.string()
6454
+ tvl: z9.object({
6455
+ v2ReserveUnitEstimate: z9.number(),
6456
+ vaultManagedVotingPower: z9.string()
4816
6457
  }),
4817
- vaults: z8.object({
4818
- relayCount: z8.number(),
4819
- managedVotingPower: z8.string(),
4820
- note: z8.string().nullable()
6458
+ vaults: z9.object({
6459
+ relayCount: z9.number(),
6460
+ managedVotingPower: z9.string(),
6461
+ note: z9.string().nullable()
4821
6462
  }),
4822
- lending: z8.object({
4823
- available: z8.boolean(),
4824
- morpho: z8.string(),
4825
- marketCount: z8.number(),
4826
- supplyByLoanToken: z8.array(
4827
- z8.object({
4828
- token: z8.string(),
4829
- symbol: z8.string(),
4830
- decimals: z8.number(),
4831
- totalSupplyAssets: z8.string(),
4832
- totalBorrowAssets: z8.string()
6463
+ lending: z9.object({
6464
+ available: z9.boolean(),
6465
+ morpho: z9.string(),
6466
+ marketCount: z9.number(),
6467
+ supplyByLoanToken: z9.array(
6468
+ z9.object({
6469
+ token: z9.string(),
6470
+ symbol: z9.string(),
6471
+ decimals: z9.number(),
6472
+ totalSupplyAssets: z9.string(),
6473
+ totalBorrowAssets: z9.string()
4833
6474
  })
4834
6475
  ),
4835
- note: z8.string().nullable()
6476
+ note: z9.string().nullable()
4836
6477
  })
4837
6478
  }),
4838
6479
  examples: [{ description: "Fetch the current Aborean protocol status" }],