@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/LICENSE +201 -0
- package/README.md +99 -0
- package/dist/commands/credential.d.ts +6 -0
- package/dist/commands/credential.js +78 -0
- package/dist/commands/demo.d.ts +7 -0
- package/dist/commands/demo.js +346 -0
- package/dist/commands/deploy.d.ts +10 -0
- package/dist/commands/deploy.js +94 -0
- package/dist/commands/init.d.ts +22 -0
- package/dist/commands/init.js +88 -0
- package/dist/commands/liquidity.d.ts +51 -0
- package/dist/commands/liquidity.js +249 -0
- package/dist/commands/mint.d.ts +11 -0
- package/dist/commands/mint.js +146 -0
- package/dist/commands/pool.d.ts +15 -0
- package/dist/commands/pool.js +115 -0
- package/dist/commands/proof.d.ts +16 -0
- package/dist/commands/proof.js +132 -0
- package/dist/commands/prove.d.ts +26 -0
- package/dist/commands/prove.js +292 -0
- package/dist/commands/session.d.ts +11 -0
- package/dist/commands/session.js +105 -0
- package/dist/commands/status.d.ts +14 -0
- package/dist/commands/status.js +164 -0
- package/dist/commands/swap.d.ts +38 -0
- package/dist/commands/swap.js +284 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.js +75 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +278 -0
- package/dist/ui.d.ts +59 -0
- package/dist/ui.js +218 -0
- package/package.json +48 -0
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
|
+
}
|