@ilalv3/cli 0.1.2 → 0.2.0
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 +20 -15
- package/dist/commands/credential.js +4 -2
- package/dist/commands/demo.js +2 -2
- package/dist/commands/oracle.d.ts +50 -0
- package/dist/commands/oracle.js +281 -0
- package/dist/commands/prove.d.ts +5 -4
- package/dist/commands/prove.js +5 -26
- package/dist/commands/status.js +2 -1
- package/dist/commands/swap.d.ts +1 -0
- package/dist/commands/swap.js +9 -1
- package/dist/index.js +51 -1
- package/dist/ui.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,11 +25,12 @@ ilal init
|
|
|
25
25
|
# 2. Check credential + pool status
|
|
26
26
|
ilal status
|
|
27
27
|
|
|
28
|
-
# 3. Mint a CNF via ZK proof
|
|
29
|
-
|
|
28
|
+
# 3. Mint a CNF via ZK proof
|
|
29
|
+
# Requires the issuer root to already include your wallet.
|
|
30
|
+
PRIVATE_KEY=0x... ilal credential prove --wallet 0xYourWallet
|
|
30
31
|
|
|
31
32
|
# 4. Execute a compliant swap
|
|
32
|
-
PRIVATE_KEY=0x... ilal swap --amount-in 0.001 --token-in
|
|
33
|
+
PRIVATE_KEY=0x... ilal swap --amount-in 0.001 --token-in 0x2E0dEd1CF4ec6106079df4eF1200959c2a454f3A --min-amount-out 0
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
## Getting a CNF credential
|
|
@@ -49,10 +50,10 @@ PRIVATE_KEY=0x... ilal swap --amount-in 0.001 --token-in 0x60Fa08963dD59724a188A
|
|
|
49
50
|
### Path B — ZK proof (privacy-preserving)
|
|
50
51
|
|
|
51
52
|
```bash
|
|
52
|
-
PRIVATE_KEY=0x... ilal credential prove --wallet 0xYourWallet
|
|
53
|
+
PRIVATE_KEY=0x... ilal credential prove --wallet 0xYourWallet
|
|
53
54
|
```
|
|
54
55
|
|
|
55
|
-
Generates a Groth16 proof locally (~5s), verifies it on-chain, and mints/renews your CNF without revealing identity.
|
|
56
|
+
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.
|
|
56
57
|
|
|
57
58
|
## Command reference
|
|
58
59
|
|
|
@@ -60,14 +61,18 @@ Generates a Groth16 proof locally (~5s), verifies it on-chain, and mints/renews
|
|
|
60
61
|
|---|---|
|
|
61
62
|
| `ilal init` | Create `.ilal.json` with contract addresses |
|
|
62
63
|
| `ilal status` | Dashboard: credential · issuer config · pool policy |
|
|
63
|
-
| `ilal credential prove` | ZK proof → mint or renew CNF
|
|
64
|
+
| `ilal credential prove` | Trader flow: local ZK proof → mint or renew CNF |
|
|
64
65
|
| `ilal credential mint` | Mint CNF via Coinbase EAS attestation |
|
|
65
66
|
| `ilal credential renew` | Renew CNF via EAS attestation |
|
|
66
|
-
| `ilal swap` | Compliant swap via ILALRouter |
|
|
67
|
+
| `ilal swap` | Compliant swap via ILALRouter with optional `--min-amount-out` |
|
|
67
68
|
| `ilal pool add-liquidity` | Add liquidity to a compliant pool |
|
|
68
69
|
| `ilal pool remove-liquidity` | Remove liquidity from a compliant pool |
|
|
69
70
|
| `ilal pool policy set` | Register compliance policy for a pool |
|
|
70
71
|
| `ilal pool policy get` | Read pool compliance policy |
|
|
72
|
+
| `ilal oracle propose-root` | Operator flow: queue a new Merkle root behind the 48h timelock |
|
|
73
|
+
| `ilal oracle activate-root` | Operator flow: activate the pending Merkle root after timelock |
|
|
74
|
+
| `ilal oracle propose-verifier` | Operator flow: queue a new ZK verifier behind the 72h timelock |
|
|
75
|
+
| `ilal oracle activate-verifier` | Operator flow: activate the pending ZK verifier after timelock |
|
|
71
76
|
| `ilal session sign` | Sign a standalone SessionToken |
|
|
72
77
|
| `ilal proof mint` | Mint CNF from existing proof.json + public.json |
|
|
73
78
|
| `ilal deploy` | Deploy full ILAL contract stack |
|
|
@@ -78,10 +83,10 @@ The CLI reads `.ilal.json` in the current directory. Run `ilal init` to create i
|
|
|
78
83
|
|
|
79
84
|
```bash
|
|
80
85
|
ilal swap \
|
|
81
|
-
--router
|
|
82
|
-
--hook
|
|
83
|
-
--issuer
|
|
84
|
-
--pool-id
|
|
86
|
+
--router 0xd0aF4D1EFF36CB2a1E88017eA398dCaDe1Ac0040 \
|
|
87
|
+
--hook 0x6C57b50Ef9286b132066012B19b291FB120ACa80 \
|
|
88
|
+
--issuer 0xB13AE2498Df62A85768a4b783109C05fCf5A264a \
|
|
89
|
+
--pool-id 0x16b3e7a5c52216925f705673b3ab25db5e6025da530cf53b3bcb5affeb18d95f \
|
|
85
90
|
--amount-in 0.001
|
|
86
91
|
```
|
|
87
92
|
|
|
@@ -89,10 +94,10 @@ ilal swap \
|
|
|
89
94
|
|
|
90
95
|
| Contract | Address |
|
|
91
96
|
|---|---|
|
|
92
|
-
| CNFIssuer | `
|
|
93
|
-
| ComplianceHook | `
|
|
94
|
-
| ILALRouter | `
|
|
95
|
-
| PolicyRegistry | `
|
|
97
|
+
| CNFIssuer | `0xB13AE2498Df62A85768a4b783109C05fCf5A264a` |
|
|
98
|
+
| ComplianceHook | `0x6C57b50Ef9286b132066012B19b291FB120ACa80` |
|
|
99
|
+
| ILALRouter | `0xd0aF4D1EFF36CB2a1E88017eA398dCaDe1Ac0040` |
|
|
100
|
+
| PolicyRegistry | `0x19fD4eCF4359fCc8d5E79916691a28c24A22a9B4` |
|
|
96
101
|
|
|
97
102
|
## License
|
|
98
103
|
|
|
@@ -46,8 +46,10 @@ export async function credentialStatus(opts) {
|
|
|
46
46
|
console.log(` ${fmt.gray("3.")} Run: ${fmt.cyan("ilal credential mint --attestation <uid>")}`);
|
|
47
47
|
console.log();
|
|
48
48
|
console.log(fmt.bold(" Path B — ZK proof (privacy-preserving, no KYC data on-chain)"));
|
|
49
|
-
console.log(` ${fmt.gray("1.")}
|
|
50
|
-
console.log(` ${fmt.gray("2.")}
|
|
49
|
+
console.log(` ${fmt.gray("1.")} Issuer/operator adds wallet to the Merkle tree`);
|
|
50
|
+
console.log(` ${fmt.gray("2.")} Operator queues root: ${fmt.cyan("ilal oracle propose-root --root <newMerkleRoot>")}`);
|
|
51
|
+
console.log(` ${fmt.gray("3.")} After timelock, operator activates: ${fmt.cyan("ilal oracle activate-root")}`);
|
|
52
|
+
console.log(` ${fmt.gray("4.")} Trader runs: ${fmt.cyan("ilal credential prove --wallet " + opts.wallet)}`);
|
|
51
53
|
console.log();
|
|
52
54
|
return;
|
|
53
55
|
}
|
package/dist/commands/demo.js
CHANGED
|
@@ -92,9 +92,9 @@ export async function demo(opts) {
|
|
|
92
92
|
console.log();
|
|
93
93
|
log.section("Live Demo Commands");
|
|
94
94
|
log.command("ilal status --wallet <wallet>");
|
|
95
|
-
log.command("ilal credential prove --wallet <wallet>
|
|
95
|
+
log.command("ilal credential prove --wallet <wallet>");
|
|
96
96
|
log.command("ilal session sign --pool <poolId> --action swap --hook <hook> --issuer <issuer> --caller <router>");
|
|
97
|
-
log.command("ilal swap --amount-in 100 --token-in <token> --pool-id <poolId>");
|
|
97
|
+
log.command("ilal swap --amount-in 100 --token-in <token> --pool-id <poolId> --min-amount-out 0");
|
|
98
98
|
}
|
|
99
99
|
console.log();
|
|
100
100
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oracle.ts — `ilal oracle`
|
|
3
|
+
*
|
|
4
|
+
* Operator-only commands for managing the CNFIssuer Merkle root and
|
|
5
|
+
* ZK verifier via the timelock mechanism.
|
|
6
|
+
*
|
|
7
|
+
* Merkle root and ZK verifier changes are protected by a 2-step propose → activate
|
|
8
|
+
* timelock (ROOT_DELAY = 48 h, VERIFIER_DELAY = 72 h). Only the contract
|
|
9
|
+
* owner can call these.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* # Step 1 — queue a new root (requires owner key, executes immediately)
|
|
13
|
+
* PRIVATE_KEY=0x... ilal oracle propose-root \
|
|
14
|
+
* --root 0xDEADBEEF... \
|
|
15
|
+
* --issuer 0x319c0...
|
|
16
|
+
*
|
|
17
|
+
* # Step 2 — after ROOT_DELAY (48 h) has elapsed, activate it
|
|
18
|
+
* PRIVATE_KEY=0x... ilal oracle activate-root \
|
|
19
|
+
* --issuer 0x319c0...
|
|
20
|
+
*
|
|
21
|
+
* # Same pattern for the ZK verifier (VERIFIER_DELAY = 72 h)
|
|
22
|
+
* PRIVATE_KEY=0x... ilal oracle propose-verifier --verifier 0x... --issuer 0x...
|
|
23
|
+
* PRIVATE_KEY=0x... ilal oracle activate-verifier --issuer 0x...
|
|
24
|
+
*/
|
|
25
|
+
export declare function oracleProposeRoot(opts: {
|
|
26
|
+
root?: string;
|
|
27
|
+
issuer?: string;
|
|
28
|
+
chain?: string;
|
|
29
|
+
rpc?: string;
|
|
30
|
+
privateKey?: string;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
export declare function oracleActivateRoot(opts: {
|
|
33
|
+
issuer?: string;
|
|
34
|
+
chain?: string;
|
|
35
|
+
rpc?: string;
|
|
36
|
+
privateKey?: string;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
export declare function oracleProposeVerifier(opts: {
|
|
39
|
+
verifier?: string;
|
|
40
|
+
issuer?: string;
|
|
41
|
+
chain?: string;
|
|
42
|
+
rpc?: string;
|
|
43
|
+
privateKey?: string;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
export declare function oracleActivateVerifier(opts: {
|
|
46
|
+
issuer?: string;
|
|
47
|
+
chain?: string;
|
|
48
|
+
rpc?: string;
|
|
49
|
+
privateKey?: string;
|
|
50
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oracle.ts — `ilal oracle`
|
|
3
|
+
*
|
|
4
|
+
* Operator-only commands for managing the CNFIssuer Merkle root and
|
|
5
|
+
* ZK verifier via the timelock mechanism.
|
|
6
|
+
*
|
|
7
|
+
* Merkle root and ZK verifier changes are protected by a 2-step propose → activate
|
|
8
|
+
* timelock (ROOT_DELAY = 48 h, VERIFIER_DELAY = 72 h). Only the contract
|
|
9
|
+
* owner can call these.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* # Step 1 — queue a new root (requires owner key, executes immediately)
|
|
13
|
+
* PRIVATE_KEY=0x... ilal oracle propose-root \
|
|
14
|
+
* --root 0xDEADBEEF... \
|
|
15
|
+
* --issuer 0x319c0...
|
|
16
|
+
*
|
|
17
|
+
* # Step 2 — after ROOT_DELAY (48 h) has elapsed, activate it
|
|
18
|
+
* PRIVATE_KEY=0x... ilal oracle activate-root \
|
|
19
|
+
* --issuer 0x319c0...
|
|
20
|
+
*
|
|
21
|
+
* # Same pattern for the ZK verifier (VERIFIER_DELAY = 72 h)
|
|
22
|
+
* PRIVATE_KEY=0x... ilal oracle propose-verifier --verifier 0x... --issuer 0x...
|
|
23
|
+
* PRIVATE_KEY=0x... ilal oracle activate-verifier --issuer 0x...
|
|
24
|
+
*/
|
|
25
|
+
import { createPublicClient, createWalletClient, http, isAddress, } from "viem";
|
|
26
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
27
|
+
import { base, baseSepolia } from "viem/chains";
|
|
28
|
+
import { fmt, log, header, Spinner, die, dieOnContract } from "../ui.js";
|
|
29
|
+
import { withConfig } from "../config.js";
|
|
30
|
+
const CHAINS = { "8453": base, "84532": baseSepolia };
|
|
31
|
+
const ORACLE_ABI = [
|
|
32
|
+
{
|
|
33
|
+
name: "proposeMerkleRoot", type: "function", stateMutability: "nonpayable",
|
|
34
|
+
inputs: [{ name: "_root", type: "uint256" }], outputs: [],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "activateMerkleRoot", type: "function", stateMutability: "nonpayable",
|
|
38
|
+
inputs: [], outputs: [],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "proposeZKVerifier", type: "function", stateMutability: "nonpayable",
|
|
42
|
+
inputs: [{ name: "_verifier", type: "address" }], outputs: [],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "activateZKVerifier", type: "function", stateMutability: "nonpayable",
|
|
46
|
+
inputs: [], outputs: [],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "pendingRoot", type: "function", stateMutability: "view",
|
|
50
|
+
inputs: [], outputs: [{ type: "uint256" }],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "pendingRootActivatesAt", type: "function", stateMutability: "view",
|
|
54
|
+
inputs: [], outputs: [{ type: "uint256" }],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "pendingZKVerifier", type: "function", stateMutability: "view",
|
|
58
|
+
inputs: [], outputs: [{ type: "address" }],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "pendingVerifierActivatesAt", type: "function", stateMutability: "view",
|
|
62
|
+
inputs: [], outputs: [{ type: "uint256" }],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "ROOT_DELAY", type: "function", stateMutability: "view",
|
|
66
|
+
inputs: [], outputs: [{ type: "uint256" }],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "VERIFIER_DELAY", type: "function", stateMutability: "view",
|
|
70
|
+
inputs: [], outputs: [{ type: "uint256" }],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "merkleRoot", type: "function", stateMutability: "view",
|
|
74
|
+
inputs: [], outputs: [{ type: "uint256" }],
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
function txUrl(chain, hash) {
|
|
78
|
+
const baseUrl = chain.blockExplorers?.default?.url;
|
|
79
|
+
return baseUrl ? `${baseUrl}/tx/${hash}` : undefined;
|
|
80
|
+
}
|
|
81
|
+
function makeClients(cfg, opts) {
|
|
82
|
+
const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
|
|
83
|
+
if (!rawKey)
|
|
84
|
+
die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
|
|
85
|
+
const chain = CHAINS[opts.chain ?? "84532"] ?? baseSepolia;
|
|
86
|
+
const transport = opts.rpc ? http(opts.rpc) : http();
|
|
87
|
+
const account = privateKeyToAccount(rawKey);
|
|
88
|
+
return {
|
|
89
|
+
chain,
|
|
90
|
+
account,
|
|
91
|
+
pubClient: createPublicClient({ chain, transport }),
|
|
92
|
+
walClient: createWalletClient({ account, chain, transport }),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// ─── propose-root ─────────────────────────────────────────────────────────────
|
|
96
|
+
export async function oracleProposeRoot(opts) {
|
|
97
|
+
const cfg = withConfig(opts);
|
|
98
|
+
if (!cfg.issuer)
|
|
99
|
+
die("CNFIssuer address required. Use --issuer or set in .ilal.json");
|
|
100
|
+
if (!isAddress(cfg.issuer))
|
|
101
|
+
die(`Invalid issuer address: ${cfg.issuer}`);
|
|
102
|
+
if (!opts.root)
|
|
103
|
+
die("--root <uint256> required");
|
|
104
|
+
const root = BigInt(opts.root);
|
|
105
|
+
const { chain, account, pubClient, walClient } = makeClients(cfg, opts);
|
|
106
|
+
header("ILAL Oracle — Propose Merkle Root", chain.name);
|
|
107
|
+
log.kv("issuer", fmt.cyan(cfg.issuer));
|
|
108
|
+
log.kv("new root", fmt.gray(root.toString().slice(0, 22) + "…"));
|
|
109
|
+
// Show current root and delay
|
|
110
|
+
const [currentRoot, delay] = await Promise.all([
|
|
111
|
+
pubClient.readContract({ address: cfg.issuer, abi: ORACLE_ABI, functionName: "merkleRoot" }),
|
|
112
|
+
pubClient.readContract({ address: cfg.issuer, abi: ORACLE_ABI, functionName: "ROOT_DELAY" }),
|
|
113
|
+
]);
|
|
114
|
+
log.kv("current root", fmt.gray(currentRoot.toString().slice(0, 22) + "…"));
|
|
115
|
+
log.kv("timelock delay", `${Number(delay) / 3600} hours`);
|
|
116
|
+
log.line();
|
|
117
|
+
const spin = new Spinner("Proposing new Merkle root…").start();
|
|
118
|
+
let txHash;
|
|
119
|
+
try {
|
|
120
|
+
txHash = await walClient.writeContract({
|
|
121
|
+
account,
|
|
122
|
+
address: cfg.issuer,
|
|
123
|
+
abi: ORACLE_ABI,
|
|
124
|
+
functionName: "proposeMerkleRoot",
|
|
125
|
+
args: [root],
|
|
126
|
+
});
|
|
127
|
+
await pubClient.waitForTransactionReceipt({ hash: txHash });
|
|
128
|
+
spin.succeed(`Merkle root proposed ${fmt.gray(fmt.hash(txHash))}`);
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
spin.fail("proposeMerkleRoot failed");
|
|
132
|
+
dieOnContract(e);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const activatesAt = new Date((Date.now() + Number(delay) * 1000));
|
|
136
|
+
log.line();
|
|
137
|
+
log.callout("Root queued", `Activate after ${activatesAt.toISOString()}\n Run: ilal oracle activate-root --issuer ${cfg.issuer}`, "cyan");
|
|
138
|
+
const explorer = txUrl(chain, txHash);
|
|
139
|
+
if (explorer)
|
|
140
|
+
log.kv("explorer", fmt.cyan(explorer));
|
|
141
|
+
console.log();
|
|
142
|
+
}
|
|
143
|
+
// ─── activate-root ────────────────────────────────────────────────────────────
|
|
144
|
+
export async function oracleActivateRoot(opts) {
|
|
145
|
+
const cfg = withConfig(opts);
|
|
146
|
+
if (!cfg.issuer)
|
|
147
|
+
die("CNFIssuer address required. Use --issuer or set in .ilal.json");
|
|
148
|
+
if (!isAddress(cfg.issuer))
|
|
149
|
+
die(`Invalid issuer address: ${cfg.issuer}`);
|
|
150
|
+
const { chain, account, pubClient, walClient } = makeClients(cfg, opts);
|
|
151
|
+
header("ILAL Oracle — Activate Merkle Root", chain.name);
|
|
152
|
+
log.kv("issuer", fmt.cyan(cfg.issuer));
|
|
153
|
+
const [pendingRoot, activatesAt] = await Promise.all([
|
|
154
|
+
pubClient.readContract({ address: cfg.issuer, abi: ORACLE_ABI, functionName: "pendingRoot" }),
|
|
155
|
+
pubClient.readContract({ address: cfg.issuer, abi: ORACLE_ABI, functionName: "pendingRootActivatesAt" }),
|
|
156
|
+
]);
|
|
157
|
+
if (pendingRoot === 0n)
|
|
158
|
+
die("No pending root — run `ilal oracle propose-root` first");
|
|
159
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
160
|
+
if (activatesAt > now) {
|
|
161
|
+
const remaining = Number(activatesAt - now);
|
|
162
|
+
die(`Timelock not elapsed. Activate in ${Math.ceil(remaining / 3600)} hour(s) (${new Date(Number(activatesAt) * 1000).toISOString()})`);
|
|
163
|
+
}
|
|
164
|
+
log.kv("pending root", fmt.cyan(pendingRoot.toString().slice(0, 22) + "…"));
|
|
165
|
+
log.line();
|
|
166
|
+
const spin = new Spinner("Activating Merkle root…").start();
|
|
167
|
+
let txHash;
|
|
168
|
+
try {
|
|
169
|
+
txHash = await walClient.writeContract({
|
|
170
|
+
account,
|
|
171
|
+
address: cfg.issuer,
|
|
172
|
+
abi: ORACLE_ABI,
|
|
173
|
+
functionName: "activateMerkleRoot",
|
|
174
|
+
args: [],
|
|
175
|
+
});
|
|
176
|
+
await pubClient.waitForTransactionReceipt({ hash: txHash });
|
|
177
|
+
spin.succeed(`Merkle root activated ${fmt.gray(fmt.hash(txHash))}`);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
spin.fail("activateMerkleRoot failed");
|
|
181
|
+
dieOnContract(e);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
log.line();
|
|
185
|
+
log.callout("Root live", "New Merkle root is now enforced on all ZK proof verifications", "green");
|
|
186
|
+
const explorer = txUrl(chain, txHash);
|
|
187
|
+
if (explorer)
|
|
188
|
+
log.kv("explorer", fmt.cyan(explorer));
|
|
189
|
+
console.log();
|
|
190
|
+
}
|
|
191
|
+
// ─── propose-verifier ─────────────────────────────────────────────────────────
|
|
192
|
+
export async function oracleProposeVerifier(opts) {
|
|
193
|
+
const cfg = withConfig(opts);
|
|
194
|
+
if (!cfg.issuer)
|
|
195
|
+
die("CNFIssuer address required. Use --issuer or set in .ilal.json");
|
|
196
|
+
if (!isAddress(cfg.issuer))
|
|
197
|
+
die(`Invalid issuer address: ${cfg.issuer}`);
|
|
198
|
+
if (!opts.verifier || !isAddress(opts.verifier))
|
|
199
|
+
die("--verifier <address> required");
|
|
200
|
+
const { chain, account, pubClient, walClient } = makeClients(cfg, opts);
|
|
201
|
+
header("ILAL Oracle — Propose ZK Verifier", chain.name);
|
|
202
|
+
log.kv("issuer", fmt.cyan(cfg.issuer));
|
|
203
|
+
log.kv("new verifier", fmt.cyan(opts.verifier));
|
|
204
|
+
const delay = await pubClient.readContract({
|
|
205
|
+
address: cfg.issuer, abi: ORACLE_ABI, functionName: "VERIFIER_DELAY",
|
|
206
|
+
});
|
|
207
|
+
log.kv("timelock delay", `${Number(delay) / 3600} hours`);
|
|
208
|
+
log.line();
|
|
209
|
+
const spin = new Spinner("Proposing new ZK verifier…").start();
|
|
210
|
+
let txHash;
|
|
211
|
+
try {
|
|
212
|
+
txHash = await walClient.writeContract({
|
|
213
|
+
account,
|
|
214
|
+
address: cfg.issuer,
|
|
215
|
+
abi: ORACLE_ABI,
|
|
216
|
+
functionName: "proposeZKVerifier",
|
|
217
|
+
args: [opts.verifier],
|
|
218
|
+
});
|
|
219
|
+
await pubClient.waitForTransactionReceipt({ hash: txHash });
|
|
220
|
+
spin.succeed(`ZK verifier proposed ${fmt.gray(fmt.hash(txHash))}`);
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
spin.fail("proposeZKVerifier failed");
|
|
224
|
+
dieOnContract(e);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const activatesAt = new Date((Date.now() + Number(delay) * 1000));
|
|
228
|
+
log.line();
|
|
229
|
+
log.callout("Verifier queued", `Activate after ${activatesAt.toISOString()}\n Run: ilal oracle activate-verifier --issuer ${cfg.issuer}`, "cyan");
|
|
230
|
+
const explorer = txUrl(chain, txHash);
|
|
231
|
+
if (explorer)
|
|
232
|
+
log.kv("explorer", fmt.cyan(explorer));
|
|
233
|
+
console.log();
|
|
234
|
+
}
|
|
235
|
+
// ─── activate-verifier ────────────────────────────────────────────────────────
|
|
236
|
+
export async function oracleActivateVerifier(opts) {
|
|
237
|
+
const cfg = withConfig(opts);
|
|
238
|
+
if (!cfg.issuer)
|
|
239
|
+
die("CNFIssuer address required. Use --issuer or set in .ilal.json");
|
|
240
|
+
if (!isAddress(cfg.issuer))
|
|
241
|
+
die(`Invalid issuer address: ${cfg.issuer}`);
|
|
242
|
+
const { chain, account, pubClient, walClient } = makeClients(cfg, opts);
|
|
243
|
+
header("ILAL Oracle — Activate ZK Verifier", chain.name);
|
|
244
|
+
const [pending, activatesAt] = await Promise.all([
|
|
245
|
+
pubClient.readContract({ address: cfg.issuer, abi: ORACLE_ABI, functionName: "pendingZKVerifier" }),
|
|
246
|
+
pubClient.readContract({ address: cfg.issuer, abi: ORACLE_ABI, functionName: "pendingVerifierActivatesAt" }),
|
|
247
|
+
]);
|
|
248
|
+
if (pending === "0x0000000000000000000000000000000000000000")
|
|
249
|
+
die("No pending verifier — run `ilal oracle propose-verifier` first");
|
|
250
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
251
|
+
if (activatesAt > now) {
|
|
252
|
+
const remaining = Number(activatesAt - now);
|
|
253
|
+
die(`Timelock not elapsed. Activate in ${Math.ceil(remaining / 3600)} hour(s)`);
|
|
254
|
+
}
|
|
255
|
+
log.kv("pending verifier", fmt.cyan(pending));
|
|
256
|
+
log.line();
|
|
257
|
+
const spin = new Spinner("Activating ZK verifier…").start();
|
|
258
|
+
let txHash;
|
|
259
|
+
try {
|
|
260
|
+
txHash = await walClient.writeContract({
|
|
261
|
+
account,
|
|
262
|
+
address: cfg.issuer,
|
|
263
|
+
abi: ORACLE_ABI,
|
|
264
|
+
functionName: "activateZKVerifier",
|
|
265
|
+
args: [],
|
|
266
|
+
});
|
|
267
|
+
await pubClient.waitForTransactionReceipt({ hash: txHash });
|
|
268
|
+
spin.succeed(`ZK verifier activated ${fmt.gray(fmt.hash(txHash))}`);
|
|
269
|
+
}
|
|
270
|
+
catch (e) {
|
|
271
|
+
spin.fail("activateZKVerifier failed");
|
|
272
|
+
dieOnContract(e);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
log.line();
|
|
276
|
+
log.callout("Verifier live", "New ZK verifier is now active for all proof verifications", "green");
|
|
277
|
+
const explorer = txUrl(chain, txHash);
|
|
278
|
+
if (explorer)
|
|
279
|
+
log.kv("explorer", fmt.cyan(explorer));
|
|
280
|
+
console.log();
|
|
281
|
+
}
|
package/dist/commands/prove.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* prove.ts — `ilal credential prove`
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Trader command: builds a local Merkle proof, generates a Groth16 ZK proof,
|
|
5
|
+
* then mints or renews the CNF if the issuer's active root already includes it.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* ilal credential prove \
|
|
@@ -10,15 +10,16 @@
|
|
|
10
10
|
* --issuer 0x319c0... \
|
|
11
11
|
* --chain 84532 \
|
|
12
12
|
* --action mint # or renew (default: auto-detect)
|
|
13
|
-
* --update-root # call setMerkleRoot before minting
|
|
14
13
|
* --circuit-dir ./circuits/build
|
|
14
|
+
*
|
|
15
|
+
* Operator root changes live under `ilal oracle propose-root` and
|
|
16
|
+
* `ilal oracle activate-root`.
|
|
15
17
|
*/
|
|
16
18
|
export declare function credentialProve(opts: {
|
|
17
19
|
wallet?: string;
|
|
18
20
|
issuer?: string;
|
|
19
21
|
chain?: string;
|
|
20
22
|
action?: string;
|
|
21
|
-
updateRoot: boolean;
|
|
22
23
|
circuitDir?: string;
|
|
23
24
|
outDir?: string;
|
|
24
25
|
rpc?: string;
|
package/dist/commands/prove.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* prove.ts — `ilal credential prove`
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Trader command: builds a local Merkle proof, generates a Groth16 ZK proof,
|
|
5
|
+
* then mints or renews the CNF if the issuer's active root already includes it.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* ilal credential prove \
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* --issuer 0x319c0... \
|
|
11
11
|
* --chain 84532 \
|
|
12
12
|
* --action mint # or renew (default: auto-detect)
|
|
13
|
-
* --update-root # call setMerkleRoot before minting
|
|
14
13
|
* --circuit-dir ./circuits/build
|
|
14
|
+
*
|
|
15
|
+
* Operator root changes live under `ilal oracle propose-root` and
|
|
16
|
+
* `ilal oracle activate-root`.
|
|
15
17
|
*/
|
|
16
18
|
import { execSync } from "child_process";
|
|
17
19
|
import { mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
@@ -42,11 +44,6 @@ const CNF_ABI = [
|
|
|
42
44
|
inputs: [{ name: "proof", type: "bytes" }, { name: "publicInputs", type: "uint256[]" }],
|
|
43
45
|
outputs: [],
|
|
44
46
|
},
|
|
45
|
-
{
|
|
46
|
-
name: "setMerkleRoot", type: "function", stateMutability: "nonpayable",
|
|
47
|
-
inputs: [{ name: "_root", type: "uint256" }],
|
|
48
|
-
outputs: [],
|
|
49
|
-
},
|
|
50
47
|
{
|
|
51
48
|
name: "isValid", type: "function", stateMutability: "view",
|
|
52
49
|
inputs: [{ name: "wallet", type: "address" }],
|
|
@@ -236,24 +233,6 @@ export async function credentialProve(opts) {
|
|
|
236
233
|
log.kv("expiresAt", fmt.cyan(new Date((Date.now() + 90 * 24 * 3600 * 1000)).toISOString().split("T")[0]));
|
|
237
234
|
log.kv("merkleRoot", fmt.gray(proofResult.merkleRoot.toString().slice(0, 22) + "…"));
|
|
238
235
|
log.line();
|
|
239
|
-
// ── Update merkle root on-chain ────────────────────────────────────────────
|
|
240
|
-
if (opts.updateRoot) {
|
|
241
|
-
const rootSpin = new Spinner("Updating merkleRoot on-chain…").start();
|
|
242
|
-
try {
|
|
243
|
-
const rootHash = await walClient.writeContract({
|
|
244
|
-
address: cfg.issuer,
|
|
245
|
-
abi: CNF_ABI,
|
|
246
|
-
functionName: "setMerkleRoot",
|
|
247
|
-
args: [proofResult.merkleRoot],
|
|
248
|
-
});
|
|
249
|
-
await pubClient.waitForTransactionReceipt({ hash: rootHash });
|
|
250
|
-
rootSpin.succeed(`merkleRoot updated ${fmt.gray(fmt.hash(rootHash))}`);
|
|
251
|
-
}
|
|
252
|
-
catch (e) {
|
|
253
|
-
rootSpin.fail("setMerkleRoot failed");
|
|
254
|
-
dieOnContract(e);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
236
|
// ── Send mint / renew tx ───────────────────────────────────────────────────
|
|
258
237
|
const { proofBytes, publicInputs } = encodeProof(proofResult.proofJson, proofResult.publicJson);
|
|
259
238
|
const fnName = action === "mint" ? "mintWithProof" : "renewWithProof";
|
package/dist/commands/status.js
CHANGED
|
@@ -55,7 +55,8 @@ export async function status(opts) {
|
|
|
55
55
|
log.kv("issuer", fmt.cyan(cfg.issuer));
|
|
56
56
|
if (tokenId === 0n) {
|
|
57
57
|
log.kv("status", fmt.badge("missing", "red"));
|
|
58
|
-
log.command("ilal credential prove --wallet " + cfg.wallet
|
|
58
|
+
log.command("ilal credential prove --wallet " + cfg.wallet);
|
|
59
|
+
console.log(fmt.gray("If root mismatch occurs, ask the issuer to queue and activate the updated root via `ilal oracle`."));
|
|
59
60
|
credentialReady = false;
|
|
60
61
|
}
|
|
61
62
|
else {
|
package/dist/commands/swap.d.ts
CHANGED
package/dist/commands/swap.js
CHANGED
|
@@ -50,6 +50,7 @@ const ROUTER_ABI = [
|
|
|
50
50
|
{ name: "amountSpecified", type: "int256" },
|
|
51
51
|
{ name: "sqrtPriceLimitX96", type: "uint160" },
|
|
52
52
|
] },
|
|
53
|
+
{ name: "minAmountOut", type: "uint256" },
|
|
53
54
|
{ name: "hookData", type: "bytes" },
|
|
54
55
|
],
|
|
55
56
|
outputs: [{ name: "delta", type: "int256" }],
|
|
@@ -250,6 +251,13 @@ export async function swap(opts) {
|
|
|
250
251
|
amountSpecified: -amountIn, // negative = exactIn
|
|
251
252
|
sqrtPriceLimitX96: zeroForOne ? MIN_SQRT_PRICE : MAX_SQRT_PRICE,
|
|
252
253
|
};
|
|
254
|
+
// Slippage: parse --min-amount-out if provided (0 = disabled)
|
|
255
|
+
// We don't know the tokenOut decimals here without another RPC call,
|
|
256
|
+
// so we accept wei (raw bigint) from the flag. The CLI documents this.
|
|
257
|
+
const minAmountOut = opts.minAmountOut ? BigInt(opts.minAmountOut) : 0n;
|
|
258
|
+
if (minAmountOut > 0n) {
|
|
259
|
+
log.kv("min-amount-out", `${fmt.cyan(minAmountOut.toString())} wei (slippage protection on)`);
|
|
260
|
+
}
|
|
253
261
|
// Execute swap
|
|
254
262
|
const txSpin = new Spinner("Sending swap tx…").start();
|
|
255
263
|
let txHash;
|
|
@@ -258,7 +266,7 @@ export async function swap(opts) {
|
|
|
258
266
|
address: cfg.router,
|
|
259
267
|
abi: ROUTER_ABI,
|
|
260
268
|
functionName: "swap",
|
|
261
|
-
args: [poolKey, swapParams, hookData],
|
|
269
|
+
args: [poolKey, swapParams, minAmountOut, hookData],
|
|
262
270
|
value: 0n,
|
|
263
271
|
});
|
|
264
272
|
txSpin.update(`Confirming ${fmt.gray(fmt.hash(txHash))}…`);
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { credentialStatus } from "./commands/credential.js";
|
|
4
4
|
import { credentialProve } from "./commands/prove.js";
|
|
5
|
+
import { oracleProposeRoot, oracleActivateRoot, oracleProposeVerifier, oracleActivateVerifier } from "./commands/oracle.js";
|
|
5
6
|
import { mintCredential, renewCredential } from "./commands/mint.js";
|
|
6
7
|
import { proofMint, proofRenew } from "./commands/proof.js";
|
|
7
8
|
import { sessionSign } from "./commands/session.js";
|
|
@@ -92,7 +93,6 @@ credential
|
|
|
92
93
|
.option("-w, --wallet <address>", "Wallet address to prove eligibility for")
|
|
93
94
|
.option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
|
|
94
95
|
.option("-a, --action <action>", "mint or renew (default: auto-detect)")
|
|
95
|
-
.option("--update-root", "Call setMerkleRoot on-chain before minting (requires owner key)", false)
|
|
96
96
|
.option("--circuit-dir <path>", "Path to circuits/build directory (auto-detected by default)")
|
|
97
97
|
.option("--out-dir <path>", "Directory to write proof/witness files")
|
|
98
98
|
.option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
|
|
@@ -243,6 +243,7 @@ program
|
|
|
243
243
|
.command("swap")
|
|
244
244
|
.description("Execute a compliant token swap through the ILAL channel")
|
|
245
245
|
.requiredOption("--amount-in <amount>", "Input amount in human-readable units (e.g. 100)")
|
|
246
|
+
.option("--min-amount-out <wei>", "Minimum output amount in wei — reverts if pool gives less (default: 0 = off)")
|
|
246
247
|
.option("--token-in <address>", "Token to sell (defaults to tokenA from config)")
|
|
247
248
|
.option("--token-a <address>", "currency0 token address (or set in .ilal.json)")
|
|
248
249
|
.option("--token-b <address>", "currency1 token address (or set in .ilal.json)")
|
|
@@ -260,6 +261,55 @@ program
|
|
|
260
261
|
.action(async (opts) => {
|
|
261
262
|
await swap(opts).catch(err);
|
|
262
263
|
});
|
|
264
|
+
// ─── oracle ───────────────────────────────────────────────────────────────────
|
|
265
|
+
// Operator-only commands — require owner key.
|
|
266
|
+
// Merkle root and ZK verifier changes are timelocked:
|
|
267
|
+
// ROOT_DELAY = 48h, VERIFIER_DELAY = 72h.
|
|
268
|
+
const oracle = program
|
|
269
|
+
.command("oracle")
|
|
270
|
+
.description("Operator commands for managing timelocked Merkle root and ZK verifier");
|
|
271
|
+
oracle
|
|
272
|
+
.command("propose-root")
|
|
273
|
+
.description("Queue a new Merkle root (step 1 of 2 — owner only, ROOT_DELAY = 48 h timelock)")
|
|
274
|
+
.requiredOption("--root <uint256>", "New Merkle root value (decimal or 0x hex)")
|
|
275
|
+
.option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
|
|
276
|
+
.option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
|
|
277
|
+
.option("-r, --rpc <url>", "Custom RPC URL")
|
|
278
|
+
.option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
|
|
279
|
+
.action(async (opts) => {
|
|
280
|
+
await oracleProposeRoot(opts).catch(err);
|
|
281
|
+
});
|
|
282
|
+
oracle
|
|
283
|
+
.command("activate-root")
|
|
284
|
+
.description("Activate the pending Merkle root after the 48-hour timelock has elapsed (step 2 of 2)")
|
|
285
|
+
.option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
|
|
286
|
+
.option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
|
|
287
|
+
.option("-r, --rpc <url>", "Custom RPC URL")
|
|
288
|
+
.option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
|
|
289
|
+
.action(async (opts) => {
|
|
290
|
+
await oracleActivateRoot(opts).catch(err);
|
|
291
|
+
});
|
|
292
|
+
oracle
|
|
293
|
+
.command("propose-verifier")
|
|
294
|
+
.description("Queue a new ZK verifier contract (step 1 of 2 — owner only, VERIFIER_DELAY = 72 h)")
|
|
295
|
+
.requiredOption("--verifier <address>", "New IGroth16Verifier contract address")
|
|
296
|
+
.option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
|
|
297
|
+
.option("-c, --chain <chainId>", "Chain ID", "84532")
|
|
298
|
+
.option("-r, --rpc <url>", "Custom RPC URL")
|
|
299
|
+
.option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
|
|
300
|
+
.action(async (opts) => {
|
|
301
|
+
await oracleProposeVerifier(opts).catch(err);
|
|
302
|
+
});
|
|
303
|
+
oracle
|
|
304
|
+
.command("activate-verifier")
|
|
305
|
+
.description("Activate the pending ZK verifier after the 72-hour timelock has elapsed (step 2 of 2)")
|
|
306
|
+
.option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
|
|
307
|
+
.option("-c, --chain <chainId>", "Chain ID", "84532")
|
|
308
|
+
.option("-r, --rpc <url>", "Custom RPC URL")
|
|
309
|
+
.option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
|
|
310
|
+
.action(async (opts) => {
|
|
311
|
+
await oracleActivateVerifier(opts).catch(err);
|
|
312
|
+
});
|
|
263
313
|
// ─── deploy ───────────────────────────────────────────────────────────────────
|
|
264
314
|
program
|
|
265
315
|
.command("deploy")
|
package/dist/ui.js
CHANGED
|
@@ -169,8 +169,8 @@ const CONTRACT_ERRORS = {
|
|
|
169
169
|
"0x724efe91": "Credential already exists — use `ilal credential prove` to renew",
|
|
170
170
|
"0xe6567cc4": "ZK verifier not set on CNFIssuer — contact the issuer admin",
|
|
171
171
|
"0xd611c318": "ZK proof verification failed — regenerate your proof",
|
|
172
|
-
"0x9dd854d3": "Invalid Merkle root —
|
|
173
|
-
"0x0432f01c": "Merkle root mismatch —
|
|
172
|
+
"0x9dd854d3": "Invalid Merkle root — issuer must queue and activate the updated root with `ilal oracle`",
|
|
173
|
+
"0x0432f01c": "Merkle root mismatch — issuer must queue and activate the updated root with `ilal oracle`",
|
|
174
174
|
"0xddefae28": "Credential already minted for this wallet",
|
|
175
175
|
"0x30cd7471": "Not the contract owner",
|
|
176
176
|
"0xf6f992e7": "Credential has expired — renew it",
|