@ilalv3/cli 0.2.3 → 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 +40 -16
- package/dist/commands/demo.d.ts +5 -0
- package/dist/commands/demo.js +125 -5
- 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/session.d.ts +4 -4
- package/dist/commands/session.js +23 -16
- package/dist/commands/status.js +24 -0
- package/dist/commands/swap.d.ts +1 -0
- package/dist/commands/swap.js +64 -28
- package/dist/index.js +28 -8
- 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:
|
|
@@ -52,6 +52,10 @@ PRIVATE_KEY=0x... ilal credential mint \
|
|
|
52
52
|
--attestation <AttestationUID> \
|
|
53
53
|
--chain 84532
|
|
54
54
|
|
|
55
|
+
# Or, with the MockEAS owner key, create a fresh test attestation:
|
|
56
|
+
PRIVATE_KEY=0x... ilal demo attest --wallet 0xYourWallet
|
|
57
|
+
PRIVATE_KEY=0xYourWalletKey ilal credential mint --attestation <uid>
|
|
58
|
+
|
|
55
59
|
# If the wallet needs more demo tokens:
|
|
56
60
|
PRIVATE_KEY=0x... ilal demo faucet --wallet 0xYourWallet
|
|
57
61
|
```
|
|
@@ -72,8 +76,22 @@ PRIVATE_KEY=0x... ilal demo faucet --wallet 0xYourWallet
|
|
|
72
76
|
|
|
73
77
|
### Path B — ZK proof (privacy-preserving)
|
|
74
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
|
+
|
|
75
83
|
```bash
|
|
76
|
-
|
|
84
|
+
ilal credential zk-root \
|
|
85
|
+
--wallet 0xYourWallet \
|
|
86
|
+
--expires-at 1800000000
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Trader proves against the same expiry:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
PRIVATE_KEY=0x... ilal credential prove \
|
|
93
|
+
--wallet 0xYourWallet \
|
|
94
|
+
--expires-at 1800000000
|
|
77
95
|
```
|
|
78
96
|
|
|
79
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.
|
|
@@ -84,6 +102,7 @@ Generates a Groth16 proof locally (~5s), verifies it on-chain, and mints/renews
|
|
|
84
102
|
|---|---|
|
|
85
103
|
| `ilal init` | Create `.ilal.json` with contract addresses |
|
|
86
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 |
|
|
87
106
|
| `ilal credential prove` | Trader flow: local ZK proof → mint or renew CNF |
|
|
88
107
|
| `ilal credential mint` | Mint CNF via Coinbase EAS attestation |
|
|
89
108
|
| `ilal credential renew` | Renew CNF via EAS attestation |
|
|
@@ -99,19 +118,22 @@ Generates a Groth16 proof locally (~5s), verifies it on-chain, and mints/renews
|
|
|
99
118
|
| `ilal session sign` | Sign a standalone SessionToken |
|
|
100
119
|
| `ilal proof mint` | Mint CNF from existing proof.json + public.json |
|
|
101
120
|
| `ilal deploy --mock` | Deploy a seeded testnet demo stack with MockEAS, tokens, router, hook, and policy |
|
|
121
|
+
| `ilal demo attest` | Create a MockEAS test attestation so a wallet can mint CNF |
|
|
102
122
|
| `ilal demo faucet` | Mint mock demo TOKA/TOKB to a wallet |
|
|
103
123
|
| `ilal deploy` | Deploy full ILAL contract stack |
|
|
104
124
|
|
|
125
|
+
Session note: ILAL hookData is a one-time EIP-712 authorization with a deadline and nonce. The expensive compliance step is the CNF issuance or renewal; swaps do not verify a fresh ZK proof. Use `ilal session sign` to export hookData, and `ilal swap --hook-data <hex>` to execute with an externally signed authorization.
|
|
126
|
+
|
|
105
127
|
## Configuration
|
|
106
128
|
|
|
107
129
|
The CLI reads `.ilal.json` in the current directory. Run `ilal init` to create it, or pass flags directly:
|
|
108
130
|
|
|
109
131
|
```bash
|
|
110
132
|
ilal swap \
|
|
111
|
-
--router
|
|
112
|
-
--hook
|
|
113
|
-
--issuer
|
|
114
|
-
--pool-id
|
|
133
|
+
--router 0xf7DBe6721AE935FA25D963076cd202994E0D5e17 \
|
|
134
|
+
--hook 0x1623276697B4e6609F8887C9Caa9dB6A6fa08A80 \
|
|
135
|
+
--issuer 0x18EF418Ca1C81d37BD3247D34c19Adc42306535F \
|
|
136
|
+
--pool-id 0xf32ae7435348041d4e979a24ce417bfe71d0f6642d2dcb2326e01acfe660fa0d \
|
|
115
137
|
--amount-in 0.001
|
|
116
138
|
```
|
|
117
139
|
|
|
@@ -119,19 +141,21 @@ ilal swap \
|
|
|
119
141
|
|
|
120
142
|
| Contract | Address |
|
|
121
143
|
|---|---|
|
|
122
|
-
| CNFIssuer | `
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
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` |
|
|
129
153
|
|
|
130
154
|
Live proof:
|
|
131
155
|
|
|
132
|
-
- CNF mint tx: `
|
|
133
|
-
- Add liquidity tx: `
|
|
134
|
-
- Swap tx: `
|
|
156
|
+
- CNF ZK mint tx: `0x3e770104228abc547664df2958ce8f88ddd6d66dd11a78fa1ba1b3569a75a8dc`
|
|
157
|
+
- Add liquidity tx: `0x39f82fdc8e8a8aa6fe1d6cd98adac15f79fdce2b99cc82955dd35eedef89b9d0`
|
|
158
|
+
- Swap tx: `0x3ff5b22707eb4172816359d61b1d97f0086b08c47f396fd44ee1c68471a7b8cc`
|
|
135
159
|
|
|
136
160
|
## License
|
|
137
161
|
|
package/dist/commands/demo.d.ts
CHANGED
package/dist/commands/demo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createPublicClient, createWalletClient, formatUnits, http, isAddress, isHex, parseUnits, } from "viem";
|
|
1
|
+
import { createPublicClient, createWalletClient, decodeEventLog, formatUnits, http, isAddress, isHex, parseUnits, } from "viem";
|
|
2
2
|
import { privateKeyToAccount } from "viem/accounts";
|
|
3
3
|
import { base, baseSepolia } from "viem/chains";
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
@@ -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" }] }] },
|
|
@@ -39,6 +45,30 @@ const ERC20_ABI = [
|
|
|
39
45
|
{ name: "allowance", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }, { name: "spender", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
40
46
|
{ name: "mint", type: "function", stateMutability: "nonpayable", inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256" }], outputs: [] },
|
|
41
47
|
];
|
|
48
|
+
const MOCK_EAS_ABI = [
|
|
49
|
+
{
|
|
50
|
+
type: "event",
|
|
51
|
+
name: "AttestationCreated",
|
|
52
|
+
inputs: [
|
|
53
|
+
{ name: "uid", type: "bytes32", indexed: true },
|
|
54
|
+
{ name: "recipient", type: "address", indexed: true },
|
|
55
|
+
{ name: "attester", type: "address", indexed: true },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "attest",
|
|
60
|
+
type: "function",
|
|
61
|
+
stateMutability: "nonpayable",
|
|
62
|
+
inputs: [
|
|
63
|
+
{ name: "schema", type: "bytes32" },
|
|
64
|
+
{ name: "recipient", type: "address" },
|
|
65
|
+
{ name: "attester", type: "address" },
|
|
66
|
+
{ name: "expirationTime", type: "uint64" },
|
|
67
|
+
{ name: "data", type: "bytes" },
|
|
68
|
+
],
|
|
69
|
+
outputs: [{ type: "bytes32" }],
|
|
70
|
+
},
|
|
71
|
+
];
|
|
42
72
|
function stage(n, title, subtitle) {
|
|
43
73
|
console.log();
|
|
44
74
|
console.log(` ${fmt.badge(`step ${n}`, "cyan")} ${fmt.bold(title)}`);
|
|
@@ -250,6 +280,24 @@ export async function demoCheck(opts) {
|
|
|
250
280
|
]);
|
|
251
281
|
const hasEASPath = eas !== ZERO;
|
|
252
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
|
+
}
|
|
253
301
|
if (hasEASPath)
|
|
254
302
|
ok("issuance path", `${fmt.badge("EAS/mock", "green")} ${fmt.addr(eas)}`);
|
|
255
303
|
else if (hasZKPath)
|
|
@@ -441,3 +489,75 @@ export async function demoFaucet(opts) {
|
|
|
441
489
|
log.callout("Demo tokens ready", "wallet can now pass token-balance preflight checks", "green");
|
|
442
490
|
console.log();
|
|
443
491
|
}
|
|
492
|
+
export async function demoAttest(opts) {
|
|
493
|
+
const cfg = loadConfig();
|
|
494
|
+
const chain = CHAINS[cfg.chain ?? "84532"] ?? baseSepolia;
|
|
495
|
+
const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
|
|
496
|
+
if (!rawKey || !isHex(rawKey) || rawKey.length !== 66) {
|
|
497
|
+
die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
|
|
498
|
+
}
|
|
499
|
+
if (!cfg.issuer || !isAddress(cfg.issuer))
|
|
500
|
+
die("CNFIssuer required. Run `ilal init` first.");
|
|
501
|
+
if (!isAddress(opts.wallet))
|
|
502
|
+
die(`Invalid wallet address: ${opts.wallet}`);
|
|
503
|
+
const account = privateKeyToAccount(rawKey);
|
|
504
|
+
const client = createPublicClient({ chain, transport: http(cfg.rpc) });
|
|
505
|
+
const walletClient = createWalletClient({ account, chain, transport: http(cfg.rpc) });
|
|
506
|
+
const [eas, schemaUID, trustedAttester] = await Promise.all([
|
|
507
|
+
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "eas" }),
|
|
508
|
+
client.readContract({ address: cfg.issuer, abi: [
|
|
509
|
+
{ name: "schemaUID", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "bytes32" }] },
|
|
510
|
+
], functionName: "schemaUID" }),
|
|
511
|
+
client.readContract({ address: cfg.issuer, abi: [
|
|
512
|
+
{ name: "trustedAttester", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
513
|
+
], functionName: "trustedAttester" }),
|
|
514
|
+
]);
|
|
515
|
+
if (eas === ZERO)
|
|
516
|
+
die("Configured issuer has no EAS/MockEAS path. Use a mock demo issuer or Coinbase EAS attestation.");
|
|
517
|
+
const days = BigInt(parseInt(opts.expiresInDays ?? "90", 10));
|
|
518
|
+
const expiration = BigInt(Math.floor(Date.now() / 1000)) + days * 24n * 60n * 60n;
|
|
519
|
+
header("ILAL Demo Attestation", chain.name);
|
|
520
|
+
log.kv("mockEAS", fmt.addr(eas));
|
|
521
|
+
log.kv("issuer", fmt.addr(cfg.issuer));
|
|
522
|
+
log.kv("recipient", fmt.addr(opts.wallet));
|
|
523
|
+
log.kv("attester", fmt.addr(trustedAttester));
|
|
524
|
+
log.line();
|
|
525
|
+
const spin = new Spinner("Creating MockEAS attestation…").start();
|
|
526
|
+
const hash = await walletClient.writeContract({
|
|
527
|
+
address: eas,
|
|
528
|
+
abi: MOCK_EAS_ABI,
|
|
529
|
+
functionName: "attest",
|
|
530
|
+
args: [
|
|
531
|
+
schemaUID,
|
|
532
|
+
opts.wallet,
|
|
533
|
+
trustedAttester,
|
|
534
|
+
expiration,
|
|
535
|
+
"0x",
|
|
536
|
+
],
|
|
537
|
+
});
|
|
538
|
+
const receipt = await client.waitForTransactionReceipt({ hash });
|
|
539
|
+
spin.succeed(`Attestation created ${fmt.gray(fmt.hash(hash))}`);
|
|
540
|
+
let uid;
|
|
541
|
+
for (const logItem of receipt.logs) {
|
|
542
|
+
if (logItem.address.toLowerCase() !== eas.toLowerCase())
|
|
543
|
+
continue;
|
|
544
|
+
try {
|
|
545
|
+
const decoded = decodeEventLog({ abi: MOCK_EAS_ABI, data: logItem.data, topics: logItem.topics });
|
|
546
|
+
if (decoded.eventName === "AttestationCreated") {
|
|
547
|
+
uid = decoded.args.uid;
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
catch { }
|
|
552
|
+
}
|
|
553
|
+
log.line();
|
|
554
|
+
if (uid)
|
|
555
|
+
log.kv("attestation", fmt.cyan(uid));
|
|
556
|
+
log.kv("tx", fmt.gray(hash));
|
|
557
|
+
log.callout("CNF mint path ready", "the recipient wallet can now run `ilal credential mint --attestation <uid>`", "green");
|
|
558
|
+
if (uid) {
|
|
559
|
+
console.log();
|
|
560
|
+
log.command(`PRIVATE_KEY=0x... ilal credential mint --issuer ${cfg.issuer} --attestation ${uid} --chain ${chain.id}`);
|
|
561
|
+
}
|
|
562
|
+
console.log();
|
|
563
|
+
}
|
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
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export declare function sessionSign(opts: {
|
|
2
2
|
user?: string;
|
|
3
|
-
pool
|
|
3
|
+
pool?: string;
|
|
4
4
|
action: string;
|
|
5
|
-
hook
|
|
6
|
-
issuer
|
|
5
|
+
hook?: string;
|
|
6
|
+
issuer?: string;
|
|
7
7
|
caller?: string;
|
|
8
|
-
chain
|
|
8
|
+
chain?: string;
|
|
9
9
|
ttl: number;
|
|
10
10
|
privateKey?: string;
|
|
11
11
|
}): Promise<void>;
|
package/dist/commands/session.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createWalletClient, encodeAbiParameters, http, isAddress, isHex, parseA
|
|
|
2
2
|
import { privateKeyToAccount } from "viem/accounts";
|
|
3
3
|
import { base, baseSepolia } from "viem/chains";
|
|
4
4
|
import { fmt, log, header, die } from "../ui.js";
|
|
5
|
+
import { withConfig } from "../config.js";
|
|
5
6
|
const CHAINS = {
|
|
6
7
|
"8453": base,
|
|
7
8
|
"84532": baseSepolia,
|
|
@@ -27,6 +28,7 @@ const HOOK_DATA_ABI = parseAbiParameters([
|
|
|
27
28
|
"bytes signature",
|
|
28
29
|
]);
|
|
29
30
|
export async function sessionSign(opts) {
|
|
31
|
+
const cfg = withConfig({ chain: opts.chain, hook: opts.hook, issuer: opts.issuer });
|
|
30
32
|
// Resolve private key
|
|
31
33
|
const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
|
|
32
34
|
if (!rawKey)
|
|
@@ -35,32 +37,36 @@ export async function sessionSign(opts) {
|
|
|
35
37
|
die("Invalid private key format (expected 0x + 32 bytes).");
|
|
36
38
|
const account = privateKeyToAccount(rawKey);
|
|
37
39
|
const user = (opts.user ?? account.address);
|
|
38
|
-
const
|
|
40
|
+
const pool = opts.pool ?? cfg.poolId;
|
|
41
|
+
const hook = opts.hook ?? cfg.hook;
|
|
42
|
+
const issuer = opts.issuer ?? cfg.issuer;
|
|
43
|
+
const authorizedCaller = (opts.caller ?? cfg.router ?? user);
|
|
44
|
+
const chainId = opts.chain ?? cfg.chain ?? "84532";
|
|
39
45
|
if (!isAddress(user))
|
|
40
46
|
die(`Invalid user address: ${user}`);
|
|
41
47
|
if (!isAddress(authorizedCaller))
|
|
42
48
|
die(`Invalid authorized caller address: ${authorizedCaller}`);
|
|
43
|
-
if (!isAddress(
|
|
44
|
-
die(`Invalid hook address: ${
|
|
45
|
-
if (!isAddress(
|
|
46
|
-
die(`Invalid issuer address: ${
|
|
47
|
-
if (!isHex(
|
|
48
|
-
die("poolId must be a 32-byte hex string
|
|
49
|
+
if (!hook || !isAddress(hook))
|
|
50
|
+
die(`Invalid hook address: ${hook ?? "<missing>"}. Use --hook or run ilal init.`);
|
|
51
|
+
if (!issuer || !isAddress(issuer))
|
|
52
|
+
die(`Invalid issuer address: ${issuer ?? "<missing>"}. Use --issuer or run ilal init.`);
|
|
53
|
+
if (!pool || !isHex(pool) || pool.length !== 66)
|
|
54
|
+
die("poolId must be a 32-byte hex string. Use --pool or run ilal init.");
|
|
49
55
|
const actionKey = opts.action.toLowerCase().replace(/[^a-z]/g, "");
|
|
50
56
|
const actionCode = ACTIONS[actionKey];
|
|
51
57
|
if (actionCode === undefined)
|
|
52
58
|
die(`Unknown action "${opts.action}". Use: swap | addLiquidity | removeLiquidity`);
|
|
53
|
-
const chain = CHAINS[
|
|
59
|
+
const chain = CHAINS[chainId] ?? baseSepolia;
|
|
54
60
|
const walletClient = createWalletClient({ account, chain, transport: http() });
|
|
55
61
|
const deadline = BigInt(Math.floor(Date.now() / 1000) + opts.ttl);
|
|
56
62
|
const nonce = `0x${Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex")}`;
|
|
57
63
|
const token = {
|
|
58
64
|
user,
|
|
59
65
|
authorizedCaller,
|
|
60
|
-
cnfIssuer:
|
|
66
|
+
cnfIssuer: issuer,
|
|
61
67
|
chainId: BigInt(chain.id),
|
|
62
|
-
verifyingHook:
|
|
63
|
-
poolId:
|
|
68
|
+
verifyingHook: hook,
|
|
69
|
+
poolId: pool,
|
|
64
70
|
action: actionCode,
|
|
65
71
|
deadline,
|
|
66
72
|
nonce: nonce,
|
|
@@ -69,11 +75,11 @@ export async function sessionSign(opts) {
|
|
|
69
75
|
log.section("Session");
|
|
70
76
|
log.kv("user", fmt.addr(user));
|
|
71
77
|
log.kv("caller", fmt.addr(authorizedCaller));
|
|
72
|
-
log.kv("chain", chain.name);
|
|
73
|
-
log.kv("pool", fmt.hash(
|
|
78
|
+
log.kv("chain", `${chain.name} (${chain.id})`);
|
|
79
|
+
log.kv("pool", fmt.hash(pool));
|
|
74
80
|
log.kv("action", opts.action);
|
|
75
|
-
log.kv("hook", fmt.addr(
|
|
76
|
-
log.kv("issuer", fmt.addr(
|
|
81
|
+
log.kv("hook", fmt.addr(hook));
|
|
82
|
+
log.kv("issuer", fmt.addr(issuer));
|
|
77
83
|
log.kv("deadline", new Date(Number(deadline) * 1000).toISOString());
|
|
78
84
|
log.line();
|
|
79
85
|
log.step("Signing EIP-712 session token locally…");
|
|
@@ -84,7 +90,7 @@ export async function sessionSign(opts) {
|
|
|
84
90
|
name: "ILAL ComplianceHook",
|
|
85
91
|
version: "1",
|
|
86
92
|
chainId: BigInt(chain.id),
|
|
87
|
-
verifyingContract:
|
|
93
|
+
verifyingContract: hook,
|
|
88
94
|
},
|
|
89
95
|
types: { SessionToken: SESSION_TOKEN_TYPE },
|
|
90
96
|
primaryType: "SessionToken",
|
|
@@ -101,5 +107,6 @@ export async function sessionSign(opts) {
|
|
|
101
107
|
console.log(` ${fmt.cyan(hookData)}`);
|
|
102
108
|
console.log();
|
|
103
109
|
console.log(fmt.gray(" verifies: caller, deadline, chainId, hook, pool, action, sig, CNF"));
|
|
110
|
+
console.log(fmt.gray(" note: this hookData is a one-time authorization; nonce replay is blocked on-chain"));
|
|
104
111
|
console.log();
|
|
105
112
|
}
|
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/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, encodeAbiParameters, http, isAddress, isHex, parseAbiParameters, parseUnits, } from "viem";
|
|
21
|
+
import { createPublicClient, createWalletClient, decodeAbiParameters, encodeAbiParameters, 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";
|
|
@@ -214,42 +214,78 @@ export async function swap(opts) {
|
|
|
214
214
|
{ label: "ILAL fee", value: protocolFeePips > 0 ? pipsToPercent(protocolFeePips) : "off", note: protocolFeePips > 0 ? "protocol revenue" : "legacy router", tone: protocolFeePips > 0 ? "cyan" : "gray" },
|
|
215
215
|
]);
|
|
216
216
|
log.line();
|
|
217
|
-
// Sign session token
|
|
218
|
-
const signSpin = new Spinner("Signing session token…").start();
|
|
219
217
|
const ttl = parseInt(opts.ttl ?? "600");
|
|
220
218
|
const deadline = BigInt(Math.floor(Date.now() / 1000) + ttl);
|
|
221
219
|
const nonce = `0x${Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex")}`;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
220
|
+
let hookData;
|
|
221
|
+
let sessionNonce = nonce;
|
|
222
|
+
if (opts.hookData) {
|
|
223
|
+
if (!isHex(opts.hookData))
|
|
224
|
+
die("--hook-data must be 0x-prefixed ABI-encoded hookData.");
|
|
225
|
+
try {
|
|
226
|
+
const [externalToken] = decodeAbiParameters(HOOK_DATA_ABI, opts.hookData);
|
|
227
|
+
const issues = [];
|
|
228
|
+
if (externalToken.user.toLowerCase() !== account.address.toLowerCase())
|
|
229
|
+
issues.push("user does not match signer wallet");
|
|
230
|
+
if (externalToken.authorizedCaller.toLowerCase() !== cfg.router.toLowerCase())
|
|
231
|
+
issues.push("authorizedCaller does not match router");
|
|
232
|
+
if (externalToken.cnfIssuer.toLowerCase() !== cfg.issuer.toLowerCase())
|
|
233
|
+
issues.push("cnfIssuer does not match config");
|
|
234
|
+
if (externalToken.chainId !== BigInt(chain.id))
|
|
235
|
+
issues.push(`chainId mismatch: hookData=${externalToken.chainId.toString()} config=${chain.id}`);
|
|
236
|
+
if (externalToken.verifyingHook.toLowerCase() !== cfg.hook.toLowerCase())
|
|
237
|
+
issues.push("verifyingHook does not match config");
|
|
238
|
+
if (externalToken.poolId.toLowerCase() !== cfg.poolId.toLowerCase())
|
|
239
|
+
issues.push("poolId does not match config");
|
|
240
|
+
if (externalToken.action !== 1)
|
|
241
|
+
issues.push("action is not swap");
|
|
242
|
+
if (externalToken.deadline < BigInt(Math.floor(Date.now() / 1000)))
|
|
243
|
+
issues.push("session deadline has expired");
|
|
244
|
+
if (issues.length > 0)
|
|
245
|
+
die(`Invalid --hook-data for this swap: ${issues.join("; ")}`);
|
|
246
|
+
sessionNonce = externalToken.nonce;
|
|
247
|
+
hookData = opts.hookData;
|
|
248
|
+
log.ok("Using externally supplied one-time session authorization");
|
|
249
|
+
}
|
|
250
|
+
catch (e) {
|
|
251
|
+
die(`Could not decode --hook-data: ${e instanceof Error ? e.message : String(e)}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// Sign session token
|
|
256
|
+
const signSpin = new Spinner("Signing one-time session authorization…").start();
|
|
257
|
+
const token = {
|
|
258
|
+
user: account.address,
|
|
259
|
+
authorizedCaller: cfg.router,
|
|
260
|
+
cnfIssuer: cfg.issuer,
|
|
238
261
|
chainId: BigInt(chain.id),
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
262
|
+
verifyingHook: cfg.hook,
|
|
263
|
+
poolId: cfg.poolId,
|
|
264
|
+
action: 1, // ACTION_SWAP
|
|
265
|
+
deadline,
|
|
266
|
+
nonce,
|
|
267
|
+
};
|
|
268
|
+
const signature = await walClient.signTypedData({
|
|
269
|
+
account,
|
|
270
|
+
domain: {
|
|
271
|
+
name: "ILAL ComplianceHook",
|
|
272
|
+
version: "1",
|
|
273
|
+
chainId: BigInt(chain.id),
|
|
274
|
+
verifyingContract: cfg.hook,
|
|
275
|
+
},
|
|
276
|
+
types: { SessionToken: SESSION_TOKEN_TYPE },
|
|
277
|
+
primaryType: "SessionToken",
|
|
278
|
+
message: token,
|
|
279
|
+
});
|
|
280
|
+
hookData = encodeAbiParameters(HOOK_DATA_ABI, [token, signature]);
|
|
281
|
+
signSpin.succeed(`Session authorization signed (expires in ${ttl}s, one-time nonce)`);
|
|
282
|
+
}
|
|
247
283
|
const fee = parseInt(cfg.fee ?? "3000");
|
|
248
284
|
const tickSpacing = parseInt(cfg.tickSpacing ?? "60");
|
|
249
285
|
log.section("Gate Checks");
|
|
250
286
|
log.kv("credential", `${fmt.badge("required", "cyan")} issuer ${fmt.addr(cfg.issuer)}`);
|
|
251
287
|
log.kv("caller", `${fmt.badge("bound", "green")} ${fmt.addr(cfg.router)}`);
|
|
252
|
-
log.kv("nonce", `${fmt.badge("fresh", "green")} ${fmt.hash(
|
|
288
|
+
log.kv("nonce", `${opts.hookData ? fmt.badge("external", "cyan") : fmt.badge("fresh", "green")} ${fmt.hash(sessionNonce)}`);
|
|
253
289
|
log.kv("fee", feeLabel(fee));
|
|
254
290
|
if (protocolFeePips > 0) {
|
|
255
291
|
log.kv("protocol fee", `${fmt.badge("ILAL", "cyan")} ${pipsToPercent(protocolFeePips)} to ${treasury ? fmt.addr(treasury) : "treasury"}`);
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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";
|
|
8
8
|
import { sessionSign } from "./commands/session.js";
|
|
9
9
|
import { poolPolicySet, poolPolicyGet } from "./commands/pool.js";
|
|
10
10
|
import { deploy } from "./commands/deploy.js";
|
|
11
|
-
import { demo, demoCheck, demoFaucet } from "./commands/demo.js";
|
|
11
|
+
import { demo, demoCheck, demoFaucet, demoAttest } from "./commands/demo.js";
|
|
12
12
|
import { init } from "./commands/init.js";
|
|
13
13
|
import { status } from "./commands/status.js";
|
|
14
14
|
import { swap } from "./commands/swap.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
|
|
@@ -81,6 +81,15 @@ demoCommand
|
|
|
81
81
|
.action(async (opts) => {
|
|
82
82
|
await demoFaucet(opts).catch(err);
|
|
83
83
|
});
|
|
84
|
+
demoCommand
|
|
85
|
+
.command("attest")
|
|
86
|
+
.description("Create a MockEAS test attestation for a wallet (demo issuer owner only)")
|
|
87
|
+
.requiredOption("-w, --wallet <address>", "Recipient wallet that will mint the CNF")
|
|
88
|
+
.option("--expires-in-days <days>", "Attestation lifetime in days", "90")
|
|
89
|
+
.option("-k, --private-key <hex>", "MockEAS owner private key")
|
|
90
|
+
.action(async (opts) => {
|
|
91
|
+
await demoAttest(opts).catch(err);
|
|
92
|
+
});
|
|
84
93
|
const err = (e) => {
|
|
85
94
|
console.error(fmt.red(`\nError: ${e instanceof Error ? e.message : String(e)}\n`));
|
|
86
95
|
process.exit(1);
|
|
@@ -107,9 +116,19 @@ credential
|
|
|
107
116
|
.option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
|
|
108
117
|
.option("-r, --rpc <url>", "Custom RPC URL")
|
|
109
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)")
|
|
110
120
|
.action(async (opts) => {
|
|
111
121
|
await credentialProve(opts).catch(err);
|
|
112
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
|
+
});
|
|
113
132
|
credential
|
|
114
133
|
.command("mint")
|
|
115
134
|
.description("Mint a CNF credential using a Coinbase EAS attestation")
|
|
@@ -165,13 +184,13 @@ const session = program.command("session").description("Session token operations
|
|
|
165
184
|
session
|
|
166
185
|
.command("sign")
|
|
167
186
|
.description("Sign an EIP-712 session token locally — no ILAL API call")
|
|
168
|
-
.
|
|
187
|
+
.option("-p, --pool <bytes32>", "Pool ID (bytes32 hex, defaults to .ilal.json poolId)")
|
|
169
188
|
.requiredOption("-a, --action <action>", "Action: swap | addLiquidity | removeLiquidity")
|
|
170
|
-
.
|
|
171
|
-
.
|
|
189
|
+
.option("-H, --hook <address>", "ComplianceHook contract address (defaults to .ilal.json hook)")
|
|
190
|
+
.option("-i, --issuer <address>", "CNFIssuer contract address (defaults to .ilal.json issuer)")
|
|
172
191
|
.option("-u, --user <address>", "Trader address (defaults to key's address)")
|
|
173
|
-
.option("--caller <address>", "Authorized v4 caller (defaults to
|
|
174
|
-
.option("-c, --chain <chainId>", "Chain ID
|
|
192
|
+
.option("--caller <address>", "Authorized v4 caller (defaults to .ilal.json router, then user)")
|
|
193
|
+
.option("-c, --chain <chainId>", "Chain ID (defaults to .ilal.json chain, then 84532)")
|
|
175
194
|
.option("-t, --ttl <seconds>", "Session lifetime in seconds", "600")
|
|
176
195
|
.option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
|
|
177
196
|
.action(async (opts) => {
|
|
@@ -266,6 +285,7 @@ program
|
|
|
266
285
|
.option("-r, --rpc <url>", "Custom RPC URL")
|
|
267
286
|
.option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
|
|
268
287
|
.option("--ttl <seconds>", "Session token lifetime in seconds", "600")
|
|
288
|
+
.option("--hook-data <hex>", "Use externally signed one-time hookData instead of signing inside swap")
|
|
269
289
|
.option("--simulate", "Sign session without sending tx", false)
|
|
270
290
|
.action(async (opts) => {
|
|
271
291
|
await swap(opts).catch(err);
|