@ilalv3/cli 0.2.7 → 0.2.8

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,25 @@ 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
+ }
106
125
  // ─── Shared core ──────────────────────────────────────────────────────────────
107
126
  async function executeLiquidity(action, opts) {
108
127
  const cfg = withConfig(opts);
@@ -156,7 +175,7 @@ async function executeLiquidity(action, opts) {
156
175
  die("liquidity must be greater than 0. No approval or liquidity transaction was sent.");
157
176
  }
158
177
  const preflightSpin = new Spinner("Running preflight checks…").start();
159
- const [root, verifier, eas, valid, tokenId, sym0, sym1, bal0, bal1] = await Promise.all([
178
+ const [root, verifier, eas, valid, tokenId, sym0, sym1, dec0, dec1, bal0, bal1] = await Promise.all([
160
179
  pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "merkleRoot" }),
161
180
  pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "zkVerifier" }),
162
181
  pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "eas" }),
@@ -164,6 +183,8 @@ async function executeLiquidity(action, opts) {
164
183
  pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "credentialOf", args: [account.address] }),
165
184
  pubClient.readContract({ address: c0, abi: ERC20_ABI, functionName: "symbol" }),
166
185
  pubClient.readContract({ address: c1, abi: ERC20_ABI, functionName: "symbol" }),
186
+ pubClient.readContract({ address: c0, abi: ERC20_ABI, functionName: "decimals" }),
187
+ pubClient.readContract({ address: c1, abi: ERC20_ABI, functionName: "decimals" }),
167
188
  pubClient.readContract({ address: c0, abi: ERC20_ABI, functionName: "balanceOf", args: [account.address] }),
168
189
  pubClient.readContract({ address: c1, abi: ERC20_ABI, functionName: "balanceOf", args: [account.address] }),
169
190
  ]);
@@ -183,8 +204,24 @@ async function executeLiquidity(action, opts) {
183
204
  else if (!valid)
184
205
  preflightErrors.push("wallet CNF credential exists but is not valid.");
185
206
  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.`);
207
+ preflightErrors.push(`token balances are not ready for adding liquidity: ${sym0}=${tokenAmount(bal0, dec0, sym0)}, ${sym1}=${tokenAmount(bal1, dec1, sym1)}.`);
187
208
  }
209
+ log.section("Preflight Checks");
210
+ if (tokenId !== 0n && valid)
211
+ log.ok(`CNF credential token #${tokenId.toString()}`);
212
+ else
213
+ log.fail("CNF credential missing or invalid");
214
+ log.ok(`Issuer config (${hasEASPath ? "EAS" : "no EAS"}${hasZKPath ? " + ZK" : ""})`);
215
+ if (action !== "add" || bal0 > 0n)
216
+ log.ok(`${sym0} balance ${tokenAmount(bal0, dec0, sym0)}`);
217
+ else
218
+ log.fail(`${sym0} balance ${tokenAmount(bal0, dec0, sym0)}`);
219
+ if (action !== "add" || bal1 > 0n)
220
+ log.ok(`${sym1} balance ${tokenAmount(bal1, dec1, sym1)}`);
221
+ else
222
+ log.fail(`${sym1} balance ${tokenAmount(bal1, dec1, sym1)}`);
223
+ log.ok(`Route bound to router ${fmt.addr(cfg.router)} and hook ${fmt.addr(cfg.hook)}`);
224
+ log.line();
188
225
  if (preflightErrors.length > 0) {
189
226
  log.section("Preflight Failed");
190
227
  for (const error of preflightErrors)
@@ -194,9 +231,10 @@ async function executeLiquidity(action, opts) {
194
231
  }
195
232
  // Approve both tokens if adding liquidity
196
233
  if (action === "add") {
197
- const MAX = 2n ** 256n - 1n;
234
+ const MAX = MAX_UINT256;
198
235
  for (const token of [c0, c1]) {
199
236
  const sym = token.toLowerCase() === c0.toLowerCase() ? sym0 : sym1;
237
+ const decimals = token.toLowerCase() === c0.toLowerCase() ? dec0 : dec1;
200
238
  const allowed = await pubClient.readContract({
201
239
  address: token, abi: ERC20_ABI, functionName: "allowance",
202
240
  args: [account.address, cfg.router],
@@ -210,6 +248,9 @@ async function executeLiquidity(action, opts) {
210
248
  await pubClient.waitForTransactionReceipt({ hash: h });
211
249
  appSpin.succeed(`Approved ${sym} ${fmt.gray(fmt.hash(h))}`);
212
250
  }
251
+ else {
252
+ log.ok(`${sym} allowance: ${allowanceLabel(allowed, decimals, sym)}`);
253
+ }
213
254
  }
214
255
  }
215
256
  // Sign session token
@@ -243,7 +284,7 @@ async function executeLiquidity(action, opts) {
243
284
  message: token,
244
285
  });
245
286
  const hookData = encodeAbiParameters(HOOK_DATA_ABI, [token, signature]);
246
- signSpin.succeed(`Session signed (expires in ${ttl}s)`);
287
+ signSpin.succeed(`Session authorization signed (expires in ${ttl}s, one-time nonce)`);
247
288
  log.section("Gate Checks");
248
289
  log.kv("credential", `${fmt.badge("required", "cyan")} issuer ${fmt.addr(cfg.issuer)}`);
249
290
  log.kv("caller", `${fmt.badge("bound", "green")} ${fmt.addr(cfg.router)}`);
@@ -261,20 +302,30 @@ async function executeLiquidity(action, opts) {
261
302
  const liquidityParams = { tickLower, tickUpper, liquidityDelta, salt };
262
303
  const fnName = action === "add" ? "addLiquidity" : "removeLiquidity";
263
304
  // Execute
264
- const txSpin = new Spinner(`Sending ${fnName}…`).start();
305
+ const txSpin = new Spinner(`Submitting ${fnName} tx…`).start();
265
306
  let txHash;
307
+ let receipt;
266
308
  try {
309
+ const startMs = Date.now();
267
310
  const baseArgs = [poolKey, liquidityParams, hookData];
268
311
  txHash = await (action === "add"
269
312
  ? walClient.writeContract({ address: cfg.router, abi: ROUTER_LIQUIDITY_ABI, functionName: "addLiquidity", args: baseArgs, value: 0n })
270
313
  : 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 });
314
+ txSpin.succeed(`Submitted to mempool ${fmt.gray(fmt.hash(txHash))}`);
315
+ const confirmSpin = new Spinner(`Confirming ${fmt.gray(fmt.hash(txHash))}…`).start();
316
+ receipt = await pubClient.waitForTransactionReceipt({ hash: txHash });
273
317
  if (receipt.status !== "success") {
274
- txSpin.fail("Transaction reverted");
318
+ confirmSpin.fail("Transaction reverted");
275
319
  die(`Tx failed: ${txHash}`);
276
320
  }
277
- txSpin.succeed(fmt.bold(fmt.green(`Liquidity ${action === "add" ? "added" : "removed"} via ILAL channel ✓`)));
321
+ confirmSpin.succeed(`Confirmed in block ${receipt.blockNumber.toString()}`);
322
+ const effectiveGasPrice = receipt.effectiveGasPrice;
323
+ log.metrics([
324
+ { label: "finality", value: secondsSince(startMs), tone: "green" },
325
+ { label: "gas used", value: receipt.gasUsed.toString(), tone: "cyan" },
326
+ ...(effectiveGasPrice ? [{ label: "gas cost", value: `${trimDecimals(formatEther(receipt.gasUsed * effectiveGasPrice), 8)} ETH`, tone: "cyan" }] : []),
327
+ ]);
328
+ log.ok(fmt.bold(fmt.green(`Liquidity ${action === "add" ? "added" : "removed"} via ILAL channel`)));
278
329
  }
279
330
  catch (e) {
280
331
  txSpin.fail(`${fnName} failed`);
@@ -283,7 +334,7 @@ async function executeLiquidity(action, opts) {
283
334
  log.line();
284
335
  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
336
  log.kv("tx", fmt.gray(txHash));
286
- log.kv("block", fmt.gray((await pubClient.getTransactionReceipt({ hash: txHash })).blockNumber.toString()));
337
+ log.kv("block", fmt.gray((receipt ?? await pubClient.getTransactionReceipt({ hash: txHash })).blockNumber.toString()));
287
338
  const explorer = txUrl(chain, txHash);
288
339
  if (explorer)
289
340
  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.8")
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
@@ -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.8",
4
4
  "description": "ILAL Protocol CLI — compliant swaps and credential management for Uniswap v4",
5
5
  "type": "module",
6
6
  "bin": {