@ilalv3/cli 0.1.0
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/LICENSE +201 -0
- package/README.md +99 -0
- package/dist/commands/credential.d.ts +6 -0
- package/dist/commands/credential.js +78 -0
- package/dist/commands/demo.d.ts +7 -0
- package/dist/commands/demo.js +346 -0
- package/dist/commands/deploy.d.ts +10 -0
- package/dist/commands/deploy.js +94 -0
- package/dist/commands/init.d.ts +22 -0
- package/dist/commands/init.js +88 -0
- package/dist/commands/liquidity.d.ts +51 -0
- package/dist/commands/liquidity.js +249 -0
- package/dist/commands/mint.d.ts +11 -0
- package/dist/commands/mint.js +146 -0
- package/dist/commands/pool.d.ts +15 -0
- package/dist/commands/pool.js +115 -0
- package/dist/commands/proof.d.ts +16 -0
- package/dist/commands/proof.js +132 -0
- package/dist/commands/prove.d.ts +26 -0
- package/dist/commands/prove.js +292 -0
- package/dist/commands/session.d.ts +11 -0
- package/dist/commands/session.js +105 -0
- package/dist/commands/status.d.ts +14 -0
- package/dist/commands/status.js +164 -0
- package/dist/commands/swap.d.ts +38 -0
- package/dist/commands/swap.js +284 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.js +75 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +278 -0
- package/dist/ui.d.ts +59 -0
- package/dist/ui.js +218 -0
- package/package.json +48 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { createPublicClient, formatUnits, http, isAddress, isHex, } from "viem";
|
|
2
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
3
|
+
import { base, baseSepolia } from "viem/chains";
|
|
4
|
+
import { loadConfig } from "../config.js";
|
|
5
|
+
import { fmt, header, log } from "../ui.js";
|
|
6
|
+
const CHAINS = { "8453": base, "84532": baseSepolia };
|
|
7
|
+
const POOL_MANAGER = {
|
|
8
|
+
"84532": "0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408",
|
|
9
|
+
"8453": "0x498581fF718922c3f8e6A244956aF099B2652b2b",
|
|
10
|
+
};
|
|
11
|
+
const SAMPLE = {
|
|
12
|
+
wallet: "0x1b869CaC69Df23Ad9D727932496AEb3605538c8D",
|
|
13
|
+
issuer: "0x319c0F1cb46c85B42E051251c4db04BA6BD265a2",
|
|
14
|
+
hook: "0xdFF2ebBAc963f5Ed0B0EBCf021aB5EA16d57ea94",
|
|
15
|
+
router: "0x4A1F7E7d9D2D1f2A0c4A2F4A8C1A0B3E9E5d1111",
|
|
16
|
+
pool: "0x7ef1c0ffee00000000000000000000000000000000000000000000000000bEEF",
|
|
17
|
+
proof: "0x91f2b8a0c43e902f7f1a8c0d",
|
|
18
|
+
session: "0x6b84eac5e0db21f8d5d43b7a",
|
|
19
|
+
};
|
|
20
|
+
const ZERO = "0x0000000000000000000000000000000000000000";
|
|
21
|
+
const CNF_ABI = [
|
|
22
|
+
{ name: "isValid", type: "function", stateMutability: "view", inputs: [{ name: "wallet", type: "address" }], outputs: [{ type: "bool" }] },
|
|
23
|
+
{ name: "credentialOf", type: "function", stateMutability: "view", inputs: [{ name: "wallet", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
24
|
+
{ name: "merkleRoot", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
|
|
25
|
+
{ name: "zkVerifier", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
26
|
+
];
|
|
27
|
+
const REGISTRY_ABI = [
|
|
28
|
+
{ 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" }] }] },
|
|
29
|
+
];
|
|
30
|
+
const ROUTER_ABI = [
|
|
31
|
+
{ name: "protocolFeePips", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint24" }] },
|
|
32
|
+
{ name: "treasury", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
33
|
+
];
|
|
34
|
+
const ERC20_ABI = [
|
|
35
|
+
{ name: "symbol", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
|
|
36
|
+
{ name: "decimals", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] },
|
|
37
|
+
{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
38
|
+
{ name: "allowance", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }, { name: "spender", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
39
|
+
];
|
|
40
|
+
function stage(n, title, subtitle) {
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(` ${fmt.badge(`step ${n}`, "cyan")} ${fmt.bold(title)}`);
|
|
43
|
+
console.log(` ${fmt.gray(subtitle)}`);
|
|
44
|
+
}
|
|
45
|
+
function verdict(label, value, tone) {
|
|
46
|
+
console.log(` ${tone === "green" ? fmt.green("●") : tone === "yellow" ? fmt.yellow("●") : tone === "red" ? fmt.red("●") : fmt.cyan("●")} ${fmt.gray(label.padEnd(20))} ${value}`);
|
|
47
|
+
}
|
|
48
|
+
function flowStep(label, value, tone = "cyan") {
|
|
49
|
+
const color = tone === "green" ? fmt.green : tone === "yellow" ? fmt.yellow : tone === "red" ? fmt.red : fmt.cyan;
|
|
50
|
+
console.log(` ${color("●")} ${fmt.gray(label.padEnd(14))} ${value}`);
|
|
51
|
+
}
|
|
52
|
+
function pipsToPercent(pips) {
|
|
53
|
+
return `${(pips / 10_000).toFixed(4).replace(/0+$/, "").replace(/\.$/, "")}%`;
|
|
54
|
+
}
|
|
55
|
+
export async function demo(opts) {
|
|
56
|
+
header("Institutional DeFi Demo", "local preview");
|
|
57
|
+
log.section("Thesis");
|
|
58
|
+
log.kv("problem", "institutions face a compliance / cost / privacy trilemma");
|
|
59
|
+
log.kv("answer", "verified eligibility becomes low-cost private trading access");
|
|
60
|
+
log.kv("native layer", "Uniswap v4 Hook, not a wrapper or critical-path API");
|
|
61
|
+
log.kv("demo proof", "real Base Sepolia liquidity + swap through PoolManager");
|
|
62
|
+
log.line();
|
|
63
|
+
stage(1, "Non-eligible wallet attempts to trade", "The pool rejects access before swap execution.");
|
|
64
|
+
verdict("wallet", fmt.addr("0x000000000000000000000000000000000000dEaD"), "cyan");
|
|
65
|
+
verdict("credential", fmt.badge("missing", "red"), "red");
|
|
66
|
+
verdict("hook check", "beforeSwap -> CredentialInvalid", "red");
|
|
67
|
+
verdict("result", fmt.badge("reverted", "red") + " no trade, no bypass", "red");
|
|
68
|
+
stage(2, "Verified trader mints a CNF credential", "Coinbase EAS or ZK proof creates an expiring, non-transferable credential.");
|
|
69
|
+
verdict("wallet", fmt.addr(SAMPLE.wallet), "cyan");
|
|
70
|
+
verdict("issuer", fmt.addr(SAMPLE.issuer), "cyan");
|
|
71
|
+
verdict("proof", fmt.hash(SAMPLE.proof), "cyan");
|
|
72
|
+
verdict("credential", fmt.badge("valid", "green") + " expires in 90d", "green");
|
|
73
|
+
stage(3, "Trader signs a local session", "No API call. No gas. Session binds user + authorized caller + pool + action.");
|
|
74
|
+
verdict("user", fmt.addr(SAMPLE.wallet), "cyan");
|
|
75
|
+
verdict("caller", fmt.addr(SAMPLE.router), "cyan");
|
|
76
|
+
verdict("pool", fmt.hash(SAMPLE.pool), "cyan");
|
|
77
|
+
verdict("session", fmt.hash(SAMPLE.session), "cyan");
|
|
78
|
+
verdict("cost", fmt.badge("0 gas", "green") + " local EIP-712 signature", "green");
|
|
79
|
+
stage(4, "Compliant swap executes through the Hook", "The Hook enforces policy, credential type, caller binding, and nonce replay protection.");
|
|
80
|
+
verdict("hook", fmt.addr(SAMPLE.hook), "cyan");
|
|
81
|
+
verdict("policy", fmt.badge("configured", "green") + " required CNF type matched", "green");
|
|
82
|
+
verdict("nonce", fmt.badge("unused", "green") + " consumed on success", "green");
|
|
83
|
+
verdict("result", fmt.badge("swap allowed", "green") + " institutional liquidity path opened", "green");
|
|
84
|
+
console.log();
|
|
85
|
+
log.section("What Changed Since The Prototype");
|
|
86
|
+
log.kv("before", "API verified proofs and activated sessions");
|
|
87
|
+
log.kv("now", "CLI/SDK generate locally; contracts enforce on-chain");
|
|
88
|
+
log.kv("trust path", fmt.badge("no API critical path", "green") + " Hook-native enforcement");
|
|
89
|
+
log.line();
|
|
90
|
+
log.result("Pitch line", "ILAL solves the compliance-cost-privacy trilemma for institutional DeFi.", "cyan");
|
|
91
|
+
if (opts.commands) {
|
|
92
|
+
console.log();
|
|
93
|
+
log.section("Live Demo Commands");
|
|
94
|
+
log.command("ilal status --wallet <wallet>");
|
|
95
|
+
log.command("ilal credential prove --wallet <wallet> --update-root");
|
|
96
|
+
log.command("ilal session sign --pool <poolId> --action swap --hook <hook> --issuer <issuer> --caller <router>");
|
|
97
|
+
log.command("ilal swap --amount-in 100 --token-in <token> --pool-id <poolId>");
|
|
98
|
+
}
|
|
99
|
+
console.log();
|
|
100
|
+
}
|
|
101
|
+
function ok(label, value) {
|
|
102
|
+
console.log(` ${fmt.green("●")} ${fmt.gray(label.padEnd(20))} ${value}`);
|
|
103
|
+
}
|
|
104
|
+
function warn(label, value) {
|
|
105
|
+
console.log(` ${fmt.yellow("●")} ${fmt.gray(label.padEnd(20))} ${value}`);
|
|
106
|
+
}
|
|
107
|
+
function bad(label, value) {
|
|
108
|
+
console.log(` ${fmt.red("●")} ${fmt.gray(label.padEnd(20))} ${value}`);
|
|
109
|
+
}
|
|
110
|
+
async function hasCode(client, label, address) {
|
|
111
|
+
if (!address) {
|
|
112
|
+
bad(label, fmt.badge("missing", "red"));
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (!isAddress(address)) {
|
|
116
|
+
bad(label, `invalid address ${address}`);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const code = await client.getBytecode({ address: address });
|
|
121
|
+
if (code && code !== "0x") {
|
|
122
|
+
ok(label, fmt.addr(address));
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
bad(label, `${fmt.addr(address)} ${fmt.badge("no code", "red")}`);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
bad(label, e instanceof Error ? e.message.split("\n")[0] : String(e));
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export async function demoCheck(opts) {
|
|
134
|
+
const cfg = loadConfig();
|
|
135
|
+
const chain = CHAINS[cfg.chain ?? "84532"] ?? baseSepolia;
|
|
136
|
+
const client = createPublicClient({ chain, transport: http(cfg.rpc) });
|
|
137
|
+
const rawKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
|
|
138
|
+
const wallet = opts.wallet
|
|
139
|
+
?? (rawKey && isHex(rawKey) && rawKey.length === 66 ? privateKeyToAccount(rawKey).address : undefined);
|
|
140
|
+
header("Live Demo Preflight", chain.name);
|
|
141
|
+
log.deal([
|
|
142
|
+
{ label: "Private proof", value: "ZK/CNF", note: "eligibility without full identity", tone: "cyan" },
|
|
143
|
+
{ label: "Verified LP fee", value: "0.05%", note: "dynamic fee override", tone: "green" },
|
|
144
|
+
{ label: "ILAL protocol fee", value: "0.005%", note: "on-chain verified flow revenue", tone: "cyan" },
|
|
145
|
+
]);
|
|
146
|
+
log.line();
|
|
147
|
+
let score = 0;
|
|
148
|
+
let total = 0;
|
|
149
|
+
const pass = (condition) => {
|
|
150
|
+
total++;
|
|
151
|
+
if (condition)
|
|
152
|
+
score++;
|
|
153
|
+
};
|
|
154
|
+
log.section("Network");
|
|
155
|
+
log.kv("rpc", cfg.rpc ?? "default viem transport");
|
|
156
|
+
try {
|
|
157
|
+
const block = await client.getBlockNumber();
|
|
158
|
+
ok("latest block", block.toString());
|
|
159
|
+
pass(true);
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
bad("latest block", e instanceof Error ? e.message.split("\n")[0] : String(e));
|
|
163
|
+
pass(false);
|
|
164
|
+
}
|
|
165
|
+
log.line();
|
|
166
|
+
log.section("Configuration", "addresses that define the live path");
|
|
167
|
+
const configItems = [
|
|
168
|
+
["issuer", cfg.issuer, "address"],
|
|
169
|
+
["hook", cfg.hook, "address"],
|
|
170
|
+
["registry", cfg.registry, "address"],
|
|
171
|
+
["router", cfg.router, "address"],
|
|
172
|
+
["treasury", cfg.treasury, "address"],
|
|
173
|
+
["currency0", cfg.tokenA, "address"],
|
|
174
|
+
["currency1", cfg.tokenB, "address"],
|
|
175
|
+
["poolId", cfg.poolId, "bytes32"],
|
|
176
|
+
["fee", cfg.fee ?? "3000", "text"],
|
|
177
|
+
["tickSpacing", cfg.tickSpacing ?? "60", "text"],
|
|
178
|
+
];
|
|
179
|
+
for (const [label, value, kind] of configItems) {
|
|
180
|
+
const valid = !!value && (kind === "address" ? isAddress(value) : kind === "bytes32" ? isHex(value) && value.length === 66 : true);
|
|
181
|
+
if (valid) {
|
|
182
|
+
const display = kind === "bytes32"
|
|
183
|
+
? fmt.hash(value)
|
|
184
|
+
: kind === "address"
|
|
185
|
+
? fmt.addr(value)
|
|
186
|
+
: label === "fee" && value === "8388608"
|
|
187
|
+
? `${fmt.badge("dynamic", "green")} verified flow 0.05%`
|
|
188
|
+
: value;
|
|
189
|
+
ok(label, display);
|
|
190
|
+
}
|
|
191
|
+
else
|
|
192
|
+
bad(label, fmt.badge("missing", "red"));
|
|
193
|
+
pass(valid);
|
|
194
|
+
}
|
|
195
|
+
log.line();
|
|
196
|
+
log.section("Contract Code", "must exist on-chain");
|
|
197
|
+
pass(await hasCode(client, "CNFIssuer", cfg.issuer));
|
|
198
|
+
pass(await hasCode(client, "ComplianceHook", cfg.hook));
|
|
199
|
+
pass(await hasCode(client, "PolicyRegistry", cfg.registry));
|
|
200
|
+
pass(await hasCode(client, "ILALRouter", cfg.router));
|
|
201
|
+
pass(await hasCode(client, "currency0", cfg.tokenA));
|
|
202
|
+
pass(await hasCode(client, "currency1", cfg.tokenB));
|
|
203
|
+
log.line();
|
|
204
|
+
if (cfg.router && isAddress(cfg.router)) {
|
|
205
|
+
log.section("Verified Flow Economics");
|
|
206
|
+
try {
|
|
207
|
+
const [protocolFeePips, treasury] = await Promise.all([
|
|
208
|
+
client.readContract({ address: cfg.router, abi: ROUTER_ABI, functionName: "protocolFeePips" }),
|
|
209
|
+
client.readContract({ address: cfg.router, abi: ROUTER_ABI, functionName: "treasury" }),
|
|
210
|
+
]);
|
|
211
|
+
ok("LP fee", cfg.fee === "8388608" ? `${fmt.badge("dynamic", "green")} verified 0.05%` : "pool fee tier");
|
|
212
|
+
ok("ILAL fee", protocolFeePips > 0 ? `${fmt.badge("protocol", "cyan")} ${pipsToPercent(protocolFeePips)}` : fmt.badge("off", "yellow"));
|
|
213
|
+
ok("treasury", fmt.addr(treasury));
|
|
214
|
+
pass(true);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
warn("protocol fee", fmt.badge("legacy router", "yellow"));
|
|
218
|
+
pass(true);
|
|
219
|
+
}
|
|
220
|
+
log.line();
|
|
221
|
+
}
|
|
222
|
+
if (cfg.issuer && isAddress(cfg.issuer)) {
|
|
223
|
+
log.section("Issuer State");
|
|
224
|
+
try {
|
|
225
|
+
const [root, verifier] = await Promise.all([
|
|
226
|
+
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "merkleRoot" }),
|
|
227
|
+
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "zkVerifier" }),
|
|
228
|
+
]);
|
|
229
|
+
if (root === 0n)
|
|
230
|
+
warn("merkleRoot", fmt.badge("not set", "yellow"));
|
|
231
|
+
else
|
|
232
|
+
ok("merkleRoot", root.toString().slice(0, 24) + "...");
|
|
233
|
+
if (verifier === ZERO)
|
|
234
|
+
warn("zkVerifier", fmt.badge("not set", "yellow"));
|
|
235
|
+
else
|
|
236
|
+
ok("zkVerifier", fmt.addr(verifier));
|
|
237
|
+
}
|
|
238
|
+
catch (e) {
|
|
239
|
+
bad("issuer reads", e instanceof Error ? e.message.split("\n")[0] : String(e));
|
|
240
|
+
}
|
|
241
|
+
if (wallet && isAddress(wallet)) {
|
|
242
|
+
try {
|
|
243
|
+
const [valid, tokenId] = await Promise.all([
|
|
244
|
+
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "isValid", args: [wallet] }),
|
|
245
|
+
client.readContract({ address: cfg.issuer, abi: CNF_ABI, functionName: "credentialOf", args: [wallet] }),
|
|
246
|
+
]);
|
|
247
|
+
if (valid)
|
|
248
|
+
ok("credential", `${fmt.badge("valid", "green")} token #${tokenId}`);
|
|
249
|
+
else
|
|
250
|
+
warn("credential", tokenId === 0n ? fmt.badge("missing", "yellow") : fmt.badge("invalid", "yellow"));
|
|
251
|
+
pass(valid);
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
bad("credential", e instanceof Error ? e.message.split("\n")[0] : String(e));
|
|
255
|
+
pass(false);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
warn("wallet", "pass --wallet or set PRIVATE_KEY for credential checks");
|
|
260
|
+
}
|
|
261
|
+
log.line();
|
|
262
|
+
}
|
|
263
|
+
if (cfg.registry && cfg.poolId && isAddress(cfg.registry) && isHex(cfg.poolId) && cfg.poolId.length === 66) {
|
|
264
|
+
log.section("Pool Policy");
|
|
265
|
+
try {
|
|
266
|
+
const policy = await client.readContract({
|
|
267
|
+
address: cfg.registry,
|
|
268
|
+
abi: REGISTRY_ABI,
|
|
269
|
+
functionName: "getPolicy",
|
|
270
|
+
args: [cfg.poolId],
|
|
271
|
+
});
|
|
272
|
+
const issuerMatches = policy.cnfIssuer.toLowerCase() === (cfg.issuer ?? "").toLowerCase();
|
|
273
|
+
const ready = policy.enabled && issuerMatches;
|
|
274
|
+
(ready ? ok : warn)("policy", `${policy.enabled ? fmt.badge("enabled", "green") : fmt.badge("disabled", "yellow")} issuer ${fmt.addr(policy.cnfIssuer)}`);
|
|
275
|
+
pass(ready);
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
bad("policy", e instanceof Error ? e.message.split("\n")[0] : String(e));
|
|
279
|
+
pass(false);
|
|
280
|
+
}
|
|
281
|
+
log.line();
|
|
282
|
+
}
|
|
283
|
+
if (wallet && cfg.router && cfg.tokenA && cfg.tokenB && isAddress(wallet) && isAddress(cfg.router)) {
|
|
284
|
+
log.section("Wallet Balances");
|
|
285
|
+
for (const [label, token] of [["currency0", cfg.tokenA], ["currency1", cfg.tokenB]]) {
|
|
286
|
+
if (!token || !isAddress(token))
|
|
287
|
+
continue;
|
|
288
|
+
try {
|
|
289
|
+
const [symbol, decimals, balance, allowance] = await Promise.all([
|
|
290
|
+
client.readContract({ address: token, abi: ERC20_ABI, functionName: "symbol" }),
|
|
291
|
+
client.readContract({ address: token, abi: ERC20_ABI, functionName: "decimals" }),
|
|
292
|
+
client.readContract({ address: token, abi: ERC20_ABI, functionName: "balanceOf", args: [wallet] }),
|
|
293
|
+
client.readContract({ address: token, abi: ERC20_ABI, functionName: "allowance", args: [wallet, cfg.router] }),
|
|
294
|
+
]);
|
|
295
|
+
const balanceText = `${formatUnits(balance, decimals)} ${symbol}`;
|
|
296
|
+
const allowanceText = allowance > 0n ? fmt.badge("approved", "green") : fmt.badge("needs approval", "yellow");
|
|
297
|
+
(balance > 0n ? ok : warn)(label, `${balanceText} ${allowanceText}`);
|
|
298
|
+
pass(balance > 0n);
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
bad(label, e instanceof Error ? e.message.split("\n")[0] : String(e));
|
|
302
|
+
pass(false);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
log.line();
|
|
306
|
+
}
|
|
307
|
+
const readiness = total === 0 ? 0 : Math.round((score / total) * 100);
|
|
308
|
+
log.section("Readiness");
|
|
309
|
+
const tone = readiness >= 85 ? "green" : readiness >= 60 ? "yellow" : "red";
|
|
310
|
+
log.progress("score", readiness, tone);
|
|
311
|
+
log.metrics([
|
|
312
|
+
{ label: "credential", value: wallet ? "ready" : "missing", tone: wallet ? "green" : "yellow" },
|
|
313
|
+
{ label: "policy", value: cfg.poolId ? "enabled" : "missing", tone: cfg.poolId ? "green" : "yellow" },
|
|
314
|
+
{ label: "deal", value: cfg.fee === "8388608" ? "better" : "standard", tone: cfg.fee === "8388608" ? "green" : "gray" },
|
|
315
|
+
]);
|
|
316
|
+
if (readiness >= 85) {
|
|
317
|
+
log.callout("Live demo ready", "credential, policy, hook, router, pool, and balances are aligned", "green");
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
log.callout("Live demo not ready", "fill the missing config/state first", tone);
|
|
321
|
+
}
|
|
322
|
+
log.line();
|
|
323
|
+
log.section("Live Path", readiness >= 85 ? "what the judge is about to see" : "target flow");
|
|
324
|
+
flowStep("credential", wallet ? `${fmt.addr(wallet)} holds a valid CNF` : "wallet not selected", wallet ? "green" : "yellow");
|
|
325
|
+
flowStep("session", `local EIP-712 binds user + router + pool + action`, "green");
|
|
326
|
+
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("missing", "red"), cfg.poolId ? "green" : "red");
|
|
328
|
+
flowStep("result", readiness >= 85 ? fmt.badge("ready for real tx", "green") : fmt.badge("preflight incomplete", "yellow"), readiness >= 85 ? "green" : "yellow");
|
|
329
|
+
log.line();
|
|
330
|
+
log.section("Next Commands");
|
|
331
|
+
if (!cfg.router || !cfg.tokenA || !cfg.tokenB || !cfg.poolId) {
|
|
332
|
+
const poolManager = POOL_MANAGER[cfg.chain ?? "84532"] ?? POOL_MANAGER["84532"];
|
|
333
|
+
log.command(`POOL_MANAGER=${poolManager} CNF_ISSUER=${cfg.issuer ?? "<issuer>"} HOOK_ADDR=${cfg.hook ?? "<hook>"} REGISTRY_ADDR=${cfg.registry ?? "<registry>"} forge script contracts/script/DeployDemo.s.sol --rpc-url ${cfg.rpc ?? "https://sepolia.base.org"} --broadcast`);
|
|
334
|
+
log.info("Then copy router/tokenA/tokenB/poolId into .ilal.json");
|
|
335
|
+
}
|
|
336
|
+
if (wallet) {
|
|
337
|
+
log.command(`ilal status --wallet ${wallet}`);
|
|
338
|
+
log.command(`ilal session sign --pool ${cfg.poolId ?? "<poolId>"} --action swap --hook ${cfg.hook ?? "<hook>"} --issuer ${cfg.issuer ?? "<issuer>"} --caller ${cfg.router ?? "<router>"}`);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
log.command("ilal demo check --wallet <wallet>");
|
|
342
|
+
}
|
|
343
|
+
const suggestedTokenIn = cfg.tokenB ?? "<token>";
|
|
344
|
+
log.command(`ilal swap --amount-in 0.001 --token-in ${suggestedTokenIn}`);
|
|
345
|
+
console.log();
|
|
346
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import { fmt, log, die } from "../ui.js";
|
|
5
|
+
import { EAS_ADDRESSES, COINBASE_ATTESTER, COINBASE_SCHEMA_UID } from "../constants.js";
|
|
6
|
+
const POOL_MANAGERS = {
|
|
7
|
+
"8453": "0x498581ff718922c3f8e6a244956af099b2652b2b", // Base mainnet
|
|
8
|
+
"84532": "0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408", // Base Sepolia
|
|
9
|
+
};
|
|
10
|
+
const RPC_URLS = {
|
|
11
|
+
"8453": "https://mainnet.base.org",
|
|
12
|
+
"84532": "https://sepolia.base.org",
|
|
13
|
+
};
|
|
14
|
+
export async function deploy(opts) {
|
|
15
|
+
const privateKey = opts.privateKey ?? process.env["PRIVATE_KEY"];
|
|
16
|
+
if (!privateKey)
|
|
17
|
+
die("Private key required. Use --private-key or set PRIVATE_KEY env var.");
|
|
18
|
+
const chainId = opts.chain;
|
|
19
|
+
const poolManager = POOL_MANAGERS[chainId];
|
|
20
|
+
if (!poolManager)
|
|
21
|
+
die(`Unsupported chain: ${chainId}. Supported: 8453 (Base), 84532 (Base Sepolia).`);
|
|
22
|
+
const easAddress = EAS_ADDRESSES[parseInt(chainId)];
|
|
23
|
+
if (!easAddress)
|
|
24
|
+
die(`No EAS address for chain ${chainId}.`);
|
|
25
|
+
const rpc = opts.rpc ?? RPC_URLS[chainId] ?? die(`No default RPC for chain ${chainId}. Use --rpc.`);
|
|
26
|
+
// Find contracts directory
|
|
27
|
+
const contractsDir = opts.contractsDir
|
|
28
|
+
? resolve(opts.contractsDir)
|
|
29
|
+
: resolve(process.cwd(), "../contracts");
|
|
30
|
+
if (!existsSync(resolve(contractsDir, "foundry.toml"))) {
|
|
31
|
+
die(`Contracts directory not found at ${contractsDir}.\nUse --contracts-dir or run from the ilal project root.`);
|
|
32
|
+
}
|
|
33
|
+
const isMock = opts.mock;
|
|
34
|
+
if (isMock && chainId === "8453")
|
|
35
|
+
die("--mock is for testnets only. Use --chain 84532.");
|
|
36
|
+
if (isMock && !opts.walletToSeed)
|
|
37
|
+
die("--mock requires --wallet-to-seed <address>.");
|
|
38
|
+
console.log();
|
|
39
|
+
console.log(fmt.bold(` ILAL Protocol Deploy${isMock ? fmt.yellow(" [MOCK / TESTNET]") : ""}`));
|
|
40
|
+
log.line();
|
|
41
|
+
log.kv("chain", chainId === "8453" ? "Base mainnet" : "Base Sepolia");
|
|
42
|
+
log.kv("poolManager", poolManager);
|
|
43
|
+
if (isMock) {
|
|
44
|
+
log.kv("eas", fmt.yellow("MockEAS (testnet only)"));
|
|
45
|
+
log.kv("walletToSeed", opts.walletToSeed);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
log.kv("eas", easAddress);
|
|
49
|
+
log.kv("attester", COINBASE_ATTESTER);
|
|
50
|
+
log.kv("schemaUID", COINBASE_SCHEMA_UID.slice(0, 20) + "...");
|
|
51
|
+
}
|
|
52
|
+
log.kv("broadcast", opts.broadcast ? fmt.yellow("yes - tx will be sent") : "no (dry run)");
|
|
53
|
+
log.line();
|
|
54
|
+
const env = {
|
|
55
|
+
...process.env,
|
|
56
|
+
PRIVATE_KEY: privateKey,
|
|
57
|
+
POOL_MANAGER: poolManager,
|
|
58
|
+
};
|
|
59
|
+
if (isMock) {
|
|
60
|
+
env["WALLET_TO_SEED"] = opts.walletToSeed;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
env["EAS_ADDRESS"] = easAddress;
|
|
64
|
+
env["SCHEMA_UID"] = COINBASE_SCHEMA_UID;
|
|
65
|
+
env["TRUSTED_ATTESTER"] = COINBASE_ATTESTER;
|
|
66
|
+
}
|
|
67
|
+
const script = isMock ? "script/DeployMock.s.sol" : "script/Deploy.s.sol";
|
|
68
|
+
const flags = [
|
|
69
|
+
`--rpc-url ${rpc}`,
|
|
70
|
+
opts.broadcast ? "--broadcast" : "",
|
|
71
|
+
opts.verify ? "--verify" : "",
|
|
72
|
+
"--slow",
|
|
73
|
+
].filter(Boolean).join(" ");
|
|
74
|
+
const cmd = `forge script ${script} ${flags}`;
|
|
75
|
+
log.step(`Running: ${fmt.gray(cmd)}`);
|
|
76
|
+
console.log();
|
|
77
|
+
try {
|
|
78
|
+
execSync(cmd, {
|
|
79
|
+
cwd: contractsDir,
|
|
80
|
+
env,
|
|
81
|
+
stdio: "inherit",
|
|
82
|
+
});
|
|
83
|
+
console.log();
|
|
84
|
+
log.ok(fmt.bold(fmt.green("Deployment complete")));
|
|
85
|
+
if (opts.broadcast) {
|
|
86
|
+
log.step("Update your CLI commands with the deployed addresses:");
|
|
87
|
+
console.log(` ${fmt.gray("ilal pool policy set --registry <PolicyRegistry> --issuer <CNFIssuer> ...")}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
die("forge script failed. Check output above.");
|
|
92
|
+
}
|
|
93
|
+
console.log();
|
|
94
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* init.ts — `ilal init`
|
|
3
|
+
*
|
|
4
|
+
* Creates a .ilal.json config file in the current directory so you never
|
|
5
|
+
* have to pass --issuer, --chain, etc. on every command.
|
|
6
|
+
*/
|
|
7
|
+
export declare function init(opts: {
|
|
8
|
+
issuer?: string;
|
|
9
|
+
hook?: string;
|
|
10
|
+
registry?: string;
|
|
11
|
+
router?: string;
|
|
12
|
+
treasury?: string;
|
|
13
|
+
tokenA?: string;
|
|
14
|
+
tokenB?: string;
|
|
15
|
+
poolId?: string;
|
|
16
|
+
fee?: string;
|
|
17
|
+
tickSpacing?: string;
|
|
18
|
+
chain: string;
|
|
19
|
+
rpc?: string;
|
|
20
|
+
circuitDir?: string;
|
|
21
|
+
force: boolean;
|
|
22
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* init.ts — `ilal init`
|
|
3
|
+
*
|
|
4
|
+
* Creates a .ilal.json config file in the current directory so you never
|
|
5
|
+
* have to pass --issuer, --chain, etc. on every command.
|
|
6
|
+
*/
|
|
7
|
+
import { isAddress } from "viem";
|
|
8
|
+
import { writeConfig, configFilePath } from "../config.js";
|
|
9
|
+
import { fmt, log, header, die } from "../ui.js";
|
|
10
|
+
// Known testnet / mainnet addresses for quick init
|
|
11
|
+
const PRESETS = {
|
|
12
|
+
"84532": {
|
|
13
|
+
issuer: "0x319c0F1cb46c85B42E051251c4db04BA6BD265a2",
|
|
14
|
+
hook: "0xdFF2ebBAc963f5Ed0B0EBCf021aB5EA16d57ea94",
|
|
15
|
+
registry: "0x72A425672c1D0FA95C75F5073e6DAf72194A1E0F",
|
|
16
|
+
rpc: "https://sepolia.base.org",
|
|
17
|
+
},
|
|
18
|
+
"8453": {
|
|
19
|
+
rpc: "https://mainnet.base.org",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export async function init(opts) {
|
|
23
|
+
header("ILAL Init", "Creating .ilal.json in current directory");
|
|
24
|
+
const existing = configFilePath();
|
|
25
|
+
if (existing && !opts.force) {
|
|
26
|
+
log.warn(`Config already exists: ${fmt.cyan(existing)}`);
|
|
27
|
+
log.info(`Use ${fmt.cyan("--force")} to overwrite.`);
|
|
28
|
+
console.log();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// Start with preset for the chain
|
|
32
|
+
const preset = PRESETS[opts.chain] ?? {};
|
|
33
|
+
const config = {
|
|
34
|
+
chain: opts.chain,
|
|
35
|
+
issuer: opts.issuer ?? preset["issuer"],
|
|
36
|
+
hook: opts.hook ?? preset["hook"],
|
|
37
|
+
registry: opts.registry ?? preset["registry"],
|
|
38
|
+
router: opts.router,
|
|
39
|
+
treasury: opts.treasury,
|
|
40
|
+
tokenA: opts.tokenA,
|
|
41
|
+
tokenB: opts.tokenB,
|
|
42
|
+
poolId: opts.poolId,
|
|
43
|
+
fee: opts.fee,
|
|
44
|
+
tickSpacing: opts.tickSpacing,
|
|
45
|
+
rpc: opts.rpc ?? preset["rpc"],
|
|
46
|
+
...(opts.circuitDir ? { circuitDir: opts.circuitDir } : {}),
|
|
47
|
+
};
|
|
48
|
+
// Validate addresses
|
|
49
|
+
for (const [key, val] of Object.entries(config)) {
|
|
50
|
+
if (val && (key === "issuer" || key === "hook" || key === "registry" || key === "router" || key === "treasury" || key === "tokenA" || key === "tokenB")) {
|
|
51
|
+
if (!isAddress(val))
|
|
52
|
+
die(`Invalid ${key} address: ${val}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const path = writeConfig(config);
|
|
56
|
+
log.ok(`Created ${fmt.cyan(path)}`);
|
|
57
|
+
log.line();
|
|
58
|
+
if (config.chain)
|
|
59
|
+
log.kv("chain", config.chain === "84532" ? "Base Sepolia" : "Base");
|
|
60
|
+
if (config.issuer)
|
|
61
|
+
log.kv("issuer", fmt.cyan(config.issuer));
|
|
62
|
+
if (config.hook)
|
|
63
|
+
log.kv("hook", fmt.cyan(config.hook));
|
|
64
|
+
if (config.registry)
|
|
65
|
+
log.kv("registry", fmt.cyan(config.registry));
|
|
66
|
+
if (config.router)
|
|
67
|
+
log.kv("router", fmt.cyan(config.router));
|
|
68
|
+
if (config.treasury)
|
|
69
|
+
log.kv("treasury", fmt.cyan(config.treasury));
|
|
70
|
+
if (config.tokenA)
|
|
71
|
+
log.kv("tokenA", fmt.cyan(config.tokenA));
|
|
72
|
+
if (config.tokenB)
|
|
73
|
+
log.kv("tokenB", fmt.cyan(config.tokenB));
|
|
74
|
+
if (config.poolId)
|
|
75
|
+
log.kv("poolId", fmt.hash(config.poolId));
|
|
76
|
+
if (config.fee)
|
|
77
|
+
log.kv("fee", config.fee === "8388608" ? "dynamic" : config.fee);
|
|
78
|
+
if (config.tickSpacing)
|
|
79
|
+
log.kv("tickSpacing", config.tickSpacing);
|
|
80
|
+
if (config.rpc)
|
|
81
|
+
log.kv("rpc", config.rpc);
|
|
82
|
+
log.line();
|
|
83
|
+
console.log(` ${fmt.gray("You can now run commands without --issuer and --chain flags:")}`);
|
|
84
|
+
console.log();
|
|
85
|
+
console.log(` ${fmt.cyan("ilal credential prove --wallet 0x...")}`);
|
|
86
|
+
console.log(` ${fmt.cyan("ilal status --wallet 0x...")}`);
|
|
87
|
+
console.log();
|
|
88
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* liquidity.ts — `ilal pool add-liquidity` / `ilal pool remove-liquidity`
|
|
3
|
+
*
|
|
4
|
+
* Add or remove liquidity from an ILAL-compliant Uniswap v4 pool.
|
|
5
|
+
*
|
|
6
|
+
* Signs a fresh SessionToken internally, calls ILALRouter.addLiquidity()
|
|
7
|
+
* or removeLiquidity(). The ComplianceHook verifies session + CNF.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ilal pool add-liquidity \
|
|
11
|
+
* --tick-lower -600 --tick-upper 600 \
|
|
12
|
+
* --liquidity 1000000000000000000 \
|
|
13
|
+
* --router 0xROUTER --hook 0xHOOK --issuer 0xISSUER \
|
|
14
|
+
* --pool-id 0xPOOLID --token-a 0xTOKA --token-b 0xTOKB
|
|
15
|
+
*/
|
|
16
|
+
export declare function addLiquidity(opts: {
|
|
17
|
+
tickLower: string;
|
|
18
|
+
tickUpper: string;
|
|
19
|
+
liquidity: string;
|
|
20
|
+
salt?: string;
|
|
21
|
+
poolId?: string;
|
|
22
|
+
router?: string;
|
|
23
|
+
hook?: string;
|
|
24
|
+
issuer?: string;
|
|
25
|
+
tokenA?: string;
|
|
26
|
+
tokenB?: string;
|
|
27
|
+
fee?: string;
|
|
28
|
+
tickSpacing?: string;
|
|
29
|
+
chain?: string;
|
|
30
|
+
rpc?: string;
|
|
31
|
+
privateKey?: string;
|
|
32
|
+
ttl?: string;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
export declare function removeLiquidity(opts: {
|
|
35
|
+
tickLower: string;
|
|
36
|
+
tickUpper: string;
|
|
37
|
+
liquidity: string;
|
|
38
|
+
salt?: string;
|
|
39
|
+
poolId?: string;
|
|
40
|
+
router?: string;
|
|
41
|
+
hook?: string;
|
|
42
|
+
issuer?: string;
|
|
43
|
+
tokenA?: string;
|
|
44
|
+
tokenB?: string;
|
|
45
|
+
fee?: string;
|
|
46
|
+
tickSpacing?: string;
|
|
47
|
+
chain?: string;
|
|
48
|
+
rpc?: string;
|
|
49
|
+
privateKey?: string;
|
|
50
|
+
ttl?: string;
|
|
51
|
+
}): Promise<void>;
|