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