@ilalv3/cli 0.2.15 → 0.2.17

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,8 +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
+ | `ilal issuer create` | Create issuer standard profile and return `standard_id` |
138
+ | `ilal issuer set-jurisdiction` | Set allowed jurisdictions |
139
+ | `ilal issuer set-type` | Set accredited-only requirement |
140
+ | `ilal issuer get` | Read standard profile and `credentialType` |
141
+ | `ilal issuer attest` | Issuer backend command: create an EAS attestation for a wallet |
137
142
  | `ilal swap` | Compliant swap via ILALRouter with optional `--min-amount-out` |
138
143
  | `ilal pool add-liquidity` | Add liquidity to a compliant pool |
139
144
  | `ilal pool remove-liquidity` | Remove liquidity from a compliant pool |
@@ -146,12 +151,25 @@ ILAL_ARTIFACT_CACHE=/opt/ilal/artifacts/ilal-v1
146
151
  | `ilal session sign` | Sign a standalone SessionToken |
147
152
  | `ilal proof mint` | Mint CNF from existing proof.json + public.json |
148
153
  | `ilal deploy --mock` | Deploy a seeded testnet demo stack with MockEAS, tokens, router, hook, and policy |
149
- | `ilal demo attest` | Create a MockEAS test attestation so a wallet can mint CNF |
154
+ | `ilal demo attest` | Legacy testnet alias for MockEAS attestation |
150
155
  | `ilal demo faucet` | Mint mock demo TOKA/TOKB to a wallet |
151
156
  | `ilal deploy` | Deploy full ILAL contract stack |
152
157
 
153
158
  Session note: ILAL hookData is a one-time EIP-712 authorization with a deadline and nonce. The expensive compliance step is the CNF issuance or renewal; swaps do not verify a fresh ZK proof. Use `ilal session sign` to export hookData, and `ilal swap --hook-data <hex>` to execute with an externally signed authorization.
154
159
 
160
+ ## Issuer standards
161
+
162
+ External issuers can define a compliance standard and use its `standard_id` as the on-chain `credentialType` for pool policy registration:
163
+
164
+ ```bash
165
+ ilal issuer create --standard "Goldfinch Accredited Investor"
166
+ ilal issuer set-jurisdiction --allow US,EU,SG
167
+ ilal issuer set-type --accredited-only true
168
+ ilal issuer get
169
+ ```
170
+
171
+ Profiles are stored in `.ilal-issuer-standards.json`. Pools enforce the returned `standard_id` through `PolicyRegistry.requiredCredentialType`.
172
+
155
173
  ## Configuration
156
174
 
157
175
  The CLI reads `.ilal.json` in the current directory. Run `ilal init` to create it, or pass flags directly:
@@ -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);
@@ -0,0 +1,26 @@
1
+ export declare function issuerCreate(opts: {
2
+ standard: string;
3
+ }): Promise<void>;
4
+ export declare function issuerSetJurisdiction(opts: {
5
+ id?: string;
6
+ allow: string;
7
+ }): Promise<void>;
8
+ export declare function issuerSetType(opts: {
9
+ id?: string;
10
+ accreditedOnly: string | boolean;
11
+ }): Promise<void>;
12
+ export declare function issuerGet(opts: {
13
+ id?: string;
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>;
@@ -0,0 +1,340 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "fs";
2
+ import { resolve } from "path";
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";
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
+ ];
80
+ function storePath() {
81
+ return resolve(process.cwd(), STORE_FILE);
82
+ }
83
+ function loadStore() {
84
+ const path = storePath();
85
+ if (!existsSync(path))
86
+ return { version: 1, standards: {} };
87
+ try {
88
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
89
+ return {
90
+ version: 1,
91
+ latest: parsed.latest,
92
+ standards: parsed.standards ?? {},
93
+ };
94
+ }
95
+ catch {
96
+ die(`Could not parse ${STORE_FILE}. Fix or remove the file and try again.`);
97
+ }
98
+ }
99
+ function saveStore(store) {
100
+ writeFileSync(storePath(), JSON.stringify(store, null, 2) + "\n");
101
+ }
102
+ function normalizeId(id, store) {
103
+ const resolved = id ?? store?.latest;
104
+ if (!resolved)
105
+ die("Standard id required. Pass --id <standard_id> or run `ilal issuer create` first.");
106
+ if (!isHex(resolved) || resolved.length !== 66)
107
+ die("standard_id must be 0x + 32 bytes.");
108
+ return resolved;
109
+ }
110
+ function parseJurisdictions(raw) {
111
+ const list = raw.split(",").map((item) => item.trim()).filter(Boolean);
112
+ if (list.length === 0)
113
+ die("--allow must include at least one jurisdiction, e.g. US,EU,SG");
114
+ return [...new Set(list)];
115
+ }
116
+ function parseBool(raw) {
117
+ if (typeof raw === "boolean")
118
+ return raw;
119
+ const value = raw.trim().toLowerCase();
120
+ if (["true", "1", "yes", "y"].includes(value))
121
+ return true;
122
+ if (["false", "0", "no", "n"].includes(value))
123
+ return false;
124
+ die("--accredited-only must be true or false");
125
+ }
126
+ function printStandard(item) {
127
+ log.kv("standard_id", fmt.cyan(item.id));
128
+ log.kv("standard", item.standard);
129
+ log.kv("jurisdictions", item.allowedJurisdictions.length ? item.allowedJurisdictions.join(", ") : fmt.badge("unset", "yellow"));
130
+ log.kv("accredited only", item.accreditedOnly === null ? fmt.badge("unset", "yellow") : String(item.accreditedOnly));
131
+ log.kv("credentialType", fmt.cyan(item.id));
132
+ log.kv("updated", fmt.gray(item.updatedAt));
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
+ }
154
+ export async function issuerCreate(opts) {
155
+ const standard = opts.standard.trim();
156
+ if (!standard)
157
+ die("--standard <name> required");
158
+ const store = loadStore();
159
+ const now = new Date().toISOString();
160
+ const id = keccak256(stringToBytes(`ILAL_STANDARD_V1:${standard}`));
161
+ const existing = store.standards[id];
162
+ const item = existing ?? {
163
+ id,
164
+ standard,
165
+ allowedJurisdictions: [],
166
+ accreditedOnly: null,
167
+ createdAt: now,
168
+ updatedAt: now,
169
+ };
170
+ item.standard = standard;
171
+ item.updatedAt = now;
172
+ store.standards[id] = item;
173
+ store.latest = id;
174
+ saveStore(store);
175
+ header("Issuer Standard Created");
176
+ log.section("Standard");
177
+ printStandard(item);
178
+ log.line();
179
+ log.callout("Use this id as credentialType", `ilal pool policy set --cred-type ${id}`, "green");
180
+ log.kv("file", fmt.gray(storePath()));
181
+ console.log();
182
+ }
183
+ export async function issuerSetJurisdiction(opts) {
184
+ const store = loadStore();
185
+ const id = normalizeId(opts.id, store);
186
+ const item = store.standards[id];
187
+ if (!item)
188
+ die(`Unknown standard_id ${id}. Run \`ilal issuer create --standard <name>\` first.`);
189
+ item.allowedJurisdictions = parseJurisdictions(opts.allow);
190
+ item.updatedAt = new Date().toISOString();
191
+ store.latest = id;
192
+ saveStore(store);
193
+ header("Issuer Jurisdiction Updated");
194
+ log.section("Standard");
195
+ printStandard(item);
196
+ log.kv("file", fmt.gray(storePath()));
197
+ console.log();
198
+ }
199
+ export async function issuerSetType(opts) {
200
+ const store = loadStore();
201
+ const id = normalizeId(opts.id, store);
202
+ const item = store.standards[id];
203
+ if (!item)
204
+ die(`Unknown standard_id ${id}. Run \`ilal issuer create --standard <name>\` first.`);
205
+ item.accreditedOnly = parseBool(opts.accreditedOnly);
206
+ item.updatedAt = new Date().toISOString();
207
+ store.latest = id;
208
+ saveStore(store);
209
+ header("Issuer Investor Type Updated");
210
+ log.section("Standard");
211
+ printStandard(item);
212
+ log.kv("file", fmt.gray(storePath()));
213
+ console.log();
214
+ }
215
+ export async function issuerGet(opts) {
216
+ const store = loadStore();
217
+ const id = normalizeId(opts.id, store);
218
+ const item = store.standards[id];
219
+ if (!item)
220
+ die(`Unknown standard_id ${id}. Available standards: ${Object.keys(store.standards).length}`);
221
+ header("Issuer Standard");
222
+ log.section("Standard");
223
+ printStandard(item);
224
+ log.line();
225
+ log.info("This CLI profile is the issuer-side descriptor. Pools enforce the matching credentialType on-chain via PolicyRegistry.");
226
+ log.command(`ilal pool policy set --cred-type ${item.id} --issuer <CNFIssuer> --registry <PolicyRegistry> --pool <poolId>`);
227
+ console.log();
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,13 +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 { issuerAttest, issuerCreate, issuerGet, issuerSetJurisdiction, issuerSetType } from "./commands/issuer.js";
16
17
  import { fmt } from "./ui.js";
17
18
  import { COINBASE_SCHEMA_UID } from "./constants.js";
18
19
  const program = new Command();
19
20
  program
20
21
  .name("ilal")
21
22
  .description("ILAL Protocol CLI — Uniswap v4 compliance hook toolkit")
22
- .version("0.2.15")
23
+ .version("0.2.17")
23
24
  .addHelpText("before", `\n ${fmt.bold(fmt.cyan("◆"))} ${fmt.bold("ILAL Protocol")} ${fmt.gray("Uniswap v4 Compliance Hook")}\n`);
24
25
  // ─── init ─────────────────────────────────────────────────────────────────────
25
26
  program
@@ -85,7 +86,7 @@ demoCommand
85
86
  });
86
87
  demoCommand
87
88
  .command("attest")
88
- .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")
89
90
  .requiredOption("-w, --wallet <address>", "Recipient wallet that will mint the CNF")
90
91
  .option("--expires-in-days <days>", "Attestation lifetime in days", "90")
91
92
  .option("-k, --private-key <hex>", "MockEAS owner private key")
@@ -96,6 +97,54 @@ const err = (e) => {
96
97
  console.error(fmt.red(`\nError: ${e instanceof Error ? e.message : String(e)}\n`));
97
98
  process.exit(1);
98
99
  };
100
+ // ─── issuer ──────────────────────────────────────────────────────────────────
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
+ });
118
+ issuer
119
+ .command("create")
120
+ .description("Create an issuer compliance standard profile and return a standard_id")
121
+ .requiredOption("--standard <name>", "Compliance standard name, e.g. Goldfinch Accredited Investor")
122
+ .action(async (opts) => {
123
+ await issuerCreate(opts).catch(err);
124
+ });
125
+ issuer
126
+ .command("set-jurisdiction")
127
+ .description("Set allowed jurisdictions for an issuer standard")
128
+ .option("--id <standard_id>", "Standard id (defaults to latest created standard)")
129
+ .requiredOption("--allow <list>", "Comma-separated jurisdictions, e.g. US,EU,SG")
130
+ .action(async (opts) => {
131
+ await issuerSetJurisdiction(opts).catch(err);
132
+ });
133
+ issuer
134
+ .command("set-type")
135
+ .description("Set investor type requirements for an issuer standard")
136
+ .option("--id <standard_id>", "Standard id (defaults to latest created standard)")
137
+ .requiredOption("--accredited-only <bool>", "true or false")
138
+ .action(async (opts) => {
139
+ await issuerSetType(opts).catch(err);
140
+ });
141
+ issuer
142
+ .command("get")
143
+ .description("Read an issuer standard profile")
144
+ .option("--id <standard_id>", "Standard id (defaults to latest created standard)")
145
+ .action(async (opts) => {
146
+ await issuerGet(opts).catch(err);
147
+ });
99
148
  // ─── credential ───────────────────────────────────────────────────────────────
100
149
  const credential = program.command("credential").description("Manage compliance credentials (CNF)");
101
150
  credential
@@ -136,7 +185,7 @@ credential
136
185
  });
137
186
  credential
138
187
  .command("mint")
139
- .description("Mint a CNF credential using a Coinbase EAS attestation")
188
+ .description("Mint a CNF credential using the issuer-configured EAS schema")
140
189
  .requiredOption("-a, --attestation <uid>", "EAS attestation UID (0x + 64 hex chars)")
141
190
  .option("-i, --issuer <address>", "CNFIssuer contract address (or set in .ilal.json)")
142
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.15",
3
+ "version": "0.2.17",
4
4
  "description": "ILAL Protocol CLI — compliant swaps and credential management for Uniswap v4",
5
5
  "type": "module",
6
6
  "bin": {