@ilalv3/cli 0.2.16 → 0.2.18

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
@@ -53,7 +53,7 @@ PRIVATE_KEY=0x... ilal credential mint \
53
53
  --chain 84532
54
54
 
55
55
  # Or, with the MockEAS owner key, create a fresh test attestation:
56
- PRIVATE_KEY=0x... ilal demo attest --wallet 0xYourWallet
56
+ PRIVATE_KEY=0x... ilal issuer attest --wallet 0xYourWallet
57
57
  PRIVATE_KEY=0xYourWalletKey ilal credential mint --attestation <uid>
58
58
 
59
59
  # If the wallet needs more demo tokens:
@@ -132,12 +132,13 @@ ILAL_ARTIFACT_CACHE=/opt/ilal/artifacts/ilal-v1
132
132
  | `ilal status` | Dashboard: credential · issuer config · pool policy |
133
133
  | `ilal credential zk-root` | Operator helper: compute the ZK Merkle root for a demo wallet/expiry |
134
134
  | `ilal credential prove` | Trader flow: hosted/cached ZK artifacts → local proof → mint or renew CNF |
135
- | `ilal credential mint` | Mint CNF via Coinbase EAS attestation |
135
+ | `ilal credential mint` | Mint CNF via the issuer-configured EAS schema |
136
136
  | `ilal credential renew` | Renew CNF via EAS attestation |
137
137
  | `ilal issuer create` | Create issuer standard profile and return `standard_id` |
138
138
  | `ilal issuer set-jurisdiction` | Set allowed jurisdictions |
139
139
  | `ilal issuer set-type` | Set accredited-only requirement |
140
140
  | `ilal issuer get` | Read standard profile and `credentialType` |
141
+ | `ilal issuer attest` | Issuer backend command: create an EAS attestation for a wallet |
141
142
  | `ilal swap` | Compliant swap via ILALRouter with optional `--min-amount-out` |
142
143
  | `ilal pool add-liquidity` | Add liquidity to a compliant pool |
143
144
  | `ilal pool remove-liquidity` | Remove liquidity from a compliant pool |
@@ -150,7 +151,7 @@ ILAL_ARTIFACT_CACHE=/opt/ilal/artifacts/ilal-v1
150
151
  | `ilal session sign` | Sign a standalone SessionToken |
151
152
  | `ilal proof mint` | Mint CNF from existing proof.json + public.json |
152
153
  | `ilal deploy --mock` | Deploy a seeded testnet demo stack with MockEAS, tokens, router, hook, and policy |
153
- | `ilal demo attest` | Create a MockEAS test attestation so a wallet can mint CNF |
154
+ | `ilal demo attest` | Legacy testnet alias for MockEAS attestation |
154
155
  | `ilal demo faucet` | Mint mock demo TOKA/TOKB to a wallet |
155
156
  | `ilal deploy` | Deploy full ILAL contract stack |
156
157
 
@@ -39,9 +39,9 @@ export async function credentialStatus(opts) {
39
39
  console.log();
40
40
  console.log(fmt.bold(" How to get a CNF credential:"));
41
41
  console.log();
42
- console.log(fmt.bold(" Demo path — issuer-created MockEAS attestation"));
42
+ console.log(fmt.bold(" Issuer path — issuer-created EAS attestation"));
43
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)}`);
44
+ console.log(` ${fmt.cyan("PRIVATE_KEY=<issuer-key> ilal issuer attest --wallet " + cfg.wallet)}`);
45
45
  console.log(` ${fmt.gray("2.")} Then mint with your wallet key:`);
46
46
  console.log(` ${fmt.cyan("PRIVATE_KEY=<wallet-key> ilal credential mint --attestation <uid>")}`);
47
47
  console.log();
@@ -444,7 +444,7 @@ export async function demoCheck(opts) {
444
444
  log.command(`ilal status --wallet ${wallet}`);
445
445
  if (!credentialReady) {
446
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}`);
447
+ log.command(`PRIVATE_KEY=<issuer-key> ilal issuer attest --wallet ${wallet}`);
448
448
  log.command("PRIVATE_KEY=<wallet-key> ilal credential mint --attestation <uid>");
449
449
  }
450
450
  log.command(`ilal session sign --pool ${cfg.poolId ?? "<poolId>"} --action swap --hook ${cfg.hook ?? "<hook>"} --issuer ${cfg.issuer ?? "<issuer>"} --caller ${cfg.router ?? "<router>"}`);
@@ -512,7 +512,7 @@ export async function demoAttest(opts) {
512
512
  ], functionName: "trustedAttester" }),
513
513
  ]);
514
514
  if (eas === ZERO)
515
- die("Configured issuer has no EAS/MockEAS path. Use a mock demo issuer or Coinbase EAS attestation.");
515
+ die("Configured issuer has no EAS path. Use an issuer with EAS configured or mint through ZK proof.");
516
516
  const days = BigInt(parseInt(opts.expiresInDays ?? "90", 10));
517
517
  const expiration = BigInt(Math.floor(Date.now() / 1000)) + days * 24n * 60n * 60n;
518
518
  header("ILAL Demo Attestation", chain.name);
@@ -12,3 +12,15 @@ export declare function issuerSetType(opts: {
12
12
  export declare function issuerGet(opts: {
13
13
  id?: string;
14
14
  }): Promise<void>;
15
+ export declare function issuerAttest(opts: {
16
+ wallet: string;
17
+ schema?: string;
18
+ eas?: string;
19
+ issuer?: string;
20
+ expiresInDays?: string;
21
+ data?: string;
22
+ revocable?: boolean;
23
+ chain?: string;
24
+ rpc?: string;
25
+ privateKey?: string;
26
+ }): Promise<void>;
@@ -1,8 +1,82 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "fs";
2
2
  import { resolve } from "path";
3
- import { keccak256, stringToBytes, isHex } from "viem";
4
- import { fmt, header, log, die } from "../ui.js";
3
+ import { createPublicClient, createWalletClient, decodeEventLog, http, isAddress, keccak256, stringToBytes, isHex, } from "viem";
4
+ import { privateKeyToAccount } from "viem/accounts";
5
+ import { base, baseSepolia } from "viem/chains";
6
+ import { fmt, header, log, die, Spinner, dieOnContract, requirePrivateKey } from "../ui.js";
7
+ import { withConfig } from "../config.js";
5
8
  const STORE_FILE = ".ilal-issuer-standards.json";
9
+ const ZERO = "0x0000000000000000000000000000000000000000";
10
+ const CHAINS = { "8453": base, "84532": baseSepolia };
11
+ const CNF_ISSUER_ATTEST_ABI = [
12
+ { name: "eas", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
13
+ { name: "schemaUID", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "bytes32" }] },
14
+ { name: "trustedAttester", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
15
+ ];
16
+ const OWNABLE_ABI = [
17
+ { name: "owner", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
18
+ ];
19
+ const MOCK_EAS_ABI = [
20
+ {
21
+ type: "event",
22
+ name: "AttestationCreated",
23
+ inputs: [
24
+ { name: "uid", type: "bytes32", indexed: true },
25
+ { name: "recipient", type: "address", indexed: true },
26
+ { name: "attester", type: "address", indexed: true },
27
+ ],
28
+ },
29
+ {
30
+ name: "attest",
31
+ type: "function",
32
+ stateMutability: "nonpayable",
33
+ inputs: [
34
+ { name: "schema", type: "bytes32" },
35
+ { name: "recipient", type: "address" },
36
+ { name: "attester", type: "address" },
37
+ { name: "expirationTime", type: "uint64" },
38
+ { name: "data", type: "bytes" },
39
+ ],
40
+ outputs: [{ name: "uid", type: "bytes32" }],
41
+ },
42
+ ];
43
+ const EAS_ABI = [
44
+ {
45
+ type: "event",
46
+ name: "Attested",
47
+ inputs: [
48
+ { name: "recipient", type: "address", indexed: true },
49
+ { name: "attester", type: "address", indexed: true },
50
+ { name: "uid", type: "bytes32", indexed: false },
51
+ { name: "schemaUID", type: "bytes32", indexed: true },
52
+ ],
53
+ },
54
+ {
55
+ name: "attest",
56
+ type: "function",
57
+ stateMutability: "payable",
58
+ inputs: [{
59
+ name: "request",
60
+ type: "tuple",
61
+ components: [
62
+ { name: "schema", type: "bytes32" },
63
+ {
64
+ name: "data",
65
+ type: "tuple",
66
+ components: [
67
+ { name: "recipient", type: "address" },
68
+ { name: "expirationTime", type: "uint64" },
69
+ { name: "revocable", type: "bool" },
70
+ { name: "refUID", type: "bytes32" },
71
+ { name: "data", type: "bytes" },
72
+ { name: "value", type: "uint256" },
73
+ ],
74
+ },
75
+ ],
76
+ }],
77
+ outputs: [{ name: "uid", type: "bytes32" }],
78
+ },
79
+ ];
6
80
  function storePath() {
7
81
  return resolve(process.cwd(), STORE_FILE);
8
82
  }
@@ -57,6 +131,26 @@ function printStandard(item) {
57
131
  log.kv("credentialType", fmt.cyan(item.id));
58
132
  log.kv("updated", fmt.gray(item.updatedAt));
59
133
  }
134
+ function txUrl(chain, hash) {
135
+ const baseUrl = chain.blockExplorers?.default?.url;
136
+ return baseUrl ? `${baseUrl}/tx/${hash}` : undefined;
137
+ }
138
+ function parseHexBytes(raw) {
139
+ if (!raw)
140
+ return "0x";
141
+ if (!isHex(raw))
142
+ die("--data must be hex bytes, e.g. 0x1234");
143
+ return raw;
144
+ }
145
+ async function isMockEAS(client, eas) {
146
+ try {
147
+ await client.readContract({ address: eas, abi: OWNABLE_ABI, functionName: "owner" });
148
+ return true;
149
+ }
150
+ catch {
151
+ return false;
152
+ }
153
+ }
60
154
  export async function issuerCreate(opts) {
61
155
  const standard = opts.standard.trim();
62
156
  if (!standard)
@@ -132,3 +226,115 @@ export async function issuerGet(opts) {
132
226
  log.command(`ilal pool policy set --cred-type ${item.id} --issuer <CNFIssuer> --registry <PolicyRegistry> --pool <poolId>`);
133
227
  console.log();
134
228
  }
229
+ export async function issuerAttest(opts) {
230
+ const cfg = withConfig(opts);
231
+ const rawKey = requirePrivateKey(cfg.privateKey ?? process.env["PRIVATE_KEY"]);
232
+ if (!isAddress(opts.wallet))
233
+ die(`Invalid wallet address: ${opts.wallet}`);
234
+ const chain = CHAINS[cfg.chain ?? "84532"] ?? baseSepolia;
235
+ const account = privateKeyToAccount(rawKey);
236
+ const transport = cfg.rpc ? http(cfg.rpc) : http();
237
+ const client = createPublicClient({ chain, transport });
238
+ const walletClient = createWalletClient({ account, chain, transport });
239
+ let eas = opts.eas;
240
+ let schema = opts.schema;
241
+ let trustedAttester = account.address;
242
+ if (cfg.issuer) {
243
+ if (!isAddress(cfg.issuer))
244
+ die(`Invalid issuer address: ${cfg.issuer}`);
245
+ const [issuerEAS, issuerSchema, issuerAttester] = await Promise.all([
246
+ client.readContract({ address: cfg.issuer, abi: CNF_ISSUER_ATTEST_ABI, functionName: "eas" }),
247
+ client.readContract({ address: cfg.issuer, abi: CNF_ISSUER_ATTEST_ABI, functionName: "schemaUID" }),
248
+ client.readContract({ address: cfg.issuer, abi: CNF_ISSUER_ATTEST_ABI, functionName: "trustedAttester" }),
249
+ ]);
250
+ if (!eas && issuerEAS !== ZERO)
251
+ eas = issuerEAS;
252
+ if (!schema)
253
+ schema = issuerSchema;
254
+ trustedAttester = issuerAttester;
255
+ }
256
+ if (!eas || !isAddress(eas))
257
+ die("EAS contract required. Use --eas <address> or configure an issuer with eas().");
258
+ if (!schema || !isHex(schema) || schema.length !== 66)
259
+ die("Schema UID required. Use --schema <bytes32> or configure an issuer with schemaUID().");
260
+ const days = BigInt(parseInt(opts.expiresInDays ?? "365", 10));
261
+ if (days <= 0n)
262
+ die("--expires-in-days must be greater than 0");
263
+ const expiration = BigInt(Math.floor(Date.now() / 1000)) + days * 24n * 60n * 60n;
264
+ const data = parseHexBytes(opts.data);
265
+ const mock = await isMockEAS(client, eas);
266
+ header("Issuer Attestation", chain.name);
267
+ log.kv("issuer", cfg.issuer ? fmt.addr(cfg.issuer) : fmt.badge("direct EAS", "yellow"));
268
+ log.kv("eas", fmt.addr(eas));
269
+ log.kv("schema", fmt.hash(schema));
270
+ log.kv("recipient", fmt.addr(opts.wallet));
271
+ log.kv("signer", fmt.addr(account.address));
272
+ log.kv("attester", mock ? fmt.addr(trustedAttester) : fmt.addr(account.address));
273
+ log.kv("expires", new Date(Number(expiration) * 1000).toISOString());
274
+ log.kv("mode", mock ? "MockEAS compatibility" : "EAS attest()");
275
+ log.line();
276
+ const spin = new Spinner("Creating issuer attestation…").start();
277
+ let hash;
278
+ try {
279
+ if (mock) {
280
+ hash = await walletClient.writeContract({
281
+ address: eas,
282
+ abi: MOCK_EAS_ABI,
283
+ functionName: "attest",
284
+ args: [schema, opts.wallet, trustedAttester, expiration, data],
285
+ });
286
+ }
287
+ else {
288
+ hash = await walletClient.writeContract({
289
+ address: eas,
290
+ abi: EAS_ABI,
291
+ functionName: "attest",
292
+ args: [{
293
+ schema: schema,
294
+ data: {
295
+ recipient: opts.wallet,
296
+ expirationTime: expiration,
297
+ revocable: opts.revocable ?? true,
298
+ refUID: `0x${"0".repeat(64)}`,
299
+ data,
300
+ value: 0n,
301
+ },
302
+ }],
303
+ value: 0n,
304
+ });
305
+ }
306
+ }
307
+ catch (e) {
308
+ spin.fail("attest failed");
309
+ dieOnContract(e);
310
+ }
311
+ const receipt = await client.waitForTransactionReceipt({ hash });
312
+ spin.succeed(`Attestation tx confirmed ${fmt.gray(fmt.hash(hash))}`);
313
+ let uid;
314
+ for (const logItem of receipt.logs) {
315
+ if (logItem.address.toLowerCase() !== eas.toLowerCase())
316
+ continue;
317
+ try {
318
+ const decoded = decodeEventLog({ abi: mock ? MOCK_EAS_ABI : EAS_ABI, data: logItem.data, topics: logItem.topics });
319
+ if (decoded.eventName === "AttestationCreated" || decoded.eventName === "Attested") {
320
+ uid = decoded.args.uid;
321
+ break;
322
+ }
323
+ }
324
+ catch { }
325
+ }
326
+ log.line();
327
+ if (uid)
328
+ log.kv("attestation", fmt.cyan(uid));
329
+ else
330
+ log.warn("Could not decode attestation UID from logs; inspect the tx in the explorer.");
331
+ log.kv("tx", fmt.gray(hash));
332
+ const explorer = txUrl(chain, hash);
333
+ if (explorer)
334
+ log.kv("explorer", fmt.cyan(explorer));
335
+ if (uid) {
336
+ log.callout("CNF mint path ready", "recipient can mint CNF without issuer involvement", "green");
337
+ log.command(`PRIVATE_KEY=<wallet-key> ilal credential mint --issuer ${cfg.issuer ?? "<CNFIssuer>"} --attestation ${uid} --chain ${chain.id}`);
338
+ }
339
+ console.log();
340
+ }
@@ -197,7 +197,7 @@ async function executeLiquidity(action, opts) {
197
197
  if (tokenId === 0n) {
198
198
  preflightErrors.push("wallet has no CNF credential; mint one before changing liquidity.");
199
199
  if (hasEASPath)
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>`.");
200
+ preflightErrors.push("issuer supports EAS attestation minting: first ask issuer to run `ilal issuer attest --wallet <wallet>`, then run `ilal credential mint --attestation <uid>`.");
201
201
  else if (hasZKPath)
202
202
  preflightErrors.push(`issuer supports ZK minting: run \`ilal credential prove --wallet ${account.address}\`.`);
203
203
  else
@@ -221,7 +221,7 @@ export async function swap(opts) {
221
221
  if (tokenId === 0n) {
222
222
  preflightErrors.push(`wallet has no CNF credential; mint one before trading.`);
223
223
  if (hasEASPath)
224
- 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>`.");
224
+ preflightErrors.push("issuer supports EAS attestation minting: first ask issuer to run `ilal issuer attest --wallet <wallet>`, then run `ilal credential mint --attestation <uid>`.");
225
225
  else if (hasZKPath)
226
226
  preflightErrors.push(`issuer supports ZK minting: run \`ilal credential prove --wallet ${account.address}\`.`);
227
227
  else
package/dist/index.js CHANGED
@@ -13,14 +13,14 @@ import { init } from "./commands/init.js";
13
13
  import { status } from "./commands/status.js";
14
14
  import { swap } from "./commands/swap.js";
15
15
  import { addLiquidity, removeLiquidity } from "./commands/liquidity.js";
16
- import { issuerCreate, issuerGet, issuerSetJurisdiction, issuerSetType } from "./commands/issuer.js";
16
+ import { issuerAttest, issuerCreate, issuerGet, issuerSetJurisdiction, issuerSetType } from "./commands/issuer.js";
17
17
  import { fmt } from "./ui.js";
18
18
  import { COINBASE_SCHEMA_UID } from "./constants.js";
19
19
  const program = new Command();
20
20
  program
21
21
  .name("ilal")
22
22
  .description("ILAL Protocol CLI — Uniswap v4 compliance hook toolkit")
23
- .version("0.2.16")
23
+ .version("0.2.17")
24
24
  .addHelpText("before", `\n ${fmt.bold(fmt.cyan("◆"))} ${fmt.bold("ILAL Protocol")} ${fmt.gray("Uniswap v4 Compliance Hook")}\n`);
25
25
  // ─── init ─────────────────────────────────────────────────────────────────────
26
26
  program
@@ -86,7 +86,7 @@ demoCommand
86
86
  });
87
87
  demoCommand
88
88
  .command("attest")
89
- .description("Create a MockEAS test attestation for a wallet (demo issuer owner only)")
89
+ .description("Legacy testnet alias: create a MockEAS attestation for a wallet")
90
90
  .requiredOption("-w, --wallet <address>", "Recipient wallet that will mint the CNF")
91
91
  .option("--expires-in-days <days>", "Attestation lifetime in days", "90")
92
92
  .option("-k, --private-key <hex>", "MockEAS owner private key")
@@ -99,6 +99,22 @@ const err = (e) => {
99
99
  };
100
100
  // ─── issuer ──────────────────────────────────────────────────────────────────
101
101
  const issuer = program.command("issuer").description("Issuer standard management");
102
+ issuer
103
+ .command("attest")
104
+ .description("Create an issuer EAS attestation for a wallet")
105
+ .requiredOption("-w, --wallet <address>", "Recipient wallet that will mint the CNF")
106
+ .option("--schema <bytes32>", "EAS schema UID (defaults to CNFIssuer.schemaUID)")
107
+ .option("--eas <address>", "EAS contract address (defaults to CNFIssuer.eas)")
108
+ .option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
109
+ .option("--expires-in-days <days>", "Attestation lifetime in days", "365")
110
+ .option("--data <hex>", "Optional attestation payload bytes", "0x")
111
+ .option("--no-revocable", "Create a non-revocable EAS attestation")
112
+ .option("-c, --chain <chainId>", "Chain ID", "84532")
113
+ .option("-r, --rpc <url>", "Custom RPC URL")
114
+ .option("-k, --private-key <hex>", "Issuer attester private key")
115
+ .action(async (opts) => {
116
+ await issuerAttest(opts).catch(err);
117
+ });
102
118
  issuer
103
119
  .command("create")
104
120
  .description("Create an issuer compliance standard profile and return a standard_id")
@@ -169,7 +185,7 @@ credential
169
185
  });
170
186
  credential
171
187
  .command("mint")
172
- .description("Mint a CNF credential using a Coinbase EAS attestation")
188
+ .description("Mint a CNF credential using the issuer-configured EAS schema")
173
189
  .requiredOption("-a, --attestation <uid>", "EAS attestation UID (0x + 64 hex chars)")
174
190
  .option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
175
191
  .option("-c, --chain <chainId>", "Chain ID", "84532")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilalv3/cli",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "ILAL Protocol CLI — compliant swaps and credential management for Uniswap v4",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  "commander": "^12.0.0",
40
40
  "poseidon-lite": "^0.3.0",
41
41
  "snarkjs": "^0.7.6",
42
- "viem": "^2.0.0"
42
+ "viem": "^2.52.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^20",
@@ -47,6 +47,7 @@
47
47
  "typescript": "^5.4.0"
48
48
  },
49
49
  "overrides": {
50
- "underscore": "1.13.8"
50
+ "underscore": "1.13.8",
51
+ "ws": "^8.21.0"
51
52
  }
52
53
  }