@ilalv3/cli 0.1.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/dist/index.js ADDED
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { credentialStatus } from "./commands/credential.js";
4
+ import { credentialProve } from "./commands/prove.js";
5
+ import { mintCredential, renewCredential } from "./commands/mint.js";
6
+ import { proofMint, proofRenew } from "./commands/proof.js";
7
+ import { sessionSign } from "./commands/session.js";
8
+ import { poolPolicySet, poolPolicyGet } from "./commands/pool.js";
9
+ import { deploy } from "./commands/deploy.js";
10
+ import { demo, demoCheck } from "./commands/demo.js";
11
+ import { init } from "./commands/init.js";
12
+ import { status } from "./commands/status.js";
13
+ import { swap } from "./commands/swap.js";
14
+ import { addLiquidity, removeLiquidity } from "./commands/liquidity.js";
15
+ import { fmt } from "./ui.js";
16
+ import { COINBASE_SCHEMA_UID } from "./constants.js";
17
+ const program = new Command();
18
+ program
19
+ .name("ilal")
20
+ .description("ILAL Protocol CLI — Uniswap v4 compliance hook toolkit")
21
+ .version("0.1.0")
22
+ .addHelpText("before", `\n ${fmt.bold(fmt.cyan("◆"))} ${fmt.bold("ILAL Protocol")} ${fmt.gray("Uniswap v4 Compliance Hook")}\n`);
23
+ // ─── init ─────────────────────────────────────────────────────────────────────
24
+ program
25
+ .command("init")
26
+ .description("Create .ilal.json config — save issuer, chain, hook addresses")
27
+ .option("-i, --issuer <address>", "CNFIssuer contract address")
28
+ .option("-H, --hook <address>", "ComplianceHook contract address")
29
+ .option("-R, --registry <address>", "PolicyRegistry contract address")
30
+ .option("--router <address>", "ILALRouter contract address")
31
+ .option("--treasury <address>", "ILAL protocol fee treasury address")
32
+ .option("--token-a <address>", "Demo token A / currency0 address")
33
+ .option("--token-b <address>", "Demo token B / currency1 address")
34
+ .option("--pool-id <bytes32>", "Default Uniswap v4 pool ID")
35
+ .option("--fee <uint24>", "Pool fee tier; 8388608 means dynamic fee")
36
+ .option("--tick-spacing <int24>", "Pool tick spacing")
37
+ .option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
38
+ .option("-r, --rpc <url>", "Custom RPC URL")
39
+ .option("--circuit-dir <path>", "Path to circuits/build directory")
40
+ .option("-f, --force", "Overwrite existing .ilal.json", false)
41
+ .action(async (opts) => {
42
+ await init(opts).catch(err);
43
+ });
44
+ // ─── status ───────────────────────────────────────────────────────────────────
45
+ program
46
+ .command("status")
47
+ .description("Dashboard: credential validity, issuer config, pool policy")
48
+ .option("-w, --wallet <address>", "Wallet address to check")
49
+ .option("-i, --issuer <address>", "CNFIssuer contract address")
50
+ .option("-H, --hook <address>", "ComplianceHook contract address")
51
+ .option("-R, --registry <address>", "PolicyRegistry contract address")
52
+ .option("-p, --pool <bytes32>", "Pool ID to check policy for")
53
+ .option("-c, --chain <chainId>", "Chain ID", "84532")
54
+ .option("-r, --rpc <url>", "Custom RPC URL")
55
+ .action(async (opts) => {
56
+ await status(opts).catch(err);
57
+ });
58
+ // ─── demo ─────────────────────────────────────────────────────────────────────
59
+ const demoCommand = program
60
+ .command("demo")
61
+ .description("Preview and preflight the ILAL institutional DeFi demo")
62
+ .option("--commands", "Show the live command sequence after the preview", false)
63
+ .action(async (opts) => {
64
+ await demo(opts).catch(err);
65
+ });
66
+ demoCommand
67
+ .command("check")
68
+ .description("Check whether the configured live demo can run on-chain")
69
+ .option("-w, --wallet <address>", "Wallet address to check (defaults to PRIVATE_KEY address)")
70
+ .option("-k, --private-key <hex>", "Private key used only to derive the wallet address")
71
+ .action(async (opts) => {
72
+ await demoCheck(opts).catch(err);
73
+ });
74
+ const err = (e) => {
75
+ console.error(fmt.red(`\nError: ${e instanceof Error ? e.message : String(e)}\n`));
76
+ process.exit(1);
77
+ };
78
+ // ─── credential ───────────────────────────────────────────────────────────────
79
+ const credential = program.command("credential").description("Manage compliance credentials (CNF)");
80
+ credential
81
+ .command("status <wallet>")
82
+ .description("Check CNF credential status for a wallet")
83
+ .requiredOption("-i, --issuer <address>", "CNFIssuer contract address")
84
+ .option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "8453")
85
+ .option("-r, --rpc <url>", "Custom RPC URL")
86
+ .action(async (wallet, opts) => {
87
+ await credentialStatus({ wallet, ...opts }).catch(err);
88
+ });
89
+ credential
90
+ .command("prove")
91
+ .description("Generate ZK proof and mint/renew CNF in one step (no shell scripts needed)")
92
+ .option("-w, --wallet <address>", "Wallet address to prove eligibility for")
93
+ .option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
94
+ .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
+ .option("--circuit-dir <path>", "Path to circuits/build directory (auto-detected by default)")
97
+ .option("--out-dir <path>", "Directory to write proof/witness files")
98
+ .option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
99
+ .option("-r, --rpc <url>", "Custom RPC URL")
100
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
101
+ .action(async (opts) => {
102
+ await credentialProve(opts).catch(err);
103
+ });
104
+ credential
105
+ .command("mint")
106
+ .description("Mint a CNF credential using a Coinbase EAS attestation")
107
+ .requiredOption("-a, --attestation <uid>", "EAS attestation UID (0x + 64 hex chars)")
108
+ .requiredOption("-i, --issuer <address>", "CNFIssuer contract address")
109
+ .option("-c, --chain <chainId>", "Chain ID", "8453")
110
+ .option("-r, --rpc <url>", "Custom RPC URL")
111
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
112
+ .option("--simulate", "Verify attestation without sending tx", false)
113
+ .action(async (opts) => {
114
+ await mintCredential(opts).catch(err);
115
+ });
116
+ credential
117
+ .command("renew")
118
+ .description("Renew an existing CNF credential with a fresh EAS attestation")
119
+ .requiredOption("-a, --attestation <uid>", "EAS attestation UID (0x + 64 hex chars)")
120
+ .requiredOption("-i, --issuer <address>", "CNFIssuer contract address")
121
+ .option("-c, --chain <chainId>", "Chain ID", "8453")
122
+ .option("-r, --rpc <url>", "Custom RPC URL")
123
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
124
+ .option("--simulate", "Verify attestation without sending tx", false)
125
+ .action(async (opts) => {
126
+ await renewCredential(opts).catch(err);
127
+ });
128
+ // ─── proof ────────────────────────────────────────────────────────────────────
129
+ const proof = program.command("proof").description("ZK proof credential operations (Phase 4)");
130
+ proof
131
+ .command("mint")
132
+ .description("Mint a CNF using a Groth16 ZK proof (snarkjs format)")
133
+ .requiredOption("-p, --proof <path>", "Path to snarkjs proof.json")
134
+ .requiredOption("-P, --public <path>", "Path to snarkjs public.json")
135
+ .requiredOption("-i, --issuer <address>", "CNFIssuer contract address")
136
+ .option("-c, --chain <chainId>", "Chain ID", "8453")
137
+ .option("-r, --rpc <url>", "Custom RPC URL")
138
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
139
+ .action(async (opts) => {
140
+ await proofMint(opts).catch(err);
141
+ });
142
+ proof
143
+ .command("renew")
144
+ .description("Renew a CNF using a Groth16 ZK proof (snarkjs format)")
145
+ .requiredOption("-p, --proof <path>", "Path to snarkjs proof.json")
146
+ .requiredOption("-P, --public <path>", "Path to snarkjs public.json")
147
+ .requiredOption("-i, --issuer <address>", "CNFIssuer contract address")
148
+ .option("-c, --chain <chainId>", "Chain ID", "8453")
149
+ .option("-r, --rpc <url>", "Custom RPC URL")
150
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
151
+ .action(async (opts) => {
152
+ await proofRenew(opts).catch(err);
153
+ });
154
+ // ─── session ──────────────────────────────────────────────────────────────────
155
+ const session = program.command("session").description("Session token operations");
156
+ session
157
+ .command("sign")
158
+ .description("Sign an EIP-712 session token locally — no ILAL API call")
159
+ .requiredOption("-p, --pool <bytes32>", "Pool ID (bytes32 hex)")
160
+ .requiredOption("-a, --action <action>", "Action: swap | addLiquidity | removeLiquidity")
161
+ .requiredOption("-H, --hook <address>", "ComplianceHook contract address")
162
+ .requiredOption("-i, --issuer <address>", "CNFIssuer contract address")
163
+ .option("-u, --user <address>", "Trader address (defaults to key's address)")
164
+ .option("--caller <address>", "Authorized v4 caller (defaults to user; use ILALRouter address for router flows)")
165
+ .option("-c, --chain <chainId>", "Chain ID", "8453")
166
+ .option("-t, --ttl <seconds>", "Session lifetime in seconds", "600")
167
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
168
+ .action(async (opts) => {
169
+ await sessionSign({ ...opts, ttl: parseInt(opts.ttl, 10) }).catch(err);
170
+ });
171
+ // ─── pool ─────────────────────────────────────────────────────────────────────
172
+ const pool = program.command("pool").description("Pool operator commands");
173
+ const policy = pool.command("policy").description("Pool compliance policy commands");
174
+ policy
175
+ .command("set")
176
+ .description("Register a compliance policy for a pool (pool operator only)")
177
+ .requiredOption("-p, --pool <bytes32>", "Pool ID (bytes32 hex)")
178
+ .requiredOption("-i, --issuer <address>", "CNFIssuer contract address")
179
+ .requiredOption("-R, --registry <address>", "PolicyRegistry contract address")
180
+ .option("-T, --cred-type <bytes32>", "Required credential type", COINBASE_SCHEMA_UID)
181
+ .option("-c, --chain <chainId>", "Chain ID", "8453")
182
+ .option("-r, --rpc <url>", "Custom RPC URL")
183
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
184
+ .action(async (opts) => {
185
+ await poolPolicySet(opts).catch(err);
186
+ });
187
+ policy
188
+ .command("get")
189
+ .description("Read the compliance policy for a pool")
190
+ .requiredOption("-p, --pool <bytes32>", "Pool ID (bytes32 hex)")
191
+ .requiredOption("-R, --registry <address>", "PolicyRegistry contract address")
192
+ .option("-c, --chain <chainId>", "Chain ID", "8453")
193
+ .option("-r, --rpc <url>", "Custom RPC URL")
194
+ .action(async (opts) => {
195
+ await poolPolicyGet(opts).catch(err);
196
+ });
197
+ pool
198
+ .command("add-liquidity")
199
+ .description("Add liquidity to a compliant Uniswap v4 pool through the ILAL channel")
200
+ .requiredOption("--tick-lower <int24>", "Lower tick of position")
201
+ .requiredOption("--tick-upper <int24>", "Upper tick of position")
202
+ .requiredOption("--liquidity <uint128>", "Liquidity amount to add (in raw units)")
203
+ .option("--salt <bytes32>", "Position salt for multiple positions at the same range", "0x0000000000000000000000000000000000000000000000000000000000000000")
204
+ .option("--pool-id <bytes32>", "Pool ID (or set in .ilal.json)")
205
+ .option("--router <address>", "ILALRouter address (or set in .ilal.json)")
206
+ .option("-H, --hook <address>", "ComplianceHook address (or set in .ilal.json)")
207
+ .option("-i, --issuer <address>", "CNFIssuer address (or set in .ilal.json)")
208
+ .option("--token-a <address>", "currency0 token address (or set in .ilal.json)")
209
+ .option("--token-b <address>", "currency1 token address (or set in .ilal.json)")
210
+ .option("--fee <uint24>", "Pool fee tier (default: config or 3000; 8388608=dynamic)")
211
+ .option("--tick-spacing <int24>", "Tick spacing (default: config or 60)")
212
+ .option("-c, --chain <chainId>", "Chain ID", "84532")
213
+ .option("-r, --rpc <url>", "Custom RPC URL")
214
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
215
+ .option("--ttl <seconds>", "Session token lifetime in seconds", "600")
216
+ .action(async (opts) => {
217
+ await addLiquidity(opts).catch(err);
218
+ });
219
+ pool
220
+ .command("remove-liquidity")
221
+ .description("Remove liquidity from a compliant Uniswap v4 pool through the ILAL channel")
222
+ .requiredOption("--tick-lower <int24>", "Lower tick of position")
223
+ .requiredOption("--tick-upper <int24>", "Upper tick of position")
224
+ .requiredOption("--liquidity <uint128>", "Liquidity amount to remove (in raw units)")
225
+ .option("--salt <bytes32>", "Position salt", "0x0000000000000000000000000000000000000000000000000000000000000000")
226
+ .option("--pool-id <bytes32>", "Pool ID (or set in .ilal.json)")
227
+ .option("--router <address>", "ILALRouter address (or set in .ilal.json)")
228
+ .option("-H, --hook <address>", "ComplianceHook address (or set in .ilal.json)")
229
+ .option("-i, --issuer <address>", "CNFIssuer address (or set in .ilal.json)")
230
+ .option("--token-a <address>", "currency0 token address (or set in .ilal.json)")
231
+ .option("--token-b <address>", "currency1 token address (or set in .ilal.json)")
232
+ .option("--fee <uint24>", "Pool fee tier (default: config or 3000; 8388608=dynamic)")
233
+ .option("--tick-spacing <int24>", "Tick spacing (default: config or 60)")
234
+ .option("-c, --chain <chainId>", "Chain ID", "84532")
235
+ .option("-r, --rpc <url>", "Custom RPC URL")
236
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
237
+ .option("--ttl <seconds>", "Session token lifetime in seconds", "600")
238
+ .action(async (opts) => {
239
+ await removeLiquidity(opts).catch(err);
240
+ });
241
+ // ─── swap ─────────────────────────────────────────────────────────────────────
242
+ program
243
+ .command("swap")
244
+ .description("Execute a compliant token swap through the ILAL channel")
245
+ .requiredOption("--amount-in <amount>", "Input amount in human-readable units (e.g. 100)")
246
+ .option("--token-in <address>", "Token to sell (defaults to tokenA from config)")
247
+ .option("--token-a <address>", "currency0 token address (or set in .ilal.json)")
248
+ .option("--token-b <address>", "currency1 token address (or set in .ilal.json)")
249
+ .option("--pool-id <bytes32>", "Pool ID (or set in .ilal.json)")
250
+ .option("--router <address>", "ILALRouter address (or set in .ilal.json)")
251
+ .option("-H, --hook <address>", "ComplianceHook address (or set in .ilal.json)")
252
+ .option("-i, --issuer <address>", "CNFIssuer address (or set in .ilal.json)")
253
+ .option("--fee <uint24>", "Pool fee tier (default: config or 3000; 8388608=dynamic)")
254
+ .option("--tick-spacing <int24>", "Tick spacing (default: config or 60)")
255
+ .option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
256
+ .option("-r, --rpc <url>", "Custom RPC URL")
257
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
258
+ .option("--ttl <seconds>", "Session token lifetime in seconds", "600")
259
+ .option("--simulate", "Sign session without sending tx", false)
260
+ .action(async (opts) => {
261
+ await swap(opts).catch(err);
262
+ });
263
+ // ─── deploy ───────────────────────────────────────────────────────────────────
264
+ program
265
+ .command("deploy")
266
+ .description("Deploy ILAL contracts (PolicyRegistry + CNFIssuer + ComplianceHook)")
267
+ .option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
268
+ .option("-r, --rpc <url>", "Custom RPC URL")
269
+ .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
270
+ .option("--broadcast", "Send transactions (omit for dry run)", false)
271
+ .option("--verify", "Verify contracts on Etherscan/Basescan", false)
272
+ .option("--mock", "Use MockEAS for testnet (Base Sepolia only)", false)
273
+ .option("--wallet-to-seed <address>", "Wallet that receives a seeded test attestation (--mock only)")
274
+ .option("--contracts-dir <path>", "Path to contracts/ directory")
275
+ .action(async (opts) => {
276
+ await deploy(opts).catch(err);
277
+ });
278
+ program.parse();
package/dist/ui.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ export declare const fmt: {
2
+ bold: (s: string) => string;
3
+ dim: (s: string) => string;
4
+ green: (s: string) => string;
5
+ red: (s: string) => string;
6
+ yellow: (s: string) => string;
7
+ cyan: (s: string) => string;
8
+ gray: (s: string) => string;
9
+ blue: (s: string) => string;
10
+ magenta: (s: string) => string;
11
+ addr: (s: string) => string;
12
+ hash: (s: string) => string;
13
+ mono: (s: string) => string;
14
+ percent: (n: number) => string;
15
+ badge: (s: string, tone?: "green" | "yellow" | "red" | "cyan" | "gray") => string;
16
+ };
17
+ export declare class Spinner {
18
+ private timer;
19
+ private frame;
20
+ private text;
21
+ constructor(text: string);
22
+ start(): this;
23
+ update(text: string): this;
24
+ private render;
25
+ succeed(msg?: string): void;
26
+ fail(msg?: string): void;
27
+ stop(): void;
28
+ }
29
+ export declare const log: {
30
+ step: (msg: string) => void;
31
+ ok: (msg: string) => void;
32
+ fail: (msg: string) => void;
33
+ warn: (msg: string) => void;
34
+ info: (msg: string) => void;
35
+ line: () => void;
36
+ gap: () => void;
37
+ section: (title: string, meta?: string) => void;
38
+ end: () => void;
39
+ kv: (key: string, val: string) => void;
40
+ kvdim: (key: string, val: string) => void;
41
+ result: (label: string, value: string, tone?: "green" | "yellow" | "red" | "cyan") => void;
42
+ command: (cmd: string) => void;
43
+ progress: (label: string, percent: number, tone?: "green" | "yellow" | "red" | "cyan") => void;
44
+ callout: (title: string, body: string, tone?: "green" | "yellow" | "red" | "cyan") => void;
45
+ metrics: (items: Array<{
46
+ label: string;
47
+ value: string;
48
+ tone?: "green" | "yellow" | "red" | "cyan" | "gray";
49
+ }>) => void;
50
+ deal: (items: Array<{
51
+ label: string;
52
+ value: string;
53
+ note?: string;
54
+ tone?: "green" | "yellow" | "red" | "cyan" | "gray";
55
+ }>) => void;
56
+ };
57
+ export declare function header(title: string, subtitle?: string): void;
58
+ export declare function die(msg: string): never;
59
+ export declare function dieOnContract(e: unknown): never;
package/dist/ui.js ADDED
@@ -0,0 +1,218 @@
1
+ // ─── ANSI codes ───────────────────────────────────────────────────────────────
2
+ const C = {
3
+ reset: "\x1b[0m",
4
+ bold: "\x1b[1m",
5
+ dim: "\x1b[2m",
6
+ green: "\x1b[32m",
7
+ red: "\x1b[31m",
8
+ yellow: "\x1b[33m",
9
+ cyan: "\x1b[36m",
10
+ gray: "\x1b[90m",
11
+ blue: "\x1b[34m",
12
+ white: "\x1b[37m",
13
+ magenta: "\x1b[35m",
14
+ clearLine: "\x1b[2K\r",
15
+ };
16
+ const isTTY = process.stdout.isTTY ?? false;
17
+ // ─── Formatters ───────────────────────────────────────────────────────────────
18
+ export const fmt = {
19
+ bold: (s) => `${C.bold}${s}${C.reset}`,
20
+ dim: (s) => `${C.dim}${s}${C.reset}`,
21
+ green: (s) => `${C.green}${s}${C.reset}`,
22
+ red: (s) => `${C.red}${s}${C.reset}`,
23
+ yellow: (s) => `${C.yellow}${s}${C.reset}`,
24
+ cyan: (s) => `${C.cyan}${s}${C.reset}`,
25
+ gray: (s) => `${C.gray}${s}${C.reset}`,
26
+ blue: (s) => `${C.blue}${s}${C.reset}`,
27
+ magenta: (s) => `${C.magenta}${s}${C.reset}`,
28
+ addr: (s) => fmt.cyan(shortHex(s, 6, 4)),
29
+ hash: (s) => fmt.gray(shortHex(s, 10, 6)),
30
+ mono: (s) => fmt.gray(s),
31
+ percent: (n) => `${Math.max(0, Math.min(100, Math.round(n)))}%`,
32
+ badge: (s, tone = "cyan") => {
33
+ const color = tone === "green" ? fmt.green : tone === "yellow" ? fmt.yellow : tone === "red" ? fmt.red : tone === "gray" ? fmt.gray : fmt.cyan;
34
+ return color(`[${s}]`);
35
+ },
36
+ };
37
+ function visibleLength(s) {
38
+ return s.replace(/\x1b\[[0-9;]*m/g, "").length;
39
+ }
40
+ function shortHex(s, head, tail) {
41
+ if (!s || s.length <= head + tail + 1)
42
+ return s;
43
+ return `${s.slice(0, head)}…${s.slice(-tail)}`;
44
+ }
45
+ function padVisible(s, width) {
46
+ return s + " ".repeat(Math.max(0, width - visibleLength(s)));
47
+ }
48
+ // ─── Spinner ──────────────────────────────────────────────────────────────────
49
+ const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
50
+ export class Spinner {
51
+ timer = null;
52
+ frame = 0;
53
+ text;
54
+ constructor(text) {
55
+ this.text = text;
56
+ }
57
+ start() {
58
+ if (!isTTY) {
59
+ process.stdout.write(` ${fmt.gray("›")} ${this.text}\n`);
60
+ return this;
61
+ }
62
+ process.stdout.write("\x1b[?25l"); // hide cursor
63
+ this.render();
64
+ this.timer = setInterval(() => this.render(), 80);
65
+ return this;
66
+ }
67
+ update(text) {
68
+ this.text = text;
69
+ return this;
70
+ }
71
+ render() {
72
+ const frame = fmt.cyan(FRAMES[this.frame % FRAMES.length]);
73
+ process.stdout.write(`${C.clearLine} ${frame} ${this.text}`);
74
+ this.frame++;
75
+ }
76
+ succeed(msg) {
77
+ this.stop();
78
+ console.log(` ${fmt.green("✓")} ${msg ?? this.text}`);
79
+ }
80
+ fail(msg) {
81
+ this.stop();
82
+ console.log(` ${fmt.red("✗")} ${msg ?? this.text}`);
83
+ }
84
+ stop() {
85
+ if (this.timer) {
86
+ clearInterval(this.timer);
87
+ this.timer = null;
88
+ }
89
+ if (isTTY) {
90
+ process.stdout.write(`${C.clearLine}\x1b[?25h`); // show cursor
91
+ }
92
+ }
93
+ }
94
+ // ─── Logger ───────────────────────────────────────────────────────────────────
95
+ export const log = {
96
+ step: (msg) => console.log(` ${fmt.gray("›")} ${msg}`),
97
+ ok: (msg) => console.log(` ${fmt.green("✓")} ${msg}`),
98
+ fail: (msg) => console.log(` ${fmt.red("✗")} ${msg}`),
99
+ warn: (msg) => console.log(` ${fmt.yellow("!")} ${msg}`),
100
+ info: (msg) => console.log(` ${fmt.gray("·")} ${msg}`),
101
+ line: () => console.log(fmt.gray(" " + "─".repeat(64))),
102
+ gap: () => console.log(),
103
+ section: (title, meta) => console.log(` ${fmt.cyan("┌")} ${fmt.bold(title)}${meta ? ` ${fmt.gray(meta)}` : ""}`),
104
+ end: () => console.log(` ${fmt.cyan("└")} ${fmt.gray("done")}`),
105
+ kv: (key, val) => console.log(` ${fmt.gray("│")} ${fmt.gray(padVisible(key, 18))} ${val}`),
106
+ kvdim: (key, val) => console.log(` ${fmt.gray("│")} ${fmt.gray(padVisible(key, 18))} ${fmt.dim(val)}`),
107
+ result: (label, value, tone = "green") => {
108
+ const color = tone === "green" ? fmt.green : tone === "yellow" ? fmt.yellow : tone === "red" ? fmt.red : fmt.cyan;
109
+ console.log(` ${color("●")} ${fmt.bold(label)} ${value}`);
110
+ },
111
+ command: (cmd) => console.log(` ${fmt.gray("$")} ${fmt.cyan(cmd)}`),
112
+ progress: (label, percent, tone = "cyan") => {
113
+ const clamped = Math.max(0, Math.min(100, Math.round(percent)));
114
+ const width = 28;
115
+ const filled = Math.round((clamped / 100) * width);
116
+ const color = tone === "green" ? fmt.green : tone === "yellow" ? fmt.yellow : tone === "red" ? fmt.red : fmt.cyan;
117
+ const bar = color("█".repeat(filled)) + fmt.gray("░".repeat(width - filled));
118
+ console.log(` ${fmt.gray("│")} ${fmt.gray(padVisible(label, 18))} ${bar} ${color(`${clamped}%`)}`);
119
+ },
120
+ callout: (title, body, tone = "cyan") => {
121
+ const color = tone === "green" ? fmt.green : tone === "yellow" ? fmt.yellow : tone === "red" ? fmt.red : fmt.cyan;
122
+ console.log(` ${color("◆")} ${fmt.bold(title)} ${body}`);
123
+ },
124
+ metrics: (items) => {
125
+ const width = 20;
126
+ const cells = items.map((item) => {
127
+ const color = item.tone === "green" ? fmt.green
128
+ : item.tone === "yellow" ? fmt.yellow
129
+ : item.tone === "red" ? fmt.red
130
+ : item.tone === "gray" ? fmt.gray
131
+ : fmt.cyan;
132
+ return `${fmt.gray(item.label)} ${color(fmt.bold(item.value))}`;
133
+ });
134
+ console.log(` ${cells.map((cell) => padVisible(cell, width)).join(fmt.gray("│ "))}`);
135
+ },
136
+ deal: (items) => {
137
+ const width = 22;
138
+ console.log(` ${fmt.cyan("╭")}${fmt.cyan("─".repeat(70))}${fmt.cyan("╮")}`);
139
+ for (const item of items) {
140
+ const color = item.tone === "green" ? fmt.green
141
+ : item.tone === "yellow" ? fmt.yellow
142
+ : item.tone === "red" ? fmt.red
143
+ : item.tone === "gray" ? fmt.gray
144
+ : fmt.cyan;
145
+ const left = fmt.gray(padVisible(item.label, width));
146
+ const right = `${color(fmt.bold(item.value))}${item.note ? ` ${fmt.gray(item.note)}` : ""}`;
147
+ console.log(` ${fmt.cyan("│")} ${left} ${padVisible(right, 46)}${fmt.cyan("│")}`);
148
+ }
149
+ console.log(` ${fmt.cyan("╰")}${fmt.cyan("─".repeat(70))}${fmt.cyan("╯")}`);
150
+ },
151
+ };
152
+ // ─── Header ───────────────────────────────────────────────────────────────────
153
+ export function header(title, subtitle) {
154
+ console.log();
155
+ const brand = `${fmt.bold(fmt.cyan("ILAL"))} ${fmt.gray("Institutional Liquidity Access Layer")}`;
156
+ const slogan = `${fmt.green("Compliance is the hook.")} ${fmt.gray("Just prove it and swap.")}`;
157
+ const heading = `${fmt.bold(title)}${subtitle ? ` ${fmt.badge(subtitle, "gray")}` : ""}`;
158
+ const width = Math.max(70, visibleLength(brand), visibleLength(slogan), visibleLength(heading)) + 2;
159
+ console.log(fmt.cyan(` ╭${"─".repeat(width)}╮`));
160
+ console.log(`${fmt.cyan(" │")} ${padVisible(brand, width - 1)}${fmt.cyan("│")}`);
161
+ console.log(`${fmt.cyan(" │")} ${padVisible(slogan, width - 1)}${fmt.cyan("│")}`);
162
+ console.log(`${fmt.cyan(" ├")}${fmt.cyan("─".repeat(width))}${fmt.cyan("┤")}`);
163
+ console.log(`${fmt.cyan(" │")} ${padVisible(heading, width - 1)}${fmt.cyan("│")}`);
164
+ console.log(fmt.cyan(` ╰${"─".repeat(width)}╯`));
165
+ }
166
+ // ─── Error handling ───────────────────────────────────────────────────────────
167
+ // Known contract error selectors → human-readable messages
168
+ const CONTRACT_ERRORS = {
169
+ "0x724efe91": "Credential already exists — use `ilal credential prove` to renew",
170
+ "0xe6567cc4": "ZK verifier not set on CNFIssuer — contact the issuer admin",
171
+ "0xd611c318": "ZK proof verification failed — regenerate your proof",
172
+ "0x9dd854d3": "Invalid Merkle root — re-run with --update-root",
173
+ "0x0432f01c": "Merkle root mismatch — re-run with --update-root",
174
+ "0xddefae28": "Credential already minted for this wallet",
175
+ "0x30cd7471": "Not the contract owner",
176
+ "0xf6f992e7": "Credential has expired — renew it",
177
+ "0x6773afec": "Invalid public inputs length",
178
+ "0xfa9f081c": "Schema hash mismatch",
179
+ "0xb7e9429b": "Issuer hash mismatch",
180
+ "0x0e917e64": "Wallet hash mismatch — wrong wallet key",
181
+ };
182
+ function parseViemError(e) {
183
+ const msg = e instanceof Error ? e.message : String(e);
184
+ if (process.env["ILAL_DEBUG"]) {
185
+ console.error(e);
186
+ }
187
+ // Extract 4-byte selector from viem error
188
+ const selMatch = msg.match(/0x[0-9a-f]{8}/i);
189
+ if (selMatch) {
190
+ const readable = CONTRACT_ERRORS[selMatch[0].toLowerCase()];
191
+ if (readable)
192
+ return readable;
193
+ }
194
+ // Simplify common viem errors
195
+ if (msg.includes("reverted")) {
196
+ const inner = msg.match(/reverted.*?["']([^"']+)["']/)?.[1];
197
+ if (inner)
198
+ return `Transaction reverted: ${inner}`;
199
+ return "Transaction reverted — check contract state";
200
+ }
201
+ if (msg.includes("insufficient funds"))
202
+ return "Insufficient gas funds in wallet";
203
+ if (msg.includes("nonce"))
204
+ return "Nonce error — try again";
205
+ if (msg.includes("timeout") || msg.includes("ETIMEDOUT"))
206
+ return "RPC timeout — try again or use --rpc with a faster endpoint";
207
+ // Trim the verbose viem stack
208
+ return msg.split("\n")[0].slice(0, 120);
209
+ }
210
+ export function die(msg) {
211
+ console.error();
212
+ console.error(` ${fmt.red("✗")} ${fmt.bold("Error:")} ${msg}`);
213
+ console.error();
214
+ process.exit(1);
215
+ }
216
+ export function dieOnContract(e) {
217
+ die(parseViemError(e));
218
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@ilalv3/cli",
3
+ "version": "0.1.0",
4
+ "description": "ILAL Protocol CLI — compliant swaps and credential management for Uniswap v4",
5
+ "type": "module",
6
+ "bin": {
7
+ "ilal": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "license": "MIT",
14
+ "keywords": [
15
+ "uniswap",
16
+ "uniswap-v4",
17
+ "compliance",
18
+ "kyc",
19
+ "zk-proof",
20
+ "defi",
21
+ "rwa",
22
+ "cli"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/ilal-protocol/ilal"
27
+ },
28
+ "homepage": "https://github.com/ilal-protocol/ilal#readme",
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "dev": "node --loader ts-node/esm src/index.ts",
35
+ "start": "node dist/index.js"
36
+ },
37
+ "dependencies": {
38
+ "@zk-kit/incremental-merkle-tree": "^1.1.0",
39
+ "commander": "^12.0.0",
40
+ "poseidon-lite": "^0.3.0",
41
+ "viem": "^2.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20",
45
+ "ts-node": "^10.9.0",
46
+ "typescript": "^5.4.0"
47
+ }
48
+ }