@spectratools/aborean-cli 0.9.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 +1170 -20
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1991,12 +1991,496 @@ cl.command("swap", {
|
|
|
1991
1991
|
});
|
|
1992
1992
|
}
|
|
1993
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
|
+
});
|
|
1994
2425
|
|
|
1995
2426
|
// src/commands/gauges.ts
|
|
2427
|
+
import { isAddress as isAddress2 } from "@spectratools/cli-shared";
|
|
1996
2428
|
import { Cli as Cli2, z as z3 } from "incur";
|
|
1997
2429
|
var env2 = z3.object({
|
|
1998
2430
|
ABSTRACT_RPC_URL: z3.string().optional().describe("Abstract RPC URL override")
|
|
1999
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
|
+
];
|
|
2000
2484
|
async function discoverGaugePools(client) {
|
|
2001
2485
|
const [v2PoolCount, clPoolCount] = await Promise.all([
|
|
2002
2486
|
client.readContract({
|
|
@@ -2414,9 +2898,189 @@ gauges.command("staked", {
|
|
|
2414
2898
|
);
|
|
2415
2899
|
}
|
|
2416
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
|
+
});
|
|
2417
3081
|
|
|
2418
3082
|
// src/commands/lending.ts
|
|
2419
|
-
import { checksumAddress as checksumAddress3, isAddress as
|
|
3083
|
+
import { checksumAddress as checksumAddress3, isAddress as isAddress3 } from "@spectratools/cli-shared";
|
|
2420
3084
|
import { Cli as Cli3, z as z4 } from "incur";
|
|
2421
3085
|
import { formatUnits as formatUnits2 } from "viem";
|
|
2422
3086
|
var MORPHO_DEPLOY_BLOCK = 13947713n;
|
|
@@ -2921,7 +3585,7 @@ lending.command("position", {
|
|
|
2921
3585
|
message: "marketId must be a 32-byte hex string (0x + 64 hex chars)"
|
|
2922
3586
|
});
|
|
2923
3587
|
}
|
|
2924
|
-
if (!
|
|
3588
|
+
if (!isAddress3(c.args.user)) {
|
|
2925
3589
|
return c.error({
|
|
2926
3590
|
code: "INVALID_ARGUMENT",
|
|
2927
3591
|
message: "user must be a valid address"
|
|
@@ -3032,13 +3696,13 @@ lending.command("position", {
|
|
|
3032
3696
|
});
|
|
3033
3697
|
|
|
3034
3698
|
// src/commands/pools.ts
|
|
3035
|
-
import { checksumAddress as checksumAddress4, isAddress as
|
|
3699
|
+
import { checksumAddress as checksumAddress4, isAddress as isAddress4 } from "@spectratools/cli-shared";
|
|
3036
3700
|
import { Cli as Cli4, z as z5 } from "incur";
|
|
3037
3701
|
import { formatUnits as formatUnits3, parseUnits as parseUnits2 } from "viem";
|
|
3038
3702
|
var MULTICALL_BATCH_SIZE2 = 100;
|
|
3039
3703
|
var DEFAULT_SLIPPAGE_PERCENT2 = 0.5;
|
|
3040
3704
|
var DEFAULT_DEADLINE_SECONDS2 = 300;
|
|
3041
|
-
var
|
|
3705
|
+
var erc20ApproveAbi3 = [
|
|
3042
3706
|
{
|
|
3043
3707
|
type: "function",
|
|
3044
3708
|
name: "approve",
|
|
@@ -3092,7 +3756,7 @@ var tokenSchema2 = z5.object({
|
|
|
3092
3756
|
symbol: z5.string(),
|
|
3093
3757
|
decimals: z5.number()
|
|
3094
3758
|
});
|
|
3095
|
-
var
|
|
3759
|
+
var amountSchema2 = z5.object({
|
|
3096
3760
|
raw: z5.string(),
|
|
3097
3761
|
decimal: z5.string()
|
|
3098
3762
|
});
|
|
@@ -3108,8 +3772,8 @@ var poolSummarySchema = z5.object({
|
|
|
3108
3772
|
token0: tokenSchema2,
|
|
3109
3773
|
token1: tokenSchema2,
|
|
3110
3774
|
reserves: z5.object({
|
|
3111
|
-
token0:
|
|
3112
|
-
token1:
|
|
3775
|
+
token0: amountSchema2,
|
|
3776
|
+
token1: amountSchema2,
|
|
3113
3777
|
blockTimestampLast: z5.number()
|
|
3114
3778
|
}),
|
|
3115
3779
|
totalSupply: z5.string(),
|
|
@@ -3416,7 +4080,7 @@ pools.command("pool", {
|
|
|
3416
4080
|
})
|
|
3417
4081
|
}),
|
|
3418
4082
|
async run(c) {
|
|
3419
|
-
if (!
|
|
4083
|
+
if (!isAddress4(c.args.address)) {
|
|
3420
4084
|
return c.error({
|
|
3421
4085
|
code: "INVALID_ADDRESS",
|
|
3422
4086
|
message: `Invalid pool address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
|
|
@@ -3525,12 +4189,12 @@ pools.command("quote", {
|
|
|
3525
4189
|
stable: z5.boolean(),
|
|
3526
4190
|
tokenIn: tokenSchema2,
|
|
3527
4191
|
tokenOut: tokenSchema2,
|
|
3528
|
-
amountIn:
|
|
3529
|
-
amountOut:
|
|
4192
|
+
amountIn: amountSchema2,
|
|
4193
|
+
amountOut: amountSchema2,
|
|
3530
4194
|
priceOutPerIn: z5.number().nullable()
|
|
3531
4195
|
}),
|
|
3532
4196
|
async run(c) {
|
|
3533
|
-
if (!
|
|
4197
|
+
if (!isAddress4(c.args.tokenIn) || !isAddress4(c.args.tokenOut)) {
|
|
3534
4198
|
return c.error({
|
|
3535
4199
|
code: "INVALID_ADDRESS",
|
|
3536
4200
|
message: "tokenIn and tokenOut must both be valid 0x-prefixed 20-byte addresses."
|
|
@@ -3640,7 +4304,7 @@ pools.command("fees", {
|
|
|
3640
4304
|
volatileFee: feeSchema
|
|
3641
4305
|
}),
|
|
3642
4306
|
async run(c) {
|
|
3643
|
-
if (!
|
|
4307
|
+
if (!isAddress4(c.args.pool)) {
|
|
3644
4308
|
return c.error({
|
|
3645
4309
|
code: "INVALID_ADDRESS",
|
|
3646
4310
|
message: `Invalid pool address: "${c.args.pool}". Use a valid 0x-prefixed 20-byte hex address.`
|
|
@@ -3723,9 +4387,9 @@ pools.command("swap", {
|
|
|
3723
4387
|
stable: z5.boolean(),
|
|
3724
4388
|
tokenIn: tokenSchema2,
|
|
3725
4389
|
tokenOut: tokenSchema2,
|
|
3726
|
-
amountIn:
|
|
3727
|
-
expectedAmountOut:
|
|
3728
|
-
minAmountOut:
|
|
4390
|
+
amountIn: amountSchema2,
|
|
4391
|
+
expectedAmountOut: amountSchema2,
|
|
4392
|
+
minAmountOut: amountSchema2,
|
|
3729
4393
|
slippagePercent: z5.number(),
|
|
3730
4394
|
effectivePrice: z5.number().nullable(),
|
|
3731
4395
|
tx: z5.union([
|
|
@@ -3744,7 +4408,7 @@ pools.command("swap", {
|
|
|
3744
4408
|
async run(c) {
|
|
3745
4409
|
const tokenInRaw = c.options["token-in"];
|
|
3746
4410
|
const tokenOutRaw = c.options["token-out"];
|
|
3747
|
-
if (!
|
|
4411
|
+
if (!isAddress4(tokenInRaw) || !isAddress4(tokenOutRaw)) {
|
|
3748
4412
|
return c.error({
|
|
3749
4413
|
code: "INVALID_ADDRESS",
|
|
3750
4414
|
message: "token-in and token-out must both be valid 0x-prefixed 20-byte addresses."
|
|
@@ -3804,7 +4468,7 @@ pools.command("swap", {
|
|
|
3804
4468
|
const account = resolveAccount(c.env);
|
|
3805
4469
|
if (!c.options["dry-run"]) {
|
|
3806
4470
|
const currentAllowance = await client.readContract({
|
|
3807
|
-
abi:
|
|
4471
|
+
abi: erc20ApproveAbi3,
|
|
3808
4472
|
address: tokenIn,
|
|
3809
4473
|
functionName: "allowance",
|
|
3810
4474
|
args: [account.address, ABOREAN_V2_ADDRESSES.router]
|
|
@@ -3814,7 +4478,7 @@ pools.command("swap", {
|
|
|
3814
4478
|
env: c.env,
|
|
3815
4479
|
options: { ...c.options, "dry-run": false },
|
|
3816
4480
|
address: tokenIn,
|
|
3817
|
-
abi:
|
|
4481
|
+
abi: erc20ApproveAbi3,
|
|
3818
4482
|
functionName: "approve",
|
|
3819
4483
|
args: [ABOREAN_V2_ADDRESSES.router, amountInRaw]
|
|
3820
4484
|
});
|
|
@@ -3868,9 +4532,348 @@ pools.command("swap", {
|
|
|
3868
4532
|
});
|
|
3869
4533
|
}
|
|
3870
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;
|
|
4775
|
+
try {
|
|
4776
|
+
liquidityAmount = BigInt(c.options.liquidity);
|
|
4777
|
+
} catch {
|
|
4778
|
+
return c.error({
|
|
4779
|
+
code: "INVALID_AMOUNT",
|
|
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."
|
|
4787
|
+
});
|
|
4788
|
+
}
|
|
4789
|
+
const client = createAboreanPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
4790
|
+
const poolAddress = await client.readContract({
|
|
4791
|
+
abi: poolFactoryAbi,
|
|
4792
|
+
address: ABOREAN_V2_ADDRESSES.poolFactory,
|
|
4793
|
+
functionName: "getPool",
|
|
4794
|
+
args: [tokenA, tokenB, c.options.stable]
|
|
4795
|
+
});
|
|
4796
|
+
if (poolAddress.toLowerCase() === ZERO_ADDRESS.toLowerCase()) {
|
|
4797
|
+
return c.error({
|
|
4798
|
+
code: "POOL_NOT_FOUND",
|
|
4799
|
+
message: `No ${c.options.stable ? "stable" : "volatile"} V2 pool found for pair ${checksumAddress4(tokenA)}/${checksumAddress4(tokenB)}.`
|
|
4800
|
+
});
|
|
4801
|
+
}
|
|
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,
|
|
4838
|
+
address: ABOREAN_V2_ADDRESSES.router,
|
|
4839
|
+
abi: v2RouterRemoveLiquidityAbi,
|
|
4840
|
+
functionName: "removeLiquidity",
|
|
4841
|
+
args: [
|
|
4842
|
+
tokenA,
|
|
4843
|
+
tokenB,
|
|
4844
|
+
c.options.stable,
|
|
4845
|
+
liquidityAmount,
|
|
4846
|
+
amountAMin,
|
|
4847
|
+
amountBMin,
|
|
4848
|
+
account.address,
|
|
4849
|
+
deadlineTimestamp
|
|
4850
|
+
]
|
|
4851
|
+
});
|
|
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)
|
|
4860
|
+
},
|
|
4861
|
+
amountAMin: {
|
|
4862
|
+
raw: amountAMin.toString(),
|
|
4863
|
+
decimal: formatUnits3(amountAMin, aMeta.decimals)
|
|
4864
|
+
},
|
|
4865
|
+
amountBMin: {
|
|
4866
|
+
raw: amountBMin.toString(),
|
|
4867
|
+
decimal: formatUnits3(amountBMin, bMeta.decimals)
|
|
4868
|
+
},
|
|
4869
|
+
slippagePercent: c.options.slippage,
|
|
4870
|
+
tx
|
|
4871
|
+
});
|
|
4872
|
+
}
|
|
4873
|
+
});
|
|
3871
4874
|
|
|
3872
4875
|
// src/commands/vaults.ts
|
|
3873
|
-
import { checksumAddress as checksumAddress5, isAddress as
|
|
4876
|
+
import { checksumAddress as checksumAddress5, isAddress as isAddress5 } from "@spectratools/cli-shared";
|
|
3874
4877
|
import { Cli as Cli5, z as z6 } from "incur";
|
|
3875
4878
|
var env5 = z6.object({
|
|
3876
4879
|
ABSTRACT_RPC_URL: z6.string().optional().describe("Abstract RPC URL override")
|
|
@@ -4139,7 +5142,7 @@ vaults.command("relay", {
|
|
|
4139
5142
|
}
|
|
4140
5143
|
],
|
|
4141
5144
|
async run(c) {
|
|
4142
|
-
if (!
|
|
5145
|
+
if (!isAddress5(c.args.relay)) {
|
|
4143
5146
|
return c.error({
|
|
4144
5147
|
code: "INVALID_ARGUMENT",
|
|
4145
5148
|
message: "relay must be a valid address"
|
|
@@ -4488,7 +5491,21 @@ ve.command("voting-power", {
|
|
|
4488
5491
|
});
|
|
4489
5492
|
|
|
4490
5493
|
// src/commands/voter.ts
|
|
5494
|
+
import { isAddress as isAddress6 } from "@spectratools/cli-shared";
|
|
4491
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
|
+
];
|
|
4492
5509
|
var env7 = z8.object({
|
|
4493
5510
|
ABSTRACT_RPC_URL: z8.string().optional().describe("Abstract RPC URL override")
|
|
4494
5511
|
});
|
|
@@ -4917,6 +5934,139 @@ voter.command("bribes", {
|
|
|
4917
5934
|
);
|
|
4918
5935
|
}
|
|
4919
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
|
+
});
|
|
4920
6070
|
|
|
4921
6071
|
// src/error-handling.ts
|
|
4922
6072
|
import { AsyncLocalStorage } from "async_hooks";
|