@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 +21 -3
- package/dist/commands/credential.js +2 -2
- package/dist/commands/demo.js +2 -2
- package/dist/commands/issuer.d.ts +26 -0
- package/dist/commands/issuer.js +340 -0
- package/dist/commands/liquidity.js +1 -1
- package/dist/commands/swap.js +1 -1
- package/dist/index.js +52 -3
- package/package.json +1 -1
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
|
|
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
|
|
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` |
|
|
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("
|
|
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-
|
|
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();
|
package/dist/commands/demo.js
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
|
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
|
package/dist/commands/swap.js
CHANGED
|
@@ -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
|
|
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.
|
|
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("
|
|
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
|
|
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")
|