@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.
- package/dist/cli.js +2788 -267
- 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 {
|
|
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/
|
|
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/
|
|
765
|
-
|
|
766
|
-
var
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
|
775
|
-
return Number(value);
|
|
1134
|
+
function finiteOrNull(value) {
|
|
1135
|
+
return Number.isFinite(value) ? value : null;
|
|
776
1136
|
}
|
|
777
|
-
function
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
|
788
|
-
|
|
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
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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:
|
|
814
|
-
|
|
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
|
|
831
|
-
if (!
|
|
1660
|
+
const pools2 = [...v2Pools, ...clPools];
|
|
1661
|
+
if (!pools2.length) return [];
|
|
832
1662
|
const gauges2 = await client.multicall({
|
|
833
1663
|
allowFailure: false,
|
|
834
|
-
contracts:
|
|
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
|
|
1671
|
+
return pools2.map((pool, index) => ({ pool, gauge: gauges2[index] })).filter(({ gauge }) => gauge.toLowerCase() !== ZERO_ADDRESS.toLowerCase());
|
|
842
1672
|
}
|
|
843
|
-
var 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:
|
|
850
|
-
gauges:
|
|
851
|
-
|
|
852
|
-
pool:
|
|
853
|
-
gauge:
|
|
854
|
-
rewardToken:
|
|
855
|
-
rewardRate:
|
|
856
|
-
totalStaked:
|
|
857
|
-
claimableEmissions:
|
|
858
|
-
periodFinish:
|
|
859
|
-
periodFinishRelative:
|
|
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:
|
|
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:
|
|
929
|
-
gauge:
|
|
1758
|
+
args: z2.object({
|
|
1759
|
+
gauge: z2.string().describe("Gauge contract address")
|
|
930
1760
|
}),
|
|
931
|
-
env,
|
|
932
|
-
output:
|
|
933
|
-
gauge:
|
|
934
|
-
pool:
|
|
935
|
-
isAlive:
|
|
936
|
-
stakingToken:
|
|
937
|
-
rewardToken:
|
|
938
|
-
totalStaked:
|
|
939
|
-
rewardRate:
|
|
940
|
-
rewardPerTokenStored:
|
|
941
|
-
fees0:
|
|
942
|
-
fees1:
|
|
943
|
-
left:
|
|
944
|
-
periodFinish:
|
|
945
|
-
periodFinishRelative:
|
|
946
|
-
lastUpdateTime:
|
|
947
|
-
bribeContract:
|
|
948
|
-
feeContract:
|
|
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:
|
|
1073
|
-
address:
|
|
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
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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: {
|
|
1092
|
-
description: "
|
|
3314
|
+
args: { relay: ABOREAN_VAULT_ADDRESSES.veAbxMaxiRelay },
|
|
3315
|
+
description: "Inspect the veABX maxi relay"
|
|
1093
3316
|
}
|
|
1094
3317
|
],
|
|
1095
3318
|
async run(c) {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
|
1156
|
-
var
|
|
1157
|
-
ABSTRACT_RPC_URL:
|
|
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 =
|
|
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:
|
|
1165
|
-
output:
|
|
1166
|
-
token:
|
|
1167
|
-
totalVotingPower:
|
|
1168
|
-
totalLocked:
|
|
1169
|
-
permanentLocked:
|
|
1170
|
-
epoch:
|
|
1171
|
-
decayBias:
|
|
1172
|
-
decaySlope:
|
|
1173
|
-
lastCheckpointTimestamp:
|
|
1174
|
-
lastCheckpointBlock:
|
|
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:
|
|
1228
|
-
tokenId:
|
|
3414
|
+
args: z6.object({
|
|
3415
|
+
tokenId: z6.coerce.number().int().nonnegative().describe("veNFT token id")
|
|
1229
3416
|
}),
|
|
1230
|
-
env:
|
|
1231
|
-
output:
|
|
1232
|
-
tokenId:
|
|
1233
|
-
owner:
|
|
1234
|
-
amount:
|
|
1235
|
-
unlockTime:
|
|
1236
|
-
isPermanent:
|
|
1237
|
-
votingPower:
|
|
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:
|
|
1276
|
-
address:
|
|
3462
|
+
args: z6.object({
|
|
3463
|
+
address: z6.string().describe("Owner address")
|
|
1277
3464
|
}),
|
|
1278
|
-
env:
|
|
1279
|
-
output:
|
|
1280
|
-
address:
|
|
1281
|
-
locks:
|
|
1282
|
-
|
|
1283
|
-
tokenId:
|
|
1284
|
-
amount:
|
|
1285
|
-
unlockTime:
|
|
1286
|
-
isPermanent:
|
|
1287
|
-
votingPower:
|
|
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:
|
|
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:
|
|
1363
|
-
tokenId:
|
|
3549
|
+
args: z6.object({
|
|
3550
|
+
tokenId: z6.coerce.number().int().nonnegative().describe("veNFT token id")
|
|
1364
3551
|
}),
|
|
1365
|
-
env:
|
|
1366
|
-
output:
|
|
1367
|
-
tokenId:
|
|
1368
|
-
votingPower:
|
|
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
|
|
1388
|
-
var
|
|
1389
|
-
ABSTRACT_RPC_URL:
|
|
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 =
|
|
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:
|
|
1434
|
-
output:
|
|
1435
|
-
activePeriod:
|
|
1436
|
-
epochEnd:
|
|
1437
|
-
secondsRemaining:
|
|
1438
|
-
timeRemaining:
|
|
1439
|
-
weekSeconds:
|
|
1440
|
-
epochCount:
|
|
1441
|
-
weeklyEmission:
|
|
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:
|
|
1484
|
-
output:
|
|
1485
|
-
totalWeight:
|
|
1486
|
-
pools:
|
|
1487
|
-
|
|
1488
|
-
pool:
|
|
1489
|
-
gauge:
|
|
1490
|
-
weight:
|
|
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:
|
|
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
|
|
1499
|
-
if (!
|
|
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:
|
|
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 =
|
|
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:
|
|
1552
|
-
tokenId:
|
|
3738
|
+
args: z7.object({
|
|
3739
|
+
tokenId: z7.coerce.number().int().nonnegative().describe("veNFT token id")
|
|
1553
3740
|
}),
|
|
1554
|
-
env:
|
|
1555
|
-
output:
|
|
1556
|
-
tokenId:
|
|
1557
|
-
rewardToken:
|
|
1558
|
-
claimableRebase:
|
|
1559
|
-
timeCursor:
|
|
1560
|
-
lastTokenTime:
|
|
1561
|
-
distributorStartTime:
|
|
1562
|
-
usedWeight:
|
|
1563
|
-
lastVoted:
|
|
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:
|
|
1633
|
-
pool:
|
|
3819
|
+
args: z7.object({
|
|
3820
|
+
pool: z7.string().describe("Pool address")
|
|
1634
3821
|
}),
|
|
1635
|
-
env:
|
|
1636
|
-
output:
|
|
1637
|
-
pool:
|
|
1638
|
-
gauge:
|
|
1639
|
-
bribeContract:
|
|
1640
|
-
epochStart:
|
|
1641
|
-
rewardTokens:
|
|
1642
|
-
|
|
1643
|
-
token:
|
|
1644
|
-
epochAmount:
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
1881
|
-
|
|
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: "
|
|
4275
|
+
description: "Cross-protocol Aborean snapshot (TVL estimates, epoch, top pools, ve lock, vaults, Morpho lending).",
|
|
1885
4276
|
env: rootEnv,
|
|
1886
|
-
output:
|
|
1887
|
-
v2PoolCount:
|
|
1888
|
-
clPoolCount:
|
|
1889
|
-
gaugeCount:
|
|
1890
|
-
totalVotingWeight:
|
|
1891
|
-
veABXTotalSupply:
|
|
1892
|
-
veABXLockedSupply:
|
|
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
|
});
|