@ilalv3/cli 0.2.10 → 0.2.11

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 CHANGED
@@ -94,7 +94,35 @@ PRIVATE_KEY=0x... ilal credential prove \
94
94
  --expires-at 1800000000
95
95
  ```
96
96
 
97
- Generates a Groth16 proof locally (~5s), verifies it on-chain, and mints/renews your CNF without revealing identity. If the Merkle root does not match, the issuer/operator must queue the updated root with `ilal oracle propose-root --root <newRoot>` and activate it after the timelock.
97
+ The CLI automatically prepares proving artifacts:
98
+
99
+ 1. Use `--circuit-dir` when a local `circuits/build` directory exists.
100
+ 2. Otherwise use the local cache at `~/.ilal/artifacts/ilal-v1`.
101
+ 3. If the cache is empty, download hosted artifacts from the ILAL release CDN.
102
+
103
+ No institution needs to compile Circom in its backend. The flow 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.
104
+
105
+ Advanced artifact controls:
106
+
107
+ ```bash
108
+ # Use a custom enterprise artifact mirror
109
+ PRIVATE_KEY=0x... ilal credential prove \
110
+ --wallet 0xYourWallet \
111
+ --artifact-url https://zk-artifacts.yourdomain.example/ilal-v1
112
+
113
+ # Pre-seeded/offline mode
114
+ PRIVATE_KEY=0x... ilal credential prove \
115
+ --wallet 0xYourWallet \
116
+ --artifact-cache /opt/ilal/artifacts/ilal-v1 \
117
+ --offline
118
+ ```
119
+
120
+ Equivalent environment variables:
121
+
122
+ ```bash
123
+ ILAL_ARTIFACT_BASE_URL=https://zk-artifacts.yourdomain.example/ilal-v1
124
+ ILAL_ARTIFACT_CACHE=/opt/ilal/artifacts/ilal-v1
125
+ ```
98
126
 
99
127
  ## Command reference
100
128
 
@@ -103,7 +131,7 @@ Generates a Groth16 proof locally (~5s), verifies it on-chain, and mints/renews
103
131
  | `ilal init` | Create `.ilal.json` with contract addresses |
104
132
  | `ilal status` | Dashboard: credential · issuer config · pool policy |
105
133
  | `ilal credential zk-root` | Operator helper: compute the ZK Merkle root for a demo wallet/expiry |
106
- | `ilal credential prove` | Trader flow: local ZK proof → mint or renew CNF |
134
+ | `ilal credential prove` | Trader flow: hosted/cached ZK artifacts → local proof → mint or renew CNF |
107
135
  | `ilal credential mint` | Mint CNF via Coinbase EAS attestation |
108
136
  | `ilal credential renew` | Renew CNF via EAS attestation |
109
137
  | `ilal swap` | Compliant swap via ILALRouter with optional `--min-amount-out` |
@@ -18,5 +18,7 @@ export declare function init(opts: {
18
18
  chain: string;
19
19
  rpc?: string;
20
20
  circuitDir?: string;
21
+ artifactUrl?: string;
22
+ artifactCache?: string;
21
23
  force: boolean;
22
24
  }): Promise<void>;
@@ -51,6 +51,8 @@ export async function init(opts) {
51
51
  tickSpacing: opts.tickSpacing ?? preset["tickSpacing"],
52
52
  rpc: opts.rpc ?? preset["rpc"],
53
53
  ...(opts.circuitDir ? { circuitDir: opts.circuitDir } : {}),
54
+ ...(opts.artifactUrl ? { artifactUrl: opts.artifactUrl } : {}),
55
+ ...(opts.artifactCache ? { artifactCache: opts.artifactCache } : {}),
54
56
  };
55
57
  // Validate addresses
56
58
  for (const [key, val] of Object.entries(config)) {
@@ -86,6 +88,10 @@ export async function init(opts) {
86
88
  log.kv("tickSpacing", config.tickSpacing);
87
89
  if (config.rpc)
88
90
  log.kv("rpc", config.rpc);
91
+ if (config.artifactUrl)
92
+ log.kv("artifactUrl", config.artifactUrl);
93
+ if (config.artifactCache)
94
+ log.kv("artifactCache", config.artifactCache);
89
95
  log.line();
90
96
  console.log(` ${fmt.gray("You can now run commands without --issuer and --chain flags:")}`);
91
97
  console.log();
@@ -21,6 +21,9 @@ export declare function credentialProve(opts: {
21
21
  chain?: string;
22
22
  action?: string;
23
23
  circuitDir?: string;
24
+ artifactUrl?: string;
25
+ artifactCache?: string;
26
+ offline?: boolean;
24
27
  outDir?: string;
25
28
  rpc?: string;
26
29
  privateKey?: string;
@@ -16,8 +16,11 @@
16
16
  * `ilal oracle activate-root`.
17
17
  */
18
18
  import { execSync } from "child_process";
19
- import { mkdirSync, writeFileSync, readFileSync } from "fs";
19
+ import { createWriteStream, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
20
+ import { homedir } from "os";
20
21
  import { resolve, dirname } from "path";
22
+ import { Readable } from "stream";
23
+ import { pipeline } from "stream/promises";
21
24
  import { fileURLToPath } from "url";
22
25
  import { createPublicClient, createWalletClient, encodeAbiParameters, http, isAddress, keccak256, } from "viem";
23
26
  import { privateKeyToAccount } from "viem/accounts";
@@ -32,6 +35,15 @@ import { COINBASE_SCHEMA_UID } from "../constants.js";
32
35
  const __dirname = dirname(fileURLToPath(import.meta.url));
33
36
  const CHAINS = { "8453": base, "84532": baseSepolia };
34
37
  const DEPTH = 20;
38
+ const DEFAULT_ARTIFACT_URL = "https://unpkg.com/@ilalv3/proving-artifacts@0.1.0";
39
+ const DEFAULT_ARTIFACT_CACHE = resolve(homedir(), ".ilal/artifacts/ilal-v1");
40
+ const PROVING_ARTIFACTS = [
41
+ { asset: "ilal.zkey", rel: "ilal.zkey", minBytes: 50_000_000 },
42
+ { asset: "ilal_vkey.json", rel: "ilal_vkey.json", minBytes: 1_000 },
43
+ { asset: "ilal_js/ilal.wasm", rel: "ilal_js/ilal.wasm", minBytes: 1_000_000 },
44
+ { asset: "ilal_js/generate_witness.js", rel: "ilal_js/generate_witness.js", minBytes: 100 },
45
+ { asset: "ilal_js/witness_calculator.js", rel: "ilal_js/witness_calculator.js", minBytes: 1_000 },
46
+ ];
35
47
  // ─── ABI ──────────────────────────────────────────────────────────────────────
36
48
  const CNF_ABI = [
37
49
  {
@@ -82,26 +94,81 @@ function schemaHash(schemaUID) {
82
94
  const schemaHi = BigInt("0x" + schemaHex.slice(0, 32));
83
95
  return poseidon2([schemaLo, schemaHi]);
84
96
  }
85
- function findCircuitDir(override) {
86
- if (override)
87
- return resolve(override);
97
+ function hasCircuitArtifacts(dir) {
98
+ return PROVING_ARTIFACTS.every((artifact) => {
99
+ const file = resolve(dir, artifact.rel);
100
+ try {
101
+ return existsSync(file) && statSync(file).size >= artifact.minBytes;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ });
107
+ }
108
+ function requireCircuitArtifacts(dir) {
109
+ const resolved = resolve(dir);
110
+ const missing = PROVING_ARTIFACTS.filter((artifact) => {
111
+ const file = resolve(resolved, artifact.rel);
112
+ try {
113
+ return !existsSync(file) || statSync(file).size < artifact.minBytes;
114
+ }
115
+ catch {
116
+ return true;
117
+ }
118
+ });
119
+ if (missing.length > 0) {
120
+ die(`Proving artifacts are incomplete in ${resolved}.\n` +
121
+ ` Missing: ${missing.map((x) => x.rel).join(", ")}\n` +
122
+ " Pass --artifact-url <base-url> to download them, or use --circuit-dir <path>.");
123
+ }
124
+ return resolved;
125
+ }
126
+ async function downloadFile(url, target) {
127
+ const res = await fetch(url);
128
+ if (!res.ok || !res.body) {
129
+ throw new Error(`download failed ${res.status} ${res.statusText}: ${url}`);
130
+ }
131
+ mkdirSync(dirname(target), { recursive: true });
132
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(target));
133
+ }
134
+ async function ensureHostedArtifacts(opts) {
135
+ const cacheDir = resolve(opts.artifactCache ?? DEFAULT_ARTIFACT_CACHE);
136
+ if (hasCircuitArtifacts(cacheDir))
137
+ return cacheDir;
138
+ if (opts.offline) {
139
+ die("Hosted proving artifacts are not cached locally.\n" +
140
+ ` Cache: ${cacheDir}\n` +
141
+ " Re-run without --offline, or pass --circuit-dir <path>.");
142
+ }
143
+ const baseUrl = (opts.artifactUrl ?? DEFAULT_ARTIFACT_URL).replace(/\/+$/, "");
144
+ for (const artifact of PROVING_ARTIFACTS) {
145
+ const target = resolve(cacheDir, artifact.rel);
146
+ if (existsSync(target) && statSync(target).size >= artifact.minBytes)
147
+ continue;
148
+ await downloadFile(`${baseUrl}/${artifact.asset}`, target);
149
+ }
150
+ return requireCircuitArtifacts(cacheDir);
151
+ }
152
+ async function findCircuitDir(opts) {
153
+ if (opts.override) {
154
+ return { dir: requireCircuitArtifacts(opts.override), source: "local" };
155
+ }
88
156
  // Look relative to the CLI package root (cli/ → circuits/build)
89
157
  const candidates = [
90
158
  resolve(__dirname, "../../../../circuits/build"), // dev: cli/src/commands → circuits/build
91
159
  resolve(__dirname, "../../../circuits/build"),
92
160
  resolve(process.cwd(), "circuits/build"),
93
161
  resolve(process.cwd(), "build"),
162
+ resolve(opts.artifactCache ?? DEFAULT_ARTIFACT_CACHE),
94
163
  ];
95
164
  for (const p of candidates) {
96
- try {
97
- readFileSync(resolve(p, "ilal.zkey"));
98
- return p;
165
+ if (hasCircuitArtifacts(p)) {
166
+ const source = resolve(p) === resolve(opts.artifactCache ?? DEFAULT_ARTIFACT_CACHE) ? "cache" : "local";
167
+ return { dir: p, source };
99
168
  }
100
- catch { /* not found */ }
101
169
  }
102
- die("Circuit build directory not found.\n" +
103
- " Run: bash circuits/scripts/compile.sh\n" +
104
- " Or pass: --circuit-dir <path/to/circuits/build>");
170
+ const dir = await ensureHostedArtifacts(opts);
171
+ return { dir, source: "download" };
105
172
  }
106
173
  function resolveExpiresAt(expiresAt) {
107
174
  if (!expiresAt)
@@ -224,11 +291,29 @@ export async function credentialProve(opts) {
224
291
  action = tokenId === 0n ? "mint" : "renew";
225
292
  spin.succeed(`Action: ${fmt.cyan(action)}${tokenId > 0n ? fmt.gray(` (token #${tokenId})`) : ""}`);
226
293
  }
227
- // ── Find circuit build dir ─────────────────────────────────────────────────
228
- const circuitDir = findCircuitDir(cfg.circuitDir);
229
- const outDir = cfg.outDir
230
- ? resolve(cfg.outDir)
231
- : resolve(circuitDir, "../../outputs");
294
+ // ── Prepare hosted/local proving artifacts ────────────────────────────────
295
+ const artifactSpin = new Spinner("Preparing proving artifacts…").start();
296
+ let circuitDir;
297
+ try {
298
+ const artifacts = await findCircuitDir({
299
+ override: cfg.circuitDir,
300
+ artifactUrl: cfg.artifactUrl,
301
+ artifactCache: cfg.artifactCache,
302
+ offline: cfg.offline,
303
+ });
304
+ circuitDir = artifacts.dir;
305
+ const sourceLabel = artifacts.source === "download" ? "downloaded to cache" :
306
+ artifacts.source === "cache" ? "cache" :
307
+ "local";
308
+ artifactSpin.succeed(`Proving artifacts ready (${sourceLabel})`);
309
+ }
310
+ catch (e) {
311
+ artifactSpin.fail("Proving artifacts unavailable");
312
+ die(e instanceof Error ? e.message : String(e));
313
+ }
314
+ const outDir = cfg.outDir ? resolve(cfg.outDir) : resolve(process.cwd(), "outputs");
315
+ log.kv("artifacts", fmt.gray(circuitDir));
316
+ log.kv("proofOut", fmt.gray(outDir));
232
317
  log.line();
233
318
  // ── Generate proof ─────────────────────────────────────────────────────────
234
319
  const spin = new Spinner("Building Merkle tree & generating ZK proof…").start();
package/dist/config.d.ts CHANGED
@@ -20,6 +20,8 @@ export interface ILALConfig {
20
20
  chain?: string;
21
21
  rpc?: string;
22
22
  circuitDir?: string;
23
+ artifactUrl?: string;
24
+ artifactCache?: string;
23
25
  outDir?: string;
24
26
  }
25
27
  export declare function loadConfig(): ILALConfig;
package/dist/config.js CHANGED
@@ -52,6 +52,10 @@ export function loadConfig() {
52
52
  chain: process.env["ILAL_CHAIN"] ?? fileConfig.chain,
53
53
  rpc: process.env["ILAL_RPC"] ?? fileConfig.rpc,
54
54
  circuitDir: process.env["ILAL_CIRCUIT_DIR"] ?? fileConfig.circuitDir,
55
+ artifactUrl: process.env["ILAL_ARTIFACT_BASE_URL"]
56
+ ?? process.env["ILAL_ARTIFACT_URL"]
57
+ ?? fileConfig.artifactUrl,
58
+ artifactCache: process.env["ILAL_ARTIFACT_CACHE"] ?? fileConfig.artifactCache,
55
59
  outDir: process.env["ILAL_OUT_DIR"] ?? fileConfig.outDir,
56
60
  };
57
61
  return _config;
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ const program = new Command();
19
19
  program
20
20
  .name("ilal")
21
21
  .description("ILAL Protocol CLI — Uniswap v4 compliance hook toolkit")
22
- .version("0.2.10")
22
+ .version("0.2.11")
23
23
  .addHelpText("before", `\n ${fmt.bold(fmt.cyan("◆"))} ${fmt.bold("ILAL Protocol")} ${fmt.gray("Uniswap v4 Compliance Hook")}\n`);
24
24
  // ─── init ─────────────────────────────────────────────────────────────────────
25
25
  program
@@ -38,6 +38,8 @@ program
38
38
  .option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
39
39
  .option("-r, --rpc <url>", "Custom RPC URL")
40
40
  .option("--circuit-dir <path>", "Path to circuits/build directory")
41
+ .option("--artifact-url <url>", "Hosted proving artifact base URL")
42
+ .option("--artifact-cache <path>", "Local proving artifact cache directory")
41
43
  .option("-f, --force", "Overwrite existing .ilal.json", false)
42
44
  .action(async (opts) => {
43
45
  await init(opts).catch(err);
@@ -111,7 +113,10 @@ credential
111
113
  .option("-w, --wallet <address>", "Wallet address to prove eligibility for")
112
114
  .option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
113
115
  .option("-a, --action <action>", "mint or renew (default: auto-detect)")
114
- .option("--circuit-dir <path>", "Path to circuits/build directory (auto-detected by default)")
116
+ .option("--circuit-dir <path>", "Path to circuits/build directory (dev/offline override)")
117
+ .option("--artifact-url <url>", "Hosted proving artifact base URL (defaults to ILAL release artifacts)")
118
+ .option("--artifact-cache <path>", "Local proving artifact cache directory (default: ~/.ilal/artifacts/ilal-v1)")
119
+ .option("--offline", "Do not download proving artifacts; require cache or --circuit-dir", false)
115
120
  .option("--out-dir <path>", "Directory to write proof/witness files")
116
121
  .option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
117
122
  .option("-r, --rpc <url>", "Custom RPC URL")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilalv3/cli",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "ILAL Protocol CLI — compliant swaps and credential management for Uniswap v4",
5
5
  "type": "module",
6
6
  "bin": {