@spectratools/aborean-cli 0.3.0 → 0.6.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 +2788 -267
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -4,10 +4,14 @@
4
4
  import { readFileSync } from "fs";
5
5
  import { dirname, resolve } from "path";
6
6
  import { fileURLToPath } from "url";
7
- import { Cli as Cli4, z as z4 } from "incur";
7
+ import { checksumAddress as checksumAddress6 } from "@spectratools/cli-shared";
8
+ import { Cli as Cli8, z as z8 } from "incur";
9
+ import { formatUnits as formatUnits4 } from "viem";
8
10
 
9
- // src/commands/gauges.ts
11
+ // src/commands/cl.ts
12
+ import { checksumAddress, isAddress } from "@spectratools/cli-shared";
10
13
  import { Cli, z } from "incur";
14
+ import { formatUnits, parseUnits } from "viem";
11
15
 
12
16
  // src/contracts/abis/CLFactory.abi.json
13
17
  var CLFactory_abi_default = [
@@ -122,6 +126,73 @@ var CLFactory_abi_default = [
122
126
  }
123
127
  ];
124
128
 
129
+ // src/contracts/abis/CLPool.abi.json
130
+ var CLPool_abi_default = [
131
+ {
132
+ type: "function",
133
+ name: "slot0",
134
+ inputs: [],
135
+ outputs: [
136
+ { name: "sqrtPriceX96", type: "uint160", internalType: "uint160" },
137
+ { name: "tick", type: "int24", internalType: "int24" },
138
+ { name: "observationIndex", type: "uint16", internalType: "uint16" },
139
+ { name: "observationCardinality", type: "uint16", internalType: "uint16" },
140
+ { name: "observationCardinalityNext", type: "uint16", internalType: "uint16" },
141
+ { name: "unlocked", type: "bool", internalType: "bool" }
142
+ ],
143
+ stateMutability: "view"
144
+ },
145
+ {
146
+ type: "function",
147
+ name: "liquidity",
148
+ inputs: [],
149
+ outputs: [{ name: "", type: "uint128", internalType: "uint128" }],
150
+ stateMutability: "view"
151
+ },
152
+ {
153
+ type: "function",
154
+ name: "token0",
155
+ inputs: [],
156
+ outputs: [{ name: "", type: "address", internalType: "address" }],
157
+ stateMutability: "view"
158
+ },
159
+ {
160
+ type: "function",
161
+ name: "token1",
162
+ inputs: [],
163
+ outputs: [{ name: "", type: "address", internalType: "address" }],
164
+ stateMutability: "view"
165
+ },
166
+ {
167
+ type: "function",
168
+ name: "tickSpacing",
169
+ inputs: [],
170
+ outputs: [{ name: "", type: "int24", internalType: "int24" }],
171
+ stateMutability: "view"
172
+ },
173
+ {
174
+ type: "function",
175
+ name: "factory",
176
+ inputs: [],
177
+ outputs: [{ name: "", type: "address", internalType: "address" }],
178
+ stateMutability: "view"
179
+ },
180
+ {
181
+ type: "function",
182
+ name: "gauge",
183
+ inputs: [],
184
+ outputs: [{ name: "", type: "address", internalType: "address" }],
185
+ stateMutability: "view"
186
+ },
187
+ {
188
+ type: "function",
189
+ name: "fee",
190
+ inputs: [],
191
+ outputs: [{ name: "", type: "uint24", internalType: "uint24" }],
192
+ stateMutability: "view"
193
+ }
194
+ ];
195
+
125
196
  // src/contracts/abis/Gauge.abi.json
126
197
  var Gauge_abi_default = [
127
198
  {
@@ -256,6 +327,68 @@ var Minter_abi_default = [
256
327
  }
257
328
  ];
258
329
 
330
+ // src/contracts/abis/NonfungiblePositionManager.abi.json
331
+ var NonfungiblePositionManager_abi_default = [
332
+ {
333
+ type: "function",
334
+ name: "positions",
335
+ inputs: [{ name: "tokenId", type: "uint256", internalType: "uint256" }],
336
+ outputs: [
337
+ { name: "nonce", type: "uint96", internalType: "uint96" },
338
+ { name: "operator", type: "address", internalType: "address" },
339
+ { name: "token0", type: "address", internalType: "address" },
340
+ { name: "token1", type: "address", internalType: "address" },
341
+ { name: "tickSpacing", type: "int24", internalType: "int24" },
342
+ { name: "tickLower", type: "int24", internalType: "int24" },
343
+ { name: "tickUpper", type: "int24", internalType: "int24" },
344
+ { name: "liquidity", type: "uint128", internalType: "uint128" },
345
+ { name: "feeGrowthInside0LastX128", type: "uint256", internalType: "uint256" },
346
+ { name: "feeGrowthInside1LastX128", type: "uint256", internalType: "uint256" },
347
+ { name: "tokensOwed0", type: "uint128", internalType: "uint128" },
348
+ { name: "tokensOwed1", type: "uint128", internalType: "uint128" }
349
+ ],
350
+ stateMutability: "view"
351
+ },
352
+ {
353
+ type: "function",
354
+ name: "balanceOf",
355
+ inputs: [{ name: "owner", type: "address", internalType: "address" }],
356
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
357
+ stateMutability: "view"
358
+ },
359
+ {
360
+ type: "function",
361
+ name: "tokenOfOwnerByIndex",
362
+ inputs: [
363
+ { name: "owner", type: "address", internalType: "address" },
364
+ { name: "index", type: "uint256", internalType: "uint256" }
365
+ ],
366
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
367
+ stateMutability: "view"
368
+ },
369
+ {
370
+ type: "function",
371
+ name: "totalSupply",
372
+ inputs: [],
373
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
374
+ stateMutability: "view"
375
+ },
376
+ {
377
+ type: "function",
378
+ name: "factory",
379
+ inputs: [],
380
+ outputs: [{ name: "", type: "address", internalType: "address" }],
381
+ stateMutability: "view"
382
+ },
383
+ {
384
+ type: "function",
385
+ name: "WETH9",
386
+ inputs: [],
387
+ outputs: [{ name: "", type: "address", internalType: "address" }],
388
+ stateMutability: "view"
389
+ }
390
+ ];
391
+
259
392
  // src/contracts/abis/PoolFactory.abi.json
260
393
  var PoolFactory_abi_default = [
261
394
  {
@@ -316,6 +449,64 @@ var PoolFactory_abi_default = [
316
449
  }
317
450
  ];
318
451
 
452
+ // src/contracts/abis/QuoterV2.abi.json
453
+ var QuoterV2_abi_default = [
454
+ {
455
+ type: "function",
456
+ name: "factory",
457
+ inputs: [],
458
+ outputs: [{ name: "", type: "address", internalType: "address" }],
459
+ stateMutability: "view"
460
+ },
461
+ {
462
+ type: "function",
463
+ name: "WETH9",
464
+ inputs: [],
465
+ outputs: [{ name: "", type: "address", internalType: "address" }],
466
+ stateMutability: "view"
467
+ },
468
+ {
469
+ type: "function",
470
+ name: "quoteExactInputSingle",
471
+ inputs: [
472
+ {
473
+ name: "params",
474
+ type: "tuple",
475
+ internalType: "struct IQuoterV2.QuoteExactInputSingleParams",
476
+ components: [
477
+ { name: "tokenIn", type: "address", internalType: "address" },
478
+ { name: "tokenOut", type: "address", internalType: "address" },
479
+ { name: "amountIn", type: "uint256", internalType: "uint256" },
480
+ { name: "tickSpacing", type: "int24", internalType: "int24" },
481
+ { name: "sqrtPriceLimitX96", type: "uint160", internalType: "uint160" }
482
+ ]
483
+ }
484
+ ],
485
+ outputs: [
486
+ { name: "amountOut", type: "uint256", internalType: "uint256" },
487
+ { name: "sqrtPriceX96After", type: "uint160", internalType: "uint160" },
488
+ { name: "initializedTicksCrossed", type: "uint32", internalType: "uint32" },
489
+ { name: "gasEstimate", type: "uint256", internalType: "uint256" }
490
+ ],
491
+ stateMutability: "nonpayable"
492
+ },
493
+ {
494
+ type: "function",
495
+ name: "quoteExactInput",
496
+ inputs: [
497
+ { name: "path", type: "bytes", internalType: "bytes" },
498
+ { name: "amountIn", type: "uint256", internalType: "uint256" }
499
+ ],
500
+ outputs: [
501
+ { name: "amountOut", type: "uint256", internalType: "uint256" },
502
+ { name: "sqrtPriceX96AfterList", type: "uint160[]", internalType: "uint160[]" },
503
+ { name: "initializedTicksCrossedList", type: "uint32[]", internalType: "uint32[]" },
504
+ { name: "gasEstimate", type: "uint256", internalType: "uint256" }
505
+ ],
506
+ stateMutability: "nonpayable"
507
+ }
508
+ ];
509
+
319
510
  // src/contracts/abis/RewardsDistributor.abi.json
320
511
  var RewardsDistributor_abi_default = [
321
512
  {
@@ -369,6 +560,87 @@ var RewardsDistributor_abi_default = [
369
560
  }
370
561
  ];
371
562
 
563
+ // src/contracts/abis/V2Pool.abi.json
564
+ var V2Pool_abi_default = [
565
+ {
566
+ type: "function",
567
+ name: "token0",
568
+ inputs: [],
569
+ outputs: [{ name: "", type: "address", internalType: "address" }],
570
+ stateMutability: "view"
571
+ },
572
+ {
573
+ type: "function",
574
+ name: "token1",
575
+ inputs: [],
576
+ outputs: [{ name: "", type: "address", internalType: "address" }],
577
+ stateMutability: "view"
578
+ },
579
+ {
580
+ type: "function",
581
+ name: "stable",
582
+ inputs: [],
583
+ outputs: [{ name: "", type: "bool", internalType: "bool" }],
584
+ stateMutability: "view"
585
+ },
586
+ {
587
+ type: "function",
588
+ name: "getReserves",
589
+ inputs: [],
590
+ outputs: [
591
+ { name: "_reserve0", type: "uint256", internalType: "uint256" },
592
+ { name: "_reserve1", type: "uint256", internalType: "uint256" },
593
+ { name: "_blockTimestampLast", type: "uint256", internalType: "uint256" }
594
+ ],
595
+ stateMutability: "view"
596
+ },
597
+ {
598
+ type: "function",
599
+ name: "totalSupply",
600
+ inputs: [],
601
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
602
+ stateMutability: "view"
603
+ },
604
+ {
605
+ type: "function",
606
+ name: "poolFees",
607
+ inputs: [],
608
+ outputs: [{ name: "", type: "address", internalType: "address" }],
609
+ stateMutability: "view"
610
+ },
611
+ {
612
+ type: "function",
613
+ name: "factory",
614
+ inputs: [],
615
+ outputs: [{ name: "", type: "address", internalType: "address" }],
616
+ stateMutability: "view"
617
+ }
618
+ ];
619
+
620
+ // src/contracts/abis/V2Router.abi.json
621
+ var V2Router_abi_default = [
622
+ {
623
+ type: "function",
624
+ name: "getAmountsOut",
625
+ inputs: [
626
+ { name: "amountIn", type: "uint256", internalType: "uint256" },
627
+ {
628
+ name: "routes",
629
+ type: "tuple[]",
630
+ internalType: "struct IRouter.Route[]",
631
+ components: [
632
+ { name: "from", type: "address", internalType: "address" },
633
+ { name: "to", type: "address", internalType: "address" },
634
+ { name: "stable", type: "bool", internalType: "bool" },
635
+ { name: "factory", type: "address", internalType: "address" }
636
+ ]
637
+ }
638
+ ],
639
+ outputs: [{ name: "amounts", type: "uint256[]", internalType: "uint256[]" }],
640
+ stateMutability: "view"
641
+ }
642
+ ];
643
+
372
644
  // src/contracts/abis/Voter.abi.json
373
645
  var Voter_abi_default = [
374
646
  {
@@ -668,10 +940,15 @@ var VotingReward_abi_default = [
668
940
 
669
941
  // src/contracts/abis.ts
670
942
  var clFactoryAbi = CLFactory_abi_default;
943
+ var clPoolAbi = CLPool_abi_default;
671
944
  var gaugeAbi = Gauge_abi_default;
672
945
  var minterAbi = Minter_abi_default;
946
+ var nonfungiblePositionManagerAbi = NonfungiblePositionManager_abi_default;
673
947
  var poolFactoryAbi = PoolFactory_abi_default;
948
+ var quoterV2Abi = QuoterV2_abi_default;
674
949
  var rewardsDistributorAbi = RewardsDistributor_abi_default;
950
+ var v2PoolAbi = V2Pool_abi_default;
951
+ var v2RouterAbi = V2Router_abi_default;
675
952
  var voterAbi = Voter_abi_default;
676
953
  var votingEscrowAbi = VotingEscrow_abi_default;
677
954
  var votingRewardAbi = VotingReward_abi_default;
@@ -733,9 +1010,25 @@ var ABOREAN_CL_ADDRESSES = {
733
1010
  /** CL swap router */
734
1011
  swapRouter: "0xAda5d0E79681038A9547fe6a59f1413F3E720839"
735
1012
  };
1013
+ var ABOREAN_VAULT_ADDRESSES = {
1014
+ /** Factory for AutoCompounder relay vaults */
1015
+ autoCompounderFactory: "0x35b320599C1434291a0003E40Fcd1e40fA9E0222",
1016
+ /** Factory for AutoConverter relay vaults */
1017
+ autoConverterFactory: "0xf114D2aCF8aAFDde833e0a8ba2dc5A3946614354",
1018
+ /** veABX Maxi relay vault */
1019
+ veAbxMaxiRelay: "0xcbeB1A72A31670AE5ba27798c124Fcf3Ca1971df",
1020
+ /** ABX rewards relay vault */
1021
+ abxRewardsRelay: "0x3E8D887Bba5D4A757FaE757883CA35882AB4a0ee"
1022
+ };
1023
+ var ABOREAN_LENDING_ADDRESSES = {
1024
+ /** Morpho Blue core contract on Abstract */
1025
+ morphoBlue: "0xc85CE8ffdA27b646D269516B8d0Fa6ec2E958B55"
1026
+ };
736
1027
  var ABOREAN_ADDRESSES = {
737
1028
  ...ABOREAN_V2_ADDRESSES,
738
- ...ABOREAN_CL_ADDRESSES
1029
+ ...ABOREAN_CL_ADDRESSES,
1030
+ ...ABOREAN_VAULT_ADDRESSES,
1031
+ ...ABOREAN_LENDING_ADDRESSES
739
1032
  };
740
1033
 
741
1034
  // src/contracts/client.ts
@@ -761,57 +1054,594 @@ function createAboreanPublicClient(rpcUrl) {
761
1054
  });
762
1055
  }
763
1056
 
764
- // src/commands/_common.ts
765
- import { checksumAddress, weiToEth } from "@spectratools/cli-shared";
766
- var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
767
- function toChecksum(address) {
768
- try {
769
- return checksumAddress(address);
770
- } catch {
771
- return address;
1057
+ // src/commands/cl.ts
1058
+ var Q96 = 2n ** 96n;
1059
+ var MULTICALL_BATCH_SIZE = 100;
1060
+ var env = z.object({
1061
+ ABSTRACT_RPC_URL: z.string().optional().describe("Abstract RPC URL override")
1062
+ });
1063
+ var tokenSchema = z.object({
1064
+ address: z.string(),
1065
+ symbol: z.string(),
1066
+ decimals: z.number()
1067
+ });
1068
+ var poolRowSchema = z.object({
1069
+ pool: z.string(),
1070
+ pair: z.string(),
1071
+ token0: tokenSchema,
1072
+ 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()
1084
+ }),
1085
+ price: z.object({
1086
+ token1PerToken0: z.number().nullable(),
1087
+ token0PerToken1: z.number().nullable()
1088
+ })
1089
+ });
1090
+ var quoteOutputSchema = z.object({
1091
+ pool: z.string(),
1092
+ selectedFee: z.number(),
1093
+ selectedTickSpacing: z.number(),
1094
+ tokenIn: tokenSchema,
1095
+ tokenOut: tokenSchema,
1096
+ amountIn: z.object({
1097
+ raw: z.string(),
1098
+ decimal: z.string()
1099
+ }),
1100
+ amountOut: z.object({
1101
+ raw: z.string(),
1102
+ decimal: z.string()
1103
+ }),
1104
+ execution: z.object({
1105
+ sqrtPriceX96After: z.string(),
1106
+ initializedTicksCrossed: z.number(),
1107
+ gasEstimate: z.string()
1108
+ }),
1109
+ prices: z.object({
1110
+ poolMidPriceOutPerIn: z.number().nullable(),
1111
+ quotePriceOutPerIn: z.number().nullable(),
1112
+ priceImpactPct: z.number().nullable()
1113
+ })
1114
+ });
1115
+ var erc20MetadataAbi = [
1116
+ {
1117
+ type: "function",
1118
+ name: "symbol",
1119
+ stateMutability: "view",
1120
+ inputs: [],
1121
+ outputs: [{ type: "string" }]
1122
+ },
1123
+ {
1124
+ type: "function",
1125
+ name: "decimals",
1126
+ stateMutability: "view",
1127
+ inputs: [],
1128
+ outputs: [{ type: "uint8" }]
772
1129
  }
1130
+ ];
1131
+ function shortAddress(address) {
1132
+ return `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
773
1133
  }
774
- function asNum(value) {
775
- return Number(value);
1134
+ function finiteOrNull(value) {
1135
+ return Number.isFinite(value) ? value : null;
776
1136
  }
777
- function relTime(unixSeconds) {
778
- const ts = typeof unixSeconds === "bigint" ? Number(unixSeconds) : unixSeconds;
779
- if (!Number.isFinite(ts) || ts <= 0) return "n/a";
780
- const delta = ts - Math.floor(Date.now() / 1e3);
781
- const abs = Math.abs(delta);
782
- const hours = Math.floor(abs / 3600);
783
- const minutes = Math.floor(abs % 3600 / 60);
784
- const label = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
785
- return delta >= 0 ? `in ${label}` : `${label} ago`;
1137
+ function toTokenMetaFallback(address) {
1138
+ return {
1139
+ address,
1140
+ symbol: shortAddress(checksumAddress(address)),
1141
+ decimals: 18
1142
+ };
786
1143
  }
787
- function clampPositive(seconds) {
788
- return seconds > 0 ? seconds : 0;
1144
+ function chunk(items, size) {
1145
+ const out = [];
1146
+ for (let i = 0; i < items.length; i += size) {
1147
+ out.push(items.slice(i, i + size));
1148
+ }
1149
+ return out;
789
1150
  }
790
-
791
- // src/commands/gauges.ts
792
- var env = z.object({
793
- ABSTRACT_RPC_URL: z.string().optional().describe("Abstract RPC URL override")
794
- });
795
- async function discoverGaugePools(client) {
796
- const [v2PoolCount, clPoolCount] = await Promise.all([
797
- client.readContract({
798
- abi: poolFactoryAbi,
799
- address: ABOREAN_V2_ADDRESSES.poolFactory,
800
- functionName: "allPoolsLength"
801
- }),
802
- client.readContract({
803
- abi: clFactoryAbi,
804
- address: ABOREAN_CL_ADDRESSES.clFactory,
805
- functionName: "allPoolsLength"
806
- })
807
- ]);
808
- const v2Indices = Array.from({ length: asNum(v2PoolCount) }, (_, i) => BigInt(i));
809
- const clIndices = Array.from({ length: asNum(clPoolCount) }, (_, i) => BigInt(i));
810
- const [v2Pools, clPools] = await Promise.all([
811
- v2Indices.length ? client.multicall({
1151
+ async function multicallAllowFailure(client, contracts) {
1152
+ const batches = chunk(contracts, MULTICALL_BATCH_SIZE);
1153
+ const out = [];
1154
+ for (const batch of batches) {
1155
+ const res = await client.multicall({
1156
+ allowFailure: true,
1157
+ contracts: batch
1158
+ });
1159
+ out.push(...res);
1160
+ }
1161
+ return out;
1162
+ }
1163
+ async function multicallStrict(client, contracts) {
1164
+ const batches = chunk(contracts, MULTICALL_BATCH_SIZE);
1165
+ const out = [];
1166
+ for (const batch of batches) {
1167
+ const res = await client.multicall({
812
1168
  allowFailure: false,
813
- contracts: v2Indices.map((index) => ({
814
- abi: poolFactoryAbi,
1169
+ contracts: batch
1170
+ });
1171
+ out.push(...res);
1172
+ }
1173
+ return out;
1174
+ }
1175
+ function derivePrices(sqrtPriceX96, token0Decimals, token1Decimals) {
1176
+ const sqrtRatio = Number(sqrtPriceX96) / 2 ** 96;
1177
+ const rawPrice = sqrtRatio * sqrtRatio;
1178
+ const decimalScale = 10 ** (token0Decimals - token1Decimals);
1179
+ const token1PerToken0 = finiteOrNull(rawPrice * decimalScale);
1180
+ const token0PerToken1 = token1PerToken0 === null || token1PerToken0 === 0 ? null : finiteOrNull(1 / token1PerToken0);
1181
+ return {
1182
+ token1PerToken0,
1183
+ token0PerToken1
1184
+ };
1185
+ }
1186
+ function estimateActiveLiquidity(liquidity, sqrtPriceX96, token0Decimals, token1Decimals, prices) {
1187
+ if (sqrtPriceX96 === 0n) {
1188
+ return {
1189
+ token0: "0",
1190
+ token1: "0",
1191
+ totalInToken0: null,
1192
+ totalInToken1: null
1193
+ };
1194
+ }
1195
+ const reserve0Raw = liquidity * Q96 / sqrtPriceX96;
1196
+ const reserve1Raw = liquidity * sqrtPriceX96 / Q96;
1197
+ const reserve0 = Number(formatUnits(reserve0Raw, token0Decimals));
1198
+ const reserve1 = Number(formatUnits(reserve1Raw, token1Decimals));
1199
+ const totalInToken1 = prices.token1PerToken0 === null ? null : finiteOrNull(reserve1 + reserve0 * prices.token1PerToken0);
1200
+ const totalInToken0 = prices.token0PerToken1 === null ? null : finiteOrNull(reserve0 + reserve1 * prices.token0PerToken1);
1201
+ return {
1202
+ token0: formatUnits(reserve0Raw, token0Decimals),
1203
+ token1: formatUnits(reserve1Raw, token1Decimals),
1204
+ totalInToken0,
1205
+ totalInToken1
1206
+ };
1207
+ }
1208
+ async function readTokenMetadata(client, tokenAddresses) {
1209
+ const uniqueTokens = [...new Set(tokenAddresses.map((x) => x.toLowerCase()))];
1210
+ if (uniqueTokens.length === 0) {
1211
+ return /* @__PURE__ */ new Map();
1212
+ }
1213
+ const contracts = uniqueTokens.flatMap((address) => [
1214
+ {
1215
+ abi: erc20MetadataAbi,
1216
+ address,
1217
+ functionName: "symbol"
1218
+ },
1219
+ {
1220
+ abi: erc20MetadataAbi,
1221
+ address,
1222
+ functionName: "decimals"
1223
+ }
1224
+ ]);
1225
+ const results = await multicallAllowFailure(client, contracts);
1226
+ const out = /* @__PURE__ */ new Map();
1227
+ for (let i = 0; i < uniqueTokens.length; i += 1) {
1228
+ const address = uniqueTokens[i];
1229
+ const symbolResult = results[i * 2];
1230
+ const decimalsResult = results[i * 2 + 1];
1231
+ const fallback = toTokenMetaFallback(address);
1232
+ const symbol = symbolResult && symbolResult.status === "success" && typeof symbolResult.result === "string" ? symbolResult.result : fallback.symbol;
1233
+ const decimals = decimalsResult && decimalsResult.status === "success" && typeof decimalsResult.result === "number" ? decimalsResult.result : fallback.decimals;
1234
+ out.set(address, {
1235
+ address: checksumAddress(address),
1236
+ symbol,
1237
+ decimals
1238
+ });
1239
+ }
1240
+ return out;
1241
+ }
1242
+ async function listPoolAddresses(client) {
1243
+ const count = await client.readContract({
1244
+ abi: clFactoryAbi,
1245
+ address: ABOREAN_CL_ADDRESSES.clFactory,
1246
+ functionName: "allPoolsLength"
1247
+ });
1248
+ if (count === 0n) {
1249
+ return [];
1250
+ }
1251
+ const poolIndexContracts = Array.from({ length: Number(count) }, (_, i) => ({
1252
+ abi: clFactoryAbi,
1253
+ address: ABOREAN_CL_ADDRESSES.clFactory,
1254
+ functionName: "allPools",
1255
+ args: [BigInt(i)]
1256
+ }));
1257
+ const poolAddresses = await multicallStrict(client, poolIndexContracts);
1258
+ return poolAddresses;
1259
+ }
1260
+ async function readPoolStates(client, poolAddresses) {
1261
+ if (poolAddresses.length === 0) {
1262
+ return [];
1263
+ }
1264
+ const poolContracts = poolAddresses.flatMap((pool) => [
1265
+ {
1266
+ abi: clPoolAbi,
1267
+ address: pool,
1268
+ functionName: "token0"
1269
+ },
1270
+ {
1271
+ abi: clPoolAbi,
1272
+ address: pool,
1273
+ functionName: "token1"
1274
+ },
1275
+ {
1276
+ abi: clPoolAbi,
1277
+ address: pool,
1278
+ functionName: "tickSpacing"
1279
+ },
1280
+ {
1281
+ abi: clPoolAbi,
1282
+ address: pool,
1283
+ functionName: "fee"
1284
+ },
1285
+ {
1286
+ abi: clPoolAbi,
1287
+ address: pool,
1288
+ functionName: "liquidity"
1289
+ },
1290
+ {
1291
+ abi: clPoolAbi,
1292
+ address: pool,
1293
+ functionName: "slot0"
1294
+ }
1295
+ ]);
1296
+ const values = await multicallStrict(client, poolContracts);
1297
+ return poolAddresses.map((pool, i) => ({
1298
+ pool,
1299
+ token0: values[i * 6],
1300
+ token1: values[i * 6 + 1],
1301
+ tickSpacing: Number(values[i * 6 + 2]),
1302
+ fee: Number(values[i * 6 + 3]),
1303
+ liquidity: values[i * 6 + 4],
1304
+ slot0: values[i * 6 + 5]
1305
+ }));
1306
+ }
1307
+ function toPoolRow(pool, tokenMeta) {
1308
+ const token0 = tokenMeta.get(pool.token0) ?? toTokenMetaFallback(pool.token0);
1309
+ const token1 = tokenMeta.get(pool.token1) ?? toTokenMetaFallback(pool.token1);
1310
+ const prices = derivePrices(pool.slot0[0], token0.decimals, token1.decimals);
1311
+ const activeLiquidityEstimate = estimateActiveLiquidity(
1312
+ pool.liquidity,
1313
+ pool.slot0[0],
1314
+ token0.decimals,
1315
+ token1.decimals,
1316
+ prices
1317
+ );
1318
+ return {
1319
+ pool: checksumAddress(pool.pool),
1320
+ pair: `${token0.symbol}/${token1.symbol}`,
1321
+ token0,
1322
+ token1,
1323
+ fee: pool.fee,
1324
+ feePercent: pool.fee / 1e4,
1325
+ tickSpacing: pool.tickSpacing,
1326
+ liquidity: pool.liquidity.toString(),
1327
+ currentTick: pool.slot0[1],
1328
+ sqrtPriceX96: pool.slot0[0].toString(),
1329
+ activeLiquidityEstimate,
1330
+ price: prices
1331
+ };
1332
+ }
1333
+ function normalizeAddress(address) {
1334
+ return address.toLowerCase();
1335
+ }
1336
+ var cl = Cli.create("cl", {
1337
+ description: "Concentrated liquidity (Slipstream) pools, positions, and quotes."
1338
+ });
1339
+ cl.command("pools", {
1340
+ description: "List Slipstream pools with current state, prices, and active liquidity estimate.",
1341
+ env,
1342
+ output: z.object({
1343
+ count: z.number(),
1344
+ pools: z.array(poolRowSchema)
1345
+ }),
1346
+ async run(c) {
1347
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1348
+ const pools2 = await listPoolAddresses(client);
1349
+ const poolStates = await readPoolStates(client, pools2);
1350
+ const tokenMeta = await readTokenMetadata(
1351
+ client,
1352
+ poolStates.flatMap((pool) => [pool.token0, pool.token1])
1353
+ );
1354
+ const rows = poolStates.map((pool) => toPoolRow(pool, tokenMeta));
1355
+ return c.ok({
1356
+ count: rows.length,
1357
+ pools: rows
1358
+ });
1359
+ }
1360
+ });
1361
+ cl.command("pool", {
1362
+ description: "Get detailed state for a Slipstream pool address.",
1363
+ args: z.object({
1364
+ pool: z.string().describe("Pool address")
1365
+ }),
1366
+ env,
1367
+ output: z.object({
1368
+ pool: poolRowSchema
1369
+ }),
1370
+ async run(c) {
1371
+ if (!isAddress(c.args.pool)) {
1372
+ return c.error({
1373
+ code: "INVALID_ADDRESS",
1374
+ message: `Invalid pool address: "${c.args.pool}". Use a valid 0x-prefixed 20-byte hex address.`
1375
+ });
1376
+ }
1377
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1378
+ const checksummedPool = checksumAddress(c.args.pool);
1379
+ const [poolState] = await readPoolStates(client, [checksummedPool]);
1380
+ const tokenMeta = await readTokenMetadata(client, [poolState.token0, poolState.token1]);
1381
+ return c.ok({
1382
+ pool: toPoolRow(poolState, tokenMeta)
1383
+ });
1384
+ }
1385
+ });
1386
+ cl.command("positions", {
1387
+ description: "List concentrated liquidity NFT positions for an owner.",
1388
+ args: z.object({
1389
+ owner: z.string().describe("Owner wallet address")
1390
+ }),
1391
+ env,
1392
+ output: z.object({
1393
+ owner: z.string(),
1394
+ count: z.number(),
1395
+ positions: z.array(
1396
+ z.object({
1397
+ tokenId: z.string(),
1398
+ pair: z.string(),
1399
+ token0: tokenSchema,
1400
+ token1: tokenSchema,
1401
+ tickSpacing: z.number(),
1402
+ tickLower: z.number(),
1403
+ tickUpper: z.number(),
1404
+ liquidity: z.string(),
1405
+ tokensOwed0: z.object({ raw: z.string(), decimal: z.string() }),
1406
+ tokensOwed1: z.object({ raw: z.string(), decimal: z.string() })
1407
+ })
1408
+ )
1409
+ }),
1410
+ async run(c) {
1411
+ if (!isAddress(c.args.owner)) {
1412
+ return c.error({
1413
+ code: "INVALID_ADDRESS",
1414
+ message: `Invalid owner address: "${c.args.owner}". Use a valid 0x-prefixed 20-byte hex address.`
1415
+ });
1416
+ }
1417
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1418
+ const owner = checksumAddress(c.args.owner);
1419
+ const balance = await client.readContract({
1420
+ abi: nonfungiblePositionManagerAbi,
1421
+ address: ABOREAN_CL_ADDRESSES.nonfungiblePositionManager,
1422
+ functionName: "balanceOf",
1423
+ args: [owner]
1424
+ });
1425
+ if (balance === 0n) {
1426
+ return c.ok({ owner, count: 0, positions: [] });
1427
+ }
1428
+ const tokenIdContracts = Array.from({ length: Number(balance) }, (_, i) => ({
1429
+ abi: nonfungiblePositionManagerAbi,
1430
+ address: ABOREAN_CL_ADDRESSES.nonfungiblePositionManager,
1431
+ functionName: "tokenOfOwnerByIndex",
1432
+ args: [owner, BigInt(i)]
1433
+ }));
1434
+ const tokenIds = await multicallStrict(client, tokenIdContracts);
1435
+ const positionContracts = tokenIds.map((tokenId) => ({
1436
+ abi: nonfungiblePositionManagerAbi,
1437
+ address: ABOREAN_CL_ADDRESSES.nonfungiblePositionManager,
1438
+ functionName: "positions",
1439
+ args: [tokenId]
1440
+ }));
1441
+ const positionsRaw = await multicallStrict(client, positionContracts);
1442
+ const tokenMeta = await readTokenMetadata(
1443
+ client,
1444
+ positionsRaw.flatMap((position) => [position[2], position[3]])
1445
+ );
1446
+ const positions = tokenIds.map((tokenId, i) => {
1447
+ const position = positionsRaw[i];
1448
+ const token0 = tokenMeta.get(position[2]) ?? toTokenMetaFallback(position[2]);
1449
+ const token1 = tokenMeta.get(position[3]) ?? toTokenMetaFallback(position[3]);
1450
+ return {
1451
+ tokenId: tokenId.toString(),
1452
+ pair: `${token0.symbol}/${token1.symbol}`,
1453
+ token0,
1454
+ token1,
1455
+ tickSpacing: position[4],
1456
+ tickLower: position[5],
1457
+ tickUpper: position[6],
1458
+ liquidity: position[7].toString(),
1459
+ tokensOwed0: {
1460
+ raw: position[10].toString(),
1461
+ decimal: formatUnits(position[10], token0.decimals)
1462
+ },
1463
+ tokensOwed1: {
1464
+ raw: position[11].toString(),
1465
+ decimal: formatUnits(position[11], token1.decimals)
1466
+ }
1467
+ };
1468
+ });
1469
+ return c.ok({
1470
+ owner,
1471
+ count: positions.length,
1472
+ positions
1473
+ });
1474
+ }
1475
+ });
1476
+ cl.command("quote", {
1477
+ description: "Quote a single-hop Slipstream swap via QuoterV2.",
1478
+ args: z.object({
1479
+ tokenIn: z.string().describe("Input token address"),
1480
+ tokenOut: z.string().describe("Output token address"),
1481
+ amountIn: z.string().describe("Input amount in human-readable decimal units")
1482
+ }),
1483
+ options: z.object({
1484
+ fee: z.coerce.number().int().positive().optional().describe("Optional fee tier filter")
1485
+ }),
1486
+ env,
1487
+ output: quoteOutputSchema,
1488
+ async run(c) {
1489
+ const { tokenIn, tokenOut, amountIn } = c.args;
1490
+ if (!isAddress(tokenIn) || !isAddress(tokenOut)) {
1491
+ return c.error({
1492
+ code: "INVALID_ADDRESS",
1493
+ message: "tokenIn and tokenOut must both be valid 0x-prefixed 20-byte addresses."
1494
+ });
1495
+ }
1496
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1497
+ const inAddress = checksumAddress(tokenIn);
1498
+ const outAddress = checksumAddress(tokenOut);
1499
+ const allPools = await listPoolAddresses(client);
1500
+ const poolStates = await readPoolStates(client, allPools);
1501
+ const pairPools = poolStates.filter((pool) => {
1502
+ const a = normalizeAddress(pool.token0);
1503
+ const b = normalizeAddress(pool.token1);
1504
+ const tokenInNorm = normalizeAddress(inAddress);
1505
+ const tokenOutNorm = normalizeAddress(outAddress);
1506
+ return a === tokenInNorm && b === tokenOutNorm || a === tokenOutNorm && b === tokenInNorm;
1507
+ });
1508
+ const filteredPools = typeof c.options.fee === "number" ? pairPools.filter((pool) => pool.fee === c.options.fee) : pairPools;
1509
+ if (filteredPools.length === 0) {
1510
+ return c.error({
1511
+ code: "POOL_NOT_FOUND",
1512
+ message: typeof c.options.fee === "number" ? `No Slipstream pool found for pair ${inAddress}/${outAddress} at fee tier ${c.options.fee}.` : `No Slipstream pool found for pair ${inAddress}/${outAddress}.`
1513
+ });
1514
+ }
1515
+ const selectedPool = [...filteredPools].sort((a, b) => {
1516
+ if (a.liquidity === b.liquidity) return 0;
1517
+ return a.liquidity > b.liquidity ? -1 : 1;
1518
+ })[0];
1519
+ const tokenMeta = await readTokenMetadata(client, [inAddress, outAddress]);
1520
+ const inMeta = tokenMeta.get(inAddress) ?? toTokenMetaFallback(inAddress);
1521
+ const outMeta = tokenMeta.get(outAddress) ?? toTokenMetaFallback(outAddress);
1522
+ let amountInRaw;
1523
+ try {
1524
+ amountInRaw = parseUnits(amountIn, inMeta.decimals);
1525
+ } catch {
1526
+ return c.error({
1527
+ code: "INVALID_AMOUNT",
1528
+ message: `Invalid amountIn: "${amountIn}" for token ${inMeta.symbol} (${inMeta.decimals} decimals).`
1529
+ });
1530
+ }
1531
+ const quote = await client.readContract({
1532
+ abi: quoterV2Abi,
1533
+ address: ABOREAN_CL_ADDRESSES.quoterV2,
1534
+ functionName: "quoteExactInputSingle",
1535
+ args: [
1536
+ {
1537
+ tokenIn: inAddress,
1538
+ tokenOut: outAddress,
1539
+ amountIn: amountInRaw,
1540
+ tickSpacing: selectedPool.tickSpacing,
1541
+ sqrtPriceLimitX96: 0n
1542
+ }
1543
+ ]
1544
+ });
1545
+ const amountOutRaw = quote[0];
1546
+ const amountOutDecimal = formatUnits(amountOutRaw, outMeta.decimals);
1547
+ const amountInDecimal = formatUnits(amountInRaw, inMeta.decimals);
1548
+ const quotePriceOutPerIn = Number(amountInDecimal) === 0 ? null : finiteOrNull(Number(amountOutDecimal) / Number(amountInDecimal));
1549
+ const poolTokenMeta = await readTokenMetadata(client, [
1550
+ selectedPool.token0,
1551
+ selectedPool.token1
1552
+ ]);
1553
+ const poolToken0Meta = poolTokenMeta.get(selectedPool.token0) ?? toTokenMetaFallback(selectedPool.token0);
1554
+ const poolToken1Meta = poolTokenMeta.get(selectedPool.token1) ?? toTokenMetaFallback(selectedPool.token1);
1555
+ const poolPrices = derivePrices(
1556
+ selectedPool.slot0[0],
1557
+ poolToken0Meta.decimals,
1558
+ poolToken1Meta.decimals
1559
+ );
1560
+ const inIsToken0 = normalizeAddress(inAddress) === normalizeAddress(selectedPool.token0);
1561
+ const poolMidPriceOutPerIn = inIsToken0 ? poolPrices.token1PerToken0 : poolPrices.token0PerToken1;
1562
+ const priceImpactPct = quotePriceOutPerIn === null || poolMidPriceOutPerIn === null || poolMidPriceOutPerIn === 0 ? null : finiteOrNull((poolMidPriceOutPerIn - quotePriceOutPerIn) / poolMidPriceOutPerIn * 100);
1563
+ return c.ok({
1564
+ pool: checksumAddress(selectedPool.pool),
1565
+ selectedFee: selectedPool.fee,
1566
+ selectedTickSpacing: selectedPool.tickSpacing,
1567
+ tokenIn: inMeta,
1568
+ tokenOut: outMeta,
1569
+ amountIn: {
1570
+ raw: amountInRaw.toString(),
1571
+ decimal: amountInDecimal
1572
+ },
1573
+ amountOut: {
1574
+ raw: amountOutRaw.toString(),
1575
+ decimal: amountOutDecimal
1576
+ },
1577
+ execution: {
1578
+ sqrtPriceX96After: quote[1].toString(),
1579
+ initializedTicksCrossed: quote[2],
1580
+ gasEstimate: quote[3].toString()
1581
+ },
1582
+ prices: {
1583
+ poolMidPriceOutPerIn,
1584
+ quotePriceOutPerIn,
1585
+ priceImpactPct
1586
+ }
1587
+ });
1588
+ }
1589
+ });
1590
+
1591
+ // src/commands/gauges.ts
1592
+ import { Cli as Cli2, z as z2 } from "incur";
1593
+
1594
+ // src/commands/_common.ts
1595
+ import { checksumAddress as checksumAddress2, weiToEth } from "@spectratools/cli-shared";
1596
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
1597
+ function toChecksum(address) {
1598
+ try {
1599
+ return checksumAddress2(address);
1600
+ } catch {
1601
+ return address;
1602
+ }
1603
+ }
1604
+ function asNum(value) {
1605
+ return Number(value);
1606
+ }
1607
+ function relTime(unixSeconds) {
1608
+ const ts = typeof unixSeconds === "bigint" ? Number(unixSeconds) : unixSeconds;
1609
+ if (!Number.isFinite(ts) || ts <= 0) return "n/a";
1610
+ const delta = ts - Math.floor(Date.now() / 1e3);
1611
+ const abs = Math.abs(delta);
1612
+ const hours = Math.floor(abs / 3600);
1613
+ const minutes = Math.floor(abs % 3600 / 60);
1614
+ const label = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
1615
+ return delta >= 0 ? `in ${label}` : `${label} ago`;
1616
+ }
1617
+ function clampPositive(seconds) {
1618
+ return seconds > 0 ? seconds : 0;
1619
+ }
1620
+
1621
+ // src/commands/gauges.ts
1622
+ var env2 = z2.object({
1623
+ ABSTRACT_RPC_URL: z2.string().optional().describe("Abstract RPC URL override")
1624
+ });
1625
+ async function discoverGaugePools(client) {
1626
+ const [v2PoolCount, clPoolCount] = await Promise.all([
1627
+ client.readContract({
1628
+ abi: poolFactoryAbi,
1629
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
1630
+ functionName: "allPoolsLength"
1631
+ }),
1632
+ client.readContract({
1633
+ abi: clFactoryAbi,
1634
+ address: ABOREAN_CL_ADDRESSES.clFactory,
1635
+ functionName: "allPoolsLength"
1636
+ })
1637
+ ]);
1638
+ const v2Indices = Array.from({ length: asNum(v2PoolCount) }, (_, i) => BigInt(i));
1639
+ const clIndices = Array.from({ length: asNum(clPoolCount) }, (_, i) => BigInt(i));
1640
+ const [v2Pools, clPools] = await Promise.all([
1641
+ v2Indices.length ? client.multicall({
1642
+ allowFailure: false,
1643
+ contracts: v2Indices.map((index) => ({
1644
+ abi: poolFactoryAbi,
815
1645
  address: ABOREAN_V2_ADDRESSES.poolFactory,
816
1646
  functionName: "allPools",
817
1647
  args: [index]
@@ -827,39 +1657,39 @@ async function discoverGaugePools(client) {
827
1657
  }))
828
1658
  }) : Promise.resolve([])
829
1659
  ]);
830
- const pools = [...v2Pools, ...clPools];
831
- if (!pools.length) return [];
1660
+ const pools2 = [...v2Pools, ...clPools];
1661
+ if (!pools2.length) return [];
832
1662
  const gauges2 = await client.multicall({
833
1663
  allowFailure: false,
834
- contracts: pools.map((pool) => ({
1664
+ contracts: pools2.map((pool) => ({
835
1665
  abi: voterAbi,
836
1666
  address: ABOREAN_V2_ADDRESSES.voter,
837
1667
  functionName: "gauges",
838
1668
  args: [pool]
839
1669
  }))
840
1670
  });
841
- return pools.map((pool, index) => ({ pool, gauge: gauges2[index] })).filter(({ gauge }) => gauge.toLowerCase() !== ZERO_ADDRESS.toLowerCase());
1671
+ return pools2.map((pool, index) => ({ pool, gauge: gauges2[index] })).filter(({ gauge }) => gauge.toLowerCase() !== ZERO_ADDRESS.toLowerCase());
842
1672
  }
843
- var gauges = Cli.create("gauges", {
1673
+ var gauges = Cli2.create("gauges", {
844
1674
  description: "Inspect Aborean gauge emissions, staking, and user positions."
845
1675
  });
846
1676
  gauges.command("list", {
847
1677
  description: "List active gauges with pool, emissions, and staking stats.",
848
- env,
849
- output: z.object({
850
- gauges: z.array(
851
- z.object({
852
- pool: z.string(),
853
- gauge: z.string(),
854
- rewardToken: z.string(),
855
- rewardRate: z.string(),
856
- totalStaked: z.string(),
857
- claimableEmissions: z.string(),
858
- periodFinish: z.number(),
859
- periodFinishRelative: z.string()
1678
+ env: env2,
1679
+ output: z2.object({
1680
+ gauges: z2.array(
1681
+ z2.object({
1682
+ pool: z2.string(),
1683
+ gauge: z2.string(),
1684
+ rewardToken: z2.string(),
1685
+ rewardRate: z2.string(),
1686
+ totalStaked: z2.string(),
1687
+ claimableEmissions: z2.string(),
1688
+ periodFinish: z2.number(),
1689
+ periodFinishRelative: z2.string()
860
1690
  })
861
1691
  ),
862
- count: z.number()
1692
+ count: z2.number()
863
1693
  }),
864
1694
  examples: [{ description: "List all active gauges and current emissions state" }],
865
1695
  async run(c) {
@@ -925,27 +1755,27 @@ gauges.command("list", {
925
1755
  });
926
1756
  gauges.command("info", {
927
1757
  description: "Get detailed state for one gauge address.",
928
- args: z.object({
929
- gauge: z.string().describe("Gauge contract address")
1758
+ args: z2.object({
1759
+ gauge: z2.string().describe("Gauge contract address")
930
1760
  }),
931
- env,
932
- output: z.object({
933
- gauge: z.string(),
934
- pool: z.string(),
935
- isAlive: z.boolean(),
936
- stakingToken: z.string(),
937
- rewardToken: z.string(),
938
- totalStaked: z.string(),
939
- rewardRate: z.string(),
940
- rewardPerTokenStored: z.string(),
941
- fees0: z.string(),
942
- fees1: z.string(),
943
- left: z.string(),
944
- periodFinish: z.number(),
945
- periodFinishRelative: z.string(),
946
- lastUpdateTime: z.number(),
947
- bribeContract: z.string(),
948
- feeContract: z.string()
1761
+ env: env2,
1762
+ output: z2.object({
1763
+ gauge: z2.string(),
1764
+ pool: z2.string(),
1765
+ isAlive: z2.boolean(),
1766
+ stakingToken: z2.string(),
1767
+ rewardToken: z2.string(),
1768
+ totalStaked: z2.string(),
1769
+ rewardRate: z2.string(),
1770
+ rewardPerTokenStored: z2.string(),
1771
+ fees0: z2.string(),
1772
+ fees1: z2.string(),
1773
+ left: z2.string(),
1774
+ periodFinish: z2.number(),
1775
+ periodFinishRelative: z2.string(),
1776
+ lastUpdateTime: z2.number(),
1777
+ bribeContract: z2.string(),
1778
+ feeContract: z2.string()
949
1779
  }),
950
1780
  examples: [
951
1781
  {
@@ -1067,111 +1897,1468 @@ gauges.command("info", {
1067
1897
  });
1068
1898
  }
1069
1899
  });
1070
- gauges.command("staked", {
1071
- description: "Show one address staking positions across all gauges.",
1072
- args: z.object({
1073
- address: z.string().describe("Wallet address to inspect")
1900
+ gauges.command("staked", {
1901
+ description: "Show one address staking positions across all gauges.",
1902
+ args: z2.object({
1903
+ address: z2.string().describe("Wallet address to inspect")
1904
+ }),
1905
+ env: env2,
1906
+ output: z2.object({
1907
+ address: z2.string(),
1908
+ positions: z2.array(
1909
+ z2.object({
1910
+ pool: z2.string(),
1911
+ gauge: z2.string(),
1912
+ rewardToken: z2.string(),
1913
+ staked: z2.string(),
1914
+ earned: z2.string()
1915
+ })
1916
+ ),
1917
+ count: z2.number()
1918
+ }),
1919
+ examples: [
1920
+ {
1921
+ args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
1922
+ description: "List gauge positions for a wallet"
1923
+ }
1924
+ ],
1925
+ async run(c) {
1926
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1927
+ const gaugePools = await discoverGaugePools(client);
1928
+ if (!gaugePools.length) {
1929
+ return c.ok({
1930
+ address: toChecksum(c.args.address),
1931
+ positions: [],
1932
+ count: 0
1933
+ });
1934
+ }
1935
+ const positionData = await client.multicall({
1936
+ allowFailure: false,
1937
+ contracts: gaugePools.flatMap(({ gauge }) => [
1938
+ {
1939
+ abi: gaugeAbi,
1940
+ address: gauge,
1941
+ functionName: "balanceOf",
1942
+ args: [c.args.address]
1943
+ },
1944
+ {
1945
+ abi: gaugeAbi,
1946
+ address: gauge,
1947
+ functionName: "earned",
1948
+ args: [c.args.address]
1949
+ },
1950
+ {
1951
+ abi: gaugeAbi,
1952
+ address: gauge,
1953
+ functionName: "rewardToken"
1954
+ }
1955
+ ])
1956
+ });
1957
+ const positions = gaugePools.map(({ pool, gauge }, index) => {
1958
+ const offset = index * 3;
1959
+ const staked = positionData[offset];
1960
+ const earned = positionData[offset + 1];
1961
+ const rewardToken = positionData[offset + 2];
1962
+ return {
1963
+ pool: toChecksum(pool),
1964
+ gauge: toChecksum(gauge),
1965
+ rewardToken: toChecksum(rewardToken),
1966
+ staked,
1967
+ earned
1968
+ };
1969
+ }).filter((position) => position.staked > 0n || position.earned > 0n).map((position) => ({
1970
+ pool: position.pool,
1971
+ gauge: position.gauge,
1972
+ rewardToken: position.rewardToken,
1973
+ staked: position.staked.toString(),
1974
+ earned: position.earned.toString()
1975
+ }));
1976
+ return c.ok({
1977
+ address: toChecksum(c.args.address),
1978
+ positions,
1979
+ count: positions.length
1980
+ });
1981
+ }
1982
+ });
1983
+
1984
+ // src/commands/lending.ts
1985
+ import { checksumAddress as checksumAddress3, isAddress as isAddress2 } from "@spectratools/cli-shared";
1986
+ import { Cli as Cli3, z as z3 } from "incur";
1987
+ import { formatUnits as formatUnits2 } from "viem";
1988
+ var MORPHO_DEPLOY_BLOCK = 13947713n;
1989
+ var env3 = z3.object({
1990
+ ABSTRACT_RPC_URL: z3.string().optional().describe("Abstract RPC URL override")
1991
+ });
1992
+ var morphoAbi = [
1993
+ {
1994
+ type: "event",
1995
+ name: "CreateMarket",
1996
+ inputs: [
1997
+ { type: "bytes32", name: "id", indexed: true },
1998
+ {
1999
+ type: "tuple",
2000
+ name: "marketParams",
2001
+ indexed: false,
2002
+ components: [
2003
+ { type: "address", name: "loanToken" },
2004
+ { type: "address", name: "collateralToken" },
2005
+ { type: "address", name: "oracle" },
2006
+ { type: "address", name: "irm" },
2007
+ { type: "uint256", name: "lltv" }
2008
+ ]
2009
+ }
2010
+ ],
2011
+ anonymous: false
2012
+ },
2013
+ {
2014
+ type: "function",
2015
+ name: "idToMarketParams",
2016
+ stateMutability: "view",
2017
+ inputs: [{ type: "bytes32", name: "id" }],
2018
+ outputs: [
2019
+ { type: "address", name: "loanToken" },
2020
+ { type: "address", name: "collateralToken" },
2021
+ { type: "address", name: "oracle" },
2022
+ { type: "address", name: "irm" },
2023
+ { type: "uint256", name: "lltv" }
2024
+ ]
2025
+ },
2026
+ {
2027
+ type: "function",
2028
+ name: "market",
2029
+ stateMutability: "view",
2030
+ inputs: [{ type: "bytes32", name: "id" }],
2031
+ outputs: [
2032
+ { type: "uint128", name: "totalSupplyAssets" },
2033
+ { type: "uint128", name: "totalSupplyShares" },
2034
+ { type: "uint128", name: "totalBorrowAssets" },
2035
+ { type: "uint128", name: "totalBorrowShares" },
2036
+ { type: "uint128", name: "lastUpdate" },
2037
+ { type: "uint128", name: "fee" }
2038
+ ]
2039
+ },
2040
+ {
2041
+ type: "function",
2042
+ name: "position",
2043
+ stateMutability: "view",
2044
+ inputs: [
2045
+ { type: "bytes32", name: "id" },
2046
+ { type: "address", name: "user" }
2047
+ ],
2048
+ outputs: [
2049
+ { type: "uint256", name: "supplyShares" },
2050
+ { type: "uint128", name: "borrowShares" },
2051
+ { type: "uint128", name: "collateral" }
2052
+ ]
2053
+ }
2054
+ ];
2055
+ var erc20MetadataAbi2 = [
2056
+ {
2057
+ type: "function",
2058
+ name: "symbol",
2059
+ stateMutability: "view",
2060
+ inputs: [],
2061
+ outputs: [{ type: "string" }]
2062
+ },
2063
+ {
2064
+ type: "function",
2065
+ name: "decimals",
2066
+ stateMutability: "view",
2067
+ inputs: [],
2068
+ outputs: [{ type: "uint8" }]
2069
+ }
2070
+ ];
2071
+ var tokenMetaSchema = z3.object({
2072
+ address: z3.string(),
2073
+ symbol: z3.string(),
2074
+ decimals: z3.number()
2075
+ });
2076
+ var lendingMarketRowSchema = z3.object({
2077
+ marketId: z3.string(),
2078
+ loanToken: tokenMetaSchema,
2079
+ collateralToken: tokenMetaSchema,
2080
+ oracle: z3.string(),
2081
+ irm: z3.string(),
2082
+ lltvBps: z3.number(),
2083
+ lltvPercent: z3.number(),
2084
+ totalSupplyAssets: z3.string(),
2085
+ totalBorrowAssets: z3.string(),
2086
+ totalSupplyShares: z3.string(),
2087
+ totalBorrowShares: z3.string(),
2088
+ availableLiquidityAssets: z3.string(),
2089
+ utilization: z3.number().nullable(),
2090
+ feeWad: z3.string(),
2091
+ lastUpdate: z3.number()
2092
+ });
2093
+ function isMarketId(value) {
2094
+ return /^0x[0-9a-fA-F]{64}$/.test(value);
2095
+ }
2096
+ function shortAddress2(address) {
2097
+ return `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
2098
+ }
2099
+ function finiteOrNull2(value) {
2100
+ return Number.isFinite(value) ? value : null;
2101
+ }
2102
+ function sharesToAssets(shares, totalShares, totalAssets) {
2103
+ if (shares === 0n || totalShares === 0n) return 0n;
2104
+ return shares * totalAssets / totalShares;
2105
+ }
2106
+ function normalizeMarketParams(value) {
2107
+ if (Array.isArray(value)) {
2108
+ return {
2109
+ loanToken: checksumAddress3(String(value[0])),
2110
+ collateralToken: checksumAddress3(String(value[1])),
2111
+ oracle: checksumAddress3(String(value[2])),
2112
+ irm: checksumAddress3(String(value[3])),
2113
+ lltv: BigInt(value[4])
2114
+ };
2115
+ }
2116
+ if (typeof value !== "object" || value === null) {
2117
+ throw new Error("invalid market params");
2118
+ }
2119
+ const v = value;
2120
+ return {
2121
+ loanToken: checksumAddress3(String(v.loanToken)),
2122
+ collateralToken: checksumAddress3(String(v.collateralToken)),
2123
+ oracle: checksumAddress3(String(v.oracle)),
2124
+ irm: checksumAddress3(String(v.irm)),
2125
+ lltv: BigInt(v.lltv)
2126
+ };
2127
+ }
2128
+ function normalizeMarketState(value) {
2129
+ if (Array.isArray(value)) {
2130
+ return {
2131
+ totalSupplyAssets: BigInt(value[0]),
2132
+ totalSupplyShares: BigInt(value[1]),
2133
+ totalBorrowAssets: BigInt(value[2]),
2134
+ totalBorrowShares: BigInt(value[3]),
2135
+ lastUpdate: BigInt(value[4]),
2136
+ fee: BigInt(value[5])
2137
+ };
2138
+ }
2139
+ if (typeof value !== "object" || value === null) {
2140
+ throw new Error("invalid market state");
2141
+ }
2142
+ const v = value;
2143
+ return {
2144
+ totalSupplyAssets: BigInt(v.totalSupplyAssets),
2145
+ totalSupplyShares: BigInt(v.totalSupplyShares),
2146
+ totalBorrowAssets: BigInt(v.totalBorrowAssets),
2147
+ totalBorrowShares: BigInt(v.totalBorrowShares),
2148
+ lastUpdate: BigInt(v.lastUpdate),
2149
+ fee: BigInt(v.fee)
2150
+ };
2151
+ }
2152
+ function toMarketRow(marketId, params, state, tokenMeta) {
2153
+ const loanMeta = tokenMeta.get(params.loanToken.toLowerCase()) ?? {
2154
+ address: params.loanToken,
2155
+ symbol: shortAddress2(params.loanToken),
2156
+ decimals: 18
2157
+ };
2158
+ const collateralMeta = tokenMeta.get(params.collateralToken.toLowerCase()) ?? {
2159
+ address: params.collateralToken,
2160
+ symbol: shortAddress2(params.collateralToken),
2161
+ decimals: 18
2162
+ };
2163
+ const utilization = state.totalSupplyAssets === 0n ? null : finiteOrNull2(Number(state.totalBorrowAssets) / Number(state.totalSupplyAssets));
2164
+ const lltvPercent = Number(params.lltv) / 1e16;
2165
+ return {
2166
+ marketId,
2167
+ loanToken: {
2168
+ address: toChecksum(loanMeta.address),
2169
+ symbol: loanMeta.symbol,
2170
+ decimals: loanMeta.decimals
2171
+ },
2172
+ collateralToken: {
2173
+ address: toChecksum(collateralMeta.address),
2174
+ symbol: collateralMeta.symbol,
2175
+ decimals: collateralMeta.decimals
2176
+ },
2177
+ oracle: toChecksum(params.oracle),
2178
+ irm: toChecksum(params.irm),
2179
+ lltvBps: Math.round(lltvPercent * 100),
2180
+ lltvPercent,
2181
+ totalSupplyAssets: state.totalSupplyAssets.toString(),
2182
+ totalBorrowAssets: state.totalBorrowAssets.toString(),
2183
+ totalSupplyShares: state.totalSupplyShares.toString(),
2184
+ totalBorrowShares: state.totalBorrowShares.toString(),
2185
+ availableLiquidityAssets: (state.totalSupplyAssets - state.totalBorrowAssets).toString(),
2186
+ utilization,
2187
+ feeWad: state.fee.toString(),
2188
+ lastUpdate: asNum(state.lastUpdate)
2189
+ };
2190
+ }
2191
+ async function readTokenMetadata2(client, addresses) {
2192
+ const unique = [...new Set(addresses.map((address) => address.toLowerCase()))];
2193
+ if (unique.length === 0) {
2194
+ return /* @__PURE__ */ new Map();
2195
+ }
2196
+ const contracts = unique.flatMap((address) => [
2197
+ {
2198
+ abi: erc20MetadataAbi2,
2199
+ address,
2200
+ functionName: "symbol"
2201
+ },
2202
+ {
2203
+ abi: erc20MetadataAbi2,
2204
+ address,
2205
+ functionName: "decimals"
2206
+ }
2207
+ ]);
2208
+ const results = await client.multicall({
2209
+ allowFailure: true,
2210
+ contracts
2211
+ });
2212
+ const map = /* @__PURE__ */ new Map();
2213
+ for (let i = 0; i < unique.length; i += 1) {
2214
+ const address = unique[i];
2215
+ const symbolResult = results[i * 2];
2216
+ const decimalsResult = results[i * 2 + 1];
2217
+ const symbol = symbolResult && symbolResult.status === "success" && typeof symbolResult.result === "string" ? symbolResult.result : shortAddress2(address);
2218
+ const decimals = decimalsResult && decimalsResult.status === "success" && typeof decimalsResult.result === "number" ? decimalsResult.result : 18;
2219
+ map.set(address.toLowerCase(), {
2220
+ address: checksumAddress3(address),
2221
+ symbol,
2222
+ decimals
2223
+ });
2224
+ }
2225
+ return map;
2226
+ }
2227
+ async function discoverMarketIds(client) {
2228
+ const logs = await client.getContractEvents({
2229
+ address: ABOREAN_LENDING_ADDRESSES.morphoBlue,
2230
+ abi: morphoAbi,
2231
+ eventName: "CreateMarket",
2232
+ fromBlock: MORPHO_DEPLOY_BLOCK,
2233
+ toBlock: "latest"
2234
+ });
2235
+ const seen = /* @__PURE__ */ new Set();
2236
+ const ids = [];
2237
+ for (const log of logs) {
2238
+ const id = log.args.id;
2239
+ if (!id) continue;
2240
+ const key = id.toLowerCase();
2241
+ if (seen.has(key)) continue;
2242
+ seen.add(key);
2243
+ ids.push(id);
2244
+ }
2245
+ return ids;
2246
+ }
2247
+ async function readMarkets(client, marketIds) {
2248
+ if (marketIds.length === 0) return [];
2249
+ const contracts = marketIds.flatMap((marketId) => [
2250
+ {
2251
+ abi: morphoAbi,
2252
+ address: ABOREAN_LENDING_ADDRESSES.morphoBlue,
2253
+ functionName: "idToMarketParams",
2254
+ args: [marketId]
2255
+ },
2256
+ {
2257
+ abi: morphoAbi,
2258
+ address: ABOREAN_LENDING_ADDRESSES.morphoBlue,
2259
+ functionName: "market",
2260
+ args: [marketId]
2261
+ }
2262
+ ]);
2263
+ const values = await client.multicall({
2264
+ allowFailure: false,
2265
+ contracts
2266
+ });
2267
+ return marketIds.map((marketId, index) => {
2268
+ const paramsValue = values[index * 2];
2269
+ const stateValue = values[index * 2 + 1];
2270
+ return {
2271
+ marketId,
2272
+ params: normalizeMarketParams(paramsValue),
2273
+ state: normalizeMarketState(stateValue)
2274
+ };
2275
+ });
2276
+ }
2277
+ function summarizeByLoanToken(markets, tokenMeta) {
2278
+ const byLoanToken = /* @__PURE__ */ new Map();
2279
+ for (const { params, state } of markets) {
2280
+ const key = params.loanToken.toLowerCase();
2281
+ const prev = byLoanToken.get(key);
2282
+ if (prev) {
2283
+ prev.totalSupply += state.totalSupplyAssets;
2284
+ prev.totalBorrow += state.totalBorrowAssets;
2285
+ continue;
2286
+ }
2287
+ byLoanToken.set(key, {
2288
+ token: params.loanToken,
2289
+ totalSupply: state.totalSupplyAssets,
2290
+ totalBorrow: state.totalBorrowAssets
2291
+ });
2292
+ }
2293
+ return [...byLoanToken.values()].map((entry) => {
2294
+ const meta = tokenMeta.get(entry.token.toLowerCase()) ?? {
2295
+ address: entry.token,
2296
+ symbol: shortAddress2(entry.token),
2297
+ decimals: 18
2298
+ };
2299
+ return {
2300
+ token: toChecksum(entry.token),
2301
+ symbol: meta.symbol,
2302
+ decimals: meta.decimals,
2303
+ totalSupplyAssets: entry.totalSupply.toString(),
2304
+ totalBorrowAssets: entry.totalBorrow.toString()
2305
+ };
2306
+ });
2307
+ }
2308
+ async function readLendingSummary(client) {
2309
+ const marketIds = await discoverMarketIds(client);
2310
+ const markets = await readMarkets(client, marketIds);
2311
+ const tokenMeta = await readTokenMetadata2(
2312
+ client,
2313
+ markets.flatMap((market) => [market.params.loanToken, market.params.collateralToken])
2314
+ );
2315
+ return {
2316
+ available: true,
2317
+ morpho: toChecksum(ABOREAN_LENDING_ADDRESSES.morphoBlue),
2318
+ marketCount: marketIds.length,
2319
+ supplyByLoanToken: summarizeByLoanToken(markets, tokenMeta)
2320
+ };
2321
+ }
2322
+ var lending = Cli3.create("lending", {
2323
+ description: "Inspect Morpho lending markets on Abstract."
2324
+ });
2325
+ lending.command("markets", {
2326
+ description: "List Morpho markets discovered from CreateMarket events.",
2327
+ args: z3.object({
2328
+ limit: z3.coerce.number().int().positive().max(200).default(25).describe("Max markets to return")
2329
+ }),
2330
+ env: env3,
2331
+ output: z3.object({
2332
+ morpho: z3.string(),
2333
+ marketCount: z3.number(),
2334
+ markets: z3.array(lendingMarketRowSchema),
2335
+ totalsByLoanToken: z3.array(
2336
+ z3.object({
2337
+ token: z3.string(),
2338
+ symbol: z3.string(),
2339
+ decimals: z3.number(),
2340
+ totalSupplyAssets: z3.string(),
2341
+ totalBorrowAssets: z3.string()
2342
+ })
2343
+ )
2344
+ }),
2345
+ examples: [{ description: "List active Morpho markets on Abstract" }],
2346
+ async run(c) {
2347
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
2348
+ const marketIds = await discoverMarketIds(client);
2349
+ const markets = await readMarkets(client, marketIds);
2350
+ const tokenMeta = await readTokenMetadata2(
2351
+ client,
2352
+ markets.flatMap((market) => [market.params.loanToken, market.params.collateralToken])
2353
+ );
2354
+ const rows = markets.map((market) => toMarketRow(market.marketId, market.params, market.state, tokenMeta)).sort(
2355
+ (a, b) => BigInt(a.totalSupplyAssets) > BigInt(b.totalSupplyAssets) ? -1 : BigInt(a.totalSupplyAssets) < BigInt(b.totalSupplyAssets) ? 1 : 0
2356
+ ).slice(0, c.args.limit);
2357
+ return c.ok({
2358
+ morpho: toChecksum(ABOREAN_LENDING_ADDRESSES.morphoBlue),
2359
+ marketCount: marketIds.length,
2360
+ markets: rows,
2361
+ totalsByLoanToken: summarizeByLoanToken(markets, tokenMeta)
2362
+ });
2363
+ }
2364
+ });
2365
+ lending.command("market", {
2366
+ description: "Get details for one Morpho market id (bytes32).",
2367
+ args: z3.object({
2368
+ marketId: z3.string().describe("Morpho market id (bytes32 hex)")
2369
+ }),
2370
+ env: env3,
2371
+ output: lendingMarketRowSchema,
2372
+ examples: [
2373
+ {
2374
+ args: { marketId: "0xfe1d7da2fbde85b1fee120c88df3e6b55164a2442dab97486d3d4f719a5ff1fb" },
2375
+ description: "Inspect one Morpho market by id"
2376
+ }
2377
+ ],
2378
+ async run(c) {
2379
+ if (!isMarketId(c.args.marketId)) {
2380
+ return c.error({
2381
+ code: "INVALID_ARGUMENT",
2382
+ message: "marketId must be a 32-byte hex string (0x + 64 hex chars)"
2383
+ });
2384
+ }
2385
+ const marketId = c.args.marketId;
2386
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
2387
+ const [paramsRaw, stateRaw] = await Promise.all([
2388
+ client.readContract({
2389
+ abi: morphoAbi,
2390
+ address: ABOREAN_LENDING_ADDRESSES.morphoBlue,
2391
+ functionName: "idToMarketParams",
2392
+ args: [marketId]
2393
+ }),
2394
+ client.readContract({
2395
+ abi: morphoAbi,
2396
+ address: ABOREAN_LENDING_ADDRESSES.morphoBlue,
2397
+ functionName: "market",
2398
+ args: [marketId]
2399
+ })
2400
+ ]);
2401
+ const params = normalizeMarketParams(paramsRaw);
2402
+ const state = normalizeMarketState(stateRaw);
2403
+ const tokenMeta = await readTokenMetadata2(client, [params.loanToken, params.collateralToken]);
2404
+ return c.ok(toMarketRow(marketId, params, state, tokenMeta));
2405
+ }
2406
+ });
2407
+ lending.command("position", {
2408
+ description: "Inspect one user position in a Morpho market.",
2409
+ args: z3.object({
2410
+ marketId: z3.string().describe("Morpho market id (bytes32 hex)"),
2411
+ user: z3.string().describe("Position owner address")
2412
+ }),
2413
+ env: env3,
2414
+ output: z3.object({
2415
+ marketId: z3.string(),
2416
+ user: z3.string(),
2417
+ loanToken: tokenMetaSchema,
2418
+ collateralToken: tokenMetaSchema,
2419
+ supplyShares: z3.string(),
2420
+ supplyAssetsEstimate: z3.object({
2421
+ raw: z3.string(),
2422
+ decimal: z3.string()
2423
+ }),
2424
+ borrowShares: z3.string(),
2425
+ borrowAssetsEstimate: z3.object({
2426
+ raw: z3.string(),
2427
+ decimal: z3.string()
2428
+ }),
2429
+ collateralAssets: z3.object({
2430
+ raw: z3.string(),
2431
+ decimal: z3.string()
2432
+ })
2433
+ }),
2434
+ examples: [
2435
+ {
2436
+ args: {
2437
+ marketId: "0xfe1d7da2fbde85b1fee120c88df3e6b55164a2442dab97486d3d4f719a5ff1fb",
2438
+ user: "0x0000000000000000000000000000000000000000"
2439
+ },
2440
+ description: "Inspect one user position in a market"
2441
+ }
2442
+ ],
2443
+ async run(c) {
2444
+ if (!isMarketId(c.args.marketId)) {
2445
+ return c.error({
2446
+ code: "INVALID_ARGUMENT",
2447
+ message: "marketId must be a 32-byte hex string (0x + 64 hex chars)"
2448
+ });
2449
+ }
2450
+ if (!isAddress2(c.args.user)) {
2451
+ return c.error({
2452
+ code: "INVALID_ARGUMENT",
2453
+ message: "user must be a valid address"
2454
+ });
2455
+ }
2456
+ const marketId = c.args.marketId;
2457
+ const user = checksumAddress3(c.args.user);
2458
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
2459
+ const [paramsRaw, stateRaw, positionRaw] = await Promise.all([
2460
+ client.readContract({
2461
+ abi: morphoAbi,
2462
+ address: ABOREAN_LENDING_ADDRESSES.morphoBlue,
2463
+ functionName: "idToMarketParams",
2464
+ args: [marketId]
2465
+ }),
2466
+ client.readContract({
2467
+ abi: morphoAbi,
2468
+ address: ABOREAN_LENDING_ADDRESSES.morphoBlue,
2469
+ functionName: "market",
2470
+ args: [marketId]
2471
+ }),
2472
+ client.readContract({
2473
+ abi: morphoAbi,
2474
+ address: ABOREAN_LENDING_ADDRESSES.morphoBlue,
2475
+ functionName: "position",
2476
+ args: [marketId, user]
2477
+ })
2478
+ ]);
2479
+ const params = normalizeMarketParams(paramsRaw);
2480
+ const state = normalizeMarketState(stateRaw);
2481
+ const position = Array.isArray(positionRaw) ? {
2482
+ supplyShares: BigInt(positionRaw[0]),
2483
+ borrowShares: BigInt(positionRaw[1]),
2484
+ collateral: BigInt(positionRaw[2])
2485
+ } : {
2486
+ supplyShares: BigInt(positionRaw.supplyShares),
2487
+ borrowShares: BigInt(positionRaw.borrowShares),
2488
+ collateral: BigInt(positionRaw.collateral)
2489
+ };
2490
+ const tokenMeta = await readTokenMetadata2(client, [params.loanToken, params.collateralToken]);
2491
+ const loanMeta = tokenMeta.get(params.loanToken.toLowerCase()) ?? {
2492
+ address: params.loanToken,
2493
+ symbol: shortAddress2(params.loanToken),
2494
+ decimals: 18
2495
+ };
2496
+ const collateralMeta = tokenMeta.get(params.collateralToken.toLowerCase()) ?? {
2497
+ address: params.collateralToken,
2498
+ symbol: shortAddress2(params.collateralToken),
2499
+ decimals: 18
2500
+ };
2501
+ const supplyAssetsEstimate = sharesToAssets(
2502
+ BigInt(position.supplyShares),
2503
+ state.totalSupplyShares,
2504
+ state.totalSupplyAssets
2505
+ );
2506
+ const borrowAssetsEstimate = sharesToAssets(
2507
+ BigInt(position.borrowShares),
2508
+ state.totalBorrowShares,
2509
+ state.totalBorrowAssets
2510
+ );
2511
+ return c.ok({
2512
+ marketId,
2513
+ user: toChecksum(user),
2514
+ loanToken: {
2515
+ address: toChecksum(loanMeta.address),
2516
+ symbol: loanMeta.symbol,
2517
+ decimals: loanMeta.decimals
2518
+ },
2519
+ collateralToken: {
2520
+ address: toChecksum(collateralMeta.address),
2521
+ symbol: collateralMeta.symbol,
2522
+ decimals: collateralMeta.decimals
2523
+ },
2524
+ supplyShares: position.supplyShares.toString(),
2525
+ supplyAssetsEstimate: {
2526
+ raw: supplyAssetsEstimate.toString(),
2527
+ decimal: formatUnits2(supplyAssetsEstimate, loanMeta.decimals)
2528
+ },
2529
+ borrowShares: position.borrowShares.toString(),
2530
+ borrowAssetsEstimate: {
2531
+ raw: borrowAssetsEstimate.toString(),
2532
+ decimal: formatUnits2(borrowAssetsEstimate, loanMeta.decimals)
2533
+ },
2534
+ collateralAssets: {
2535
+ raw: position.collateral.toString(),
2536
+ decimal: formatUnits2(BigInt(position.collateral), collateralMeta.decimals)
2537
+ }
2538
+ });
2539
+ }
2540
+ });
2541
+
2542
+ // src/commands/pools.ts
2543
+ import { checksumAddress as checksumAddress4, isAddress as isAddress3 } from "@spectratools/cli-shared";
2544
+ import { Cli as Cli4, z as z4 } from "incur";
2545
+ import { formatUnits as formatUnits3, parseUnits as parseUnits2 } from "viem";
2546
+ var MULTICALL_BATCH_SIZE2 = 100;
2547
+ var env4 = z4.object({
2548
+ ABSTRACT_RPC_URL: z4.string().optional().describe("Abstract RPC URL override")
2549
+ });
2550
+ var tokenSchema2 = z4.object({
2551
+ address: z4.string(),
2552
+ symbol: z4.string(),
2553
+ decimals: z4.number()
2554
+ });
2555
+ var amountSchema = z4.object({
2556
+ raw: z4.string(),
2557
+ decimal: z4.string()
2558
+ });
2559
+ var feeSchema = z4.object({
2560
+ feeBps: z4.number(),
2561
+ feePercent: z4.number()
2562
+ }).nullable();
2563
+ var poolSummarySchema = z4.object({
2564
+ pool: z4.string(),
2565
+ pair: z4.string(),
2566
+ stable: z4.boolean(),
2567
+ poolType: z4.enum(["stable", "volatile"]),
2568
+ token0: tokenSchema2,
2569
+ token1: tokenSchema2,
2570
+ reserves: z4.object({
2571
+ token0: amountSchema,
2572
+ token1: amountSchema,
2573
+ blockTimestampLast: z4.number()
2574
+ }),
2575
+ totalSupply: z4.string(),
2576
+ fee: feeSchema
2577
+ });
2578
+ var erc20MetadataAbi3 = [
2579
+ {
2580
+ type: "function",
2581
+ name: "symbol",
2582
+ stateMutability: "view",
2583
+ inputs: [],
2584
+ outputs: [{ type: "string" }]
2585
+ },
2586
+ {
2587
+ type: "function",
2588
+ name: "decimals",
2589
+ stateMutability: "view",
2590
+ inputs: [],
2591
+ outputs: [{ type: "uint8" }]
2592
+ }
2593
+ ];
2594
+ function shortAddress3(address) {
2595
+ return `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
2596
+ }
2597
+ function toAddress(address) {
2598
+ return checksumAddress4(address);
2599
+ }
2600
+ function finiteOrNull3(value) {
2601
+ return Number.isFinite(value) ? value : null;
2602
+ }
2603
+ function toFeeInfo(value) {
2604
+ if (value === null) return null;
2605
+ const feeBps = Number(value);
2606
+ return {
2607
+ feeBps,
2608
+ feePercent: feeBps / 1e4
2609
+ };
2610
+ }
2611
+ function chunk2(items, size) {
2612
+ const output = [];
2613
+ for (let i = 0; i < items.length; i += size) {
2614
+ output.push(items.slice(i, i + size));
2615
+ }
2616
+ return output;
2617
+ }
2618
+ async function multicallStrict2(client, contracts) {
2619
+ if (contracts.length === 0) return [];
2620
+ const batches = chunk2(contracts, MULTICALL_BATCH_SIZE2);
2621
+ const output = [];
2622
+ for (const batch of batches) {
2623
+ const values = await client.multicall({
2624
+ allowFailure: false,
2625
+ contracts: batch
2626
+ });
2627
+ output.push(...values);
2628
+ }
2629
+ return output;
2630
+ }
2631
+ async function multicallAllowFailure2(client, contracts) {
2632
+ if (contracts.length === 0) return [];
2633
+ const batches = chunk2(contracts, MULTICALL_BATCH_SIZE2);
2634
+ const output = [];
2635
+ for (const batch of batches) {
2636
+ const values = await client.multicall({
2637
+ allowFailure: true,
2638
+ contracts: batch
2639
+ });
2640
+ output.push(...values);
2641
+ }
2642
+ return output;
2643
+ }
2644
+ function fallbackTokenMeta(address) {
2645
+ const checksummed = checksumAddress4(address);
2646
+ return {
2647
+ address: checksummed,
2648
+ symbol: shortAddress3(checksummed),
2649
+ decimals: 18
2650
+ };
2651
+ }
2652
+ async function readTokenMetadata3(client, tokenAddresses) {
2653
+ const unique = [
2654
+ ...new Set(tokenAddresses.map((address) => checksumAddress4(address).toLowerCase()))
2655
+ ];
2656
+ if (unique.length === 0) {
2657
+ return /* @__PURE__ */ new Map();
2658
+ }
2659
+ const contracts = unique.flatMap((address) => [
2660
+ {
2661
+ abi: erc20MetadataAbi3,
2662
+ address,
2663
+ functionName: "symbol"
2664
+ },
2665
+ {
2666
+ abi: erc20MetadataAbi3,
2667
+ address,
2668
+ functionName: "decimals"
2669
+ }
2670
+ ]);
2671
+ const values = await multicallAllowFailure2(client, contracts);
2672
+ const map = /* @__PURE__ */ new Map();
2673
+ for (let i = 0; i < unique.length; i += 1) {
2674
+ const address = unique[i];
2675
+ const symbolResult = values[i * 2];
2676
+ const decimalsResult = values[i * 2 + 1];
2677
+ const fallback = fallbackTokenMeta(address);
2678
+ const symbol = symbolResult && symbolResult.status === "success" && typeof symbolResult.result === "string" ? symbolResult.result : fallback.symbol;
2679
+ const decimals = decimalsResult && decimalsResult.status === "success" && typeof decimalsResult.result === "number" ? decimalsResult.result : fallback.decimals;
2680
+ map.set(address.toLowerCase(), {
2681
+ address: checksumAddress4(address),
2682
+ symbol,
2683
+ decimals
2684
+ });
2685
+ }
2686
+ return map;
2687
+ }
2688
+ async function readPoolStates2(client, pools2) {
2689
+ if (pools2.length === 0) {
2690
+ return [];
2691
+ }
2692
+ const contracts = pools2.flatMap((pool) => [
2693
+ {
2694
+ abi: v2PoolAbi,
2695
+ address: pool,
2696
+ functionName: "token0"
2697
+ },
2698
+ {
2699
+ abi: v2PoolAbi,
2700
+ address: pool,
2701
+ functionName: "token1"
2702
+ },
2703
+ {
2704
+ abi: v2PoolAbi,
2705
+ address: pool,
2706
+ functionName: "stable"
2707
+ },
2708
+ {
2709
+ abi: v2PoolAbi,
2710
+ address: pool,
2711
+ functionName: "getReserves"
2712
+ },
2713
+ {
2714
+ abi: v2PoolAbi,
2715
+ address: pool,
2716
+ functionName: "totalSupply"
2717
+ }
2718
+ ]);
2719
+ const values = await multicallStrict2(client, contracts);
2720
+ return pools2.map((pool, index) => {
2721
+ const offset = index * 5;
2722
+ const reserves = values[offset + 3];
2723
+ return {
2724
+ pool,
2725
+ token0: values[offset],
2726
+ token1: values[offset + 1],
2727
+ stable: values[offset + 2],
2728
+ reserve0: reserves[0],
2729
+ reserve1: reserves[1],
2730
+ blockTimestampLast: reserves[2],
2731
+ totalSupply: values[offset + 4]
2732
+ };
2733
+ });
2734
+ }
2735
+ async function readPoolFees(client, pools2) {
2736
+ if (pools2.length === 0) {
2737
+ return [];
2738
+ }
2739
+ const contracts = pools2.map(({ pool, stable }) => ({
2740
+ abi: poolFactoryAbi,
2741
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
2742
+ functionName: "getFee",
2743
+ args: [pool, stable]
2744
+ }));
2745
+ const values = await multicallAllowFailure2(client, contracts);
2746
+ return values.map(
2747
+ (value) => value.status === "success" && typeof value.result === "bigint" ? value.result : null
2748
+ );
2749
+ }
2750
+ function toAmount(raw, decimals) {
2751
+ return {
2752
+ raw: raw.toString(),
2753
+ decimal: formatUnits3(raw, decimals)
2754
+ };
2755
+ }
2756
+ function toPoolSummary(state, tokens, fee) {
2757
+ const token0 = tokens.get(state.token0.toLowerCase()) ?? fallbackTokenMeta(state.token0);
2758
+ const token1 = tokens.get(state.token1.toLowerCase()) ?? fallbackTokenMeta(state.token1);
2759
+ return {
2760
+ pool: checksumAddress4(state.pool),
2761
+ pair: `${token0.symbol}/${token1.symbol}`,
2762
+ stable: state.stable,
2763
+ poolType: state.stable ? "stable" : "volatile",
2764
+ token0,
2765
+ token1,
2766
+ reserves: {
2767
+ token0: toAmount(state.reserve0, token0.decimals),
2768
+ token1: toAmount(state.reserve1, token1.decimals),
2769
+ blockTimestampLast: Number(state.blockTimestampLast)
2770
+ },
2771
+ totalSupply: state.totalSupply.toString(),
2772
+ fee: toFeeInfo(fee)
2773
+ };
2774
+ }
2775
+ var pools = Cli4.create("pools", {
2776
+ description: "Inspect V2 AMM pools, reserves, quotes, and fee configuration."
2777
+ });
2778
+ pools.command("list", {
2779
+ description: "List V2 pools with token pairs, reserves, and stable/volatile type.",
2780
+ options: z4.object({
2781
+ offset: z4.coerce.number().int().nonnegative().default(0).describe("Pool index offset"),
2782
+ limit: z4.coerce.number().int().positive().max(500).default(50).describe("Maximum pools to return (max 500)")
2783
+ }),
2784
+ env: env4,
2785
+ output: z4.object({
2786
+ total: z4.number(),
2787
+ offset: z4.number(),
2788
+ limit: z4.number(),
2789
+ count: z4.number(),
2790
+ pools: z4.array(poolSummarySchema)
2791
+ }),
2792
+ async run(c) {
2793
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
2794
+ const totalRaw = await client.readContract({
2795
+ abi: poolFactoryAbi,
2796
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
2797
+ functionName: "allPoolsLength"
2798
+ });
2799
+ const total = Number(totalRaw);
2800
+ const offset = Math.min(c.options.offset, total);
2801
+ const end = Math.min(total, offset + c.options.limit);
2802
+ if (offset >= end) {
2803
+ return c.ok({
2804
+ total,
2805
+ offset,
2806
+ limit: c.options.limit,
2807
+ count: 0,
2808
+ pools: []
2809
+ });
2810
+ }
2811
+ const indices = Array.from({ length: end - offset }, (_, i) => BigInt(offset + i));
2812
+ const poolAddresses = await multicallStrict2(
2813
+ client,
2814
+ indices.map((index) => ({
2815
+ abi: poolFactoryAbi,
2816
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
2817
+ functionName: "allPools",
2818
+ args: [index]
2819
+ }))
2820
+ );
2821
+ const poolStates = await readPoolStates2(client, poolAddresses);
2822
+ const tokenMeta = await readTokenMetadata3(
2823
+ client,
2824
+ poolStates.flatMap((pool) => [pool.token0, pool.token1])
2825
+ );
2826
+ const fees = await readPoolFees(
2827
+ client,
2828
+ poolStates.map((pool) => ({ pool: pool.pool, stable: pool.stable }))
2829
+ );
2830
+ return c.ok({
2831
+ total,
2832
+ offset,
2833
+ limit: c.options.limit,
2834
+ count: poolStates.length,
2835
+ pools: poolStates.map((pool, index) => toPoolSummary(pool, tokenMeta, fees[index] ?? null))
2836
+ });
2837
+ }
2838
+ });
2839
+ pools.command("pool", {
2840
+ description: "Get detailed state for one V2 pool.",
2841
+ args: z4.object({
2842
+ address: z4.string().describe("Pool address")
2843
+ }),
2844
+ env: env4,
2845
+ output: z4.object({
2846
+ pool: poolSummarySchema.extend({
2847
+ poolFees: z4.string(),
2848
+ factory: z4.string()
2849
+ })
2850
+ }),
2851
+ async run(c) {
2852
+ if (!isAddress3(c.args.address)) {
2853
+ return c.error({
2854
+ code: "INVALID_ADDRESS",
2855
+ message: `Invalid pool address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
2856
+ });
2857
+ }
2858
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
2859
+ const poolAddress = toAddress(c.args.address);
2860
+ const [token0, token1, stable, reserves, totalSupply, poolFees, factory] = await multicallStrict2(client, [
2861
+ {
2862
+ abi: v2PoolAbi,
2863
+ address: poolAddress,
2864
+ functionName: "token0"
2865
+ },
2866
+ {
2867
+ abi: v2PoolAbi,
2868
+ address: poolAddress,
2869
+ functionName: "token1"
2870
+ },
2871
+ {
2872
+ abi: v2PoolAbi,
2873
+ address: poolAddress,
2874
+ functionName: "stable"
2875
+ },
2876
+ {
2877
+ abi: v2PoolAbi,
2878
+ address: poolAddress,
2879
+ functionName: "getReserves"
2880
+ },
2881
+ {
2882
+ abi: v2PoolAbi,
2883
+ address: poolAddress,
2884
+ functionName: "totalSupply"
2885
+ },
2886
+ {
2887
+ abi: v2PoolAbi,
2888
+ address: poolAddress,
2889
+ functionName: "poolFees"
2890
+ },
2891
+ {
2892
+ abi: v2PoolAbi,
2893
+ address: poolAddress,
2894
+ functionName: "factory"
2895
+ }
2896
+ ]);
2897
+ const tokenMeta = await readTokenMetadata3(client, [token0, token1]);
2898
+ const [fee] = await readPoolFees(client, [{ pool: poolAddress, stable }]);
2899
+ return c.ok({
2900
+ pool: {
2901
+ ...toPoolSummary(
2902
+ {
2903
+ pool: poolAddress,
2904
+ token0,
2905
+ token1,
2906
+ stable,
2907
+ reserve0: reserves[0],
2908
+ reserve1: reserves[1],
2909
+ blockTimestampLast: reserves[2],
2910
+ totalSupply
2911
+ },
2912
+ tokenMeta,
2913
+ fee ?? null
2914
+ ),
2915
+ poolFees: checksumAddress4(poolFees),
2916
+ factory: checksumAddress4(factory)
2917
+ }
2918
+ });
2919
+ }
2920
+ });
2921
+ pools.command("quote", {
2922
+ description: "Quote a single-hop V2 swap between tokenIn and tokenOut.",
2923
+ args: z4.object({
2924
+ tokenIn: z4.string().describe("Input token address"),
2925
+ tokenOut: z4.string().describe("Output token address"),
2926
+ amountIn: z4.string().describe("Input amount in human-readable decimal units")
2927
+ }),
2928
+ options: z4.object({
2929
+ stable: z4.boolean().default(false).describe("Use stable pool route (default: volatile)")
2930
+ }),
2931
+ env: env4,
2932
+ output: z4.object({
2933
+ pool: z4.string(),
2934
+ stable: z4.boolean(),
2935
+ tokenIn: tokenSchema2,
2936
+ tokenOut: tokenSchema2,
2937
+ amountIn: amountSchema,
2938
+ amountOut: amountSchema,
2939
+ priceOutPerIn: z4.number().nullable()
2940
+ }),
2941
+ async run(c) {
2942
+ if (!isAddress3(c.args.tokenIn) || !isAddress3(c.args.tokenOut)) {
2943
+ return c.error({
2944
+ code: "INVALID_ADDRESS",
2945
+ message: "tokenIn and tokenOut must both be valid 0x-prefixed 20-byte addresses."
2946
+ });
2947
+ }
2948
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
2949
+ const tokenIn = toAddress(c.args.tokenIn);
2950
+ const tokenOut = toAddress(c.args.tokenOut);
2951
+ const tokenMeta = await readTokenMetadata3(client, [tokenIn, tokenOut]);
2952
+ const inMeta = tokenMeta.get(tokenIn.toLowerCase()) ?? fallbackTokenMeta(tokenIn);
2953
+ const outMeta = tokenMeta.get(tokenOut.toLowerCase()) ?? fallbackTokenMeta(tokenOut);
2954
+ let amountInRaw;
2955
+ try {
2956
+ amountInRaw = parseUnits2(c.args.amountIn, inMeta.decimals);
2957
+ } catch {
2958
+ return c.error({
2959
+ code: "INVALID_AMOUNT",
2960
+ message: `Invalid amountIn: "${c.args.amountIn}" for token ${inMeta.symbol} (${inMeta.decimals} decimals).`
2961
+ });
2962
+ }
2963
+ const poolAddress = await client.readContract({
2964
+ abi: poolFactoryAbi,
2965
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
2966
+ functionName: "getPool",
2967
+ args: [tokenIn, tokenOut, c.options.stable]
2968
+ });
2969
+ if (poolAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase()) {
2970
+ return c.error({
2971
+ code: "POOL_NOT_FOUND",
2972
+ message: `No ${c.options.stable ? "stable" : "volatile"} V2 pool found for pair ${checksumAddress4(
2973
+ tokenIn
2974
+ )}/${checksumAddress4(tokenOut)}.`
2975
+ });
2976
+ }
2977
+ const amounts = await client.readContract({
2978
+ abi: v2RouterAbi,
2979
+ address: ABOREAN_V2_ADDRESSES.router,
2980
+ functionName: "getAmountsOut",
2981
+ args: [
2982
+ amountInRaw,
2983
+ [
2984
+ {
2985
+ from: tokenIn,
2986
+ to: tokenOut,
2987
+ stable: c.options.stable,
2988
+ factory: ABOREAN_V2_ADDRESSES.poolFactory
2989
+ }
2990
+ ]
2991
+ ]
2992
+ });
2993
+ const amountOutRaw = amounts[amounts.length - 1] ?? 0n;
2994
+ const amountInDecimal = formatUnits3(amountInRaw, inMeta.decimals);
2995
+ const amountOutDecimal = formatUnits3(amountOutRaw, outMeta.decimals);
2996
+ const ratio = Number(amountOutDecimal) / Number(amountInDecimal);
2997
+ return c.ok({
2998
+ pool: checksumAddress4(poolAddress),
2999
+ stable: c.options.stable,
3000
+ tokenIn: inMeta,
3001
+ tokenOut: outMeta,
3002
+ amountIn: {
3003
+ raw: amountInRaw.toString(),
3004
+ decimal: amountInDecimal
3005
+ },
3006
+ amountOut: {
3007
+ raw: amountOutRaw.toString(),
3008
+ decimal: amountOutDecimal
3009
+ },
3010
+ priceOutPerIn: Number(amountInDecimal) === 0 ? null : finiteOrNull3(ratio)
3011
+ });
3012
+ }
3013
+ });
3014
+ pools.command("fees", {
3015
+ description: "Read V2 fee configuration for a pool address.",
3016
+ args: z4.object({
3017
+ pool: z4.string().describe("Pool address")
3018
+ }),
3019
+ env: env4,
3020
+ output: z4.object({
3021
+ pool: z4.string(),
3022
+ pair: z4.string(),
3023
+ stable: z4.boolean(),
3024
+ activeFee: feeSchema,
3025
+ stableFee: feeSchema,
3026
+ volatileFee: feeSchema
3027
+ }),
3028
+ async run(c) {
3029
+ if (!isAddress3(c.args.pool)) {
3030
+ return c.error({
3031
+ code: "INVALID_ADDRESS",
3032
+ message: `Invalid pool address: "${c.args.pool}". Use a valid 0x-prefixed 20-byte hex address.`
3033
+ });
3034
+ }
3035
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
3036
+ const pool = toAddress(c.args.pool);
3037
+ const [token0, token1, stable] = await multicallStrict2(client, [
3038
+ {
3039
+ abi: v2PoolAbi,
3040
+ address: pool,
3041
+ functionName: "token0"
3042
+ },
3043
+ {
3044
+ abi: v2PoolAbi,
3045
+ address: pool,
3046
+ functionName: "token1"
3047
+ },
3048
+ {
3049
+ abi: v2PoolAbi,
3050
+ address: pool,
3051
+ functionName: "stable"
3052
+ }
3053
+ ]);
3054
+ const tokenMeta = await readTokenMetadata3(client, [token0, token1]);
3055
+ const token0Meta = tokenMeta.get(token0.toLowerCase()) ?? fallbackTokenMeta(token0);
3056
+ const token1Meta = tokenMeta.get(token1.toLowerCase()) ?? fallbackTokenMeta(token1);
3057
+ const [stableFeeRaw, volatileFeeRaw] = await readPoolFees(client, [
3058
+ { pool, stable: true },
3059
+ { pool, stable: false }
3060
+ ]);
3061
+ const stableFee = toFeeInfo(stableFeeRaw ?? null);
3062
+ const volatileFee = toFeeInfo(volatileFeeRaw ?? null);
3063
+ return c.ok({
3064
+ pool: checksumAddress4(pool),
3065
+ pair: `${token0Meta.symbol}/${token1Meta.symbol}`,
3066
+ stable,
3067
+ activeFee: stable ? stableFee : volatileFee,
3068
+ stableFee,
3069
+ volatileFee
3070
+ });
3071
+ }
3072
+ });
3073
+
3074
+ // src/commands/vaults.ts
3075
+ import { checksumAddress as checksumAddress5, isAddress as isAddress4 } from "@spectratools/cli-shared";
3076
+ import { Cli as Cli5, z as z5 } from "incur";
3077
+ var env5 = z5.object({
3078
+ ABSTRACT_RPC_URL: z5.string().optional().describe("Abstract RPC URL override")
3079
+ });
3080
+ var relayAbi = [
3081
+ {
3082
+ type: "function",
3083
+ name: "name",
3084
+ stateMutability: "view",
3085
+ inputs: [],
3086
+ outputs: [{ type: "string" }]
3087
+ },
3088
+ {
3089
+ type: "function",
3090
+ name: "mTokenId",
3091
+ stateMutability: "view",
3092
+ inputs: [],
3093
+ outputs: [{ type: "uint256" }]
3094
+ },
3095
+ {
3096
+ type: "function",
3097
+ name: "token",
3098
+ stateMutability: "view",
3099
+ inputs: [],
3100
+ outputs: [{ type: "address" }]
3101
+ },
3102
+ {
3103
+ type: "function",
3104
+ name: "keeperLastRun",
3105
+ stateMutability: "view",
3106
+ inputs: [],
3107
+ outputs: [{ type: "uint256" }]
3108
+ }
3109
+ ];
3110
+ var erc20Abi = [
3111
+ {
3112
+ type: "function",
3113
+ name: "symbol",
3114
+ stateMutability: "view",
3115
+ inputs: [],
3116
+ outputs: [{ type: "string" }]
3117
+ },
3118
+ {
3119
+ type: "function",
3120
+ name: "decimals",
3121
+ stateMutability: "view",
3122
+ inputs: [],
3123
+ outputs: [{ type: "uint8" }]
3124
+ },
3125
+ {
3126
+ type: "function",
3127
+ name: "balanceOf",
3128
+ stateMutability: "view",
3129
+ inputs: [{ type: "address", name: "owner" }],
3130
+ outputs: [{ type: "uint256" }]
3131
+ }
3132
+ ];
3133
+ var votingEscrowLiteAbi = [
3134
+ {
3135
+ type: "function",
3136
+ name: "balanceOfNFT",
3137
+ stateMutability: "view",
3138
+ inputs: [{ type: "uint256", name: "tokenId" }],
3139
+ outputs: [{ type: "uint256" }]
3140
+ }
3141
+ ];
3142
+ var relayRowSchema = z5.object({
3143
+ label: z5.string(),
3144
+ relay: z5.string(),
3145
+ name: z5.string(),
3146
+ factoryType: z5.enum(["autoCompounder", "autoConverter"]),
3147
+ managedTokenId: z5.string(),
3148
+ managedVotingPower: z5.string(),
3149
+ relayToken: z5.object({
3150
+ address: z5.string(),
3151
+ symbol: z5.string(),
3152
+ decimals: z5.number()
3153
+ }),
3154
+ relayTokenBalance: z5.string(),
3155
+ keeperLastRun: z5.number(),
3156
+ keeperLastRunRelative: z5.string(),
3157
+ secondsSinceKeeperRun: z5.number()
3158
+ });
3159
+ var KNOWN_RELAYS = [
3160
+ {
3161
+ label: "veABX Maxi Relay",
3162
+ relay: checksumAddress5(ABOREAN_VAULT_ADDRESSES.veAbxMaxiRelay),
3163
+ factoryType: "autoCompounder"
3164
+ },
3165
+ {
3166
+ label: "ABX Rewards Relay",
3167
+ relay: checksumAddress5(ABOREAN_VAULT_ADDRESSES.abxRewardsRelay),
3168
+ factoryType: "autoConverter"
3169
+ }
3170
+ ];
3171
+ function shortAddress4(address) {
3172
+ return `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
3173
+ }
3174
+ async function readRelaySnapshot(client, relay) {
3175
+ const [name, managedTokenId, relayTokenAddress, keeperLastRun] = await Promise.all([
3176
+ client.readContract({
3177
+ abi: relayAbi,
3178
+ address: relay.relay,
3179
+ functionName: "name"
3180
+ }),
3181
+ client.readContract({
3182
+ abi: relayAbi,
3183
+ address: relay.relay,
3184
+ functionName: "mTokenId"
3185
+ }),
3186
+ client.readContract({
3187
+ abi: relayAbi,
3188
+ address: relay.relay,
3189
+ functionName: "token"
3190
+ }),
3191
+ client.readContract({
3192
+ abi: relayAbi,
3193
+ address: relay.relay,
3194
+ functionName: "keeperLastRun"
3195
+ })
3196
+ ]);
3197
+ const tokenAddress = checksumAddress5(relayTokenAddress);
3198
+ const [symbol, decimals, relayTokenBalance, managedVotingPower] = await Promise.all([
3199
+ client.readContract({
3200
+ abi: erc20Abi,
3201
+ address: tokenAddress,
3202
+ functionName: "symbol"
3203
+ }),
3204
+ client.readContract({
3205
+ abi: erc20Abi,
3206
+ address: tokenAddress,
3207
+ functionName: "decimals"
3208
+ }),
3209
+ client.readContract({
3210
+ abi: erc20Abi,
3211
+ address: tokenAddress,
3212
+ functionName: "balanceOf",
3213
+ args: [relay.relay]
3214
+ }),
3215
+ client.readContract({
3216
+ abi: votingEscrowLiteAbi,
3217
+ address: ABOREAN_V2_ADDRESSES.votingEscrow,
3218
+ functionName: "balanceOfNFT",
3219
+ args: [managedTokenId]
3220
+ })
3221
+ ]);
3222
+ const keeperTs = asNum(keeperLastRun);
3223
+ const now = Math.floor(Date.now() / 1e3);
3224
+ return {
3225
+ label: relay.label,
3226
+ relay: toChecksum(relay.relay),
3227
+ name,
3228
+ factoryType: relay.factoryType,
3229
+ managedTokenId: managedTokenId.toString(),
3230
+ managedVotingPower: managedVotingPower.toString(),
3231
+ relayToken: {
3232
+ address: toChecksum(tokenAddress),
3233
+ symbol,
3234
+ decimals
3235
+ },
3236
+ relayTokenBalance: relayTokenBalance.toString(),
3237
+ keeperLastRun: keeperTs,
3238
+ keeperLastRunRelative: relTime(keeperLastRun),
3239
+ secondsSinceKeeperRun: Math.max(0, now - keeperTs)
3240
+ };
3241
+ }
3242
+ async function readVaultSummary(client) {
3243
+ const relays = await Promise.all(KNOWN_RELAYS.map((relay) => readRelaySnapshot(client, relay)));
3244
+ const tokenTotals = /* @__PURE__ */ new Map();
3245
+ let managedVotingPowerTotal = 0n;
3246
+ for (const relay of relays) {
3247
+ managedVotingPowerTotal += BigInt(relay.managedVotingPower);
3248
+ const key = relay.relayToken.address.toLowerCase();
3249
+ const prev = tokenTotals.get(key);
3250
+ const balance = BigInt(relay.relayTokenBalance);
3251
+ if (prev) {
3252
+ prev.balance += balance;
3253
+ continue;
3254
+ }
3255
+ tokenTotals.set(key, {
3256
+ token: relay.relayToken.address,
3257
+ symbol: relay.relayToken.symbol,
3258
+ decimals: relay.relayToken.decimals,
3259
+ balance
3260
+ });
3261
+ }
3262
+ const relayTokenBalances = [...tokenTotals.values()].map((row) => ({
3263
+ token: row.token,
3264
+ symbol: row.symbol,
3265
+ decimals: row.decimals,
3266
+ balance: row.balance.toString()
3267
+ }));
3268
+ return {
3269
+ relayCount: relays.length,
3270
+ relays,
3271
+ totals: {
3272
+ managedVotingPower: managedVotingPowerTotal.toString(),
3273
+ relayTokenBalances
3274
+ }
3275
+ };
3276
+ }
3277
+ var vaults = Cli5.create("vaults", {
3278
+ description: "Inspect Aborean relay vaults (auto-compounder / auto-converter)."
3279
+ });
3280
+ vaults.command("list", {
3281
+ description: "List known Aborean relay vaults with keeper and veNFT state.",
3282
+ env: env5,
3283
+ output: z5.object({
3284
+ relayCount: z5.number(),
3285
+ relays: z5.array(relayRowSchema),
3286
+ totals: z5.object({
3287
+ managedVotingPower: z5.string(),
3288
+ relayTokenBalances: z5.array(
3289
+ z5.object({
3290
+ token: z5.string(),
3291
+ symbol: z5.string(),
3292
+ decimals: z5.number(),
3293
+ balance: z5.string()
3294
+ })
3295
+ )
3296
+ })
1074
3297
  }),
1075
- env,
1076
- output: z.object({
1077
- address: z.string(),
1078
- positions: z.array(
1079
- z.object({
1080
- pool: z.string(),
1081
- gauge: z.string(),
1082
- rewardToken: z.string(),
1083
- staked: z.string(),
1084
- earned: z.string()
1085
- })
1086
- ),
1087
- count: z.number()
3298
+ examples: [{ description: "List all known vault relays on Abstract" }],
3299
+ async run(c) {
3300
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
3301
+ const snapshot = await readVaultSummary(client);
3302
+ return c.ok(snapshot);
3303
+ }
3304
+ });
3305
+ vaults.command("relay", {
3306
+ description: "Inspect one relay vault by address.",
3307
+ args: z5.object({
3308
+ relay: z5.string().describe("Relay vault contract address")
1088
3309
  }),
3310
+ env: env5,
3311
+ output: relayRowSchema,
1089
3312
  examples: [
1090
3313
  {
1091
- args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
1092
- description: "List gauge positions for a wallet"
3314
+ args: { relay: ABOREAN_VAULT_ADDRESSES.veAbxMaxiRelay },
3315
+ description: "Inspect the veABX maxi relay"
1093
3316
  }
1094
3317
  ],
1095
3318
  async run(c) {
1096
- const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1097
- const gaugePools = await discoverGaugePools(client);
1098
- if (!gaugePools.length) {
1099
- return c.ok({
1100
- address: toChecksum(c.args.address),
1101
- positions: [],
1102
- count: 0
3319
+ if (!isAddress4(c.args.relay)) {
3320
+ return c.error({
3321
+ code: "INVALID_ARGUMENT",
3322
+ message: "relay must be a valid address"
1103
3323
  });
1104
3324
  }
1105
- const positionData = await client.multicall({
1106
- allowFailure: false,
1107
- contracts: gaugePools.flatMap(({ gauge }) => [
1108
- {
1109
- abi: gaugeAbi,
1110
- address: gauge,
1111
- functionName: "balanceOf",
1112
- args: [c.args.address]
1113
- },
1114
- {
1115
- abi: gaugeAbi,
1116
- address: gauge,
1117
- functionName: "earned",
1118
- args: [c.args.address]
1119
- },
1120
- {
1121
- abi: gaugeAbi,
1122
- address: gauge,
1123
- functionName: "rewardToken"
1124
- }
1125
- ])
1126
- });
1127
- const positions = gaugePools.map(({ pool, gauge }, index) => {
1128
- const offset = index * 3;
1129
- const staked = positionData[offset];
1130
- const earned = positionData[offset + 1];
1131
- const rewardToken = positionData[offset + 2];
1132
- return {
1133
- pool: toChecksum(pool),
1134
- gauge: toChecksum(gauge),
1135
- rewardToken: toChecksum(rewardToken),
1136
- staked,
1137
- earned
1138
- };
1139
- }).filter((position) => position.staked > 0n || position.earned > 0n).map((position) => ({
1140
- pool: position.pool,
1141
- gauge: position.gauge,
1142
- rewardToken: position.rewardToken,
1143
- staked: position.staked.toString(),
1144
- earned: position.earned.toString()
1145
- }));
1146
- return c.ok({
1147
- address: toChecksum(c.args.address),
1148
- positions,
1149
- count: positions.length
1150
- });
3325
+ const relayAddress = checksumAddress5(c.args.relay);
3326
+ const known = KNOWN_RELAYS.find(
3327
+ (relay) => relay.relay.toLowerCase() === relayAddress.toLowerCase()
3328
+ );
3329
+ if (!known) {
3330
+ return c.error({
3331
+ code: "NOT_FOUND",
3332
+ message: `relay ${shortAddress4(relayAddress)} is not in the known Aborean vault set`
3333
+ });
3334
+ }
3335
+ const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
3336
+ const snapshot = await readRelaySnapshot(client, known);
3337
+ return c.ok(snapshot);
1151
3338
  }
1152
3339
  });
1153
3340
 
1154
3341
  // src/commands/ve.ts
1155
- import { Cli as Cli2, z as z2 } from "incur";
1156
- var env2 = z2.object({
1157
- ABSTRACT_RPC_URL: z2.string().optional().describe("Abstract RPC URL override")
3342
+ import { Cli as Cli6, z as z6 } from "incur";
3343
+ var env6 = z6.object({
3344
+ ABSTRACT_RPC_URL: z6.string().optional().describe("Abstract RPC URL override")
1158
3345
  });
1159
- var ve = Cli2.create("ve", {
3346
+ var ve = Cli6.create("ve", {
1160
3347
  description: "Inspect Aborean VotingEscrow (veABX) global and per-NFT lock state."
1161
3348
  });
1162
3349
  ve.command("stats", {
1163
3350
  description: "Get global VotingEscrow supply, locks, and decay checkpoint data.",
1164
- env: env2,
1165
- output: z2.object({
1166
- token: z2.string(),
1167
- totalVotingPower: z2.string(),
1168
- totalLocked: z2.string(),
1169
- permanentLocked: z2.string(),
1170
- epoch: z2.number(),
1171
- decayBias: z2.string(),
1172
- decaySlope: z2.string(),
1173
- lastCheckpointTimestamp: z2.number(),
1174
- lastCheckpointBlock: z2.number()
3351
+ env: env6,
3352
+ output: z6.object({
3353
+ token: z6.string(),
3354
+ totalVotingPower: z6.string(),
3355
+ totalLocked: z6.string(),
3356
+ permanentLocked: z6.string(),
3357
+ epoch: z6.number(),
3358
+ decayBias: z6.string(),
3359
+ decaySlope: z6.string(),
3360
+ lastCheckpointTimestamp: z6.number(),
3361
+ lastCheckpointBlock: z6.number()
1175
3362
  }),
1176
3363
  examples: [{ description: "Show global veABX state and decay metrics" }],
1177
3364
  async run(c) {
@@ -1224,17 +3411,17 @@ ve.command("stats", {
1224
3411
  });
1225
3412
  ve.command("lock", {
1226
3413
  description: "Get lock details and voting power for one veNFT token id.",
1227
- args: z2.object({
1228
- tokenId: z2.coerce.number().int().nonnegative().describe("veNFT token id")
3414
+ args: z6.object({
3415
+ tokenId: z6.coerce.number().int().nonnegative().describe("veNFT token id")
1229
3416
  }),
1230
- env: env2,
1231
- output: z2.object({
1232
- tokenId: z2.number(),
1233
- owner: z2.string(),
1234
- amount: z2.string(),
1235
- unlockTime: z2.number(),
1236
- isPermanent: z2.boolean(),
1237
- votingPower: z2.string()
3417
+ env: env6,
3418
+ output: z6.object({
3419
+ tokenId: z6.number(),
3420
+ owner: z6.string(),
3421
+ amount: z6.string(),
3422
+ unlockTime: z6.number(),
3423
+ isPermanent: z6.boolean(),
3424
+ votingPower: z6.string()
1238
3425
  }),
1239
3426
  examples: [{ args: { tokenId: 1 }, description: "Inspect lock details for veNFT #1" }],
1240
3427
  async run(c) {
@@ -1272,22 +3459,22 @@ ve.command("lock", {
1272
3459
  });
1273
3460
  ve.command("locks", {
1274
3461
  description: "List all veNFT locks owned by an address.",
1275
- args: z2.object({
1276
- address: z2.string().describe("Owner address")
3462
+ args: z6.object({
3463
+ address: z6.string().describe("Owner address")
1277
3464
  }),
1278
- env: env2,
1279
- output: z2.object({
1280
- address: z2.string(),
1281
- locks: z2.array(
1282
- z2.object({
1283
- tokenId: z2.string(),
1284
- amount: z2.string(),
1285
- unlockTime: z2.number(),
1286
- isPermanent: z2.boolean(),
1287
- votingPower: z2.string()
3465
+ env: env6,
3466
+ output: z6.object({
3467
+ address: z6.string(),
3468
+ locks: z6.array(
3469
+ z6.object({
3470
+ tokenId: z6.string(),
3471
+ amount: z6.string(),
3472
+ unlockTime: z6.number(),
3473
+ isPermanent: z6.boolean(),
3474
+ votingPower: z6.string()
1288
3475
  })
1289
3476
  ),
1290
- count: z2.number()
3477
+ count: z6.number()
1291
3478
  }),
1292
3479
  examples: [
1293
3480
  {
@@ -1359,13 +3546,13 @@ ve.command("locks", {
1359
3546
  });
1360
3547
  ve.command("voting-power", {
1361
3548
  description: "Get current voting power for one veNFT token id.",
1362
- args: z2.object({
1363
- tokenId: z2.coerce.number().int().nonnegative().describe("veNFT token id")
3549
+ args: z6.object({
3550
+ tokenId: z6.coerce.number().int().nonnegative().describe("veNFT token id")
1364
3551
  }),
1365
- env: env2,
1366
- output: z2.object({
1367
- tokenId: z2.number(),
1368
- votingPower: z2.string()
3552
+ env: env6,
3553
+ output: z6.object({
3554
+ tokenId: z6.number(),
3555
+ votingPower: z6.string()
1369
3556
  }),
1370
3557
  examples: [{ args: { tokenId: 1 }, description: "Get current voting power for veNFT #1" }],
1371
3558
  async run(c) {
@@ -1384,9 +3571,9 @@ ve.command("voting-power", {
1384
3571
  });
1385
3572
 
1386
3573
  // src/commands/voter.ts
1387
- import { Cli as Cli3, z as z3 } from "incur";
1388
- var env3 = z3.object({
1389
- ABSTRACT_RPC_URL: z3.string().optional().describe("Abstract RPC URL override")
3574
+ import { Cli as Cli7, z as z7 } from "incur";
3575
+ var env7 = z7.object({
3576
+ ABSTRACT_RPC_URL: z7.string().optional().describe("Abstract RPC URL override")
1390
3577
  });
1391
3578
  async function discoverPools(client) {
1392
3579
  const [v2PoolCount, clPoolCount] = await Promise.all([
@@ -1425,20 +3612,20 @@ async function discoverPools(client) {
1425
3612
  ]);
1426
3613
  return [...v2Pools, ...clPools];
1427
3614
  }
1428
- var voter = Cli3.create("voter", {
3615
+ var voter = Cli7.create("voter", {
1429
3616
  description: "Inspect Aborean voter epoch, pool weights, and claimable rewards context."
1430
3617
  });
1431
3618
  voter.command("epoch", {
1432
3619
  description: "Show current emissions epoch timing from Minter.",
1433
- env: env3,
1434
- output: z3.object({
1435
- activePeriod: z3.number(),
1436
- epochEnd: z3.number(),
1437
- secondsRemaining: z3.number(),
1438
- timeRemaining: z3.string(),
1439
- weekSeconds: z3.number(),
1440
- epochCount: z3.number(),
1441
- weeklyEmission: z3.string()
3620
+ env: env7,
3621
+ output: z7.object({
3622
+ activePeriod: z7.number(),
3623
+ epochEnd: z7.number(),
3624
+ secondsRemaining: z7.number(),
3625
+ timeRemaining: z7.string(),
3626
+ weekSeconds: z7.number(),
3627
+ epochCount: z7.number(),
3628
+ weeklyEmission: z7.string()
1442
3629
  }),
1443
3630
  examples: [{ description: "Inspect current voter epoch boundaries" }],
1444
3631
  async run(c) {
@@ -1480,23 +3667,23 @@ voter.command("epoch", {
1480
3667
  });
1481
3668
  voter.command("weights", {
1482
3669
  description: "Show current pool voting weight distribution.",
1483
- env: env3,
1484
- output: z3.object({
1485
- totalWeight: z3.string(),
1486
- pools: z3.array(
1487
- z3.object({
1488
- pool: z3.string(),
1489
- gauge: z3.string(),
1490
- weight: z3.string()
3670
+ env: env7,
3671
+ output: z7.object({
3672
+ totalWeight: z7.string(),
3673
+ pools: z7.array(
3674
+ z7.object({
3675
+ pool: z7.string(),
3676
+ gauge: z7.string(),
3677
+ weight: z7.string()
1491
3678
  })
1492
3679
  ),
1493
- count: z3.number()
3680
+ count: z7.number()
1494
3681
  }),
1495
3682
  examples: [{ description: "List all pools with non-zero voting weight" }],
1496
3683
  async run(c) {
1497
3684
  const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
1498
- const pools = await discoverPools(client);
1499
- if (!pools.length) {
3685
+ const pools2 = await discoverPools(client);
3686
+ if (!pools2.length) {
1500
3687
  return c.ok({ totalWeight: "0", pools: [], count: 0 });
1501
3688
  }
1502
3689
  const [totalWeight, poolData] = await Promise.all([
@@ -1507,7 +3694,7 @@ voter.command("weights", {
1507
3694
  }),
1508
3695
  client.multicall({
1509
3696
  allowFailure: false,
1510
- contracts: pools.flatMap((pool) => [
3697
+ contracts: pools2.flatMap((pool) => [
1511
3698
  {
1512
3699
  abi: voterAbi,
1513
3700
  address: ABOREAN_V2_ADDRESSES.voter,
@@ -1523,7 +3710,7 @@ voter.command("weights", {
1523
3710
  ])
1524
3711
  })
1525
3712
  ]);
1526
- const entries = pools.map((pool, index) => {
3713
+ const entries = pools2.map((pool, index) => {
1527
3714
  const offset = index * 2;
1528
3715
  const gauge = poolData[offset] ?? ZERO_ADDRESS;
1529
3716
  const weight = poolData[offset + 1] ?? 0n;
@@ -1548,19 +3735,19 @@ voter.command("weights", {
1548
3735
  });
1549
3736
  voter.command("rewards", {
1550
3737
  description: "Show claimable rebase rewards and voting context for a veNFT.",
1551
- args: z3.object({
1552
- tokenId: z3.coerce.number().int().nonnegative().describe("veNFT token id")
3738
+ args: z7.object({
3739
+ tokenId: z7.coerce.number().int().nonnegative().describe("veNFT token id")
1553
3740
  }),
1554
- env: env3,
1555
- output: z3.object({
1556
- tokenId: z3.number(),
1557
- rewardToken: z3.string(),
1558
- claimableRebase: z3.string(),
1559
- timeCursor: z3.number(),
1560
- lastTokenTime: z3.number(),
1561
- distributorStartTime: z3.number(),
1562
- usedWeight: z3.string(),
1563
- lastVoted: z3.number()
3741
+ env: env7,
3742
+ output: z7.object({
3743
+ tokenId: z7.number(),
3744
+ rewardToken: z7.string(),
3745
+ claimableRebase: z7.string(),
3746
+ timeCursor: z7.number(),
3747
+ lastTokenTime: z7.number(),
3748
+ distributorStartTime: z7.number(),
3749
+ usedWeight: z7.string(),
3750
+ lastVoted: z7.number()
1564
3751
  }),
1565
3752
  examples: [{ args: { tokenId: 1 }, description: "Check claimable voter/distributor rewards" }],
1566
3753
  async run(c) {
@@ -1629,22 +3816,22 @@ voter.command("rewards", {
1629
3816
  });
1630
3817
  voter.command("bribes", {
1631
3818
  description: "Show active bribe reward tokens and current-epoch amounts for a pool.",
1632
- args: z3.object({
1633
- pool: z3.string().describe("Pool address")
3819
+ args: z7.object({
3820
+ pool: z7.string().describe("Pool address")
1634
3821
  }),
1635
- env: env3,
1636
- output: z3.object({
1637
- pool: z3.string(),
1638
- gauge: z3.string(),
1639
- bribeContract: z3.string(),
1640
- epochStart: z3.number(),
1641
- rewardTokens: z3.array(
1642
- z3.object({
1643
- token: z3.string(),
1644
- epochAmount: z3.string()
3822
+ env: env7,
3823
+ output: z7.object({
3824
+ pool: z7.string(),
3825
+ gauge: z7.string(),
3826
+ bribeContract: z7.string(),
3827
+ epochStart: z7.number(),
3828
+ rewardTokens: z7.array(
3829
+ z7.object({
3830
+ token: z7.string(),
3831
+ epochAmount: z7.string()
1645
3832
  })
1646
3833
  ),
1647
- count: z3.number()
3834
+ count: z7.number()
1648
3835
  }),
1649
3836
  examples: [
1650
3837
  {
@@ -1870,26 +4057,283 @@ function applyFriendlyErrorHandling(cli2) {
1870
4057
  // src/cli.ts
1871
4058
  var __dirname = dirname(fileURLToPath(import.meta.url));
1872
4059
  var pkg = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf8"));
1873
- var cli = Cli4.create("aborean", {
4060
+ var cli = Cli8.create("aborean", {
1874
4061
  version: pkg.version,
1875
4062
  description: "Aborean Finance DEX CLI for Abstract chain."
1876
4063
  });
1877
4064
  cli.command(gauges);
4065
+ cli.command(pools);
1878
4066
  cli.command(ve);
1879
4067
  cli.command(voter);
1880
- var rootEnv = z4.object({
1881
- ABSTRACT_RPC_URL: z4.string().optional().describe("Abstract RPC URL override")
4068
+ cli.command(cl);
4069
+ cli.command(vaults);
4070
+ cli.command(lending);
4071
+ var rootEnv = z8.object({
4072
+ ABSTRACT_RPC_URL: z8.string().optional().describe("Abstract RPC URL override")
1882
4073
  });
4074
+ var erc20MetadataAbi4 = [
4075
+ {
4076
+ type: "function",
4077
+ name: "symbol",
4078
+ stateMutability: "view",
4079
+ inputs: [],
4080
+ outputs: [{ type: "string" }]
4081
+ },
4082
+ {
4083
+ type: "function",
4084
+ name: "decimals",
4085
+ stateMutability: "view",
4086
+ inputs: [],
4087
+ outputs: [{ type: "uint8" }]
4088
+ }
4089
+ ];
4090
+ var v2PoolLiteAbi = [
4091
+ {
4092
+ type: "function",
4093
+ name: "token0",
4094
+ stateMutability: "view",
4095
+ inputs: [],
4096
+ outputs: [{ type: "address" }]
4097
+ },
4098
+ {
4099
+ type: "function",
4100
+ name: "token1",
4101
+ stateMutability: "view",
4102
+ inputs: [],
4103
+ outputs: [{ type: "address" }]
4104
+ },
4105
+ {
4106
+ type: "function",
4107
+ name: "stable",
4108
+ stateMutability: "view",
4109
+ inputs: [],
4110
+ outputs: [{ type: "bool" }]
4111
+ },
4112
+ {
4113
+ type: "function",
4114
+ name: "getReserves",
4115
+ stateMutability: "view",
4116
+ inputs: [],
4117
+ outputs: [
4118
+ { type: "uint256", name: "reserve0" },
4119
+ { type: "uint256", name: "reserve1" },
4120
+ { type: "uint256", name: "blockTimestampLast" }
4121
+ ]
4122
+ }
4123
+ ];
4124
+ function finiteOrZero(value) {
4125
+ return Number.isFinite(value) ? value : 0;
4126
+ }
4127
+ function shortAddress5(address) {
4128
+ return `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
4129
+ }
4130
+ function normalizeReserveTuple(value) {
4131
+ if (Array.isArray(value)) {
4132
+ return {
4133
+ reserve0: BigInt(value[0]),
4134
+ reserve1: BigInt(value[1])
4135
+ };
4136
+ }
4137
+ const row = value;
4138
+ return {
4139
+ reserve0: BigInt(row.reserve0),
4140
+ reserve1: BigInt(row.reserve1)
4141
+ };
4142
+ }
4143
+ async function readTokenMetadata4(client, tokenAddresses) {
4144
+ const unique = [...new Set(tokenAddresses.map((address) => address.toLowerCase()))];
4145
+ if (!unique.length) {
4146
+ return /* @__PURE__ */ new Map();
4147
+ }
4148
+ const contracts = unique.flatMap((address) => [
4149
+ {
4150
+ abi: erc20MetadataAbi4,
4151
+ address,
4152
+ functionName: "symbol"
4153
+ },
4154
+ {
4155
+ abi: erc20MetadataAbi4,
4156
+ address,
4157
+ functionName: "decimals"
4158
+ }
4159
+ ]);
4160
+ const results = await client.multicall({
4161
+ allowFailure: true,
4162
+ contracts
4163
+ });
4164
+ const map = /* @__PURE__ */ new Map();
4165
+ for (let i = 0; i < unique.length; i += 1) {
4166
+ const address = checksumAddress6(unique[i]);
4167
+ const symbolResult = results[i * 2];
4168
+ const decimalsResult = results[i * 2 + 1];
4169
+ const symbol = symbolResult && symbolResult.status === "success" && typeof symbolResult.result === "string" ? symbolResult.result : shortAddress5(address);
4170
+ const decimals = decimalsResult && decimalsResult.status === "success" && typeof decimalsResult.result === "number" ? decimalsResult.result : 18;
4171
+ map.set(address.toLowerCase(), {
4172
+ address,
4173
+ symbol,
4174
+ decimals
4175
+ });
4176
+ }
4177
+ return map;
4178
+ }
4179
+ async function readTopV2PoolsSnapshot(client, limit) {
4180
+ const v2PoolCount = await client.readContract({
4181
+ abi: poolFactoryAbi,
4182
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
4183
+ functionName: "allPoolsLength"
4184
+ });
4185
+ if (v2PoolCount === 0n) {
4186
+ return { topPools: [], reserveUnitTvl: 0 };
4187
+ }
4188
+ const indices = Array.from({ length: Number(v2PoolCount) }, (_, i) => BigInt(i));
4189
+ const pools2 = await client.multicall({
4190
+ allowFailure: false,
4191
+ contracts: indices.map((index) => ({
4192
+ abi: poolFactoryAbi,
4193
+ address: ABOREAN_V2_ADDRESSES.poolFactory,
4194
+ functionName: "allPools",
4195
+ args: [index]
4196
+ }))
4197
+ });
4198
+ const poolData = await client.multicall({
4199
+ allowFailure: false,
4200
+ contracts: pools2.flatMap((pool) => [
4201
+ {
4202
+ abi: v2PoolLiteAbi,
4203
+ address: pool,
4204
+ functionName: "token0"
4205
+ },
4206
+ {
4207
+ abi: v2PoolLiteAbi,
4208
+ address: pool,
4209
+ functionName: "token1"
4210
+ },
4211
+ {
4212
+ abi: v2PoolLiteAbi,
4213
+ address: pool,
4214
+ functionName: "stable"
4215
+ },
4216
+ {
4217
+ abi: v2PoolLiteAbi,
4218
+ address: pool,
4219
+ functionName: "getReserves"
4220
+ }
4221
+ ])
4222
+ });
4223
+ const tokenAddresses = [];
4224
+ for (let i = 0; i < pools2.length; i += 1) {
4225
+ tokenAddresses.push(checksumAddress6(poolData[i * 4]));
4226
+ tokenAddresses.push(checksumAddress6(poolData[i * 4 + 1]));
4227
+ }
4228
+ const tokenMeta = await readTokenMetadata4(client, tokenAddresses);
4229
+ const rows = pools2.map((pool, index) => {
4230
+ const token0Address = checksumAddress6(poolData[index * 4]);
4231
+ const token1Address = checksumAddress6(poolData[index * 4 + 1]);
4232
+ const stable = Boolean(poolData[index * 4 + 2]);
4233
+ const reserves = normalizeReserveTuple(poolData[index * 4 + 3]);
4234
+ const token0 = tokenMeta.get(token0Address.toLowerCase()) ?? {
4235
+ address: token0Address,
4236
+ symbol: shortAddress5(token0Address),
4237
+ decimals: 18
4238
+ };
4239
+ const token1 = tokenMeta.get(token1Address.toLowerCase()) ?? {
4240
+ address: token1Address,
4241
+ symbol: shortAddress5(token1Address),
4242
+ decimals: 18
4243
+ };
4244
+ const reserve0Decimal = finiteOrZero(Number(formatUnits4(reserves.reserve0, token0.decimals)));
4245
+ const reserve1Decimal = finiteOrZero(Number(formatUnits4(reserves.reserve1, token1.decimals)));
4246
+ const tvlUnits = reserve0Decimal + reserve1Decimal;
4247
+ return {
4248
+ pool: checksumAddress6(pool),
4249
+ pair: `${token0.symbol}/${token1.symbol}`,
4250
+ poolType: stable ? "stable" : "volatile",
4251
+ token0: {
4252
+ address: checksumAddress6(token0.address),
4253
+ symbol: token0.symbol,
4254
+ decimals: token0.decimals
4255
+ },
4256
+ token1: {
4257
+ address: checksumAddress6(token1.address),
4258
+ symbol: token1.symbol,
4259
+ decimals: token1.decimals
4260
+ },
4261
+ reserves: {
4262
+ token0: formatUnits4(reserves.reserve0, token0.decimals),
4263
+ token1: formatUnits4(reserves.reserve1, token1.decimals)
4264
+ },
4265
+ tvlEstimateUnits: tvlUnits
4266
+ };
4267
+ });
4268
+ const reserveUnitTvl = rows.reduce((sum, row) => sum + Number(row.tvlEstimateUnits), 0);
4269
+ return {
4270
+ topPools: rows.sort((a, b) => Number(b.tvlEstimateUnits) - Number(a.tvlEstimateUnits)).slice(0, limit),
4271
+ reserveUnitTvl
4272
+ };
4273
+ }
1883
4274
  cli.command("status", {
1884
- description: "Get a cross-contract Aborean protocol snapshot (pool counts, gauge count, veABX supply).",
4275
+ description: "Cross-protocol Aborean snapshot (TVL estimates, epoch, top pools, ve lock, vaults, Morpho lending).",
1885
4276
  env: rootEnv,
1886
- output: z4.object({
1887
- v2PoolCount: z4.number().describe("Number of V2 AMM pools"),
1888
- clPoolCount: z4.number().describe("Number of Slipstream (CL) pools"),
1889
- gaugeCount: z4.number().describe("Number of pools with gauges"),
1890
- totalVotingWeight: z4.string().describe("Total voting weight (wei)"),
1891
- veABXTotalSupply: z4.string().describe("Total veABX supply (wei)"),
1892
- veABXLockedSupply: z4.string().describe("Total ABX locked in VotingEscrow (wei)")
4277
+ output: z8.object({
4278
+ v2PoolCount: z8.number().describe("Number of V2 AMM pools"),
4279
+ clPoolCount: z8.number().describe("Number of Slipstream (CL) pools"),
4280
+ gaugeCount: z8.number().describe("Number of pools with gauges"),
4281
+ totalVotingWeight: z8.string().describe("Total voting weight (wei)"),
4282
+ veABXTotalSupply: z8.string().describe("Total veABX supply (wei)"),
4283
+ veABXLockedSupply: z8.string().describe("Total ABX locked in VotingEscrow (wei)"),
4284
+ epoch: z8.object({
4285
+ activePeriod: z8.number(),
4286
+ epochEnd: z8.number(),
4287
+ secondsRemaining: z8.number(),
4288
+ epochCount: z8.number(),
4289
+ weeklyEmission: z8.string()
4290
+ }),
4291
+ topPools: z8.array(
4292
+ z8.object({
4293
+ pool: z8.string(),
4294
+ pair: z8.string(),
4295
+ poolType: z8.enum(["stable", "volatile"]),
4296
+ token0: z8.object({
4297
+ address: z8.string(),
4298
+ symbol: z8.string(),
4299
+ decimals: z8.number()
4300
+ }),
4301
+ token1: z8.object({
4302
+ address: z8.string(),
4303
+ symbol: z8.string(),
4304
+ decimals: z8.number()
4305
+ }),
4306
+ reserves: z8.object({
4307
+ token0: z8.string(),
4308
+ token1: z8.string()
4309
+ }),
4310
+ tvlEstimateUnits: z8.number()
4311
+ })
4312
+ ),
4313
+ tvl: z8.object({
4314
+ v2ReserveUnitEstimate: z8.number(),
4315
+ vaultManagedVotingPower: z8.string()
4316
+ }),
4317
+ vaults: z8.object({
4318
+ relayCount: z8.number(),
4319
+ managedVotingPower: z8.string(),
4320
+ note: z8.string().nullable()
4321
+ }),
4322
+ lending: z8.object({
4323
+ available: z8.boolean(),
4324
+ morpho: z8.string(),
4325
+ marketCount: z8.number(),
4326
+ supplyByLoanToken: z8.array(
4327
+ z8.object({
4328
+ token: z8.string(),
4329
+ symbol: z8.string(),
4330
+ decimals: z8.number(),
4331
+ totalSupplyAssets: z8.string(),
4332
+ totalBorrowAssets: z8.string()
4333
+ })
4334
+ ),
4335
+ note: z8.string().nullable()
4336
+ })
1893
4337
  }),
1894
4338
  examples: [{ description: "Fetch the current Aborean protocol status" }],
1895
4339
  async run(c) {
@@ -1900,7 +4344,12 @@ cli.command("status", {
1900
4344
  gaugeCount,
1901
4345
  totalVotingWeight,
1902
4346
  veABXTotalSupply,
1903
- veABXLockedSupply
4347
+ veABXLockedSupply,
4348
+ activePeriod,
4349
+ weekSeconds,
4350
+ epochCount,
4351
+ weeklyEmission,
4352
+ v2PoolSnapshot
1904
4353
  ] = await Promise.all([
1905
4354
  client.readContract({
1906
4355
  abi: poolFactoryAbi,
@@ -1931,15 +4380,87 @@ cli.command("status", {
1931
4380
  abi: votingEscrowAbi,
1932
4381
  address: ABOREAN_V2_ADDRESSES.votingEscrow,
1933
4382
  functionName: "supply"
1934
- })
4383
+ }),
4384
+ client.readContract({
4385
+ abi: minterAbi,
4386
+ address: ABOREAN_V2_ADDRESSES.minter,
4387
+ functionName: "activePeriod"
4388
+ }),
4389
+ client.readContract({
4390
+ abi: minterAbi,
4391
+ address: ABOREAN_V2_ADDRESSES.minter,
4392
+ functionName: "WEEK"
4393
+ }),
4394
+ client.readContract({
4395
+ abi: minterAbi,
4396
+ address: ABOREAN_V2_ADDRESSES.minter,
4397
+ functionName: "epochCount"
4398
+ }),
4399
+ client.readContract({
4400
+ abi: minterAbi,
4401
+ address: ABOREAN_V2_ADDRESSES.minter,
4402
+ functionName: "weekly"
4403
+ }),
4404
+ readTopV2PoolsSnapshot(client, 5)
1935
4405
  ]);
4406
+ let vaultRelayCount = 0;
4407
+ let vaultManagedVotingPower = "0";
4408
+ let vaultNote = null;
4409
+ try {
4410
+ const vaultSnapshot = await readVaultSummary(client);
4411
+ vaultRelayCount = vaultSnapshot.relayCount;
4412
+ vaultManagedVotingPower = vaultSnapshot.totals.managedVotingPower;
4413
+ } catch (error) {
4414
+ vaultNote = error instanceof Error ? error.message : "vault snapshot unavailable";
4415
+ }
4416
+ let lendingAvailable = false;
4417
+ let lendingMarketCount = 0;
4418
+ let lendingMorpho = "";
4419
+ let lendingSupplyByLoanToken = [];
4420
+ let lendingNote = null;
4421
+ try {
4422
+ const lendingSnapshot = await readLendingSummary(client);
4423
+ lendingAvailable = lendingSnapshot.available;
4424
+ lendingMarketCount = lendingSnapshot.marketCount;
4425
+ lendingMorpho = lendingSnapshot.morpho;
4426
+ lendingSupplyByLoanToken = lendingSnapshot.supplyByLoanToken;
4427
+ } catch (error) {
4428
+ lendingMorpho = "";
4429
+ lendingNote = error instanceof Error ? error.message : "lending snapshot unavailable";
4430
+ }
4431
+ const now = Math.floor(Date.now() / 1e3);
4432
+ const epochEnd = Number(activePeriod) + Number(weekSeconds);
1936
4433
  return c.ok({
1937
4434
  v2PoolCount: Number(v2PoolCount),
1938
4435
  clPoolCount: Number(clPoolCount),
1939
4436
  gaugeCount: Number(gaugeCount),
1940
4437
  totalVotingWeight: String(totalVotingWeight),
1941
4438
  veABXTotalSupply: String(veABXTotalSupply),
1942
- veABXLockedSupply: String(veABXLockedSupply)
4439
+ veABXLockedSupply: String(veABXLockedSupply),
4440
+ epoch: {
4441
+ activePeriod: Number(activePeriod),
4442
+ epochEnd,
4443
+ secondsRemaining: Math.max(0, epochEnd - now),
4444
+ epochCount: Number(epochCount),
4445
+ weeklyEmission: String(weeklyEmission)
4446
+ },
4447
+ topPools: v2PoolSnapshot.topPools,
4448
+ tvl: {
4449
+ v2ReserveUnitEstimate: v2PoolSnapshot.reserveUnitTvl,
4450
+ vaultManagedVotingPower
4451
+ },
4452
+ vaults: {
4453
+ relayCount: vaultRelayCount,
4454
+ managedVotingPower: vaultManagedVotingPower,
4455
+ note: vaultNote
4456
+ },
4457
+ lending: {
4458
+ available: lendingAvailable,
4459
+ morpho: lendingMorpho,
4460
+ marketCount: lendingMarketCount,
4461
+ supplyByLoanToken: lendingSupplyByLoanToken,
4462
+ note: lendingNote
4463
+ }
1943
4464
  });
1944
4465
  }
1945
4466
  });