@ilalv3/cli 0.2.4 → 0.2.5
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 +33 -16
- package/dist/commands/demo.js +28 -4
- package/dist/commands/init.js +7 -7
- package/dist/commands/prove.d.ts +6 -0
- package/dist/commands/prove.js +51 -5
- package/dist/commands/status.js +24 -0
- package/dist/index.js +12 -2
- package/package.json +1 -1
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
|
|
37
|
+
PRIVATE_KEY=0x... ilal swap --amount-in 1 --token-in 0x589dDBdf4Bd6d605bD809a540FF4BC1066f6895e --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
|
|
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
|
|
119
|
-
--hook
|
|
120
|
-
--issuer
|
|
121
|
-
--pool-id
|
|
133
|
+
--router 0xf7DBe6721AE935FA25D963076cd202994E0D5e17 \
|
|
134
|
+
--hook 0x1623276697B4e6609F8887C9Caa9dB6A6fa08A80 \
|
|
135
|
+
--issuer 0x18EF418Ca1C81d37BD3247D34c19Adc42306535F \
|
|
136
|
+
--pool-id 0xf32ae7435348041d4e979a24ce417bfe71d0f6642d2dcb2326e01acfe660fa0d \
|
|
122
137
|
--amount-in 0.001
|
|
123
138
|
```
|
|
124
139
|
|
|
@@ -126,19 +141,21 @@ ilal swap \
|
|
|
126
141
|
|
|
127
142
|
| Contract | Address |
|
|
128
143
|
|---|---|
|
|
129
|
-
| CNFIssuer | `
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
144
|
+
| CNFIssuer | `0x18EF418Ca1C81d37BD3247D34c19Adc42306535F` |
|
|
145
|
+
| MockEAS | `0x1B1867e5A98EA90865E3E3a21b31c2edAdBA7c09` |
|
|
146
|
+
| ZKVerifierAdapter | `0x9C918604069CFA897606760E53aB854BA38303Ca` |
|
|
147
|
+
| ComplianceHook | `0x1623276697B4e6609F8887C9Caa9dB6A6fa08A80` |
|
|
148
|
+
| ILALRouter | `0xf7DBe6721AE935FA25D963076cd202994E0D5e17` |
|
|
149
|
+
| PolicyRegistry | `0xB2A94DE0432c1dEDfa941816A450002C6581B0aD` |
|
|
150
|
+
| Currency0 / TOKB | `0x589dDBdf4Bd6d605bD809a540FF4BC1066f6895e` |
|
|
151
|
+
| Currency1 / TOKA | `0xA9C0AB8e7Bc6a79649903EdE052E1B41585cCd08` |
|
|
152
|
+
| Pool ID | `0xf32ae7435348041d4e979a24ce417bfe71d0f6642d2dcb2326e01acfe660fa0d` |
|
|
136
153
|
|
|
137
154
|
Live proof:
|
|
138
155
|
|
|
139
|
-
- CNF mint tx: `
|
|
140
|
-
- Add liquidity tx: `
|
|
141
|
-
- Swap tx: `
|
|
156
|
+
- CNF ZK mint tx: `0x3e770104228abc547664df2958ce8f88ddd6d66dd11a78fa1ba1b3569a75a8dc`
|
|
157
|
+
- Add liquidity tx: `0x39f82fdc8e8a8aa6fe1d6cd98adac15f79fdce2b99cc82955dd35eedef89b9d0`
|
|
158
|
+
- Swap tx: `0x3ff5b22707eb4172816359d61b1d97f0086b08c47f396fd44ee1c68471a7b8cc`
|
|
142
159
|
|
|
143
160
|
## License
|
|
144
161
|
|
package/dist/commands/demo.js
CHANGED
|
@@ -10,10 +10,10 @@ const POOL_MANAGER = {
|
|
|
10
10
|
};
|
|
11
11
|
const SAMPLE = {
|
|
12
12
|
wallet: "0xc0807D4778a9E5FE15ad68A8500e64d65BA78D58",
|
|
13
|
-
issuer: "
|
|
14
|
-
hook: "
|
|
15
|
-
router: "
|
|
16
|
-
pool: "
|
|
13
|
+
issuer: "0x18EF418Ca1C81d37BD3247D34c19Adc42306535F",
|
|
14
|
+
hook: "0x1623276697B4e6609F8887C9Caa9dB6A6fa08A80",
|
|
15
|
+
router: "0xf7DBe6721AE935FA25D963076cd202994E0D5e17",
|
|
16
|
+
pool: "0xf32ae7435348041d4e979a24ce417bfe71d0f6642d2dcb2326e01acfe660fa0d",
|
|
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)
|
package/dist/commands/init.js
CHANGED
|
@@ -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: "
|
|
14
|
-
hook: "
|
|
15
|
-
registry: "
|
|
16
|
-
router: "
|
|
13
|
+
issuer: "0x18EF418Ca1C81d37BD3247D34c19Adc42306535F",
|
|
14
|
+
hook: "0x1623276697B4e6609F8887C9Caa9dB6A6fa08A80",
|
|
15
|
+
registry: "0xB2A94DE0432c1dEDfa941816A450002C6581B0aD",
|
|
16
|
+
router: "0xf7DBe6721AE935FA25D963076cd202994E0D5e17",
|
|
17
17
|
treasury: "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
|
|
18
|
-
tokenA: "
|
|
19
|
-
tokenB: "
|
|
20
|
-
poolId: "
|
|
18
|
+
tokenA: "0x589dDBdf4Bd6d605bD809a540FF4BC1066f6895e",
|
|
19
|
+
tokenB: "0xA9C0AB8e7Bc6a79649903EdE052E1B41585cCd08",
|
|
20
|
+
poolId: "0xf32ae7435348041d4e979a24ce417bfe71d0f6642d2dcb2326e01acfe660fa0d",
|
|
21
21
|
fee: "8388608",
|
|
22
22
|
tickSpacing: "60",
|
|
23
23
|
rpc: "https://sepolia.base.org",
|
package/dist/commands/prove.d.ts
CHANGED
|
@@ -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>;
|
package/dist/commands/prove.js
CHANGED
|
@@ -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 =
|
|
129
|
+
const expiresAt = resolveExpiresAt(opts.expiresAt);
|
|
115
130
|
// Build single-leaf Poseidon Merkle tree
|
|
116
|
-
const leaf =
|
|
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({
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/commands/status.js
CHANGED
|
@@ -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.
|
|
22
|
+
.version("0.2.5")
|
|
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")
|