@ilalv3/cli 0.2.8 → 0.2.10

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.
@@ -1,6 +1,6 @@
1
1
  export declare function credentialStatus(opts: {
2
2
  wallet: string;
3
- issuer: string;
3
+ issuer?: string;
4
4
  rpc?: string;
5
- chain: string;
5
+ chain?: string;
6
6
  }): Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import { createPublicClient, http, isAddress } from "viem";
2
2
  import { base, baseSepolia } from "viem/chains";
3
3
  import { fmt, log, die } from "../ui.js";
4
+ import { withConfig } from "../config.js";
4
5
  const CNF_ABI = [
5
6
  { name: "isValid", type: "function", stateMutability: "view", inputs: [{ name: "wallet", type: "address" }], outputs: [{ type: "bool" }] },
6
7
  { name: "credentialOf", type: "function", stateMutability: "view", inputs: [{ name: "wallet", type: "address" }], outputs: [{ name: "tokenId", type: "uint256" }] },
@@ -11,30 +12,39 @@ const CHAINS = {
11
12
  "84532": baseSepolia,
12
13
  };
13
14
  export async function credentialStatus(opts) {
14
- if (!isAddress(opts.wallet))
15
- die(`Invalid wallet address: ${opts.wallet}`);
16
- if (!isAddress(opts.issuer))
17
- die(`Invalid issuer address: ${opts.issuer}`);
18
- const chain = CHAINS[opts.chain] ?? baseSepolia;
19
- const transport = opts.rpc ? http(opts.rpc) : http();
15
+ const cfg = withConfig(opts);
16
+ if (!isAddress(cfg.wallet))
17
+ die(`Invalid wallet address: ${cfg.wallet}`);
18
+ if (!cfg.issuer)
19
+ die("CNFIssuer address required. Use --issuer or run `ilal init`.");
20
+ if (!isAddress(cfg.issuer))
21
+ die(`Invalid issuer address: ${cfg.issuer}`);
22
+ const chain = CHAINS[cfg.chain ?? "84532"] ?? baseSepolia;
23
+ const transport = cfg.rpc ? http(cfg.rpc) : http();
20
24
  const client = createPublicClient({ chain, transport });
21
25
  console.log();
22
26
  console.log(fmt.bold(" ILAL Credential Status"));
23
27
  log.line();
24
- log.kv("wallet", opts.wallet);
25
- log.kv("issuer", opts.issuer);
28
+ log.kv("wallet", cfg.wallet);
29
+ log.kv("issuer", cfg.issuer);
26
30
  log.kv("chain", chain.name);
27
31
  log.line();
28
32
  log.step("Querying CNFIssuer on-chain…");
29
33
  const [valid, tokenId] = await Promise.all([
30
- client.readContract({ address: opts.issuer, abi: CNF_ABI, functionName: "isValid", args: [opts.wallet] }),
31
- client.readContract({ address: opts.issuer, abi: CNF_ABI, functionName: "credentialOf", args: [opts.wallet] }),
34
+ client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "isValid", args: [cfg.wallet] }),
35
+ client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "credentialOf", args: [cfg.wallet] }),
32
36
  ]);
33
37
  if (tokenId === 0n) {
34
38
  log.fail("No credential found for this wallet");
35
39
  console.log();
36
40
  console.log(fmt.bold(" How to get a CNF credential:"));
37
41
  console.log();
42
+ console.log(fmt.bold(" Demo path — issuer-created MockEAS attestation"));
43
+ console.log(` ${fmt.gray("1.")} Ask the issuer/operator to run:`);
44
+ console.log(` ${fmt.cyan("PRIVATE_KEY=<issuer-owner> ilal demo attest --wallet " + cfg.wallet)}`);
45
+ console.log(` ${fmt.gray("2.")} Then mint with your wallet key:`);
46
+ console.log(` ${fmt.cyan("PRIVATE_KEY=<wallet-key> ilal credential mint --attestation <uid>")}`);
47
+ console.log();
38
48
  console.log(fmt.bold(" Path A — Coinbase Verifications (EAS)"));
39
49
  console.log(` ${fmt.gray("1.")} Complete KYC at ${fmt.cyan("https://coinbase.com/onchain-verify")}`);
40
50
  console.log(` ${fmt.gray("2.")} Find your attestation UID on EAS Explorer:`);
@@ -49,12 +59,12 @@ export async function credentialStatus(opts) {
49
59
  console.log(` ${fmt.gray("1.")} Issuer/operator adds wallet to the Merkle tree`);
50
60
  console.log(` ${fmt.gray("2.")} Operator queues root: ${fmt.cyan("ilal oracle propose-root --root <newMerkleRoot>")}`);
51
61
  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)}`);
62
+ console.log(` ${fmt.gray("4.")} Trader runs: ${fmt.cyan("ilal credential prove --wallet " + cfg.wallet)}`);
53
63
  console.log();
54
64
  return;
55
65
  }
56
66
  const cred = await client.readContract({
57
- address: opts.issuer,
67
+ address: cfg.issuer,
58
68
  abi: CNF_ABI,
59
69
  functionName: "getCredential",
60
70
  args: [tokenId],
@@ -2,7 +2,7 @@ import { createPublicClient, createWalletClient, decodeEventLog, formatUnits, ht
2
2
  import { privateKeyToAccount } from "viem/accounts";
3
3
  import { base, baseSepolia } from "viem/chains";
4
4
  import { loadConfig } from "../config.js";
5
- import { die, fmt, header, log, Spinner } from "../ui.js";
5
+ import { die, fmt, header, log, Spinner, requirePrivateKey } from "../ui.js";
6
6
  const CHAINS = { "8453": base, "84532": baseSepolia };
7
7
  const POOL_MANAGER = {
8
8
  "84532": "0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408",
@@ -442,6 +442,11 @@ export async function demoCheck(opts) {
442
442
  }
443
443
  if (wallet) {
444
444
  log.command(`ilal status --wallet ${wallet}`);
445
+ if (!credentialReady) {
446
+ log.info("Credential missing: the issuer must create an attestation before the wallet can mint CNF.");
447
+ log.command(`PRIVATE_KEY=<issuer-owner> ilal demo attest --wallet ${wallet}`);
448
+ log.command("PRIVATE_KEY=<wallet-key> ilal credential mint --attestation <uid>");
449
+ }
445
450
  log.command(`ilal session sign --pool ${cfg.poolId ?? "<poolId>"} --action swap --hook ${cfg.hook ?? "<hook>"} --issuer ${cfg.issuer ?? "<issuer>"} --caller ${cfg.router ?? "<router>"}`);
446
451
  }
447
452
  else {
@@ -454,10 +459,7 @@ export async function demoCheck(opts) {
454
459
  export async function demoFaucet(opts) {
455
460
  const cfg = loadConfig();
456
461
  const chain = CHAINS[cfg.chain ?? "84532"] ?? baseSepolia;
457
- const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
458
- if (!rawKey || !isHex(rawKey) || rawKey.length !== 66) {
459
- die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
460
- }
462
+ const rawKey = requirePrivateKey(opts.privateKey ?? process.env["PRIVATE_KEY"]);
461
463
  if (!cfg.tokenA || !cfg.tokenB || !isAddress(cfg.tokenA) || !isAddress(cfg.tokenB)) {
462
464
  die("tokenA/tokenB required. Run `ilal init` with demo token addresses first.");
463
465
  }
@@ -492,10 +494,7 @@ export async function demoFaucet(opts) {
492
494
  export async function demoAttest(opts) {
493
495
  const cfg = loadConfig();
494
496
  const chain = CHAINS[cfg.chain ?? "84532"] ?? baseSepolia;
495
- const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
496
- if (!rawKey || !isHex(rawKey) || rawKey.length !== 66) {
497
- die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
498
- }
497
+ const rawKey = requirePrivateKey(opts.privateKey ?? process.env["PRIVATE_KEY"]);
499
498
  if (!cfg.issuer || !isAddress(cfg.issuer))
500
499
  die("CNFIssuer required. Run `ilal init` first.");
501
500
  if (!isAddress(opts.wallet))
@@ -1,7 +1,7 @@
1
1
  import { execSync } from "child_process";
2
2
  import { existsSync } from "fs";
3
3
  import { resolve } from "path";
4
- import { fmt, log, die } from "../ui.js";
4
+ import { fmt, log, die, requirePrivateKey } from "../ui.js";
5
5
  import { EAS_ADDRESSES, COINBASE_ATTESTER, COINBASE_SCHEMA_UID } from "../constants.js";
6
6
  const POOL_MANAGERS = {
7
7
  "8453": "0x498581ff718922c3f8e6a244956af099b2652b2b", // Base mainnet
@@ -12,9 +12,7 @@ const RPC_URLS = {
12
12
  "84532": "https://sepolia.base.org",
13
13
  };
14
14
  export async function deploy(opts) {
15
- const privateKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
16
- if (!privateKey)
17
- die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
15
+ const privateKey = requirePrivateKey(opts.privateKey ?? process.env["PRIVATE_KEY"]);
18
16
  const chainId = opts.chain;
19
17
  const poolManager = POOL_MANAGERS[chainId];
20
18
  if (!poolManager)
@@ -16,7 +16,7 @@
16
16
  import { createPublicClient, createWalletClient, encodeAbiParameters, formatEther, formatUnits, http, isAddress, isHex, parseAbiParameters, } from "viem";
17
17
  import { privateKeyToAccount } from "viem/accounts";
18
18
  import { base, baseSepolia } from "viem/chains";
19
- import { fmt, log, header, Spinner, die, dieOnContract } from "../ui.js";
19
+ import { fmt, log, header, Spinner, die, dieOnContract, requirePrivateKey } from "../ui.js";
20
20
  import { withConfig } from "../config.js";
21
21
  const CHAINS = { "8453": base, "84532": baseSepolia };
22
22
  // ─── ABIs ─────────────────────────────────────────────────────────────────────
@@ -122,12 +122,13 @@ function allowanceLabel(raw, decimals, symbol) {
122
122
  function secondsSince(startMs) {
123
123
  return `${((Date.now() - startMs) / 1000).toFixed(1)}s`;
124
124
  }
125
+ function defaultUserSalt(address) {
126
+ return `0x000000000000000000000000${address.slice(2)}`;
127
+ }
125
128
  // ─── Shared core ──────────────────────────────────────────────────────────────
126
129
  async function executeLiquidity(action, opts) {
127
130
  const cfg = withConfig(opts);
128
- const rawKey = cfg.privateKey ?? process.env["PRIVATE_KEY"];
129
- if (!rawKey)
130
- die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
131
+ const rawKey = requirePrivateKey(cfg.privateKey ?? process.env["PRIVATE_KEY"]);
131
132
  if (!cfg.router)
132
133
  die("ILALRouter address required. Use --router or set in .ilal.json");
133
134
  if (!cfg.hook)
@@ -161,7 +162,7 @@ async function executeLiquidity(action, opts) {
161
162
  const liquidity = BigInt(opts.liquidity);
162
163
  const fee = parseInt(cfg.fee ?? "3000");
163
164
  const tickSpacing = parseInt(cfg.tickSpacing ?? "60");
164
- const salt = (opts.salt ?? "0x0000000000000000000000000000000000000000000000000000000000000000");
165
+ const salt = (opts.salt ?? defaultUserSalt(account.address));
165
166
  const verb = action === "add" ? "Add" : "Remove";
166
167
  header(`ILAL ${verb} Liquidity`, chain.name);
167
168
  log.kv("router", fmt.cyan(cfg.router));
@@ -170,6 +171,7 @@ async function executeLiquidity(action, opts) {
170
171
  log.kv("tickLower", tickLower.toString());
171
172
  log.kv("tickUpper", tickUpper.toString());
172
173
  log.kv("liquidity", liquidity.toString());
174
+ log.kv("salt", opts.salt ? fmt.hash(salt) : `${fmt.hash(salt)} ${fmt.gray("user-scoped default")}`);
173
175
  log.line();
174
176
  if (liquidity <= 0n) {
175
177
  die("liquidity must be greater than 0. No approval or liquidity transaction was sent.");
@@ -195,7 +197,7 @@ async function executeLiquidity(action, opts) {
195
197
  if (tokenId === 0n) {
196
198
  preflightErrors.push("wallet has no CNF credential; mint one before changing liquidity.");
197
199
  if (hasEASPath)
198
- preflightErrors.push("issuer supports EAS/mock attestation minting: run `ilal credential mint --attestation <uid>`.");
200
+ preflightErrors.push("issuer supports EAS/mock attestation minting: first ask issuer to run `ilal demo attest --wallet <wallet>`, then run `ilal credential mint --attestation <uid>`.");
199
201
  else if (hasZKPath)
200
202
  preflightErrors.push(`issuer supports ZK minting: run \`ilal credential prove --wallet ${account.address}\`.`);
201
203
  else
@@ -1,7 +1,7 @@
1
1
  declare function sendMintTx(mode: "mint" | "renew", opts: {
2
2
  attestation: string;
3
- issuer: string;
4
- chain: string;
3
+ issuer?: string;
4
+ chain?: string;
5
5
  rpc?: string;
6
6
  privateKey?: string;
7
7
  simulate: boolean;
@@ -1,7 +1,8 @@
1
1
  import { createPublicClient, createWalletClient, http, isAddress, isHex, } from "viem";
2
2
  import { privateKeyToAccount } from "viem/accounts";
3
3
  import { base, baseSepolia } from "viem/chains";
4
- import { fmt, log, die } from "../ui.js";
4
+ import { fmt, log, die, Spinner, requirePrivateKey } from "../ui.js";
5
+ import { withConfig } from "../config.js";
5
6
  import { EAS_ADDRESSES, COINBASE_SCHEMA_UID, COINBASE_ATTESTER } from "../constants.js";
6
7
  const CHAINS = { "8453": base, "84532": baseSepolia };
7
8
  const CNF_ISSUER_ABI = [
@@ -32,25 +33,24 @@ const CNF_ISSUER_ABI = [
32
33
  ];
33
34
  const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
34
35
  async function sendMintTx(mode, opts) {
35
- const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
36
- if (!rawKey)
37
- die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
38
- if (!isHex(rawKey) || rawKey.length !== 66)
39
- die("Invalid private key (expected 0x + 32 bytes).");
40
- if (!isAddress(opts.issuer))
41
- die(`Invalid issuer address: ${opts.issuer}`);
36
+ const cfg = withConfig(opts);
37
+ const rawKey = requirePrivateKey(cfg.privateKey ?? process.env["PRIVATE_KEY"]);
38
+ if (!cfg.issuer)
39
+ die("CNFIssuer address required. Use --issuer or run `ilal init`.");
40
+ if (!isAddress(cfg.issuer))
41
+ die(`Invalid issuer address: ${cfg.issuer}`);
42
42
  if (!isHex(opts.attestation) || opts.attestation.length !== 66)
43
43
  die("Attestation UID must be 0x + 32 bytes (64 hex chars).");
44
- const chain = CHAINS[opts.chain] ?? baseSepolia;
44
+ const chain = CHAINS[cfg.chain ?? "84532"] ?? baseSepolia;
45
45
  const account = privateKeyToAccount(rawKey);
46
- const transport = opts.rpc ? http(opts.rpc) : http();
46
+ const transport = cfg.rpc ? http(cfg.rpc) : http();
47
47
  const publicClient = createPublicClient({ chain, transport });
48
48
  const walletClient = createWalletClient({ account, chain, transport });
49
49
  console.log();
50
50
  console.log(fmt.bold(` ILAL Credential ${mode === "mint" ? "Mint" : "Renew"}`));
51
51
  log.line();
52
52
  log.kv("wallet", account.address);
53
- log.kv("issuer", opts.issuer);
53
+ log.kv("issuer", cfg.issuer);
54
54
  log.kv("attestation", opts.attestation);
55
55
  log.kv("chain", chain.name);
56
56
  if (opts.simulate)
@@ -59,9 +59,9 @@ async function sendMintTx(mode, opts) {
59
59
  // Verify EAS attestation exists on-chain before sending tx
60
60
  log.step("Verifying attestation on EAS…");
61
61
  const [issuerEAS, issuerSchema, issuerAttester] = await Promise.all([
62
- publicClient.readContract({ address: opts.issuer, abi: CNF_ISSUER_ABI, functionName: "eas" }),
63
- publicClient.readContract({ address: opts.issuer, abi: CNF_ISSUER_ABI, functionName: "schemaUID" }),
64
- publicClient.readContract({ address: opts.issuer, abi: CNF_ISSUER_ABI, functionName: "trustedAttester" }),
62
+ publicClient.readContract({ address: cfg.issuer, abi: CNF_ISSUER_ABI, functionName: "eas" }),
63
+ publicClient.readContract({ address: cfg.issuer, abi: CNF_ISSUER_ABI, functionName: "schemaUID" }),
64
+ publicClient.readContract({ address: cfg.issuer, abi: CNF_ISSUER_ABI, functionName: "trustedAttester" }),
65
65
  ]);
66
66
  const easAddress = issuerEAS !== ZERO_ADDRESS ? issuerEAS : EAS_ADDRESSES[chain.id];
67
67
  if (!easAddress)
@@ -132,7 +132,7 @@ async function sendMintTx(mode, opts) {
132
132
  }
133
133
  log.step(`Sending ${mode === "mint" ? "mintWithEAS" : "renewWithEAS"} transaction…`);
134
134
  const hash = await walletClient.writeContract({
135
- address: opts.issuer,
135
+ address: cfg.issuer,
136
136
  abi: CNF_ISSUER_ABI,
137
137
  functionName: mode === "mint" ? "mintWithEAS" : "renewWithEAS",
138
138
  args: [opts.attestation],
@@ -144,13 +144,26 @@ async function sendMintTx(mode, opts) {
144
144
  log.ok(fmt.bold(fmt.green(`CNF ${mode === "mint" ? "minted" : "renewed"} successfully`)));
145
145
  log.kv("tx hash", hash);
146
146
  log.kv("block", receipt.blockNumber.toString());
147
- const valid = await publicClient.readContract({
148
- address: opts.issuer,
149
- abi: CNF_ISSUER_ABI,
150
- functionName: "isValid",
151
- args: [account.address],
152
- });
153
- log.kv("isValid()", valid ? fmt.green("true ✓") : fmt.red("false"));
147
+ const validSpin = new Spinner("Waiting for credential validity…").start();
148
+ let valid = false;
149
+ for (let attempt = 0; attempt < 6; attempt++) {
150
+ valid = await publicClient.readContract({
151
+ address: cfg.issuer,
152
+ abi: CNF_ISSUER_ABI,
153
+ functionName: "isValid",
154
+ args: [account.address],
155
+ });
156
+ if (valid)
157
+ break;
158
+ await new Promise((resolve) => setTimeout(resolve, 1000));
159
+ }
160
+ if (valid)
161
+ validSpin.succeed("Credential active");
162
+ else {
163
+ validSpin.succeed("Mint confirmed; credential validity may take a few seconds to appear on this RPC");
164
+ log.info("Run `ilal credential status <wallet>` if this RPC still shows stale state.");
165
+ }
166
+ log.kv("isValid()", valid ? fmt.green("true ✓") : fmt.yellow("pending RPC refresh"));
154
167
  }
155
168
  else {
156
169
  die(`Transaction reverted. Hash: ${hash}`);
@@ -25,7 +25,7 @@
25
25
  import { createPublicClient, createWalletClient, http, isAddress, } from "viem";
26
26
  import { privateKeyToAccount } from "viem/accounts";
27
27
  import { base, baseSepolia } from "viem/chains";
28
- import { fmt, log, header, Spinner, die, dieOnContract } from "../ui.js";
28
+ import { fmt, log, header, Spinner, die, dieOnContract, requirePrivateKey } from "../ui.js";
29
29
  import { withConfig } from "../config.js";
30
30
  const CHAINS = { "8453": base, "84532": baseSepolia };
31
31
  const ORACLE_ABI = [
@@ -79,9 +79,7 @@ function txUrl(chain, hash) {
79
79
  return baseUrl ? `${baseUrl}/tx/${hash}` : undefined;
80
80
  }
81
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.");
82
+ const rawKey = requirePrivateKey(opts.privateKey ?? process.env["PRIVATE_KEY"]);
85
83
  const chain = CHAINS[opts.chain ?? "84532"] ?? baseSepolia;
86
84
  const transport = opts.rpc ? http(opts.rpc) : http();
87
85
  const account = privateKeyToAccount(rawKey);
@@ -1,7 +1,7 @@
1
1
  import { createPublicClient, createWalletClient, http, isAddress, isHex, } from "viem";
2
2
  import { privateKeyToAccount } from "viem/accounts";
3
3
  import { base, baseSepolia } from "viem/chains";
4
- import { fmt, log, header, Spinner, die } from "../ui.js";
4
+ import { fmt, log, header, Spinner, die, requirePrivateKey } from "../ui.js";
5
5
  const CHAINS = { "8453": base, "84532": baseSepolia };
6
6
  const REGISTRY_ABI = [
7
7
  {
@@ -38,11 +38,7 @@ const REGISTRY_ABI = [
38
38
  },
39
39
  ];
40
40
  export async function poolPolicySet(opts) {
41
- const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
42
- if (!rawKey)
43
- die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
44
- if (!isHex(rawKey) || rawKey.length !== 66)
45
- die("Invalid private key.");
41
+ const rawKey = requirePrivateKey(opts.privateKey ?? process.env["PRIVATE_KEY"]);
46
42
  if (!isAddress(opts.issuer))
47
43
  die(`Invalid issuer address: ${opts.issuer}`);
48
44
  if (!isAddress(opts.registry))
@@ -2,7 +2,7 @@ import { readFileSync } from "fs";
2
2
  import { createPublicClient, createWalletClient, encodeAbiParameters, http, isAddress, } from "viem";
3
3
  import { privateKeyToAccount } from "viem/accounts";
4
4
  import { base, baseSepolia } from "viem/chains";
5
- import { fmt, log, die } from "../ui.js";
5
+ import { fmt, log, die, requirePrivateKey } from "../ui.js";
6
6
  const CHAINS = { "8453": base, "84532": baseSepolia };
7
7
  const CNF_ISSUER_ABI = [
8
8
  {
@@ -73,9 +73,7 @@ export async function proofRenew(opts) {
73
73
  await sendProofTx("renew", opts);
74
74
  }
75
75
  async function sendProofTx(mode, opts) {
76
- const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
77
- if (!rawKey)
78
- die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
76
+ const rawKey = requirePrivateKey(opts.privateKey ?? process.env["PRIVATE_KEY"]);
79
77
  if (!isAddress(opts.issuer))
80
78
  die(`Invalid issuer address: ${opts.issuer}`);
81
79
  const chain = CHAINS[opts.chain] ?? baseSepolia;
@@ -26,7 +26,7 @@ import { base, baseSepolia } from "viem/chains";
26
26
  // @ts-ignore — no bundled types for this package
27
27
  import { IncrementalMerkleTree } from "@zk-kit/incremental-merkle-tree";
28
28
  import { poseidon2, poseidon4 } from "poseidon-lite";
29
- import { fmt, log, header, Spinner, die, dieOnContract } from "../ui.js";
29
+ import { fmt, log, header, Spinner, die, dieOnContract, requirePrivateKey } from "../ui.js";
30
30
  import { withConfig } from "../config.js";
31
31
  import { COINBASE_SCHEMA_UID } from "../constants.js";
32
32
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -193,9 +193,7 @@ function encodeProof(proofJson, publicJson) {
193
193
  // ─── Main export ──────────────────────────────────────────────────────────────
194
194
  export async function credentialProve(opts) {
195
195
  const cfg = withConfig(opts);
196
- const rawKey = cfg.privateKey ?? process.env["PRIVATE_KEY"];
197
- if (!rawKey)
198
- die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
196
+ const rawKey = requirePrivateKey(cfg.privateKey ?? process.env["PRIVATE_KEY"]);
199
197
  if (!cfg.wallet)
200
198
  die("Wallet address required. Use --wallet or set issuer in .ilal.json");
201
199
  if (!cfg.issuer)
@@ -1,7 +1,7 @@
1
1
  import { createWalletClient, encodeAbiParameters, http, isAddress, isHex, parseAbiParameters, } from "viem";
2
2
  import { privateKeyToAccount } from "viem/accounts";
3
3
  import { base, baseSepolia } from "viem/chains";
4
- import { fmt, log, header, die } from "../ui.js";
4
+ import { fmt, log, header, die, requirePrivateKey } from "../ui.js";
5
5
  import { withConfig } from "../config.js";
6
6
  const CHAINS = {
7
7
  "8453": base,
@@ -30,11 +30,7 @@ const HOOK_DATA_ABI = parseAbiParameters([
30
30
  export async function sessionSign(opts) {
31
31
  const cfg = withConfig({ chain: opts.chain, hook: opts.hook, issuer: opts.issuer });
32
32
  // Resolve private key
33
- const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
34
- if (!rawKey)
35
- die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
36
- if (!isHex(rawKey) || rawKey.length !== 66)
37
- die("Invalid private key format (expected 0x + 32 bytes).");
33
+ const rawKey = requirePrivateKey(opts.privateKey ?? process.env["PRIVATE_KEY"]);
38
34
  const account = privateKeyToAccount(rawKey);
39
35
  const user = (opts.user ?? account.address);
40
36
  const pool = opts.pool ?? cfg.poolId;
@@ -21,7 +21,7 @@
21
21
  import { createPublicClient, createWalletClient, decodeAbiParameters, encodeAbiParameters, formatEther, formatUnits, http, isAddress, isHex, parseAbiParameters, parseUnits, } from "viem";
22
22
  import { privateKeyToAccount } from "viem/accounts";
23
23
  import { base, baseSepolia } from "viem/chains";
24
- import { fmt, log, header, Spinner, die, dieOnContract } from "../ui.js";
24
+ import { fmt, log, header, Spinner, die, dieOnContract, requirePrivateKey } from "../ui.js";
25
25
  import { withConfig } from "../config.js";
26
26
  const CHAINS = { "8453": base, "84532": baseSepolia };
27
27
  // ─── ABIs ─────────────────────────────────────────────────────────────────────
@@ -127,9 +127,7 @@ function secondsSince(startMs) {
127
127
  // ─── Main export ──────────────────────────────────────────────────────────────
128
128
  export async function swap(opts) {
129
129
  const cfg = withConfig(opts);
130
- const rawKey = cfg.privateKey ?? process.env["PRIVATE_KEY"];
131
- if (!rawKey)
132
- die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
130
+ const rawKey = requirePrivateKey(cfg.privateKey ?? process.env["PRIVATE_KEY"]);
133
131
  if (!cfg.router)
134
132
  die("ILALRouter address required. Use --router or set in .ilal.json");
135
133
  if (!cfg.hook)
@@ -211,7 +209,7 @@ export async function swap(opts) {
211
209
  if (tokenId === 0n) {
212
210
  preflightErrors.push(`wallet has no CNF credential; mint one before trading.`);
213
211
  if (hasEASPath)
214
- preflightErrors.push("issuer supports EAS/mock attestation minting: run `ilal credential mint --attestation <uid>`.");
212
+ preflightErrors.push("issuer supports EAS/mock attestation minting: first ask issuer to run `ilal demo attest --wallet <wallet>`, then run `ilal credential mint --attestation <uid>`.");
215
213
  else if (hasZKPath)
216
214
  preflightErrors.push(`issuer supports ZK minting: run \`ilal credential prove --wallet ${account.address}\`.`);
217
215
  else
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.8")
22
+ .version("0.2.10")
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
@@ -99,8 +99,8 @@ const credential = program.command("credential").description("Manage compliance
99
99
  credential
100
100
  .command("status <wallet>")
101
101
  .description("Check CNF credential status for a wallet")
102
- .requiredOption("-i, --issuer <address>", "CNFIssuer contract address")
103
- .option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "8453")
102
+ .option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
103
+ .option("-c, --chain <chainId>", "Chain ID (8453=Base, 84532=Base Sepolia)", "84532")
104
104
  .option("-r, --rpc <url>", "Custom RPC URL")
105
105
  .action(async (wallet, opts) => {
106
106
  await credentialStatus({ wallet, ...opts }).catch(err);
@@ -133,8 +133,8 @@ credential
133
133
  .command("mint")
134
134
  .description("Mint a CNF credential using a Coinbase EAS attestation")
135
135
  .requiredOption("-a, --attestation <uid>", "EAS attestation UID (0x + 64 hex chars)")
136
- .requiredOption("-i, --issuer <address>", "CNFIssuer contract address")
137
- .option("-c, --chain <chainId>", "Chain ID", "8453")
136
+ .option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
137
+ .option("-c, --chain <chainId>", "Chain ID", "84532")
138
138
  .option("-r, --rpc <url>", "Custom RPC URL")
139
139
  .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
140
140
  .option("--simulate", "Verify attestation without sending tx", false)
@@ -145,8 +145,8 @@ credential
145
145
  .command("renew")
146
146
  .description("Renew an existing CNF credential with a fresh EAS attestation")
147
147
  .requiredOption("-a, --attestation <uid>", "EAS attestation UID (0x + 64 hex chars)")
148
- .requiredOption("-i, --issuer <address>", "CNFIssuer contract address")
149
- .option("-c, --chain <chainId>", "Chain ID", "8453")
148
+ .option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
149
+ .option("-c, --chain <chainId>", "Chain ID", "84532")
150
150
  .option("-r, --rpc <url>", "Custom RPC URL")
151
151
  .option("-k, --private-key <hex>", "Private key (or set PRIVATE_KEY env var)")
152
152
  .option("--simulate", "Verify attestation without sending tx", false)
@@ -228,7 +228,7 @@ pool
228
228
  .requiredOption("--tick-lower <int24>", "Lower tick of position")
229
229
  .requiredOption("--tick-upper <int24>", "Upper tick of position")
230
230
  .requiredOption("--liquidity <uint128>", "Liquidity amount to add (in raw units)")
231
- .option("--salt <bytes32>", "Position salt for multiple positions at the same range", "0x0000000000000000000000000000000000000000000000000000000000000000")
231
+ .option("--salt <bytes32>", "Position salt for multiple positions at the same range (defaults to user-scoped salt)")
232
232
  .option("--pool-id <bytes32>", "Pool ID (or set in .ilal.json)")
233
233
  .option("--router <address>", "ILALRouter address (or set in .ilal.json)")
234
234
  .option("-H, --hook <address>", "ComplianceHook address (or set in .ilal.json)")
@@ -250,7 +250,7 @@ pool
250
250
  .requiredOption("--tick-lower <int24>", "Lower tick of position")
251
251
  .requiredOption("--tick-upper <int24>", "Upper tick of position")
252
252
  .requiredOption("--liquidity <uint128>", "Liquidity amount to remove (in raw units)")
253
- .option("--salt <bytes32>", "Position salt", "0x0000000000000000000000000000000000000000000000000000000000000000")
253
+ .option("--salt <bytes32>", "Position salt (defaults to user-scoped salt)")
254
254
  .option("--pool-id <bytes32>", "Pool ID (or set in .ilal.json)")
255
255
  .option("--router <address>", "ILALRouter address (or set in .ilal.json)")
256
256
  .option("-H, --hook <address>", "ComplianceHook address (or set in .ilal.json)")
package/dist/ui.d.ts CHANGED
@@ -56,4 +56,5 @@ export declare const log: {
56
56
  };
57
57
  export declare function header(title: string, subtitle?: string): void;
58
58
  export declare function die(msg: string): never;
59
+ export declare function requirePrivateKey(rawKey?: string): `0x${string}`;
59
60
  export declare function dieOnContract(e: unknown): never;
package/dist/ui.js CHANGED
@@ -213,6 +213,19 @@ export function die(msg) {
213
213
  console.error();
214
214
  process.exit(1);
215
215
  }
216
+ export function requirePrivateKey(rawKey) {
217
+ const key = rawKey?.trim();
218
+ if (!key) {
219
+ die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
220
+ }
221
+ if (/^[0-9a-fA-F]{64}$/.test(key)) {
222
+ die("Private key must include the 0x prefix. Example: PRIVATE_KEY=0x...");
223
+ }
224
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
225
+ die("Private key must be 32-byte hex and include the 0x prefix. Example: PRIVATE_KEY=0x...");
226
+ }
227
+ return key;
228
+ }
216
229
  export function dieOnContract(e) {
217
230
  die(parseViemError(e));
218
231
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilalv3/cli",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "ILAL Protocol CLI — compliant swaps and credential management for Uniswap v4",
5
5
  "type": "module",
6
6
  "bin": {