@ilalv3/cli 0.2.7 → 0.2.9
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/commands/liquidity.js +67 -12
- package/dist/commands/swap.d.ts +1 -0
- package/dist/commands/swap.js +64 -14
- package/dist/index.js +4 -3
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* --router 0xROUTER --hook 0xHOOK --issuer 0xISSUER \
|
|
14
14
|
* --pool-id 0xPOOLID --token-a 0xTOKA --token-b 0xTOKB
|
|
15
15
|
*/
|
|
16
|
-
import { createPublicClient, createWalletClient, encodeAbiParameters, http, isAddress, isHex, parseAbiParameters, } from "viem";
|
|
16
|
+
import { createPublicClient, createWalletClient, encodeAbiParameters, formatEther, formatUnits, http, isAddress, isHex, parseAbiParameters, } from "viem";
|
|
17
17
|
import { privateKeyToAccount } from "viem/accounts";
|
|
18
18
|
import { base, baseSepolia } from "viem/chains";
|
|
19
19
|
import { fmt, log, header, Spinner, die, dieOnContract } from "../ui.js";
|
|
@@ -103,6 +103,28 @@ function txUrl(chain, hash) {
|
|
|
103
103
|
return baseUrl ? `${baseUrl}/tx/${hash}` : undefined;
|
|
104
104
|
}
|
|
105
105
|
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
106
|
+
const MAX_UINT256 = 2n ** 256n - 1n;
|
|
107
|
+
function trimDecimals(value, places = 8) {
|
|
108
|
+
const [whole, frac] = value.split(".");
|
|
109
|
+
if (!frac)
|
|
110
|
+
return whole ?? value;
|
|
111
|
+
const trimmed = frac.slice(0, places).replace(/0+$/, "");
|
|
112
|
+
return trimmed ? `${whole}.${trimmed}` : whole ?? value;
|
|
113
|
+
}
|
|
114
|
+
function tokenAmount(raw, decimals, symbol, places = 8) {
|
|
115
|
+
return `${trimDecimals(formatUnits(raw, decimals), places)} ${symbol}`;
|
|
116
|
+
}
|
|
117
|
+
function allowanceLabel(raw, decimals, symbol) {
|
|
118
|
+
if (raw >= MAX_UINT256 / 2n)
|
|
119
|
+
return "unlimited (MAX)";
|
|
120
|
+
return `${tokenAmount(raw, decimals, symbol)} (${raw.toString()} wei)`;
|
|
121
|
+
}
|
|
122
|
+
function secondsSince(startMs) {
|
|
123
|
+
return `${((Date.now() - startMs) / 1000).toFixed(1)}s`;
|
|
124
|
+
}
|
|
125
|
+
function defaultUserSalt(address) {
|
|
126
|
+
return `0x000000000000000000000000${address.slice(2)}`;
|
|
127
|
+
}
|
|
106
128
|
// ─── Shared core ──────────────────────────────────────────────────────────────
|
|
107
129
|
async function executeLiquidity(action, opts) {
|
|
108
130
|
const cfg = withConfig(opts);
|
|
@@ -142,7 +164,7 @@ async function executeLiquidity(action, opts) {
|
|
|
142
164
|
const liquidity = BigInt(opts.liquidity);
|
|
143
165
|
const fee = parseInt(cfg.fee ?? "3000");
|
|
144
166
|
const tickSpacing = parseInt(cfg.tickSpacing ?? "60");
|
|
145
|
-
const salt = (opts.salt ??
|
|
167
|
+
const salt = (opts.salt ?? defaultUserSalt(account.address));
|
|
146
168
|
const verb = action === "add" ? "Add" : "Remove";
|
|
147
169
|
header(`ILAL ${verb} Liquidity`, chain.name);
|
|
148
170
|
log.kv("router", fmt.cyan(cfg.router));
|
|
@@ -151,12 +173,13 @@ async function executeLiquidity(action, opts) {
|
|
|
151
173
|
log.kv("tickLower", tickLower.toString());
|
|
152
174
|
log.kv("tickUpper", tickUpper.toString());
|
|
153
175
|
log.kv("liquidity", liquidity.toString());
|
|
176
|
+
log.kv("salt", opts.salt ? fmt.hash(salt) : `${fmt.hash(salt)} ${fmt.gray("user-scoped default")}`);
|
|
154
177
|
log.line();
|
|
155
178
|
if (liquidity <= 0n) {
|
|
156
179
|
die("liquidity must be greater than 0. No approval or liquidity transaction was sent.");
|
|
157
180
|
}
|
|
158
181
|
const preflightSpin = new Spinner("Running preflight checks…").start();
|
|
159
|
-
const [root, verifier, eas, valid, tokenId, sym0, sym1, bal0, bal1] = await Promise.all([
|
|
182
|
+
const [root, verifier, eas, valid, tokenId, sym0, sym1, dec0, dec1, bal0, bal1] = await Promise.all([
|
|
160
183
|
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "merkleRoot" }),
|
|
161
184
|
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "zkVerifier" }),
|
|
162
185
|
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "eas" }),
|
|
@@ -164,6 +187,8 @@ async function executeLiquidity(action, opts) {
|
|
|
164
187
|
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "credentialOf", args: [account.address] }),
|
|
165
188
|
pubClient.readContract({ address: c0, abi: ERC20_ABI, functionName: "symbol" }),
|
|
166
189
|
pubClient.readContract({ address: c1, abi: ERC20_ABI, functionName: "symbol" }),
|
|
190
|
+
pubClient.readContract({ address: c0, abi: ERC20_ABI, functionName: "decimals" }),
|
|
191
|
+
pubClient.readContract({ address: c1, abi: ERC20_ABI, functionName: "decimals" }),
|
|
167
192
|
pubClient.readContract({ address: c0, abi: ERC20_ABI, functionName: "balanceOf", args: [account.address] }),
|
|
168
193
|
pubClient.readContract({ address: c1, abi: ERC20_ABI, functionName: "balanceOf", args: [account.address] }),
|
|
169
194
|
]);
|
|
@@ -183,8 +208,24 @@ async function executeLiquidity(action, opts) {
|
|
|
183
208
|
else if (!valid)
|
|
184
209
|
preflightErrors.push("wallet CNF credential exists but is not valid.");
|
|
185
210
|
if (action === "add" && (bal0 === 0n || bal1 === 0n)) {
|
|
186
|
-
preflightErrors.push(`token balances are not ready for adding liquidity: ${sym0}=${bal0
|
|
211
|
+
preflightErrors.push(`token balances are not ready for adding liquidity: ${sym0}=${tokenAmount(bal0, dec0, sym0)}, ${sym1}=${tokenAmount(bal1, dec1, sym1)}.`);
|
|
187
212
|
}
|
|
213
|
+
log.section("Preflight Checks");
|
|
214
|
+
if (tokenId !== 0n && valid)
|
|
215
|
+
log.ok(`CNF credential token #${tokenId.toString()}`);
|
|
216
|
+
else
|
|
217
|
+
log.fail("CNF credential missing or invalid");
|
|
218
|
+
log.ok(`Issuer config (${hasEASPath ? "EAS" : "no EAS"}${hasZKPath ? " + ZK" : ""})`);
|
|
219
|
+
if (action !== "add" || bal0 > 0n)
|
|
220
|
+
log.ok(`${sym0} balance ${tokenAmount(bal0, dec0, sym0)}`);
|
|
221
|
+
else
|
|
222
|
+
log.fail(`${sym0} balance ${tokenAmount(bal0, dec0, sym0)}`);
|
|
223
|
+
if (action !== "add" || bal1 > 0n)
|
|
224
|
+
log.ok(`${sym1} balance ${tokenAmount(bal1, dec1, sym1)}`);
|
|
225
|
+
else
|
|
226
|
+
log.fail(`${sym1} balance ${tokenAmount(bal1, dec1, sym1)}`);
|
|
227
|
+
log.ok(`Route bound to router ${fmt.addr(cfg.router)} and hook ${fmt.addr(cfg.hook)}`);
|
|
228
|
+
log.line();
|
|
188
229
|
if (preflightErrors.length > 0) {
|
|
189
230
|
log.section("Preflight Failed");
|
|
190
231
|
for (const error of preflightErrors)
|
|
@@ -194,9 +235,10 @@ async function executeLiquidity(action, opts) {
|
|
|
194
235
|
}
|
|
195
236
|
// Approve both tokens if adding liquidity
|
|
196
237
|
if (action === "add") {
|
|
197
|
-
const MAX =
|
|
238
|
+
const MAX = MAX_UINT256;
|
|
198
239
|
for (const token of [c0, c1]) {
|
|
199
240
|
const sym = token.toLowerCase() === c0.toLowerCase() ? sym0 : sym1;
|
|
241
|
+
const decimals = token.toLowerCase() === c0.toLowerCase() ? dec0 : dec1;
|
|
200
242
|
const allowed = await pubClient.readContract({
|
|
201
243
|
address: token, abi: ERC20_ABI, functionName: "allowance",
|
|
202
244
|
args: [account.address, cfg.router],
|
|
@@ -210,6 +252,9 @@ async function executeLiquidity(action, opts) {
|
|
|
210
252
|
await pubClient.waitForTransactionReceipt({ hash: h });
|
|
211
253
|
appSpin.succeed(`Approved ${sym} ${fmt.gray(fmt.hash(h))}`);
|
|
212
254
|
}
|
|
255
|
+
else {
|
|
256
|
+
log.ok(`${sym} allowance: ${allowanceLabel(allowed, decimals, sym)}`);
|
|
257
|
+
}
|
|
213
258
|
}
|
|
214
259
|
}
|
|
215
260
|
// Sign session token
|
|
@@ -243,7 +288,7 @@ async function executeLiquidity(action, opts) {
|
|
|
243
288
|
message: token,
|
|
244
289
|
});
|
|
245
290
|
const hookData = encodeAbiParameters(HOOK_DATA_ABI, [token, signature]);
|
|
246
|
-
signSpin.succeed(`Session signed (expires in ${ttl}s)`);
|
|
291
|
+
signSpin.succeed(`Session authorization signed (expires in ${ttl}s, one-time nonce)`);
|
|
247
292
|
log.section("Gate Checks");
|
|
248
293
|
log.kv("credential", `${fmt.badge("required", "cyan")} issuer ${fmt.addr(cfg.issuer)}`);
|
|
249
294
|
log.kv("caller", `${fmt.badge("bound", "green")} ${fmt.addr(cfg.router)}`);
|
|
@@ -261,20 +306,30 @@ async function executeLiquidity(action, opts) {
|
|
|
261
306
|
const liquidityParams = { tickLower, tickUpper, liquidityDelta, salt };
|
|
262
307
|
const fnName = action === "add" ? "addLiquidity" : "removeLiquidity";
|
|
263
308
|
// Execute
|
|
264
|
-
const txSpin = new Spinner(`
|
|
309
|
+
const txSpin = new Spinner(`Submitting ${fnName} tx…`).start();
|
|
265
310
|
let txHash;
|
|
311
|
+
let receipt;
|
|
266
312
|
try {
|
|
313
|
+
const startMs = Date.now();
|
|
267
314
|
const baseArgs = [poolKey, liquidityParams, hookData];
|
|
268
315
|
txHash = await (action === "add"
|
|
269
316
|
? walClient.writeContract({ address: cfg.router, abi: ROUTER_LIQUIDITY_ABI, functionName: "addLiquidity", args: baseArgs, value: 0n })
|
|
270
317
|
: walClient.writeContract({ address: cfg.router, abi: ROUTER_LIQUIDITY_ABI, functionName: "removeLiquidity", args: baseArgs }));
|
|
271
|
-
txSpin.
|
|
272
|
-
const
|
|
318
|
+
txSpin.succeed(`Submitted to mempool ${fmt.gray(fmt.hash(txHash))}`);
|
|
319
|
+
const confirmSpin = new Spinner(`Confirming ${fmt.gray(fmt.hash(txHash))}…`).start();
|
|
320
|
+
receipt = await pubClient.waitForTransactionReceipt({ hash: txHash });
|
|
273
321
|
if (receipt.status !== "success") {
|
|
274
|
-
|
|
322
|
+
confirmSpin.fail("Transaction reverted");
|
|
275
323
|
die(`Tx failed: ${txHash}`);
|
|
276
324
|
}
|
|
277
|
-
|
|
325
|
+
confirmSpin.succeed(`Confirmed in block ${receipt.blockNumber.toString()}`);
|
|
326
|
+
const effectiveGasPrice = receipt.effectiveGasPrice;
|
|
327
|
+
log.metrics([
|
|
328
|
+
{ label: "finality", value: secondsSince(startMs), tone: "green" },
|
|
329
|
+
{ label: "gas used", value: receipt.gasUsed.toString(), tone: "cyan" },
|
|
330
|
+
...(effectiveGasPrice ? [{ label: "gas cost", value: `${trimDecimals(formatEther(receipt.gasUsed * effectiveGasPrice), 8)} ETH`, tone: "cyan" }] : []),
|
|
331
|
+
]);
|
|
332
|
+
log.ok(fmt.bold(fmt.green(`Liquidity ${action === "add" ? "added" : "removed"} via ILAL channel`)));
|
|
278
333
|
}
|
|
279
334
|
catch (e) {
|
|
280
335
|
txSpin.fail(`${fnName} failed`);
|
|
@@ -283,7 +338,7 @@ async function executeLiquidity(action, opts) {
|
|
|
283
338
|
log.line();
|
|
284
339
|
log.callout(action === "add" ? "Hook-enforced liquidity add" : "Hook-enforced liquidity removal", "pool policy, credential type, session binding, and nonce all passed on-chain", "green");
|
|
285
340
|
log.kv("tx", fmt.gray(txHash));
|
|
286
|
-
log.kv("block", fmt.gray((await pubClient.getTransactionReceipt({ hash: txHash })).blockNumber.toString()));
|
|
341
|
+
log.kv("block", fmt.gray((receipt ?? await pubClient.getTransactionReceipt({ hash: txHash })).blockNumber.toString()));
|
|
287
342
|
const explorer = txUrl(chain, txHash);
|
|
288
343
|
if (explorer)
|
|
289
344
|
log.kv("explorer", fmt.cyan(explorer));
|
package/dist/commands/swap.d.ts
CHANGED
package/dist/commands/swap.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* --pool-id 0xPOOLID \
|
|
19
19
|
* --chain 84532
|
|
20
20
|
*/
|
|
21
|
-
import { createPublicClient, createWalletClient, decodeAbiParameters, encodeAbiParameters, http, isAddress, isHex, parseAbiParameters, parseUnits, } from "viem";
|
|
21
|
+
import { createPublicClient, createWalletClient, decodeAbiParameters, encodeAbiParameters, formatEther, formatUnits, http, isAddress, isHex, parseAbiParameters, parseUnits, } from "viem";
|
|
22
22
|
import { privateKeyToAccount } from "viem/accounts";
|
|
23
23
|
import { base, baseSepolia } from "viem/chains";
|
|
24
24
|
import { fmt, log, header, Spinner, die, dieOnContract } from "../ui.js";
|
|
@@ -85,13 +85,14 @@ const MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970341n; // MA
|
|
|
85
85
|
const DYNAMIC_FEE_FLAG = 8388608;
|
|
86
86
|
const PIPS_DENOMINATOR = 1000000n;
|
|
87
87
|
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
88
|
+
const MAX_UINT256 = 2n ** 256n - 1n;
|
|
88
89
|
function txUrl(chain, hash) {
|
|
89
90
|
const baseUrl = chain.blockExplorers?.default?.url;
|
|
90
91
|
return baseUrl ? `${baseUrl}/tx/${hash}` : undefined;
|
|
91
92
|
}
|
|
92
93
|
function feeLabel(fee) {
|
|
93
94
|
if (fee === DYNAMIC_FEE_FLAG)
|
|
94
|
-
return `${fmt.badge("
|
|
95
|
+
return `${fmt.badge("verified", "green")} 0.05% (vs 0.30% standard pool)`;
|
|
95
96
|
return `${fmt.badge("static", "gray")} ${(fee / 10_000).toFixed(4).replace(/0+$/, "").replace(/\.$/, "")}%`;
|
|
96
97
|
}
|
|
97
98
|
function pipsToPercent(pips) {
|
|
@@ -102,6 +103,27 @@ function poolFeePercent(fee) {
|
|
|
102
103
|
? "0.05%"
|
|
103
104
|
: `${(fee / 10_000).toFixed(4).replace(/0+$/, "").replace(/\.$/, "")}%`;
|
|
104
105
|
}
|
|
106
|
+
function trimDecimals(value, places = 8) {
|
|
107
|
+
const [whole, frac] = value.split(".");
|
|
108
|
+
if (!frac)
|
|
109
|
+
return whole ?? value;
|
|
110
|
+
const trimmed = frac.slice(0, places).replace(/0+$/, "");
|
|
111
|
+
return trimmed ? `${whole}.${trimmed}` : whole ?? value;
|
|
112
|
+
}
|
|
113
|
+
function tokenAmount(raw, decimals, symbol, places = 8) {
|
|
114
|
+
return `${trimDecimals(formatUnits(raw, decimals), places)} ${symbol}`;
|
|
115
|
+
}
|
|
116
|
+
function tokenAmountWithWei(raw, decimals, symbol, places = 8) {
|
|
117
|
+
return `${tokenAmount(raw, decimals, symbol, places)} (${raw.toString()} wei)`;
|
|
118
|
+
}
|
|
119
|
+
function allowanceLabel(raw, decimals, symbol) {
|
|
120
|
+
if (raw >= MAX_UINT256 / 2n)
|
|
121
|
+
return "unlimited (MAX)";
|
|
122
|
+
return tokenAmountWithWei(raw, decimals, symbol);
|
|
123
|
+
}
|
|
124
|
+
function secondsSince(startMs) {
|
|
125
|
+
return `${((Date.now() - startMs) / 1000).toFixed(1)}s`;
|
|
126
|
+
}
|
|
105
127
|
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
106
128
|
export async function swap(opts) {
|
|
107
129
|
const cfg = withConfig(opts);
|
|
@@ -159,7 +181,7 @@ export async function swap(opts) {
|
|
|
159
181
|
if (amountIn <= 0n) {
|
|
160
182
|
die("amount-in must be greater than 0. Use `ilal swap --simulate` for a dry run.");
|
|
161
183
|
}
|
|
162
|
-
log.kv("amount", `${
|
|
184
|
+
log.kv("amount", `${fmt.cyan(tokenAmountWithWei(amountIn, decimals, symbol))}`);
|
|
163
185
|
let protocolFeePips = 0;
|
|
164
186
|
let treasury;
|
|
165
187
|
try {
|
|
@@ -198,7 +220,23 @@ export async function swap(opts) {
|
|
|
198
220
|
else if (!valid)
|
|
199
221
|
preflightErrors.push("wallet CNF credential exists but is not valid.");
|
|
200
222
|
if (balance < totalDebit)
|
|
201
|
-
preflightErrors.push(`insufficient ${symbol} balance: need ${totalDebit
|
|
223
|
+
preflightErrors.push(`insufficient ${symbol} balance: need ${tokenAmountWithWei(totalDebit, decimals, symbol)} including ILAL fee, have ${tokenAmountWithWei(balance, decimals, symbol)}.`);
|
|
224
|
+
log.section("Preflight Checks");
|
|
225
|
+
if (tokenId !== 0n && valid)
|
|
226
|
+
log.ok(`CNF credential token #${tokenId.toString()}`);
|
|
227
|
+
else
|
|
228
|
+
log.fail("CNF credential missing or invalid");
|
|
229
|
+
log.ok(`Issuer config (${hasEASPath ? "EAS" : "no EAS"}${hasZKPath ? " + ZK" : ""})`);
|
|
230
|
+
if (balance >= totalDebit)
|
|
231
|
+
log.ok(`Wallet balance ${tokenAmount(balance, decimals, symbol)}`);
|
|
232
|
+
else
|
|
233
|
+
log.fail(`Wallet balance ${tokenAmount(balance, decimals, symbol)}`);
|
|
234
|
+
log.ok(`Route bound to router ${fmt.addr(cfg.router)} and hook ${fmt.addr(cfg.hook)}`);
|
|
235
|
+
if (opts.explain) {
|
|
236
|
+
log.info("CNF proves this wallet is allowed to access the pool without revealing identity data.");
|
|
237
|
+
log.info("Caller binding means the signed authorization can only be used through the ILALRouter.");
|
|
238
|
+
}
|
|
239
|
+
log.line();
|
|
202
240
|
if (preflightErrors.length > 0) {
|
|
203
241
|
log.section("Preflight Failed");
|
|
204
242
|
for (const error of preflightErrors)
|
|
@@ -210,7 +248,7 @@ export async function swap(opts) {
|
|
|
210
248
|
}
|
|
211
249
|
log.deal([
|
|
212
250
|
{ label: "verified input", value: `${opts.amountIn} ${symbol}`, note: "exact-in swap", tone: "cyan" },
|
|
213
|
-
{ label: "LP fee", value: poolFeePercent(parseInt(cfg.fee ?? "3000")), note: "
|
|
251
|
+
{ label: "LP fee", value: poolFeePercent(parseInt(cfg.fee ?? "3000")), note: parseInt(cfg.fee ?? "3000") === DYNAMIC_FEE_FLAG ? "6× cheaper than 0.30% standard" : "pool fee", tone: "green" },
|
|
214
252
|
{ label: "ILAL fee", value: protocolFeePips > 0 ? pipsToPercent(protocolFeePips) : "off", note: protocolFeePips > 0 ? "protocol revenue" : "legacy router", tone: protocolFeePips > 0 ? "cyan" : "gray" },
|
|
215
253
|
]);
|
|
216
254
|
log.line();
|
|
@@ -286,10 +324,12 @@ export async function swap(opts) {
|
|
|
286
324
|
log.kv("credential", `${fmt.badge("required", "cyan")} issuer ${fmt.addr(cfg.issuer)}`);
|
|
287
325
|
log.kv("caller", `${fmt.badge("bound", "green")} ${fmt.addr(cfg.router)}`);
|
|
288
326
|
log.kv("nonce", `${opts.hookData ? fmt.badge("external", "cyan") : fmt.badge("fresh", "green")} ${fmt.hash(sessionNonce)}`);
|
|
327
|
+
if (opts.explain)
|
|
328
|
+
log.kvdim("", "↳ unique one-time session ID; prevents replay attacks");
|
|
289
329
|
log.kv("fee", feeLabel(fee));
|
|
290
330
|
if (protocolFeePips > 0) {
|
|
291
331
|
log.kv("protocol fee", `${fmt.badge("ILAL", "cyan")} ${pipsToPercent(protocolFeePips)} to ${treasury ? fmt.addr(treasury) : "treasury"}`);
|
|
292
|
-
log.kv("total debit", `${totalDebit
|
|
332
|
+
log.kv("total debit", `${tokenAmountWithWei(totalDebit, decimals, symbol)} input + ILAL fee`);
|
|
293
333
|
}
|
|
294
334
|
log.line();
|
|
295
335
|
if (opts.simulate) {
|
|
@@ -315,10 +355,10 @@ export async function swap(opts) {
|
|
|
315
355
|
args: [cfg.router, totalDebit * 10n], // approve 10× for future swaps
|
|
316
356
|
});
|
|
317
357
|
await pubClient.waitForTransactionReceipt({ hash: approveHash });
|
|
318
|
-
approveSpin.succeed(`Approved ${symbol} ${fmt.gray(fmt.hash(approveHash))}`);
|
|
358
|
+
approveSpin.succeed(`Approved ${tokenAmount(totalDebit * 10n, decimals, symbol)} ${fmt.gray(fmt.hash(approveHash))}`);
|
|
319
359
|
}
|
|
320
360
|
else {
|
|
321
|
-
approveSpin.succeed(`Allowance
|
|
361
|
+
approveSpin.succeed(`Allowance: ${allowanceLabel(allowed, decimals, symbol)}`);
|
|
322
362
|
}
|
|
323
363
|
// Build PoolKey
|
|
324
364
|
const poolKey = {
|
|
@@ -341,9 +381,11 @@ export async function swap(opts) {
|
|
|
341
381
|
log.kv("min-amount-out", `${fmt.cyan(minAmountOut.toString())} wei (slippage protection on)`);
|
|
342
382
|
}
|
|
343
383
|
// Execute swap
|
|
344
|
-
const txSpin = new Spinner("
|
|
384
|
+
const txSpin = new Spinner("Submitting swap tx…").start();
|
|
345
385
|
let txHash;
|
|
386
|
+
let receipt;
|
|
346
387
|
try {
|
|
388
|
+
const startMs = Date.now();
|
|
347
389
|
txHash = await walClient.writeContract({
|
|
348
390
|
address: cfg.router,
|
|
349
391
|
abi: ROUTER_ABI,
|
|
@@ -351,13 +393,21 @@ export async function swap(opts) {
|
|
|
351
393
|
args: [poolKey, swapParams, minAmountOut, hookData],
|
|
352
394
|
value: 0n,
|
|
353
395
|
});
|
|
354
|
-
txSpin.
|
|
355
|
-
const
|
|
396
|
+
txSpin.succeed(`Submitted to mempool ${fmt.gray(fmt.hash(txHash))}`);
|
|
397
|
+
const confirmSpin = new Spinner(`Confirming ${fmt.gray(fmt.hash(txHash))}…`).start();
|
|
398
|
+
receipt = await pubClient.waitForTransactionReceipt({ hash: txHash });
|
|
356
399
|
if (receipt.status !== "success") {
|
|
357
|
-
|
|
400
|
+
confirmSpin.fail("Transaction reverted");
|
|
358
401
|
die(`Tx failed: ${txHash}`);
|
|
359
402
|
}
|
|
360
|
-
|
|
403
|
+
confirmSpin.succeed(`Confirmed in block ${receipt.blockNumber.toString()}`);
|
|
404
|
+
const effectiveGasPrice = receipt.effectiveGasPrice;
|
|
405
|
+
log.metrics([
|
|
406
|
+
{ label: "finality", value: secondsSince(startMs), tone: "green" },
|
|
407
|
+
{ label: "gas used", value: receipt.gasUsed.toString(), tone: "cyan" },
|
|
408
|
+
...(effectiveGasPrice ? [{ label: "gas cost", value: `${trimDecimals(formatEther(receipt.gasUsed * effectiveGasPrice), 8)} ETH`, tone: "cyan" }] : []),
|
|
409
|
+
]);
|
|
410
|
+
log.ok(fmt.bold(fmt.green("Swap executed via ILAL channel")));
|
|
361
411
|
}
|
|
362
412
|
catch (e) {
|
|
363
413
|
txSpin.fail("Swap failed");
|
|
@@ -366,7 +416,7 @@ export async function swap(opts) {
|
|
|
366
416
|
log.line();
|
|
367
417
|
log.callout("Hook-enforced swap", "credential, session, caller binding, and nonce all passed on-chain", "green");
|
|
368
418
|
log.kv("tx", fmt.gray(txHash));
|
|
369
|
-
log.kv("block", fmt.gray((await pubClient.getTransactionReceipt({ hash: txHash })).blockNumber.toString()));
|
|
419
|
+
log.kv("block", fmt.gray((receipt ?? await pubClient.getTransactionReceipt({ hash: txHash })).blockNumber.toString()));
|
|
370
420
|
const explorer = txUrl(chain, txHash);
|
|
371
421
|
if (explorer)
|
|
372
422
|
log.kv("explorer", fmt.cyan(explorer));
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ const program = new Command();
|
|
|
19
19
|
program
|
|
20
20
|
.name("ilal")
|
|
21
21
|
.description("ILAL Protocol CLI — Uniswap v4 compliance hook toolkit")
|
|
22
|
-
.version("0.2.
|
|
22
|
+
.version("0.2.9")
|
|
23
23
|
.addHelpText("before", `\n ${fmt.bold(fmt.cyan("◆"))} ${fmt.bold("ILAL Protocol")} ${fmt.gray("Uniswap v4 Compliance Hook")}\n`);
|
|
24
24
|
// ─── init ─────────────────────────────────────────────────────────────────────
|
|
25
25
|
program
|
|
@@ -228,7 +228,7 @@ pool
|
|
|
228
228
|
.requiredOption("--tick-lower <int24>", "Lower tick of position")
|
|
229
229
|
.requiredOption("--tick-upper <int24>", "Upper tick of position")
|
|
230
230
|
.requiredOption("--liquidity <uint128>", "Liquidity amount to add (in raw units)")
|
|
231
|
-
.option("--salt <bytes32>", "Position salt for multiple positions at the same range
|
|
231
|
+
.option("--salt <bytes32>", "Position salt for multiple positions at the same range (defaults to user-scoped salt)")
|
|
232
232
|
.option("--pool-id <bytes32>", "Pool ID (or set in .ilal.json)")
|
|
233
233
|
.option("--router <address>", "ILALRouter address (or set in .ilal.json)")
|
|
234
234
|
.option("-H, --hook <address>", "ComplianceHook address (or set in .ilal.json)")
|
|
@@ -250,7 +250,7 @@ pool
|
|
|
250
250
|
.requiredOption("--tick-lower <int24>", "Lower tick of position")
|
|
251
251
|
.requiredOption("--tick-upper <int24>", "Upper tick of position")
|
|
252
252
|
.requiredOption("--liquidity <uint128>", "Liquidity amount to remove (in raw units)")
|
|
253
|
-
.option("--salt <bytes32>", "Position salt
|
|
253
|
+
.option("--salt <bytes32>", "Position salt (defaults to user-scoped salt)")
|
|
254
254
|
.option("--pool-id <bytes32>", "Pool ID (or set in .ilal.json)")
|
|
255
255
|
.option("--router <address>", "ILALRouter address (or set in .ilal.json)")
|
|
256
256
|
.option("-H, --hook <address>", "ComplianceHook address (or set in .ilal.json)")
|
|
@@ -286,6 +286,7 @@ program
|
|
|
286
286
|
.option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
|
|
287
287
|
.option("--ttl <seconds>", "Session token lifetime in seconds", "600")
|
|
288
288
|
.option("--hook-data <hex>", "Use externally signed one-time hookData instead of signing inside swap")
|
|
289
|
+
.option("--explain", "Show inline explanations for gate checks and session fields", false)
|
|
289
290
|
.option("--simulate", "Sign session without sending tx", false)
|
|
290
291
|
.action(async (opts) => {
|
|
291
292
|
await swap(opts).catch(err);
|