@ilalv3/cli 0.2.4 → 0.2.6

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/README.md CHANGED
@@ -34,7 +34,7 @@ ilal status --wallet 0xc0807D4778a9E5FE15ad68A8500e64d65BA78D58
34
34
  ilal demo check --wallet 0xc0807D4778a9E5FE15ad68A8500e64d65BA78D58
35
35
 
36
36
  # 4. Execute a compliant swap with the seeded reviewer key
37
- PRIVATE_KEY=0x... ilal swap --amount-in 1 --token-in 0x582362E608F36850F6f641510d5D19C1EaB4cb27 --min-amount-out 0
37
+ PRIVATE_KEY=0x... ilal swap --amount-in 1 --token-in 0x3a7d58fAc623B4C30D7735B01DcE036EfF46e079 --min-amount-out 0
38
38
  ```
39
39
 
40
40
  For a fully seeded local/testnet demo, deploy mock EAS + demo pool pieces:
@@ -76,8 +76,22 @@ PRIVATE_KEY=0x... ilal demo faucet --wallet 0xYourWallet
76
76
 
77
77
  ### Path B — ZK proof (privacy-preserving)
78
78
 
79
+ Operator prepares the active Merkle root. For a fresh demo deployment this root
80
+ can be passed into the CNFIssuer constructor as `INITIAL_MERKLE_ROOT`, avoiding a
81
+ 48-hour wait while still keeping future root updates timelocked.
82
+
83
+ ```bash
84
+ ilal credential zk-root \
85
+ --wallet 0xYourWallet \
86
+ --expires-at 1800000000
87
+ ```
88
+
89
+ Trader proves against the same expiry:
90
+
79
91
  ```bash
80
- PRIVATE_KEY=0x... ilal credential prove --wallet 0xYourWallet
92
+ PRIVATE_KEY=0x... ilal credential prove \
93
+ --wallet 0xYourWallet \
94
+ --expires-at 1800000000
81
95
  ```
82
96
 
83
97
  Generates a Groth16 proof locally (~5s), verifies it on-chain, and mints/renews your CNF without revealing identity. If the Merkle root does not match, the issuer/operator must queue the updated root with `ilal oracle propose-root --root <newRoot>` and activate it after the timelock.
@@ -88,6 +102,7 @@ Generates a Groth16 proof locally (~5s), verifies it on-chain, and mints/renews
88
102
  |---|---|
89
103
  | `ilal init` | Create `.ilal.json` with contract addresses |
90
104
  | `ilal status` | Dashboard: credential · issuer config · pool policy |
105
+ | `ilal credential zk-root` | Operator helper: compute the ZK Merkle root for a demo wallet/expiry |
91
106
  | `ilal credential prove` | Trader flow: local ZK proof → mint or renew CNF |
92
107
  | `ilal credential mint` | Mint CNF via Coinbase EAS attestation |
93
108
  | `ilal credential renew` | Renew CNF via EAS attestation |
@@ -115,10 +130,10 @@ The CLI reads `.ilal.json` in the current directory. Run `ilal init` to create i
115
130
 
116
131
  ```bash
117
132
  ilal swap \
118
- --router 0x7727F0f3EBe99A558487394D001950ee6B33BB86 \
119
- --hook 0xF5066ad9c25F3f54cfb19609A60187C48C184A80 \
120
- --issuer 0xc4E032A7574016bd0e3d1a5BbFdE886af09CeD9A \
121
- --pool-id 0xc1c8f29d6f03b5cd18bf2b862d48f45cc338022a154945b89c4bcb0a3e11e87f \
133
+ --router 0xEfB2F179F6Ce44d7af66d3e3FF792563033C9b7e \
134
+ --hook 0xaCD0fccDDd96471f7De9b3f015C5ebFaADe70a80 \
135
+ --issuer 0x108fA8db11616d73ccB67725B44C535Ddcaac5a9 \
136
+ --pool-id 0x0decaeb998563be8faf6e6b66d4a0c32025a166e35bae97b8ec62ded1b04be1b \
122
137
  --amount-in 0.001
123
138
  ```
124
139
 
@@ -126,19 +141,20 @@ ilal swap \
126
141
 
127
142
  | Contract | Address |
128
143
  |---|---|
129
- | CNFIssuer | `0xc4E032A7574016bd0e3d1a5BbFdE886af09CeD9A` |
130
- | ComplianceHook | `0xF5066ad9c25F3f54cfb19609A60187C48C184A80` |
131
- | ILALRouter | `0x7727F0f3EBe99A558487394D001950ee6B33BB86` |
132
- | PolicyRegistry | `0x910a3efDc426f3216738106dd0DC6EA696477233` |
133
- | TokenA / TOKA | `0x582362E608F36850F6f641510d5D19C1EaB4cb27` |
134
- | TokenB / TOKB | `0x6eBBdAC70EC422C512727B25c7F0D9120ed101Ff` |
135
- | Pool ID | `0xc1c8f29d6f03b5cd18bf2b862d48f45cc338022a154945b89c4bcb0a3e11e87f` |
144
+ | CNFIssuer | `0x108fA8db11616d73ccB67725B44C535Ddcaac5a9` |
145
+ | MockEAS | `0xE46d87960b8740585010ae5158193D67da7dd807` |
146
+ | ZKVerifierAdapter | `0xb77BB4566d5D1e81370E159bb0251467e4a2fcfa` |
147
+ | ComplianceHook | `0xaCD0fccDDd96471f7De9b3f015C5ebFaADe70a80` |
148
+ | ILALRouter | `0xEfB2F179F6Ce44d7af66d3e3FF792563033C9b7e` |
149
+ | PolicyRegistry | `0xC2Be4887aF9218b4B617F7125924737413292160` |
150
+ | Currency0 / TOKA | `0x3a7d58fAc623B4C30D7735B01DcE036EfF46e079` |
151
+ | Currency1 / TOKB | `0x7BC67f7Fd3892fBE6AcC4F10bc3df95b64c2eD80` |
152
+ | Pool ID | `0x0decaeb998563be8faf6e6b66d4a0c32025a166e35bae97b8ec62ded1b04be1b` |
136
153
 
137
154
  Live proof:
138
155
 
139
- - CNF mint tx: `0x676ca67698eb8fed6c905c2b3a9536d4d056e89c199c41c44085a29db8b4d462`
140
- - Add liquidity tx: `0x531fac3678878e4855471318b8ea39b2b2f3ced3d890d9d7c40721af296084ca`
141
- - Swap tx: `0xdaf4136d305e546d6936715cc0101efb4dc88abcb779add9ee03591fdf555a5a`
156
+ - CNF ZK mint tx: `0x8c0ca35cb666d839b7070ed8103d12379b12ccb399283fcacaf5caa8b86e4542`
157
+ - Current-stack add liquidity / swap: pending re-run after local RPC/TLS instability clears; router happy path is covered by the Solidity integration tests.
142
158
 
143
159
  ## License
144
160
 
@@ -10,10 +10,10 @@ const POOL_MANAGER = {
10
10
  };
11
11
  const SAMPLE = {
12
12
  wallet: "0xc0807D4778a9E5FE15ad68A8500e64d65BA78D58",
13
- issuer: "0xc4E032A7574016bd0e3d1a5BbFdE886af09CeD9A",
14
- hook: "0xF5066ad9c25F3f54cfb19609A60187C48C184A80",
15
- router: "0x7727F0f3EBe99A558487394D001950ee6B33BB86",
16
- pool: "0xc1c8f29d6f03b5cd18bf2b862d48f45cc338022a154945b89c4bcb0a3e11e87f",
13
+ issuer: "0x108fA8db11616d73ccB67725B44C535Ddcaac5a9",
14
+ hook: "0xaCD0fccDDd96471f7De9b3f015C5ebFaADe70a80",
15
+ router: "0xEfB2F179F6Ce44d7af66d3e3FF792563033C9b7e",
16
+ pool: "0x0decaeb998563be8faf6e6b66d4a0c32025a166e35bae97b8ec62ded1b04be1b",
17
17
  proof: "0x91f2b8a0c43e902f7f1a8c0d",
18
18
  session: "0x6b84eac5e0db21f8d5d43b7a",
19
19
  };
@@ -24,6 +24,12 @@ const CNF_ABI = [
24
24
  { name: "merkleRoot", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
25
25
  { name: "zkVerifier", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
26
26
  { name: "eas", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
27
+ { name: "issuerMetadata", type: "function", stateMutability: "view", inputs: [], outputs: [
28
+ { name: "name", type: "string" },
29
+ { name: "jurisdiction", type: "string" },
30
+ { name: "credentialStandard", type: "string" },
31
+ { name: "uri", type: "string" },
32
+ ] },
27
33
  ];
28
34
  const REGISTRY_ABI = [
29
35
  { name: "getPolicy", type: "function", stateMutability: "view", inputs: [{ name: "poolId", type: "bytes32" }], outputs: [{ type: "tuple", components: [{ name: "cnfIssuer", type: "address" }, { name: "requiredCredentialType", type: "bytes32" }, { name: "enabled", type: "bool" }] }] },
@@ -274,6 +280,24 @@ export async function demoCheck(opts) {
274
280
  ]);
275
281
  const hasEASPath = eas !== ZERO;
276
282
  const hasZKPath = root !== 0n && verifier !== ZERO;
283
+ try {
284
+ const meta = await client.readContract({
285
+ address: cfg.issuer,
286
+ abi: CNF_ABI,
287
+ functionName: "issuerMetadata",
288
+ });
289
+ if (meta[0])
290
+ ok("issuer name", meta[0]);
291
+ if (meta[1])
292
+ ok("jurisdiction", meta[1]);
293
+ if (meta[2])
294
+ ok("standard", meta[2]);
295
+ if (meta[3])
296
+ ok("metadata uri", fmt.gray(meta[3]));
297
+ }
298
+ catch {
299
+ warn("issuer metadata", fmt.badge("legacy issuer", "yellow"));
300
+ }
277
301
  if (hasEASPath)
278
302
  ok("issuance path", `${fmt.badge("EAS/mock", "green")} ${fmt.addr(eas)}`);
279
303
  else if (hasZKPath)
@@ -10,14 +10,14 @@ import { fmt, log, header, die } from "../ui.js";
10
10
  // Known testnet / mainnet addresses for quick init
11
11
  const PRESETS = {
12
12
  "84532": {
13
- issuer: "0xc4E032A7574016bd0e3d1a5BbFdE886af09CeD9A",
14
- hook: "0xF5066ad9c25F3f54cfb19609A60187C48C184A80",
15
- registry: "0x910a3efDc426f3216738106dd0DC6EA696477233",
16
- router: "0x7727F0f3EBe99A558487394D001950ee6B33BB86",
13
+ issuer: "0x108fA8db11616d73ccB67725B44C535Ddcaac5a9",
14
+ hook: "0xaCD0fccDDd96471f7De9b3f015C5ebFaADe70a80",
15
+ registry: "0xC2Be4887aF9218b4B617F7125924737413292160",
16
+ router: "0xEfB2F179F6Ce44d7af66d3e3FF792563033C9b7e",
17
17
  treasury: "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
18
- tokenA: "0x582362E608F36850F6f641510d5D19C1EaB4cb27",
19
- tokenB: "0x6eBBdAC70EC422C512727B25c7F0D9120ed101Ff",
20
- poolId: "0xc1c8f29d6f03b5cd18bf2b862d48f45cc338022a154945b89c4bcb0a3e11e87f",
18
+ tokenA: "0x3a7d58fAc623B4C30D7735B01DcE036EfF46e079",
19
+ tokenB: "0x7BC67f7Fd3892fBE6AcC4F10bc3df95b64c2eD80",
20
+ poolId: "0x0decaeb998563be8faf6e6b66d4a0c32025a166e35bae97b8ec62ded1b04be1b",
21
21
  fee: "8388608",
22
22
  tickSpacing: "60",
23
23
  rpc: "https://sepolia.base.org",
@@ -12,11 +12,11 @@
12
12
  * # Step 1 — queue a new root (requires owner key, executes immediately)
13
13
  * PRIVATE_KEY=0x... ilal oracle propose-root \
14
14
  * --root 0xDEADBEEF... \
15
- * --issuer 0xc4E032...
15
+ * --issuer 0x108fA8...
16
16
  *
17
17
  * # Step 2 — after ROOT_DELAY (48 h) has elapsed, activate it
18
18
  * PRIVATE_KEY=0x... ilal oracle activate-root \
19
- * --issuer 0xc4E032...
19
+ * --issuer 0x108fA8...
20
20
  *
21
21
  * # Same pattern for the ZK verifier (VERIFIER_DELAY = 72 h)
22
22
  * PRIVATE_KEY=0x... ilal oracle propose-verifier --verifier 0x... --issuer 0x...
@@ -12,11 +12,11 @@
12
12
  * # Step 1 — queue a new root (requires owner key, executes immediately)
13
13
  * PRIVATE_KEY=0x... ilal oracle propose-root \
14
14
  * --root 0xDEADBEEF... \
15
- * --issuer 0xc4E032...
15
+ * --issuer 0x108fA8...
16
16
  *
17
17
  * # Step 2 — after ROOT_DELAY (48 h) has elapsed, activate it
18
18
  * PRIVATE_KEY=0x... ilal oracle activate-root \
19
- * --issuer 0xc4E032...
19
+ * --issuer 0x108fA8...
20
20
  *
21
21
  * # Same pattern for the ZK verifier (VERIFIER_DELAY = 72 h)
22
22
  * PRIVATE_KEY=0x... ilal oracle propose-verifier --verifier 0x... --issuer 0x...
@@ -7,7 +7,7 @@
7
7
  * Usage:
8
8
  * ilal credential prove \
9
9
  * --wallet 0x1b869... \
10
- * --issuer 0xc4E032... \
10
+ * --issuer 0x108fA8... \
11
11
  * --chain 84532 \
12
12
  * --action mint # or renew (default: auto-detect)
13
13
  * --circuit-dir ./circuits/build
@@ -24,4 +24,10 @@ export declare function credentialProve(opts: {
24
24
  outDir?: string;
25
25
  rpc?: string;
26
26
  privateKey?: string;
27
+ expiresAt?: string;
28
+ }): Promise<void>;
29
+ export declare function credentialRoot(opts: {
30
+ wallet?: string;
31
+ issuer?: string;
32
+ expiresAt?: string;
27
33
  }): Promise<void>;
@@ -7,7 +7,7 @@
7
7
  * Usage:
8
8
  * ilal credential prove \
9
9
  * --wallet 0x1b869... \
10
- * --issuer 0xc4E032... \
10
+ * --issuer 0x108fA8... \
11
11
  * --chain 84532 \
12
12
  * --action mint # or renew (default: auto-detect)
13
13
  * --circuit-dir ./circuits/build
@@ -103,6 +103,21 @@ function findCircuitDir(override) {
103
103
  " Run: bash circuits/scripts/compile.sh\n" +
104
104
  " Or pass: --circuit-dir <path/to/circuits/build>");
105
105
  }
106
+ function resolveExpiresAt(expiresAt) {
107
+ if (!expiresAt)
108
+ return BigInt(Math.floor(Date.now() / 1000) + 90 * 24 * 3600);
109
+ const parsed = BigInt(expiresAt);
110
+ if (parsed <= BigInt(Math.floor(Date.now() / 1000)))
111
+ die("--expires-at must be a future Unix timestamp");
112
+ return parsed;
113
+ }
114
+ function computeZKLeafAndRoot(walletAddr, expiresAt) {
115
+ const walletField = addressToField(walletAddr);
116
+ const leaf = poseidon4([walletField, 2n, 840n, expiresAt]);
117
+ const tree = new IncrementalMerkleTree(poseidon2, DEPTH, 0n, 2);
118
+ tree.insert(leaf);
119
+ return { leaf, merkleRoot: tree.root };
120
+ }
106
121
  function generateProof(opts) {
107
122
  const { walletAddr, issuerAddr, circuitDir, outDir } = opts;
108
123
  mkdirSync(outDir, { recursive: true });
@@ -111,13 +126,12 @@ function generateProof(opts) {
111
126
  const walletHash = computeWalletHash(walletAddr);
112
127
  const issuerHash = poseidonField(addressToField(issuerAddr));
113
128
  const schemaHashValue = schemaHash(COINBASE_SCHEMA_UID);
114
- const expiresAt = BigInt(Math.floor(Date.now() / 1000) + 90 * 24 * 3600); // +90 days
129
+ const expiresAt = resolveExpiresAt(opts.expiresAt);
115
130
  // Build single-leaf Poseidon Merkle tree
116
- const leaf = poseidon4([walletField, 2n, 840n, expiresAt]);
131
+ const { leaf, merkleRoot } = computeZKLeafAndRoot(walletAddr, expiresAt);
117
132
  const tree = new IncrementalMerkleTree(poseidon2, DEPTH, 0n, 2);
118
133
  tree.insert(leaf);
119
134
  const merkleProof = tree.createProof(0);
120
- const merkleRoot = tree.root;
121
135
  // Build input.json
122
136
  const input = {
123
137
  walletField: walletField.toString(),
@@ -223,14 +237,21 @@ export async function credentialProve(opts) {
223
237
  let proofResult;
224
238
  try {
225
239
  spin.update("Generating ZK witness…");
226
- proofResult = generateProof({ walletAddr: cfg.wallet, issuerAddr: cfg.issuer, circuitDir, outDir });
240
+ proofResult = generateProof({
241
+ walletAddr: cfg.wallet,
242
+ issuerAddr: cfg.issuer,
243
+ circuitDir,
244
+ outDir,
245
+ expiresAt: opts.expiresAt,
246
+ });
227
247
  spin.succeed(`Proof generated & verified locally`);
228
248
  }
229
249
  catch (e) {
230
250
  spin.fail("Proof generation failed");
231
251
  die(e instanceof Error ? e.message.split("\n")[0] : String(e));
232
252
  }
233
- log.kv("expiresAt", fmt.cyan(new Date((Date.now() + 90 * 24 * 3600 * 1000)).toISOString().split("T")[0]));
253
+ const proofExpiresAt = BigInt(proofResult.publicJson[3]);
254
+ log.kv("expiresAt", fmt.cyan(new Date(Number(proofExpiresAt) * 1000).toISOString().split("T")[0]));
234
255
  log.kv("merkleRoot", fmt.gray(proofResult.merkleRoot.toString().slice(0, 22) + "…"));
235
256
  log.line();
236
257
  // ── Send mint / renew tx ───────────────────────────────────────────────────
@@ -269,3 +290,28 @@ export async function credentialProve(opts) {
269
290
  log.kv("isValid()", valid ? fmt.green("✓ true") : fmt.red("✗ false"));
270
291
  console.log();
271
292
  }
293
+ export async function credentialRoot(opts) {
294
+ if (!opts.wallet)
295
+ die("Wallet address required. Use --wallet <address>.");
296
+ if (!isAddress(opts.wallet))
297
+ die(`Invalid wallet address: ${opts.wallet}`);
298
+ const expiresAt = resolveExpiresAt(opts.expiresAt);
299
+ const { leaf, merkleRoot } = computeZKLeafAndRoot(opts.wallet, expiresAt);
300
+ header("ILAL ZK Root Preparation", "operator pre-deploy / pre-root");
301
+ log.kv("wallet", fmt.cyan(opts.wallet));
302
+ log.kv("kycLevel", "2");
303
+ log.kv("countryCode", "840");
304
+ log.kv("expiresAt", `${expiresAt.toString()} ${fmt.gray(new Date(Number(expiresAt) * 1000).toISOString())}`);
305
+ log.kv("leaf", leaf.toString());
306
+ log.kv("merkleRoot", fmt.cyan(merkleRoot.toString()));
307
+ if (opts.issuer) {
308
+ if (!isAddress(opts.issuer))
309
+ die(`Invalid issuer address: ${opts.issuer}`);
310
+ log.kv("issuerHash", poseidonField(addressToField(opts.issuer)).toString());
311
+ log.kv("schemaHash", schemaHash(COINBASE_SCHEMA_UID).toString());
312
+ }
313
+ log.line();
314
+ log.command(`INITIAL_MERKLE_ROOT=${merkleRoot.toString()} forge script contracts/script/DeployDemo.s.sol ...`);
315
+ log.command(`PRIVATE_KEY=0x... ilal credential prove --wallet ${opts.wallet} --expires-at ${expiresAt.toString()}`);
316
+ console.log();
317
+ }
@@ -15,6 +15,12 @@ const CNF_ABI = [
15
15
  { name: "merkleRoot", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
16
16
  { name: "zkVerifier", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
17
17
  { name: "eas", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
18
+ { name: "issuerMetadata", type: "function", stateMutability: "view", inputs: [], outputs: [
19
+ { name: "name", type: "string" },
20
+ { name: "jurisdiction", type: "string" },
21
+ { name: "credentialStandard", type: "string" },
22
+ { name: "uri", type: "string" },
23
+ ] },
18
24
  ];
19
25
  const HOOK_ABI = [
20
26
  { name: "issuer", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
@@ -100,6 +106,24 @@ export async function status(opts) {
100
106
  const hasZKPath = root !== 0n && verifier !== ZERO_ADDRESS;
101
107
  log.section("Issuer");
102
108
  log.kv("address", fmt.cyan(cfg.issuer));
109
+ try {
110
+ const meta = await client.readContract({
111
+ address: cfg.issuer,
112
+ abi: CNF_ABI,
113
+ functionName: "issuerMetadata",
114
+ });
115
+ if (meta[0])
116
+ log.kv("name", meta[0]);
117
+ if (meta[1])
118
+ log.kv("jurisdiction", meta[1]);
119
+ if (meta[2])
120
+ log.kv("standard", meta[2]);
121
+ if (meta[3])
122
+ log.kv("uri", fmt.gray(meta[3]));
123
+ }
124
+ catch {
125
+ log.kv("metadata", fmt.badge("legacy issuer", "yellow"));
126
+ }
103
127
  log.kv("issuance", hasEASPath
104
128
  ? `${fmt.badge("EAS", "green")} ${fmt.addr(eas)}`
105
129
  : hasZKPath
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import { credentialStatus } from "./commands/credential.js";
4
- import { credentialProve } from "./commands/prove.js";
4
+ import { credentialProve, credentialRoot } from "./commands/prove.js";
5
5
  import { oracleProposeRoot, oracleActivateRoot, oracleProposeVerifier, oracleActivateVerifier } from "./commands/oracle.js";
6
6
  import { mintCredential, renewCredential } from "./commands/mint.js";
7
7
  import { proofMint, proofRenew } from "./commands/proof.js";
@@ -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.4")
22
+ .version("0.2.6")
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
@@ -116,9 +116,19 @@ credential
116
116
  .option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
117
117
  .option("-r, --rpc <url>", "Custom RPC URL")
118
118
  .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
119
+ .option("--expires-at <unix>", "Unix timestamp used in the ZK proof/root (must match issuer root)")
119
120
  .action(async (opts) => {
120
121
  await credentialProve(opts).catch(err);
121
122
  });
123
+ credential
124
+ .command("zk-root")
125
+ .description("Compute the Merkle root needed for a one-wallet ZK credential demo")
126
+ .requiredOption("-w, --wallet <address>", "Wallet address included in the ZK tree")
127
+ .option("-i, --issuer <address>", "Issuer address, used to print matching public-input hashes")
128
+ .requiredOption("--expires-at <unix>", "Future Unix timestamp; pass the same value to credential prove")
129
+ .action(async (opts) => {
130
+ await credentialRoot(opts).catch(err);
131
+ });
122
132
  credential
123
133
  .command("mint")
124
134
  .description("Mint a CNF credential using a Coinbase EAS attestation")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilalv3/cli",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "ILAL Protocol CLI — compliant swaps and credential management for Uniswap v4",
5
5
  "type": "module",
6
6
  "bin": {