@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.
@@ -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 ?? "0x0000000000000000000000000000000000000000000000000000000000000000");
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.toString()} wei, ${sym1}=${bal1.toString()} wei.`);
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 = 2n ** 256n - 1n;
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(`Sending ${fnName}…`).start();
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.update(`Confirming ${fmt.gray(fmt.hash(txHash))}…`);
272
- const receipt = await pubClient.waitForTransactionReceipt({ hash: txHash });
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
- txSpin.fail("Transaction reverted");
322
+ confirmSpin.fail("Transaction reverted");
275
323
  die(`Tx failed: ${txHash}`);
276
324
  }
277
- txSpin.succeed(fmt.bold(fmt.green(`Liquidity ${action === "add" ? "added" : "removed"} via ILAL channel ✓`)));
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));
@@ -37,4 +37,5 @@ export declare function swap(opts: {
37
37
  ttl?: string;
38
38
  hookData?: string;
39
39
  simulate?: boolean;
40
+ explain?: boolean;
40
41
  }): Promise<void>;
@@ -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("fair flow", "green")} verified swap fee 0.05%`;
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", `${opts.amountIn} ${fmt.cyan(symbol)} (${amountIn.toString()} wei)`);
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.toString()} wei including ILAL fee, have ${balance.toString()} wei.`);
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: "hook-priced flow", tone: "green" },
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.toString()} wei (${symbol} input + ILAL fee)`);
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 ok (${fmt.gray(allowed.toString())} wei)`);
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("Sending swap tx…").start();
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.update(`Confirming ${fmt.gray(fmt.hash(txHash))}…`);
355
- const receipt = await pubClient.waitForTransactionReceipt({ hash: txHash });
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
- txSpin.fail("Transaction reverted");
400
+ confirmSpin.fail("Transaction reverted");
358
401
  die(`Tx failed: ${txHash}`);
359
402
  }
360
- txSpin.succeed(fmt.bold(fmt.green(`Swap executed via ILAL channel ✓`)));
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.7")
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", "0x0000000000000000000000000000000000000000000000000000000000000000")
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", "0x0000000000000000000000000000000000000000000000000000000000000000")
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilalv3/cli",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "ILAL Protocol CLI — compliant swaps and credential management for Uniswap v4",
5
5
  "type": "module",
6
6
  "bin": {