@ilalv3/cli 0.2.1 → 0.2.3
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 +49 -15
- package/dist/commands/demo.d.ts +5 -0
- package/dist/commands/demo.js +121 -24
- package/dist/commands/deploy.js +3 -1
- package/dist/commands/init.js +8 -8
- package/dist/commands/liquidity.js +50 -1
- package/dist/commands/mint.js +22 -7
- package/dist/commands/oracle.d.ts +2 -2
- package/dist/commands/oracle.js +2 -2
- package/dist/commands/prove.d.ts +1 -1
- package/dist/commands/prove.js +1 -1
- package/dist/commands/status.js +13 -3
- package/dist/commands/swap.js +69 -23
- package/dist/index.js +11 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,19 +18,42 @@ npx @ilalv3/cli <command>
|
|
|
18
18
|
|
|
19
19
|
## Quick start (Base Sepolia demo)
|
|
20
20
|
|
|
21
|
+
`ilal init` points at the current seeded Base Sepolia demo stack. The demo issuer
|
|
22
|
+
uses MockEAS, so reviewers can verify the full path without waiting for a real
|
|
23
|
+
Coinbase attestation. The seeded reviewer wallet already has CNF + TOKA/TOKB;
|
|
24
|
+
for your own wallet, deploy a mock stack with `ilal deploy --mock`.
|
|
25
|
+
|
|
21
26
|
```bash
|
|
22
27
|
# 1. Point CLI at the live demo deployment
|
|
23
28
|
ilal init
|
|
24
29
|
|
|
25
|
-
# 2. Check credential + pool status
|
|
26
|
-
ilal status
|
|
30
|
+
# 2. Check credential + pool status for the seeded reviewer wallet
|
|
31
|
+
ilal status --wallet 0xc0807D4778a9E5FE15ad68A8500e64d65BA78D58
|
|
27
32
|
|
|
28
|
-
# 3.
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
# 3. Full readiness verdict
|
|
34
|
+
ilal demo check --wallet 0xc0807D4778a9E5FE15ad68A8500e64d65BA78D58
|
|
35
|
+
|
|
36
|
+
# 4. Execute a compliant swap with the seeded reviewer key
|
|
37
|
+
PRIVATE_KEY=0x... ilal swap --amount-in 1 --token-in 0x582362E608F36850F6f641510d5D19C1EaB4cb27 --min-amount-out 0
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For a fully seeded local/testnet demo, deploy mock EAS + demo pool pieces:
|
|
31
41
|
|
|
32
|
-
|
|
33
|
-
PRIVATE_KEY=0x... ilal
|
|
42
|
+
```bash
|
|
43
|
+
PRIVATE_KEY=0x... ilal deploy \
|
|
44
|
+
--chain 84532 \
|
|
45
|
+
--mock \
|
|
46
|
+
--wallet-to-seed 0xYourWallet \
|
|
47
|
+
--broadcast
|
|
48
|
+
|
|
49
|
+
# Then mint the seeded CNF from the printed AttestationUID:
|
|
50
|
+
PRIVATE_KEY=0x... ilal credential mint \
|
|
51
|
+
--issuer <CNFIssuer> \
|
|
52
|
+
--attestation <AttestationUID> \
|
|
53
|
+
--chain 84532
|
|
54
|
+
|
|
55
|
+
# If the wallet needs more demo tokens:
|
|
56
|
+
PRIVATE_KEY=0x... ilal demo faucet --wallet 0xYourWallet
|
|
34
57
|
```
|
|
35
58
|
|
|
36
59
|
## Getting a CNF credential
|
|
@@ -75,6 +98,8 @@ Generates a Groth16 proof locally (~5s), verifies it on-chain, and mints/renews
|
|
|
75
98
|
| `ilal oracle activate-verifier` | Operator flow: activate the pending ZK verifier after timelock |
|
|
76
99
|
| `ilal session sign` | Sign a standalone SessionToken |
|
|
77
100
|
| `ilal proof mint` | Mint CNF from existing proof.json + public.json |
|
|
101
|
+
| `ilal deploy --mock` | Deploy a seeded testnet demo stack with MockEAS, tokens, router, hook, and policy |
|
|
102
|
+
| `ilal demo faucet` | Mint mock demo TOKA/TOKB to a wallet |
|
|
78
103
|
| `ilal deploy` | Deploy full ILAL contract stack |
|
|
79
104
|
|
|
80
105
|
## Configuration
|
|
@@ -83,10 +108,10 @@ The CLI reads `.ilal.json` in the current directory. Run `ilal init` to create i
|
|
|
83
108
|
|
|
84
109
|
```bash
|
|
85
110
|
ilal swap \
|
|
86
|
-
--router
|
|
87
|
-
--hook
|
|
88
|
-
--issuer
|
|
89
|
-
--pool-id
|
|
111
|
+
--router 0x7727F0f3EBe99A558487394D001950ee6B33BB86 \
|
|
112
|
+
--hook 0xF5066ad9c25F3f54cfb19609A60187C48C184A80 \
|
|
113
|
+
--issuer 0xc4E032A7574016bd0e3d1a5BbFdE886af09CeD9A \
|
|
114
|
+
--pool-id 0xc1c8f29d6f03b5cd18bf2b862d48f45cc338022a154945b89c4bcb0a3e11e87f \
|
|
90
115
|
--amount-in 0.001
|
|
91
116
|
```
|
|
92
117
|
|
|
@@ -94,10 +119,19 @@ ilal swap \
|
|
|
94
119
|
|
|
95
120
|
| Contract | Address |
|
|
96
121
|
|---|---|
|
|
97
|
-
| CNFIssuer | `
|
|
98
|
-
| ComplianceHook | `
|
|
99
|
-
| ILALRouter | `
|
|
100
|
-
| PolicyRegistry | `
|
|
122
|
+
| CNFIssuer | `0xc4E032A7574016bd0e3d1a5BbFdE886af09CeD9A` |
|
|
123
|
+
| ComplianceHook | `0xF5066ad9c25F3f54cfb19609A60187C48C184A80` |
|
|
124
|
+
| ILALRouter | `0x7727F0f3EBe99A558487394D001950ee6B33BB86` |
|
|
125
|
+
| PolicyRegistry | `0x910a3efDc426f3216738106dd0DC6EA696477233` |
|
|
126
|
+
| TokenA / TOKA | `0x582362E608F36850F6f641510d5D19C1EaB4cb27` |
|
|
127
|
+
| TokenB / TOKB | `0x6eBBdAC70EC422C512727B25c7F0D9120ed101Ff` |
|
|
128
|
+
| Pool ID | `0xc1c8f29d6f03b5cd18bf2b862d48f45cc338022a154945b89c4bcb0a3e11e87f` |
|
|
129
|
+
|
|
130
|
+
Live proof:
|
|
131
|
+
|
|
132
|
+
- CNF mint tx: `0x676ca67698eb8fed6c905c2b3a9536d4d056e89c199c41c44085a29db8b4d462`
|
|
133
|
+
- Add liquidity tx: `0x531fac3678878e4855471318b8ea39b2b2f3ced3d890d9d7c40721af296084ca`
|
|
134
|
+
- Swap tx: `0xdaf4136d305e546d6936715cc0101efb4dc88abcb779add9ee03591fdf555a5a`
|
|
101
135
|
|
|
102
136
|
## License
|
|
103
137
|
|
package/dist/commands/demo.d.ts
CHANGED
package/dist/commands/demo.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { createPublicClient, formatUnits, http, isAddress, isHex, } from "viem";
|
|
1
|
+
import { createPublicClient, createWalletClient, formatUnits, http, isAddress, isHex, parseUnits, } from "viem";
|
|
2
2
|
import { privateKeyToAccount } from "viem/accounts";
|
|
3
3
|
import { base, baseSepolia } from "viem/chains";
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
5
|
-
import { fmt, header, log } from "../ui.js";
|
|
5
|
+
import { die, fmt, header, log, Spinner } from "../ui.js";
|
|
6
6
|
const CHAINS = { "8453": base, "84532": baseSepolia };
|
|
7
7
|
const POOL_MANAGER = {
|
|
8
8
|
"84532": "0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408",
|
|
9
9
|
"8453": "0x498581fF718922c3f8e6A244956aF099B2652b2b",
|
|
10
10
|
};
|
|
11
11
|
const SAMPLE = {
|
|
12
|
-
wallet: "
|
|
13
|
-
issuer: "
|
|
14
|
-
hook: "
|
|
15
|
-
router: "
|
|
16
|
-
pool: "
|
|
12
|
+
wallet: "0xc0807D4778a9E5FE15ad68A8500e64d65BA78D58",
|
|
13
|
+
issuer: "0xc4E032A7574016bd0e3d1a5BbFdE886af09CeD9A",
|
|
14
|
+
hook: "0xF5066ad9c25F3f54cfb19609A60187C48C184A80",
|
|
15
|
+
router: "0x7727F0f3EBe99A558487394D001950ee6B33BB86",
|
|
16
|
+
pool: "0xc1c8f29d6f03b5cd18bf2b862d48f45cc338022a154945b89c4bcb0a3e11e87f",
|
|
17
17
|
proof: "0x91f2b8a0c43e902f7f1a8c0d",
|
|
18
18
|
session: "0x6b84eac5e0db21f8d5d43b7a",
|
|
19
19
|
};
|
|
@@ -23,6 +23,7 @@ const CNF_ABI = [
|
|
|
23
23
|
{ name: "credentialOf", type: "function", stateMutability: "view", inputs: [{ name: "wallet", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
24
24
|
{ name: "merkleRoot", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
|
|
25
25
|
{ name: "zkVerifier", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
26
|
+
{ name: "eas", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
26
27
|
];
|
|
27
28
|
const REGISTRY_ABI = [
|
|
28
29
|
{ name: "getPolicy", type: "function", stateMutability: "view", inputs: [{ name: "poolId", type: "bytes32" }], outputs: [{ type: "tuple", components: [{ name: "cnfIssuer", type: "address" }, { name: "requiredCredentialType", type: "bytes32" }, { name: "enabled", type: "bool" }] }] },
|
|
@@ -36,6 +37,7 @@ const ERC20_ABI = [
|
|
|
36
37
|
{ name: "decimals", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] },
|
|
37
38
|
{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
38
39
|
{ name: "allowance", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }, { name: "spender", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
40
|
+
{ name: "mint", type: "function", stateMutability: "nonpayable", inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256" }], outputs: [] },
|
|
39
41
|
];
|
|
40
42
|
function stage(n, title, subtitle) {
|
|
41
43
|
console.log();
|
|
@@ -146,6 +148,15 @@ export async function demoCheck(opts) {
|
|
|
146
148
|
log.line();
|
|
147
149
|
let score = 0;
|
|
148
150
|
let total = 0;
|
|
151
|
+
let networkReady = false;
|
|
152
|
+
let configReady = true;
|
|
153
|
+
let codeReady = true;
|
|
154
|
+
let economicsReady = false;
|
|
155
|
+
let issuerPathReady = false;
|
|
156
|
+
let credentialReady = false;
|
|
157
|
+
let policyReady = false;
|
|
158
|
+
let walletBalancesReady = false;
|
|
159
|
+
const walletBalanceChecks = [];
|
|
149
160
|
const pass = (condition) => {
|
|
150
161
|
total++;
|
|
151
162
|
if (condition)
|
|
@@ -156,6 +167,7 @@ export async function demoCheck(opts) {
|
|
|
156
167
|
try {
|
|
157
168
|
const block = await client.getBlockNumber();
|
|
158
169
|
ok("latest block", block.toString());
|
|
170
|
+
networkReady = true;
|
|
159
171
|
pass(true);
|
|
160
172
|
}
|
|
161
173
|
catch (e) {
|
|
@@ -188,18 +200,25 @@ export async function demoCheck(opts) {
|
|
|
188
200
|
: value;
|
|
189
201
|
ok(label, display);
|
|
190
202
|
}
|
|
191
|
-
else
|
|
203
|
+
else {
|
|
192
204
|
bad(label, fmt.badge("missing", "red"));
|
|
205
|
+
configReady = false;
|
|
206
|
+
}
|
|
193
207
|
pass(valid);
|
|
194
208
|
}
|
|
195
209
|
log.line();
|
|
196
210
|
log.section("Contract Code", "must exist on-chain");
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
211
|
+
const codeChecks = [
|
|
212
|
+
await hasCode(client, "CNFIssuer", cfg.issuer),
|
|
213
|
+
await hasCode(client, "ComplianceHook", cfg.hook),
|
|
214
|
+
await hasCode(client, "PolicyRegistry", cfg.registry),
|
|
215
|
+
await hasCode(client, "ILALRouter", cfg.router),
|
|
216
|
+
await hasCode(client, "currency0", cfg.tokenA),
|
|
217
|
+
await hasCode(client, "currency1", cfg.tokenB),
|
|
218
|
+
];
|
|
219
|
+
for (const check of codeChecks)
|
|
220
|
+
pass(check);
|
|
221
|
+
codeReady = codeChecks.every(Boolean);
|
|
203
222
|
log.line();
|
|
204
223
|
if (cfg.router && isAddress(cfg.router)) {
|
|
205
224
|
log.section("Verified Flow Economics");
|
|
@@ -211,10 +230,12 @@ export async function demoCheck(opts) {
|
|
|
211
230
|
ok("LP fee", cfg.fee === "8388608" ? `${fmt.badge("dynamic", "green")} verified 0.05%` : "pool fee tier");
|
|
212
231
|
ok("ILAL fee", protocolFeePips > 0 ? `${fmt.badge("protocol", "cyan")} ${pipsToPercent(protocolFeePips)}` : fmt.badge("off", "yellow"));
|
|
213
232
|
ok("treasury", fmt.addr(treasury));
|
|
233
|
+
economicsReady = true;
|
|
214
234
|
pass(true);
|
|
215
235
|
}
|
|
216
236
|
catch {
|
|
217
237
|
warn("protocol fee", fmt.badge("legacy router", "yellow"));
|
|
238
|
+
economicsReady = true;
|
|
218
239
|
pass(true);
|
|
219
240
|
}
|
|
220
241
|
log.line();
|
|
@@ -222,10 +243,20 @@ export async function demoCheck(opts) {
|
|
|
222
243
|
if (cfg.issuer && isAddress(cfg.issuer)) {
|
|
223
244
|
log.section("Issuer State");
|
|
224
245
|
try {
|
|
225
|
-
const [root, verifier] = await Promise.all([
|
|
246
|
+
const [root, verifier, eas] = await Promise.all([
|
|
226
247
|
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "merkleRoot" }),
|
|
227
248
|
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "zkVerifier" }),
|
|
249
|
+
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "eas" }),
|
|
228
250
|
]);
|
|
251
|
+
const hasEASPath = eas !== ZERO;
|
|
252
|
+
const hasZKPath = root !== 0n && verifier !== ZERO;
|
|
253
|
+
if (hasEASPath)
|
|
254
|
+
ok("issuance path", `${fmt.badge("EAS/mock", "green")} ${fmt.addr(eas)}`);
|
|
255
|
+
else if (hasZKPath)
|
|
256
|
+
ok("issuance path", fmt.badge("ZK", "green"));
|
|
257
|
+
else
|
|
258
|
+
warn("issuance path", fmt.badge("not ready", "yellow"));
|
|
259
|
+
issuerPathReady = hasEASPath || hasZKPath;
|
|
229
260
|
if (root === 0n)
|
|
230
261
|
warn("merkleRoot", fmt.badge("not set", "yellow"));
|
|
231
262
|
else
|
|
@@ -234,9 +265,11 @@ export async function demoCheck(opts) {
|
|
|
234
265
|
warn("zkVerifier", fmt.badge("not set", "yellow"));
|
|
235
266
|
else
|
|
236
267
|
ok("zkVerifier", fmt.addr(verifier));
|
|
268
|
+
pass(issuerPathReady);
|
|
237
269
|
}
|
|
238
270
|
catch (e) {
|
|
239
271
|
bad("issuer reads", e instanceof Error ? e.message.split("\n")[0] : String(e));
|
|
272
|
+
pass(false);
|
|
240
273
|
}
|
|
241
274
|
if (wallet && isAddress(wallet)) {
|
|
242
275
|
try {
|
|
@@ -248,6 +281,7 @@ export async function demoCheck(opts) {
|
|
|
248
281
|
ok("credential", `${fmt.badge("valid", "green")} token #${tokenId}`);
|
|
249
282
|
else
|
|
250
283
|
warn("credential", tokenId === 0n ? fmt.badge("missing", "yellow") : fmt.badge("invalid", "yellow"));
|
|
284
|
+
credentialReady = valid;
|
|
251
285
|
pass(valid);
|
|
252
286
|
}
|
|
253
287
|
catch (e) {
|
|
@@ -272,6 +306,7 @@ export async function demoCheck(opts) {
|
|
|
272
306
|
const issuerMatches = policy.cnfIssuer.toLowerCase() === (cfg.issuer ?? "").toLowerCase();
|
|
273
307
|
const ready = policy.enabled && issuerMatches;
|
|
274
308
|
(ready ? ok : warn)("policy", `${policy.enabled ? fmt.badge("enabled", "green") : fmt.badge("disabled", "yellow")} issuer ${fmt.addr(policy.cnfIssuer)}`);
|
|
309
|
+
policyReady = ready;
|
|
275
310
|
pass(ready);
|
|
276
311
|
}
|
|
277
312
|
catch (e) {
|
|
@@ -295,37 +330,61 @@ export async function demoCheck(opts) {
|
|
|
295
330
|
const balanceText = `${formatUnits(balance, decimals)} ${symbol}`;
|
|
296
331
|
const allowanceText = allowance > 0n ? fmt.badge("approved", "green") : fmt.badge("needs approval", "yellow");
|
|
297
332
|
(balance > 0n ? ok : warn)(label, `${balanceText} ${allowanceText}`);
|
|
333
|
+
walletBalanceChecks.push(balance > 0n);
|
|
298
334
|
pass(balance > 0n);
|
|
299
335
|
}
|
|
300
336
|
catch (e) {
|
|
301
337
|
bad(label, e instanceof Error ? e.message.split("\n")[0] : String(e));
|
|
338
|
+
walletBalanceChecks.push(false);
|
|
302
339
|
pass(false);
|
|
303
340
|
}
|
|
304
341
|
}
|
|
342
|
+
walletBalancesReady = walletBalanceChecks.length === 2 && walletBalanceChecks.every(Boolean);
|
|
305
343
|
log.line();
|
|
306
344
|
}
|
|
345
|
+
const infrastructureChecks = [networkReady, configReady, codeReady, economicsReady, issuerPathReady, policyReady];
|
|
346
|
+
const infrastructureReady = infrastructureChecks.every(Boolean);
|
|
347
|
+
const walletSelected = !!wallet && isAddress(wallet);
|
|
348
|
+
const walletReady = walletSelected && credentialReady && walletBalancesReady;
|
|
349
|
+
const realTxReady = infrastructureReady && walletReady;
|
|
307
350
|
const readiness = total === 0 ? 0 : Math.round((score / total) * 100);
|
|
351
|
+
const infraScore = Math.round((infrastructureChecks.filter(Boolean).length / infrastructureChecks.length) * 100);
|
|
352
|
+
const walletScore = walletSelected
|
|
353
|
+
? Math.round(([credentialReady, walletBalancesReady].filter(Boolean).length / 2) * 100)
|
|
354
|
+
: 0;
|
|
308
355
|
log.section("Readiness");
|
|
309
|
-
const tone =
|
|
310
|
-
log.progress("
|
|
356
|
+
const tone = realTxReady ? "green" : infrastructureReady ? "yellow" : readiness >= 60 ? "yellow" : "red";
|
|
357
|
+
log.progress("overall", readiness, tone);
|
|
358
|
+
log.progress("infrastructure", infraScore, infrastructureReady ? "green" : "yellow");
|
|
359
|
+
log.progress("wallet", walletScore, walletReady ? "green" : "yellow");
|
|
311
360
|
log.metrics([
|
|
312
|
-
{ label: "
|
|
313
|
-
{ label: "
|
|
361
|
+
{ label: "infra", value: infrastructureReady ? "ready" : "incomplete", tone: infrastructureReady ? "green" : "yellow" },
|
|
362
|
+
{ label: "wallet", value: walletReady ? "ready" : "not ready", tone: walletReady ? "green" : "yellow" },
|
|
363
|
+
{ label: "tx", value: realTxReady ? "ready" : "blocked", tone: realTxReady ? "green" : "yellow" },
|
|
364
|
+
]);
|
|
365
|
+
log.metrics([
|
|
366
|
+
{ label: "credential", value: credentialReady ? "valid" : "missing", tone: credentialReady ? "green" : "yellow" },
|
|
367
|
+
{ label: "balances", value: walletBalancesReady ? "funded" : "missing", tone: walletBalancesReady ? "green" : "yellow" },
|
|
368
|
+
{ label: "policy", value: policyReady ? "enabled" : "missing", tone: policyReady ? "green" : "yellow" },
|
|
314
369
|
{ label: "deal", value: cfg.fee === "8388608" ? "better" : "standard", tone: cfg.fee === "8388608" ? "green" : "gray" },
|
|
315
370
|
]);
|
|
316
|
-
if (
|
|
371
|
+
if (realTxReady) {
|
|
317
372
|
log.callout("Live demo ready", "credential, policy, hook, router, pool, and balances are aligned", "green");
|
|
318
373
|
}
|
|
374
|
+
else if (infrastructureReady) {
|
|
375
|
+
log.callout("Demo infrastructure ready", "wallet is not ready yet: mint CNF and fund demo tokens before real tx", "yellow");
|
|
376
|
+
}
|
|
319
377
|
else {
|
|
320
378
|
log.callout("Live demo not ready", "fill the missing config/state first", tone);
|
|
321
379
|
}
|
|
322
380
|
log.line();
|
|
323
|
-
log.section("Live Path",
|
|
324
|
-
flowStep("credential", wallet ? `${fmt.addr(wallet)} holds a valid CNF` : "wallet not selected",
|
|
381
|
+
log.section("Live Path", realTxReady ? "what the judge is about to see" : "target flow");
|
|
382
|
+
flowStep("credential", credentialReady && wallet ? `${fmt.addr(wallet)} holds a valid CNF` : wallet ? `${fmt.addr(wallet)} has no valid CNF` : "wallet not selected", credentialReady ? "green" : "yellow");
|
|
325
383
|
flowStep("session", `local EIP-712 binds user + router + pool + action`, "green");
|
|
326
384
|
flowStep("hook", `${cfg.hook ? fmt.addr(cfg.hook) : fmt.badge("missing", "red")} gates swap/liquidity`, cfg.hook ? "green" : "red");
|
|
327
|
-
flowStep("pool", cfg.poolId ? `${fmt.hash(cfg.poolId)} policy enabled` : fmt.badge("
|
|
328
|
-
flowStep("
|
|
385
|
+
flowStep("pool", policyReady && cfg.poolId ? `${fmt.hash(cfg.poolId)} policy enabled` : fmt.badge("policy not ready", "yellow"), policyReady ? "green" : "yellow");
|
|
386
|
+
flowStep("balances", walletBalancesReady ? fmt.badge("funded", "green") : fmt.badge("missing demo tokens", "yellow"), walletBalancesReady ? "green" : "yellow");
|
|
387
|
+
flowStep("result", realTxReady ? fmt.badge("ready for real tx", "green") : fmt.badge("wallet not ready for real tx", "yellow"), realTxReady ? "green" : "yellow");
|
|
329
388
|
log.line();
|
|
330
389
|
log.section("Next Commands");
|
|
331
390
|
if (!cfg.router || !cfg.tokenA || !cfg.tokenB || !cfg.poolId) {
|
|
@@ -344,3 +403,41 @@ export async function demoCheck(opts) {
|
|
|
344
403
|
log.command(`ilal swap --amount-in 0.001 --token-in ${suggestedTokenIn}`);
|
|
345
404
|
console.log();
|
|
346
405
|
}
|
|
406
|
+
export async function demoFaucet(opts) {
|
|
407
|
+
const cfg = loadConfig();
|
|
408
|
+
const chain = CHAINS[cfg.chain ?? "84532"] ?? baseSepolia;
|
|
409
|
+
const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
|
|
410
|
+
if (!rawKey || !isHex(rawKey) || rawKey.length !== 66) {
|
|
411
|
+
die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
|
|
412
|
+
}
|
|
413
|
+
if (!cfg.tokenA || !cfg.tokenB || !isAddress(cfg.tokenA) || !isAddress(cfg.tokenB)) {
|
|
414
|
+
die("tokenA/tokenB required. Run `ilal init` with demo token addresses first.");
|
|
415
|
+
}
|
|
416
|
+
const account = privateKeyToAccount(rawKey);
|
|
417
|
+
const wallet = opts.wallet ?? account.address;
|
|
418
|
+
if (!isAddress(wallet))
|
|
419
|
+
die(`Invalid wallet address: ${wallet}`);
|
|
420
|
+
const client = createPublicClient({ chain, transport: http(cfg.rpc) });
|
|
421
|
+
const walletClient = createWalletClient({ account, chain, transport: http(cfg.rpc) });
|
|
422
|
+
header("ILAL Demo Faucet", chain.name);
|
|
423
|
+
log.kv("recipient", fmt.addr(wallet));
|
|
424
|
+
log.line();
|
|
425
|
+
for (const token of [cfg.tokenA, cfg.tokenB]) {
|
|
426
|
+
const [symbol, decimals] = await Promise.all([
|
|
427
|
+
client.readContract({ address: token, abi: ERC20_ABI, functionName: "symbol" }),
|
|
428
|
+
client.readContract({ address: token, abi: ERC20_ABI, functionName: "decimals" }),
|
|
429
|
+
]);
|
|
430
|
+
const amount = parseUnits(opts.amount ?? "10000", decimals);
|
|
431
|
+
const spin = new Spinner(`Minting ${opts.amount ?? "10000"} ${symbol}…`).start();
|
|
432
|
+
const hash = await walletClient.writeContract({
|
|
433
|
+
address: token,
|
|
434
|
+
abi: ERC20_ABI,
|
|
435
|
+
functionName: "mint",
|
|
436
|
+
args: [wallet, amount],
|
|
437
|
+
});
|
|
438
|
+
await client.waitForTransactionReceipt({ hash });
|
|
439
|
+
spin.succeed(`Minted ${symbol} ${fmt.hash(hash)}`);
|
|
440
|
+
}
|
|
441
|
+
log.callout("Demo tokens ready", "wallet can now pass token-balance preflight checks", "green");
|
|
442
|
+
console.log();
|
|
443
|
+
}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -58,13 +58,15 @@ export async function deploy(opts) {
|
|
|
58
58
|
};
|
|
59
59
|
if (isMock) {
|
|
60
60
|
env["WALLET_TO_SEED"] = opts.walletToSeed;
|
|
61
|
+
env["WALLET"] = opts.walletToSeed;
|
|
62
|
+
env["MOCK_EAS"] = "true";
|
|
61
63
|
}
|
|
62
64
|
else {
|
|
63
65
|
env["EAS_ADDRESS"] = easAddress;
|
|
64
66
|
env["SCHEMA_UID"] = COINBASE_SCHEMA_UID;
|
|
65
67
|
env["TRUSTED_ATTESTER"] = COINBASE_ATTESTER;
|
|
66
68
|
}
|
|
67
|
-
const script = isMock ? "script/
|
|
69
|
+
const script = isMock ? "script/DeployDemo.s.sol" : "script/Deploy.s.sol";
|
|
68
70
|
const flags = [
|
|
69
71
|
`--rpc-url ${rpc}`,
|
|
70
72
|
opts.broadcast ? "--broadcast" : "",
|
package/dist/commands/init.js
CHANGED
|
@@ -10,14 +10,14 @@ import { fmt, log, header, die } from "../ui.js";
|
|
|
10
10
|
// Known testnet / mainnet addresses for quick init
|
|
11
11
|
const PRESETS = {
|
|
12
12
|
"84532": {
|
|
13
|
-
issuer: "
|
|
14
|
-
hook: "
|
|
15
|
-
registry: "
|
|
16
|
-
router: "
|
|
17
|
-
treasury: "
|
|
18
|
-
tokenA: "
|
|
19
|
-
tokenB: "
|
|
20
|
-
poolId: "
|
|
13
|
+
issuer: "0xc4E032A7574016bd0e3d1a5BbFdE886af09CeD9A",
|
|
14
|
+
hook: "0xF5066ad9c25F3f54cfb19609A60187C48C184A80",
|
|
15
|
+
registry: "0x910a3efDc426f3216738106dd0DC6EA696477233",
|
|
16
|
+
router: "0x7727F0f3EBe99A558487394D001950ee6B33BB86",
|
|
17
|
+
treasury: "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
|
|
18
|
+
tokenA: "0x582362E608F36850F6f641510d5D19C1EaB4cb27",
|
|
19
|
+
tokenB: "0x6eBBdAC70EC422C512727B25c7F0D9120ed101Ff",
|
|
20
|
+
poolId: "0xc1c8f29d6f03b5cd18bf2b862d48f45cc338022a154945b89c4bcb0a3e11e87f",
|
|
21
21
|
fee: "8388608",
|
|
22
22
|
tickSpacing: "60",
|
|
23
23
|
rpc: "https://sepolia.base.org",
|
|
@@ -23,9 +23,17 @@ const CHAINS = { "8453": base, "84532": baseSepolia };
|
|
|
23
23
|
const ERC20_ABI = [
|
|
24
24
|
{ name: "symbol", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
|
|
25
25
|
{ name: "decimals", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] },
|
|
26
|
+
{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
26
27
|
{ name: "allowance", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }, { name: "spender", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
27
28
|
{ name: "approve", type: "function", stateMutability: "nonpayable", inputs: [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }], outputs: [{ type: "bool" }] },
|
|
28
29
|
];
|
|
30
|
+
const CNF_ABI = [
|
|
31
|
+
{ name: "isValid", type: "function", stateMutability: "view", inputs: [{ name: "wallet", type: "address" }], outputs: [{ type: "bool" }] },
|
|
32
|
+
{ name: "credentialOf", type: "function", stateMutability: "view", inputs: [{ name: "wallet", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
33
|
+
{ name: "merkleRoot", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
|
|
34
|
+
{ name: "zkVerifier", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
35
|
+
{ name: "eas", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
36
|
+
];
|
|
29
37
|
const ROUTER_LIQUIDITY_ABI = [
|
|
30
38
|
{
|
|
31
39
|
name: "addLiquidity", type: "function", stateMutability: "payable",
|
|
@@ -94,6 +102,7 @@ function txUrl(chain, hash) {
|
|
|
94
102
|
const baseUrl = chain.blockExplorers?.default?.url;
|
|
95
103
|
return baseUrl ? `${baseUrl}/tx/${hash}` : undefined;
|
|
96
104
|
}
|
|
105
|
+
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
97
106
|
// ─── Shared core ──────────────────────────────────────────────────────────────
|
|
98
107
|
async function executeLiquidity(action, opts) {
|
|
99
108
|
const cfg = withConfig(opts);
|
|
@@ -143,11 +152,51 @@ async function executeLiquidity(action, opts) {
|
|
|
143
152
|
log.kv("tickUpper", tickUpper.toString());
|
|
144
153
|
log.kv("liquidity", liquidity.toString());
|
|
145
154
|
log.line();
|
|
155
|
+
if (liquidity <= 0n) {
|
|
156
|
+
die("liquidity must be greater than 0. No approval or liquidity transaction was sent.");
|
|
157
|
+
}
|
|
158
|
+
const preflightSpin = new Spinner("Running preflight checks…").start();
|
|
159
|
+
const [root, verifier, eas, valid, tokenId, sym0, sym1, bal0, bal1] = await Promise.all([
|
|
160
|
+
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "merkleRoot" }),
|
|
161
|
+
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "zkVerifier" }),
|
|
162
|
+
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "eas" }),
|
|
163
|
+
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "isValid", args: [account.address] }),
|
|
164
|
+
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "credentialOf", args: [account.address] }),
|
|
165
|
+
pubClient.readContract({ address: c0, abi: ERC20_ABI, functionName: "symbol" }),
|
|
166
|
+
pubClient.readContract({ address: c1, abi: ERC20_ABI, functionName: "symbol" }),
|
|
167
|
+
pubClient.readContract({ address: c0, abi: ERC20_ABI, functionName: "balanceOf", args: [account.address] }),
|
|
168
|
+
pubClient.readContract({ address: c1, abi: ERC20_ABI, functionName: "balanceOf", args: [account.address] }),
|
|
169
|
+
]);
|
|
170
|
+
preflightSpin.stop();
|
|
171
|
+
const preflightErrors = [];
|
|
172
|
+
const hasEASPath = eas !== ZERO_ADDRESS;
|
|
173
|
+
const hasZKPath = verifier !== ZERO_ADDRESS && root !== 0n;
|
|
174
|
+
if (tokenId === 0n) {
|
|
175
|
+
preflightErrors.push("wallet has no CNF credential; mint one before changing liquidity.");
|
|
176
|
+
if (hasEASPath)
|
|
177
|
+
preflightErrors.push("issuer supports EAS/mock attestation minting: run `ilal credential mint --attestation <uid>`.");
|
|
178
|
+
else if (hasZKPath)
|
|
179
|
+
preflightErrors.push(`issuer supports ZK minting: run \`ilal credential prove --wallet ${account.address}\`.`);
|
|
180
|
+
else
|
|
181
|
+
preflightErrors.push("issuer has no active issuance path: EAS is unset and ZK verifier/root are not both configured.");
|
|
182
|
+
}
|
|
183
|
+
else if (!valid)
|
|
184
|
+
preflightErrors.push("wallet CNF credential exists but is not valid.");
|
|
185
|
+
if (action === "add" && (bal0 === 0n || bal1 === 0n)) {
|
|
186
|
+
preflightErrors.push(`token balances are not ready for adding liquidity: ${sym0}=${bal0.toString()} wei, ${sym1}=${bal1.toString()} wei.`);
|
|
187
|
+
}
|
|
188
|
+
if (preflightErrors.length > 0) {
|
|
189
|
+
log.section("Preflight Failed");
|
|
190
|
+
for (const error of preflightErrors)
|
|
191
|
+
log.warn(error);
|
|
192
|
+
console.log();
|
|
193
|
+
die(`${verb} liquidity not sent. Fix the preflight issues above.`);
|
|
194
|
+
}
|
|
146
195
|
// Approve both tokens if adding liquidity
|
|
147
196
|
if (action === "add") {
|
|
148
197
|
const MAX = 2n ** 256n - 1n;
|
|
149
198
|
for (const token of [c0, c1]) {
|
|
150
|
-
const sym =
|
|
199
|
+
const sym = token.toLowerCase() === c0.toLowerCase() ? sym0 : sym1;
|
|
151
200
|
const allowed = await pubClient.readContract({
|
|
152
201
|
address: token, abi: ERC20_ABI, functionName: "allowance",
|
|
153
202
|
args: [account.address, cfg.router],
|
package/dist/commands/mint.js
CHANGED
|
@@ -26,7 +26,11 @@ const CNF_ISSUER_ABI = [
|
|
|
26
26
|
inputs: [{ name: "wallet", type: "address" }],
|
|
27
27
|
outputs: [{ type: "bool" }],
|
|
28
28
|
},
|
|
29
|
+
{ name: "eas", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
30
|
+
{ name: "schemaUID", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "bytes32" }] },
|
|
31
|
+
{ name: "trustedAttester", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
29
32
|
];
|
|
33
|
+
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
30
34
|
async function sendMintTx(mode, opts) {
|
|
31
35
|
const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
|
|
32
36
|
if (!rawKey)
|
|
@@ -54,9 +58,14 @@ async function sendMintTx(mode, opts) {
|
|
|
54
58
|
log.line();
|
|
55
59
|
// Verify EAS attestation exists on-chain before sending tx
|
|
56
60
|
log.step("Verifying attestation on EAS…");
|
|
57
|
-
const
|
|
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" }),
|
|
65
|
+
]);
|
|
66
|
+
const easAddress = issuerEAS !== ZERO_ADDRESS ? issuerEAS : EAS_ADDRESSES[chain.id];
|
|
58
67
|
if (!easAddress)
|
|
59
|
-
die(`No EAS contract known for chain ${chain.id}. Use
|
|
68
|
+
die(`No EAS contract known for chain ${chain.id}. Use an issuer with eas() configured.`);
|
|
60
69
|
const EAS_ABI = [
|
|
61
70
|
{
|
|
62
71
|
name: "getAttestation",
|
|
@@ -95,18 +104,24 @@ async function sendMintTx(mode, opts) {
|
|
|
95
104
|
if (attestation.recipient.toLowerCase() !== account.address.toLowerCase())
|
|
96
105
|
die(`Attestation recipient (${attestation.recipient}) does not match your wallet (${account.address}).`);
|
|
97
106
|
log.ok(`Attester: ${attestation.attester}`);
|
|
98
|
-
if (attestation.attester.toLowerCase() ===
|
|
107
|
+
if (attestation.attester.toLowerCase() === issuerAttester.toLowerCase()) {
|
|
108
|
+
log.ok("Issuer trusted attester confirmed");
|
|
109
|
+
}
|
|
110
|
+
else if (attestation.attester.toLowerCase() === COINBASE_ATTESTER.toLowerCase()) {
|
|
99
111
|
log.ok("Coinbase Verifications attester confirmed");
|
|
100
112
|
}
|
|
101
113
|
else {
|
|
102
|
-
log.warn(`
|
|
114
|
+
log.warn(`Attester mismatch — issuer expects ${issuerAttester}`);
|
|
103
115
|
}
|
|
104
|
-
if (attestation.schema.toLowerCase() !==
|
|
105
|
-
log.warn(`Schema mismatch —
|
|
116
|
+
if (attestation.schema.toLowerCase() !== issuerSchema.toLowerCase()) {
|
|
117
|
+
log.warn(`Schema mismatch — issuer expects ${issuerSchema}, got ${attestation.schema}`);
|
|
106
118
|
}
|
|
107
|
-
else {
|
|
119
|
+
else if (attestation.schema.toLowerCase() === COINBASE_SCHEMA_UID.toLowerCase()) {
|
|
108
120
|
log.ok("Coinbase Account Verification schema confirmed");
|
|
109
121
|
}
|
|
122
|
+
else {
|
|
123
|
+
log.ok("Issuer schema confirmed");
|
|
124
|
+
}
|
|
110
125
|
log.line();
|
|
111
126
|
if (opts.simulate) {
|
|
112
127
|
log.ok("Simulation complete — attestation valid, tx would succeed");
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
* # Step 1 — queue a new root (requires owner key, executes immediately)
|
|
13
13
|
* PRIVATE_KEY=0x... ilal oracle propose-root \
|
|
14
14
|
* --root 0xDEADBEEF... \
|
|
15
|
-
* --issuer
|
|
15
|
+
* --issuer 0xc4E032...
|
|
16
16
|
*
|
|
17
17
|
* # Step 2 — after ROOT_DELAY (48 h) has elapsed, activate it
|
|
18
18
|
* PRIVATE_KEY=0x... ilal oracle activate-root \
|
|
19
|
-
* --issuer
|
|
19
|
+
* --issuer 0xc4E032...
|
|
20
20
|
*
|
|
21
21
|
* # Same pattern for the ZK verifier (VERIFIER_DELAY = 72 h)
|
|
22
22
|
* PRIVATE_KEY=0x... ilal oracle propose-verifier --verifier 0x... --issuer 0x...
|
package/dist/commands/oracle.js
CHANGED
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
* # Step 1 — queue a new root (requires owner key, executes immediately)
|
|
13
13
|
* PRIVATE_KEY=0x... ilal oracle propose-root \
|
|
14
14
|
* --root 0xDEADBEEF... \
|
|
15
|
-
* --issuer
|
|
15
|
+
* --issuer 0xc4E032...
|
|
16
16
|
*
|
|
17
17
|
* # Step 2 — after ROOT_DELAY (48 h) has elapsed, activate it
|
|
18
18
|
* PRIVATE_KEY=0x... ilal oracle activate-root \
|
|
19
|
-
* --issuer
|
|
19
|
+
* --issuer 0xc4E032...
|
|
20
20
|
*
|
|
21
21
|
* # Same pattern for the ZK verifier (VERIFIER_DELAY = 72 h)
|
|
22
22
|
* PRIVATE_KEY=0x... ilal oracle propose-verifier --verifier 0x... --issuer 0x...
|
package/dist/commands/prove.d.ts
CHANGED
package/dist/commands/prove.js
CHANGED
package/dist/commands/status.js
CHANGED
|
@@ -14,6 +14,7 @@ const CNF_ABI = [
|
|
|
14
14
|
{ name: "getCredential", type: "function", stateMutability: "view", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ type: "tuple", components: [{ name: "holder", type: "address" }, { name: "issuer", type: "address" }, { name: "credentialType", type: "bytes32" }, { name: "issuedAt", type: "uint64" }, { name: "expiresAt", type: "uint64" }, { name: "revoked", type: "bool" }] }] },
|
|
15
15
|
{ name: "merkleRoot", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
|
|
16
16
|
{ name: "zkVerifier", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
17
|
+
{ name: "eas", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
17
18
|
];
|
|
18
19
|
const HOOK_ABI = [
|
|
19
20
|
{ name: "issuer", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
@@ -21,6 +22,7 @@ const HOOK_ABI = [
|
|
|
21
22
|
const REGISTRY_ABI = [
|
|
22
23
|
{ name: "getPolicy", type: "function", stateMutability: "view", inputs: [{ name: "poolId", type: "bytes32" }], outputs: [{ type: "tuple", components: [{ name: "cnfIssuer", type: "address" }, { name: "requiredCredentialType", type: "bytes32" }, { name: "enabled", type: "bool" }] }] },
|
|
23
24
|
];
|
|
25
|
+
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
24
26
|
function daysUntil(unixSec) {
|
|
25
27
|
return Math.floor((unixSec * 1000 - Date.now()) / 86_400_000);
|
|
26
28
|
}
|
|
@@ -88,20 +90,28 @@ export async function status(opts) {
|
|
|
88
90
|
if (cfg.issuer && isAddress(cfg.issuer)) {
|
|
89
91
|
const spin = new Spinner("Fetching issuer config…").start();
|
|
90
92
|
try {
|
|
91
|
-
const [root, verifier] = await Promise.all([
|
|
93
|
+
const [root, verifier, eas] = await Promise.all([
|
|
92
94
|
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "merkleRoot" }),
|
|
93
95
|
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "zkVerifier" }),
|
|
96
|
+
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "eas" }),
|
|
94
97
|
]);
|
|
95
98
|
spin.stop();
|
|
99
|
+
const hasEASPath = eas !== ZERO_ADDRESS;
|
|
100
|
+
const hasZKPath = root !== 0n && verifier !== ZERO_ADDRESS;
|
|
96
101
|
log.section("Issuer");
|
|
97
102
|
log.kv("address", fmt.cyan(cfg.issuer));
|
|
98
|
-
log.kv("
|
|
103
|
+
log.kv("issuance", hasEASPath
|
|
104
|
+
? `${fmt.badge("EAS", "green")} ${fmt.addr(eas)}`
|
|
105
|
+
: hasZKPath
|
|
106
|
+
? fmt.badge("ZK", "green")
|
|
107
|
+
: fmt.badge("not ready", "red"));
|
|
108
|
+
log.kv("zkVerifier", verifier === ZERO_ADDRESS
|
|
99
109
|
? fmt.badge("not set", "red")
|
|
100
110
|
: fmt.green(fmt.addr(verifier)));
|
|
101
111
|
log.kv("merkleRoot", root === 0n
|
|
102
112
|
? fmt.badge("not set", "red")
|
|
103
113
|
: fmt.gray(root.toString().slice(0, 20) + "…"));
|
|
104
|
-
issuerReady =
|
|
114
|
+
issuerReady = hasEASPath || hasZKPath;
|
|
105
115
|
}
|
|
106
116
|
catch (e) {
|
|
107
117
|
spin.stop();
|
package/dist/commands/swap.js
CHANGED
|
@@ -56,6 +56,13 @@ const ROUTER_ABI = [
|
|
|
56
56
|
outputs: [{ name: "delta", type: "int256" }],
|
|
57
57
|
},
|
|
58
58
|
];
|
|
59
|
+
const CNF_ABI = [
|
|
60
|
+
{ name: "isValid", type: "function", stateMutability: "view", inputs: [{ name: "wallet", type: "address" }], outputs: [{ type: "bool" }] },
|
|
61
|
+
{ name: "credentialOf", type: "function", stateMutability: "view", inputs: [{ name: "wallet", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
62
|
+
{ name: "merkleRoot", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
|
|
63
|
+
{ name: "zkVerifier", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
64
|
+
{ name: "eas", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
65
|
+
];
|
|
59
66
|
// ─── Session helpers ──────────────────────────────────────────────────────────
|
|
60
67
|
const SESSION_TOKEN_TYPE = [
|
|
61
68
|
{ name: "user", type: "address" },
|
|
@@ -77,6 +84,7 @@ const MIN_SQRT_PRICE = 4295128740n; // TickMath.MIN_SQRT_PRICE + 1
|
|
|
77
84
|
const MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970341n; // MAX - 1
|
|
78
85
|
const DYNAMIC_FEE_FLAG = 8388608;
|
|
79
86
|
const PIPS_DENOMINATOR = 1000000n;
|
|
87
|
+
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
80
88
|
function txUrl(chain, hash) {
|
|
81
89
|
const baseUrl = chain.blockExplorers?.default?.url;
|
|
82
90
|
return baseUrl ? `${baseUrl}/tx/${hash}` : undefined;
|
|
@@ -148,6 +156,9 @@ export async function swap(opts) {
|
|
|
148
156
|
]);
|
|
149
157
|
spin.stop();
|
|
150
158
|
const amountIn = parseUnits(opts.amountIn, decimals);
|
|
159
|
+
if (amountIn <= 0n) {
|
|
160
|
+
die("amount-in must be greater than 0. Use `ilal swap --simulate` for a dry run.");
|
|
161
|
+
}
|
|
151
162
|
log.kv("amount", `${opts.amountIn} ${fmt.cyan(symbol)} (${amountIn.toString()} wei)`);
|
|
152
163
|
let protocolFeePips = 0;
|
|
153
164
|
let treasury;
|
|
@@ -162,34 +173,47 @@ export async function swap(opts) {
|
|
|
162
173
|
}
|
|
163
174
|
const protocolFeeAmount = amountIn * BigInt(protocolFeePips) / PIPS_DENOMINATOR;
|
|
164
175
|
const totalDebit = amountIn + protocolFeeAmount;
|
|
176
|
+
const preflightSpin = new Spinner("Running preflight checks…").start();
|
|
177
|
+
const [root, verifier, eas, valid, tokenId, balance] = await Promise.all([
|
|
178
|
+
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "merkleRoot" }),
|
|
179
|
+
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "zkVerifier" }),
|
|
180
|
+
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "eas" }),
|
|
181
|
+
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "isValid", args: [account.address] }),
|
|
182
|
+
pubClient.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "credentialOf", args: [account.address] }),
|
|
183
|
+
pubClient.readContract({ address: tokenIn, abi: ERC20_ABI, functionName: "balanceOf", args: [account.address] }),
|
|
184
|
+
]);
|
|
185
|
+
preflightSpin.stop();
|
|
186
|
+
const preflightErrors = [];
|
|
187
|
+
const hasEASPath = eas !== ZERO_ADDRESS;
|
|
188
|
+
const hasZKPath = verifier !== ZERO_ADDRESS && root !== 0n;
|
|
189
|
+
if (tokenId === 0n) {
|
|
190
|
+
preflightErrors.push(`wallet has no CNF credential; mint one before trading.`);
|
|
191
|
+
if (hasEASPath)
|
|
192
|
+
preflightErrors.push("issuer supports EAS/mock attestation minting: run `ilal credential mint --attestation <uid>`.");
|
|
193
|
+
else if (hasZKPath)
|
|
194
|
+
preflightErrors.push(`issuer supports ZK minting: run \`ilal credential prove --wallet ${account.address}\`.`);
|
|
195
|
+
else
|
|
196
|
+
preflightErrors.push("issuer has no active issuance path: EAS is unset and ZK verifier/root are not both configured.");
|
|
197
|
+
}
|
|
198
|
+
else if (!valid)
|
|
199
|
+
preflightErrors.push("wallet CNF credential exists but is not valid.");
|
|
200
|
+
if (balance < totalDebit)
|
|
201
|
+
preflightErrors.push(`insufficient ${symbol} balance: need ${totalDebit.toString()} wei including ILAL fee, have ${balance.toString()} wei.`);
|
|
202
|
+
if (preflightErrors.length > 0) {
|
|
203
|
+
log.section("Preflight Failed");
|
|
204
|
+
for (const error of preflightErrors)
|
|
205
|
+
log.warn(error);
|
|
206
|
+
console.log();
|
|
207
|
+
if (!opts.simulate) {
|
|
208
|
+
die("Swap not sent. Fix the preflight issues above, or use --simulate to inspect session/hookData only.");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
165
211
|
log.deal([
|
|
166
212
|
{ label: "verified input", value: `${opts.amountIn} ${symbol}`, note: "exact-in swap", tone: "cyan" },
|
|
167
213
|
{ label: "LP fee", value: poolFeePercent(parseInt(cfg.fee ?? "3000")), note: "hook-priced flow", tone: "green" },
|
|
168
214
|
{ label: "ILAL fee", value: protocolFeePips > 0 ? pipsToPercent(protocolFeePips) : "off", note: protocolFeePips > 0 ? "protocol revenue" : "legacy router", tone: protocolFeePips > 0 ? "cyan" : "gray" },
|
|
169
215
|
]);
|
|
170
216
|
log.line();
|
|
171
|
-
// Check allowance — approve if needed
|
|
172
|
-
const approveSpin = new Spinner("Checking allowance…").start();
|
|
173
|
-
const allowed = await pubClient.readContract({
|
|
174
|
-
address: tokenIn,
|
|
175
|
-
abi: ERC20_ABI,
|
|
176
|
-
functionName: "allowance",
|
|
177
|
-
args: [account.address, cfg.router],
|
|
178
|
-
});
|
|
179
|
-
if (allowed < totalDebit) {
|
|
180
|
-
approveSpin.update(`Approving ${symbol} for ILALRouter…`);
|
|
181
|
-
const approveHash = await walClient.writeContract({
|
|
182
|
-
address: tokenIn,
|
|
183
|
-
abi: ERC20_ABI,
|
|
184
|
-
functionName: "approve",
|
|
185
|
-
args: [cfg.router, totalDebit * 10n], // approve 10× for future swaps
|
|
186
|
-
});
|
|
187
|
-
await pubClient.waitForTransactionReceipt({ hash: approveHash });
|
|
188
|
-
approveSpin.succeed(`Approved ${symbol} ${fmt.gray(fmt.hash(approveHash))}`);
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
approveSpin.succeed(`Allowance ok (${fmt.gray(allowed.toString())} wei)`);
|
|
192
|
-
}
|
|
193
217
|
// Sign session token
|
|
194
218
|
const signSpin = new Spinner("Signing session token…").start();
|
|
195
219
|
const ttl = parseInt(opts.ttl ?? "600");
|
|
@@ -233,11 +257,33 @@ export async function swap(opts) {
|
|
|
233
257
|
}
|
|
234
258
|
log.line();
|
|
235
259
|
if (opts.simulate) {
|
|
236
|
-
log.ok("Simulation mode — skipping on-chain tx");
|
|
260
|
+
log.ok("Simulation mode — skipping approval and on-chain tx");
|
|
237
261
|
log.kv("hookData", hookData.slice(0, 22) + "…");
|
|
238
262
|
console.log();
|
|
239
263
|
return;
|
|
240
264
|
}
|
|
265
|
+
// Check allowance — approve if needed
|
|
266
|
+
const approveSpin = new Spinner("Checking allowance…").start();
|
|
267
|
+
const allowed = await pubClient.readContract({
|
|
268
|
+
address: tokenIn,
|
|
269
|
+
abi: ERC20_ABI,
|
|
270
|
+
functionName: "allowance",
|
|
271
|
+
args: [account.address, cfg.router],
|
|
272
|
+
});
|
|
273
|
+
if (allowed < totalDebit) {
|
|
274
|
+
approveSpin.update(`Approving ${symbol} for ILALRouter…`);
|
|
275
|
+
const approveHash = await walClient.writeContract({
|
|
276
|
+
address: tokenIn,
|
|
277
|
+
abi: ERC20_ABI,
|
|
278
|
+
functionName: "approve",
|
|
279
|
+
args: [cfg.router, totalDebit * 10n], // approve 10× for future swaps
|
|
280
|
+
});
|
|
281
|
+
await pubClient.waitForTransactionReceipt({ hash: approveHash });
|
|
282
|
+
approveSpin.succeed(`Approved ${symbol} ${fmt.gray(fmt.hash(approveHash))}`);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
approveSpin.succeed(`Allowance ok (${fmt.gray(allowed.toString())} wei)`);
|
|
286
|
+
}
|
|
241
287
|
// Build PoolKey
|
|
242
288
|
const poolKey = {
|
|
243
289
|
currency0: c0,
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { proofMint, proofRenew } from "./commands/proof.js";
|
|
|
8
8
|
import { sessionSign } from "./commands/session.js";
|
|
9
9
|
import { poolPolicySet, poolPolicyGet } from "./commands/pool.js";
|
|
10
10
|
import { deploy } from "./commands/deploy.js";
|
|
11
|
-
import { demo, demoCheck } from "./commands/demo.js";
|
|
11
|
+
import { demo, demoCheck, demoFaucet } from "./commands/demo.js";
|
|
12
12
|
import { init } from "./commands/init.js";
|
|
13
13
|
import { status } from "./commands/status.js";
|
|
14
14
|
import { swap } from "./commands/swap.js";
|
|
@@ -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.
|
|
22
|
+
.version("0.2.3")
|
|
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
|
|
@@ -72,6 +72,15 @@ demoCommand
|
|
|
72
72
|
.action(async (opts) => {
|
|
73
73
|
await demoCheck(opts).catch(err);
|
|
74
74
|
});
|
|
75
|
+
demoCommand
|
|
76
|
+
.command("faucet")
|
|
77
|
+
.description("Mint mock demo TOKA/TOKB to a wallet (testnet mock tokens only)")
|
|
78
|
+
.option("-w, --wallet <address>", "Recipient wallet (defaults to PRIVATE_KEY address)")
|
|
79
|
+
.option("--amount <tokens>", "Human token amount to mint for each token", "10000")
|
|
80
|
+
.option("-k, --private-key <hex>", "Private key that pays gas")
|
|
81
|
+
.action(async (opts) => {
|
|
82
|
+
await demoFaucet(opts).catch(err);
|
|
83
|
+
});
|
|
75
84
|
const err = (e) => {
|
|
76
85
|
console.error(fmt.red(`\nError: ${e instanceof Error ? e.message : String(e)}\n`));
|
|
77
86
|
process.exit(1);
|