@iamnotdou/ccp 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/.env.example +29 -0
- package/dist/auditor/attest.js +141 -0
- package/dist/auditor/attest.js.map +1 -0
- package/dist/cli.js +1006 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.js +35 -0
- package/dist/client.js.map +1 -0
- package/dist/config.js +31 -0
- package/dist/config.js.map +1 -0
- package/dist/contracts/abis.js +2212 -0
- package/dist/contracts/abis.js.map +1 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/demo.js +218 -0
- package/dist/demo.js.map +1 -0
- package/dist/ens/subnames.js +33 -0
- package/dist/ens/subnames.js.map +1 -0
- package/dist/ens/textRecords.js +111 -0
- package/dist/ens/textRecords.js.map +1 -0
- package/dist/hcs/listener.js +50 -0
- package/dist/hcs/listener.js.map +1 -0
- package/dist/hcs/publisher.js +126 -0
- package/dist/hcs/publisher.js.map +1 -0
- package/dist/hedera/mirrorNode.js +75 -0
- package/dist/hedera/mirrorNode.js.map +1 -0
- package/dist/ledger/cosigner.js +79 -0
- package/dist/ledger/cosigner.js.map +1 -0
- package/dist/mcp.js +648 -0
- package/dist/mcp.js.map +1 -0
- package/dist/setup.js +222 -0
- package/dist/setup.js.map +1 -0
- package/package.json +47 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1006 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CCP — Containment Certificate Protocol CLI
|
|
4
|
+
*
|
|
5
|
+
* Usage: ccp <command> [options]
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* status Show full system overview
|
|
9
|
+
* cert:get <certHash> Get certificate details
|
|
10
|
+
* cert:lookup <agentAddress> Lookup active certificate by agent address
|
|
11
|
+
* cert:verify <agentAddress> Verify agent meets containment requirements
|
|
12
|
+
* cert:valid <certHash> Check if certificate is valid
|
|
13
|
+
* cert:publish Publish a new certificate (full flow)
|
|
14
|
+
* cert:revoke <certHash> Revoke a certificate (operator only)
|
|
15
|
+
* reserve:status Show reserve vault status
|
|
16
|
+
* reserve:deposit <amount> Deposit USDC into reserve vault
|
|
17
|
+
* reserve:lock <days> Lock reserve for N days
|
|
18
|
+
* spending:status Show spending limit status
|
|
19
|
+
* spending:pay <to> <amount> Execute a payment through SpendingLimit
|
|
20
|
+
* spending:pay:cosign <to> <amt> Execute payment with Ledger co-sign
|
|
21
|
+
* auditor:status [address] Show auditor info
|
|
22
|
+
* auditor:audit Run containment audit
|
|
23
|
+
* auditor:attest <certHash> Full attest flow (audit + stake + sign)
|
|
24
|
+
* challenge:get <id> Get challenge details
|
|
25
|
+
* challenge:list <certHash> List challenges for a certificate
|
|
26
|
+
* hcs:timeline Show HCS event timeline
|
|
27
|
+
* hcs:create-topic Create a new HCS topic
|
|
28
|
+
* addresses Show all contract addresses
|
|
29
|
+
* actors Show all actor addresses
|
|
30
|
+
* help Show this help message
|
|
31
|
+
*/
|
|
32
|
+
import "dotenv/config";
|
|
33
|
+
import { formatUnits, formatEther, parseUnits, parseEther, keccak256, encodePacked, } from "viem";
|
|
34
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
35
|
+
import { publicClient, getWalletClient } from "./client.js";
|
|
36
|
+
import { addresses, keys, hederaConfig } from "./config.js";
|
|
37
|
+
import { CCPRegistryABI, ReserveVaultABI, SpendingLimitABI, AuditorStakingABI, ChallengeManagerABI, } from "./contracts/index.js";
|
|
38
|
+
import { auditContainment, attestCertificate } from "./auditor/attest.js";
|
|
39
|
+
import { ledgerSignCertificate, executeWithLedgerCosign, } from "./ledger/cosigner.js";
|
|
40
|
+
import { publishCertificatePublished, publishAgentTransaction, publishTransactionBlocked, } from "./hcs/publisher.js";
|
|
41
|
+
import { printEventTimeline } from "./hedera/mirrorNode.js";
|
|
42
|
+
// ─── Helpers ───
|
|
43
|
+
const USDC_DECIMALS = 6;
|
|
44
|
+
function fmt(amount) {
|
|
45
|
+
return formatUnits(amount, USDC_DECIMALS);
|
|
46
|
+
}
|
|
47
|
+
function parse(amount) {
|
|
48
|
+
return parseUnits(amount, USDC_DECIMALS);
|
|
49
|
+
}
|
|
50
|
+
const STATUS_NAMES = ["ACTIVE", "REVOKED", "EXPIRED", "CHALLENGED"];
|
|
51
|
+
const CLASS_NAMES = ["NONE", "C1", "C2", "C3"];
|
|
52
|
+
const CHALLENGE_TYPE_NAMES = [
|
|
53
|
+
"RESERVE_SHORTFALL",
|
|
54
|
+
"CONSTRAINT_BYPASS",
|
|
55
|
+
"FALSE_INDEPENDENCE",
|
|
56
|
+
"INVALID_VERIFICATION",
|
|
57
|
+
"SCOPE_NOT_PERFORMED",
|
|
58
|
+
];
|
|
59
|
+
const CHALLENGE_STATUS_NAMES = [
|
|
60
|
+
"PENDING",
|
|
61
|
+
"UPHELD",
|
|
62
|
+
"REJECTED",
|
|
63
|
+
"INFORMATIONAL",
|
|
64
|
+
];
|
|
65
|
+
function line(label, value) {
|
|
66
|
+
console.log(` ${label.padEnd(28)} ${value}`);
|
|
67
|
+
}
|
|
68
|
+
function header(title) {
|
|
69
|
+
console.log(`\n${"━".repeat(50)}`);
|
|
70
|
+
console.log(` ${title}`);
|
|
71
|
+
console.log(`${"━".repeat(50)}\n`);
|
|
72
|
+
}
|
|
73
|
+
function divider() {
|
|
74
|
+
console.log(` ${"─".repeat(46)}`);
|
|
75
|
+
}
|
|
76
|
+
function getActorAddresses() {
|
|
77
|
+
return {
|
|
78
|
+
operator: privateKeyToAccount(keys.operator).address,
|
|
79
|
+
auditor: privateKeyToAccount(keys.auditor).address,
|
|
80
|
+
agent: privateKeyToAccount(keys.agent).address,
|
|
81
|
+
ledger: privateKeyToAccount(keys.ledger).address,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const ERC20_ABI = [
|
|
85
|
+
{
|
|
86
|
+
name: "balanceOf",
|
|
87
|
+
type: "function",
|
|
88
|
+
inputs: [{ name: "account", type: "address" }],
|
|
89
|
+
outputs: [{ type: "uint256" }],
|
|
90
|
+
stateMutability: "view",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "approve",
|
|
94
|
+
type: "function",
|
|
95
|
+
inputs: [
|
|
96
|
+
{ name: "spender", type: "address" },
|
|
97
|
+
{ name: "amount", type: "uint256" },
|
|
98
|
+
],
|
|
99
|
+
outputs: [{ type: "bool" }],
|
|
100
|
+
stateMutability: "nonpayable",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "mint",
|
|
104
|
+
type: "function",
|
|
105
|
+
inputs: [
|
|
106
|
+
{ name: "to", type: "address" },
|
|
107
|
+
{ name: "amount", type: "uint256" },
|
|
108
|
+
],
|
|
109
|
+
outputs: [],
|
|
110
|
+
stateMutability: "nonpayable",
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
// ─── Commands ───
|
|
114
|
+
async function cmdStatus() {
|
|
115
|
+
header("CCP Protocol Status");
|
|
116
|
+
const actors = getActorAddresses();
|
|
117
|
+
// Spending Limit
|
|
118
|
+
console.log(" SPENDING LIMIT");
|
|
119
|
+
divider();
|
|
120
|
+
const [maxSingle, maxPeriodic, cosignThreshold, ledgerCosigner, remaining] = await Promise.all([
|
|
121
|
+
publicClient.readContract({
|
|
122
|
+
address: addresses.spendingLimit,
|
|
123
|
+
abi: SpendingLimitABI,
|
|
124
|
+
functionName: "maxSingleAction",
|
|
125
|
+
}),
|
|
126
|
+
publicClient.readContract({
|
|
127
|
+
address: addresses.spendingLimit,
|
|
128
|
+
abi: SpendingLimitABI,
|
|
129
|
+
functionName: "maxPeriodicLoss",
|
|
130
|
+
}),
|
|
131
|
+
publicClient.readContract({
|
|
132
|
+
address: addresses.spendingLimit,
|
|
133
|
+
abi: SpendingLimitABI,
|
|
134
|
+
functionName: "cosignThreshold",
|
|
135
|
+
}),
|
|
136
|
+
publicClient.readContract({
|
|
137
|
+
address: addresses.spendingLimit,
|
|
138
|
+
abi: SpendingLimitABI,
|
|
139
|
+
functionName: "ledgerCosigner",
|
|
140
|
+
}),
|
|
141
|
+
publicClient.readContract({
|
|
142
|
+
address: addresses.spendingLimit,
|
|
143
|
+
abi: SpendingLimitABI,
|
|
144
|
+
functionName: "getRemainingAllowance",
|
|
145
|
+
}),
|
|
146
|
+
]);
|
|
147
|
+
const [spent, limit, periodEnd] = await publicClient.readContract({
|
|
148
|
+
address: addresses.spendingLimit,
|
|
149
|
+
abi: SpendingLimitABI,
|
|
150
|
+
functionName: "getSpentInPeriod",
|
|
151
|
+
});
|
|
152
|
+
line("Max Single Action", `$${fmt(maxSingle)} USDC`);
|
|
153
|
+
line("Max Periodic Loss", `$${fmt(maxPeriodic)} USDC`);
|
|
154
|
+
line("Cosign Threshold", `$${fmt(cosignThreshold)} USDC`);
|
|
155
|
+
line("Ledger Cosigner", ledgerCosigner);
|
|
156
|
+
line("Period Spent", `$${fmt(spent)} / $${fmt(limit)} USDC`);
|
|
157
|
+
line("Remaining Allowance", `$${fmt(remaining)} USDC`);
|
|
158
|
+
line("Period Ends", new Date(Number(periodEnd) * 1000).toISOString());
|
|
159
|
+
// Reserve Vault
|
|
160
|
+
console.log("\n RESERVE VAULT");
|
|
161
|
+
divider();
|
|
162
|
+
const [reserveBalance, isLocked, lockUntil, statedAmount] = await Promise.all([
|
|
163
|
+
publicClient.readContract({
|
|
164
|
+
address: addresses.reserveVault,
|
|
165
|
+
abi: ReserveVaultABI,
|
|
166
|
+
functionName: "getReserveBalance",
|
|
167
|
+
}),
|
|
168
|
+
publicClient.readContract({
|
|
169
|
+
address: addresses.reserveVault,
|
|
170
|
+
abi: ReserveVaultABI,
|
|
171
|
+
functionName: "isLocked",
|
|
172
|
+
}),
|
|
173
|
+
publicClient.readContract({
|
|
174
|
+
address: addresses.reserveVault,
|
|
175
|
+
abi: ReserveVaultABI,
|
|
176
|
+
functionName: "lockUntil",
|
|
177
|
+
}),
|
|
178
|
+
publicClient.readContract({
|
|
179
|
+
address: addresses.reserveVault,
|
|
180
|
+
abi: ReserveVaultABI,
|
|
181
|
+
functionName: "getStatedAmount",
|
|
182
|
+
}),
|
|
183
|
+
]);
|
|
184
|
+
line("Reserve Balance", `$${fmt(reserveBalance)} USDC`);
|
|
185
|
+
line("Stated Amount", `$${fmt(statedAmount)} USDC`);
|
|
186
|
+
line("Locked", String(isLocked));
|
|
187
|
+
line("Lock Until", Number(lockUntil) > 0
|
|
188
|
+
? new Date(Number(lockUntil) * 1000).toISOString()
|
|
189
|
+
: "Not locked");
|
|
190
|
+
// Balances (HBAR + USDC)
|
|
191
|
+
console.log("\n BALANCES (HBAR / USDC)");
|
|
192
|
+
divider();
|
|
193
|
+
for (const [name, addr] of Object.entries(actors)) {
|
|
194
|
+
const [hbar, usdc] = await Promise.all([
|
|
195
|
+
publicClient.getBalance({ address: addr }),
|
|
196
|
+
publicClient.readContract({
|
|
197
|
+
address: addresses.usdc,
|
|
198
|
+
abi: ERC20_ABI,
|
|
199
|
+
functionName: "balanceOf",
|
|
200
|
+
args: [addr],
|
|
201
|
+
}),
|
|
202
|
+
]);
|
|
203
|
+
line(`${name.charAt(0).toUpperCase() + name.slice(1)}`, `${formatEther(hbar)} HBAR | $${fmt(usdc)} USDC`);
|
|
204
|
+
}
|
|
205
|
+
const slBal = await publicClient.readContract({
|
|
206
|
+
address: addresses.usdc,
|
|
207
|
+
abi: ERC20_ABI,
|
|
208
|
+
functionName: "balanceOf",
|
|
209
|
+
args: [addresses.spendingLimit],
|
|
210
|
+
});
|
|
211
|
+
line("SpendingLimit Contract", `$${fmt(slBal)} USDC`);
|
|
212
|
+
// Active certificate for agent
|
|
213
|
+
console.log("\n AGENT CERTIFICATE");
|
|
214
|
+
divider();
|
|
215
|
+
try {
|
|
216
|
+
const certHash = await publicClient.readContract({
|
|
217
|
+
address: addresses.registry,
|
|
218
|
+
abi: CCPRegistryABI,
|
|
219
|
+
functionName: "getActiveCertificate",
|
|
220
|
+
args: [actors.agent],
|
|
221
|
+
});
|
|
222
|
+
if (certHash ===
|
|
223
|
+
"0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
224
|
+
line("Status", "No active certificate");
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
line("Cert Hash", certHash);
|
|
228
|
+
const cert = await publicClient.readContract({
|
|
229
|
+
address: addresses.registry,
|
|
230
|
+
abi: CCPRegistryABI,
|
|
231
|
+
functionName: "getCertificate",
|
|
232
|
+
args: [certHash],
|
|
233
|
+
});
|
|
234
|
+
line("Class", CLASS_NAMES[cert.certificateClass] || "Unknown");
|
|
235
|
+
line("Status", STATUS_NAMES[cert.status] || "Unknown");
|
|
236
|
+
line("Containment Bound", `$${fmt(cert.containmentBound)} USDC`);
|
|
237
|
+
line("Expires", new Date(Number(cert.expiresAt) * 1000).toISOString());
|
|
238
|
+
line("Valid", String(await publicClient.readContract({
|
|
239
|
+
address: addresses.registry,
|
|
240
|
+
abi: CCPRegistryABI,
|
|
241
|
+
functionName: "isValid",
|
|
242
|
+
args: [certHash],
|
|
243
|
+
})));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
line("Status", "No active certificate");
|
|
248
|
+
}
|
|
249
|
+
console.log("");
|
|
250
|
+
}
|
|
251
|
+
async function cmdCertGet(certHashArg) {
|
|
252
|
+
header("Certificate Details");
|
|
253
|
+
const certHash = certHashArg;
|
|
254
|
+
const cert = (await publicClient.readContract({
|
|
255
|
+
address: addresses.registry,
|
|
256
|
+
abi: CCPRegistryABI,
|
|
257
|
+
functionName: "getCertificate",
|
|
258
|
+
args: [certHash],
|
|
259
|
+
}));
|
|
260
|
+
const isValid = await publicClient.readContract({
|
|
261
|
+
address: addresses.registry,
|
|
262
|
+
abi: CCPRegistryABI,
|
|
263
|
+
functionName: "isValid",
|
|
264
|
+
args: [certHash],
|
|
265
|
+
});
|
|
266
|
+
line("Cert Hash", certHash);
|
|
267
|
+
line("Operator", cert.operator);
|
|
268
|
+
line("Agent", cert.agent);
|
|
269
|
+
line("Class", CLASS_NAMES[cert.certificateClass] || "Unknown");
|
|
270
|
+
line("Status", STATUS_NAMES[cert.status] || "Unknown");
|
|
271
|
+
line("Valid", String(isValid));
|
|
272
|
+
line("Containment Bound", `$${fmt(cert.containmentBound)} USDC`);
|
|
273
|
+
line("Issued At", new Date(Number(cert.issuedAt) * 1000).toISOString());
|
|
274
|
+
line("Expires At", new Date(Number(cert.expiresAt) * 1000).toISOString());
|
|
275
|
+
line("Reserve Vault", cert.reserveVault);
|
|
276
|
+
line("Spending Limit", cert.spendingLimit);
|
|
277
|
+
line("IPFS URI", cert.ipfsUri);
|
|
278
|
+
line("Auditors", cert.auditors?.length || 0);
|
|
279
|
+
if (cert.auditors?.length > 0) {
|
|
280
|
+
for (const auditor of cert.auditors) {
|
|
281
|
+
line(" Auditor", auditor);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
console.log("");
|
|
285
|
+
}
|
|
286
|
+
async function cmdCertLookup(agentAddress) {
|
|
287
|
+
header("Certificate Lookup");
|
|
288
|
+
const certHash = await publicClient.readContract({
|
|
289
|
+
address: addresses.registry,
|
|
290
|
+
abi: CCPRegistryABI,
|
|
291
|
+
functionName: "getActiveCertificate",
|
|
292
|
+
args: [agentAddress],
|
|
293
|
+
});
|
|
294
|
+
if (certHash ===
|
|
295
|
+
"0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
296
|
+
console.log(" No active certificate found for this agent.\n");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
line("Agent", agentAddress);
|
|
300
|
+
line("Active Cert Hash", certHash);
|
|
301
|
+
console.log("");
|
|
302
|
+
await cmdCertGet(certHash);
|
|
303
|
+
}
|
|
304
|
+
async function cmdCertVerify(agentAddress, minClass = "1", maxLoss = "100000") {
|
|
305
|
+
header("Certificate Verification");
|
|
306
|
+
const maxLossUsdc = parse(maxLoss);
|
|
307
|
+
const [acceptable, certHash] = await publicClient.readContract({
|
|
308
|
+
address: addresses.registry,
|
|
309
|
+
abi: CCPRegistryABI,
|
|
310
|
+
functionName: "verify",
|
|
311
|
+
args: [agentAddress, parseInt(minClass), maxLossUsdc],
|
|
312
|
+
});
|
|
313
|
+
line("Agent", agentAddress);
|
|
314
|
+
line("Min Class Required", `C${minClass}`);
|
|
315
|
+
line("Max Acceptable Loss", `$${maxLoss} USDC`);
|
|
316
|
+
divider();
|
|
317
|
+
line("Acceptable", String(acceptable));
|
|
318
|
+
line("Cert Hash", certHash.slice(0, 18) + "...");
|
|
319
|
+
if (acceptable) {
|
|
320
|
+
console.log("\n VERIFICATION PASSED\n");
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
console.log("\n VERIFICATION FAILED\n");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function cmdCertValid(certHash) {
|
|
327
|
+
const isValid = await publicClient.readContract({
|
|
328
|
+
address: addresses.registry,
|
|
329
|
+
abi: CCPRegistryABI,
|
|
330
|
+
functionName: "isValid",
|
|
331
|
+
args: [certHash],
|
|
332
|
+
});
|
|
333
|
+
header("Certificate Validity");
|
|
334
|
+
line("Cert Hash", certHash);
|
|
335
|
+
line("Valid", String(isValid));
|
|
336
|
+
console.log("");
|
|
337
|
+
}
|
|
338
|
+
async function cmdCertPublish() {
|
|
339
|
+
header("Publish Certificate");
|
|
340
|
+
const operatorWallet = getWalletClient(keys.operator);
|
|
341
|
+
const agentWallet = getWalletClient(keys.agent);
|
|
342
|
+
const containmentBound = 50000000000n; // $50k
|
|
343
|
+
const auditorStake = 1500000000n; // $1.5k
|
|
344
|
+
// Generate cert hash
|
|
345
|
+
const certHash = keccak256(encodePacked(["address", "address", "uint256", "string"], [
|
|
346
|
+
agentWallet.account.address,
|
|
347
|
+
operatorWallet.account.address,
|
|
348
|
+
BigInt(Date.now()),
|
|
349
|
+
"ccp-v0.2",
|
|
350
|
+
]));
|
|
351
|
+
console.log(` Cert Hash: ${certHash}`);
|
|
352
|
+
console.log(` Agent: ${agentWallet.account.address}`);
|
|
353
|
+
console.log(` Operator: ${operatorWallet.account.address}`);
|
|
354
|
+
console.log(` Containment Bound: $${fmt(containmentBound)} USDC\n`);
|
|
355
|
+
// Phase 1: Auditor attestation
|
|
356
|
+
console.log(" [1/3] Auditor audit + stake + attest...");
|
|
357
|
+
const { signature: auditorSig, auditResult } = await attestCertificate(certHash, addresses.spendingLimit, addresses.reserveVault, containmentBound, auditorStake);
|
|
358
|
+
console.log(` Audit: ${auditResult.certClass}`);
|
|
359
|
+
// Phase 2: Operator signs
|
|
360
|
+
console.log("\n [2/3] Operator signing certificate (Ledger)...");
|
|
361
|
+
const operatorSig = await ledgerSignCertificate(certHash);
|
|
362
|
+
// Phase 3: Publish on-chain
|
|
363
|
+
console.log("\n [3/3] Publishing on-chain...");
|
|
364
|
+
const publishParams = {
|
|
365
|
+
certHash,
|
|
366
|
+
agent: agentWallet.account.address,
|
|
367
|
+
certificateClass: 2,
|
|
368
|
+
expiresAt: Math.floor(Date.now() / 1000) + 60 * 24 * 3600,
|
|
369
|
+
containmentBound,
|
|
370
|
+
reserveVault: addresses.reserveVault,
|
|
371
|
+
spendingLimit: addresses.spendingLimit,
|
|
372
|
+
ipfsUri: "ipfs://QmCCPDemoCertificate",
|
|
373
|
+
};
|
|
374
|
+
const tx = await operatorWallet.writeContract({
|
|
375
|
+
address: addresses.registry,
|
|
376
|
+
abi: CCPRegistryABI,
|
|
377
|
+
functionName: "publish",
|
|
378
|
+
args: [publishParams, operatorSig, [auditorSig]],
|
|
379
|
+
});
|
|
380
|
+
await publicClient.waitForTransactionReceipt({ hash: tx });
|
|
381
|
+
console.log(`\n Certificate published!`);
|
|
382
|
+
line("TX", tx);
|
|
383
|
+
line("Cert Hash", certHash);
|
|
384
|
+
// HCS event (non-fatal)
|
|
385
|
+
try {
|
|
386
|
+
await publishCertificatePublished(certHash, agentWallet.account.address, operatorWallet.account.address, "C2", fmt(containmentBound));
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
console.log(" [HCS] Event skipped");
|
|
390
|
+
}
|
|
391
|
+
console.log("");
|
|
392
|
+
}
|
|
393
|
+
async function cmdCertRevoke(certHash) {
|
|
394
|
+
header("Revoke Certificate");
|
|
395
|
+
const operatorWallet = getWalletClient(keys.operator);
|
|
396
|
+
const tx = await operatorWallet.writeContract({
|
|
397
|
+
address: addresses.registry,
|
|
398
|
+
abi: CCPRegistryABI,
|
|
399
|
+
functionName: "revoke",
|
|
400
|
+
args: [certHash],
|
|
401
|
+
});
|
|
402
|
+
await publicClient.waitForTransactionReceipt({ hash: tx });
|
|
403
|
+
line("Cert Hash", certHash);
|
|
404
|
+
line("TX", tx);
|
|
405
|
+
console.log(" Certificate revoked.\n");
|
|
406
|
+
}
|
|
407
|
+
async function cmdReserveStatus() {
|
|
408
|
+
header("Reserve Vault Status");
|
|
409
|
+
const [balance, statedAmount, isLocked, lockUntil, operator, reserveAsset] = await Promise.all([
|
|
410
|
+
publicClient.readContract({
|
|
411
|
+
address: addresses.reserveVault,
|
|
412
|
+
abi: ReserveVaultABI,
|
|
413
|
+
functionName: "getReserveBalance",
|
|
414
|
+
}),
|
|
415
|
+
publicClient.readContract({
|
|
416
|
+
address: addresses.reserveVault,
|
|
417
|
+
abi: ReserveVaultABI,
|
|
418
|
+
functionName: "getStatedAmount",
|
|
419
|
+
}),
|
|
420
|
+
publicClient.readContract({
|
|
421
|
+
address: addresses.reserveVault,
|
|
422
|
+
abi: ReserveVaultABI,
|
|
423
|
+
functionName: "isLocked",
|
|
424
|
+
}),
|
|
425
|
+
publicClient.readContract({
|
|
426
|
+
address: addresses.reserveVault,
|
|
427
|
+
abi: ReserveVaultABI,
|
|
428
|
+
functionName: "lockUntil",
|
|
429
|
+
}),
|
|
430
|
+
publicClient.readContract({
|
|
431
|
+
address: addresses.reserveVault,
|
|
432
|
+
abi: ReserveVaultABI,
|
|
433
|
+
functionName: "operator",
|
|
434
|
+
}),
|
|
435
|
+
publicClient.readContract({
|
|
436
|
+
address: addresses.reserveVault,
|
|
437
|
+
abi: ReserveVaultABI,
|
|
438
|
+
functionName: "reserveAsset",
|
|
439
|
+
}),
|
|
440
|
+
]);
|
|
441
|
+
line("Contract", addresses.reserveVault);
|
|
442
|
+
line("Operator", operator);
|
|
443
|
+
line("Reserve Asset", reserveAsset);
|
|
444
|
+
divider();
|
|
445
|
+
line("Balance", `$${fmt(balance)} USDC`);
|
|
446
|
+
line("Stated Amount", `$${fmt(statedAmount)} USDC`);
|
|
447
|
+
line("Locked", String(isLocked));
|
|
448
|
+
line("Lock Until", Number(lockUntil) > 0
|
|
449
|
+
? new Date(Number(lockUntil) * 1000).toISOString()
|
|
450
|
+
: "Not locked");
|
|
451
|
+
// Adequacy check for C2 (3x)
|
|
452
|
+
const isAdequateC2 = await publicClient.readContract({
|
|
453
|
+
address: addresses.reserveVault,
|
|
454
|
+
abi: ReserveVaultABI,
|
|
455
|
+
functionName: "isAdequate",
|
|
456
|
+
args: [50000000000n, 30000], // $50k bound, 3x ratio
|
|
457
|
+
});
|
|
458
|
+
line("Adequate for C2 ($50k)", String(isAdequateC2));
|
|
459
|
+
console.log("");
|
|
460
|
+
}
|
|
461
|
+
async function cmdReserveDeposit(amountStr) {
|
|
462
|
+
header("Deposit Reserve");
|
|
463
|
+
const amount = parse(amountStr);
|
|
464
|
+
const operatorWallet = getWalletClient(keys.operator);
|
|
465
|
+
console.log(` Depositing $${amountStr} USDC...\n`);
|
|
466
|
+
// Approve
|
|
467
|
+
const approveTx = await operatorWallet.writeContract({
|
|
468
|
+
address: addresses.usdc,
|
|
469
|
+
abi: ERC20_ABI,
|
|
470
|
+
functionName: "approve",
|
|
471
|
+
args: [addresses.reserveVault, amount],
|
|
472
|
+
});
|
|
473
|
+
await publicClient.waitForTransactionReceipt({ hash: approveTx });
|
|
474
|
+
line("Approve TX", approveTx);
|
|
475
|
+
// Deposit
|
|
476
|
+
const depositTx = await operatorWallet.writeContract({
|
|
477
|
+
address: addresses.reserveVault,
|
|
478
|
+
abi: ReserveVaultABI,
|
|
479
|
+
functionName: "deposit",
|
|
480
|
+
args: [amount],
|
|
481
|
+
});
|
|
482
|
+
await publicClient.waitForTransactionReceipt({ hash: depositTx });
|
|
483
|
+
line("Deposit TX", depositTx);
|
|
484
|
+
const newBalance = await publicClient.readContract({
|
|
485
|
+
address: addresses.reserveVault,
|
|
486
|
+
abi: ReserveVaultABI,
|
|
487
|
+
functionName: "getReserveBalance",
|
|
488
|
+
});
|
|
489
|
+
line("New Balance", `$${fmt(newBalance)} USDC`);
|
|
490
|
+
console.log("");
|
|
491
|
+
}
|
|
492
|
+
async function cmdReserveLock(daysStr) {
|
|
493
|
+
header("Lock Reserve");
|
|
494
|
+
const days = parseInt(daysStr);
|
|
495
|
+
const lockUntil = Math.floor(Date.now() / 1000) + days * 24 * 3600;
|
|
496
|
+
const operatorWallet = getWalletClient(keys.operator);
|
|
497
|
+
const tx = await operatorWallet.writeContract({
|
|
498
|
+
address: addresses.reserveVault,
|
|
499
|
+
abi: ReserveVaultABI,
|
|
500
|
+
functionName: "lock",
|
|
501
|
+
args: [lockUntil],
|
|
502
|
+
});
|
|
503
|
+
await publicClient.waitForTransactionReceipt({ hash: tx });
|
|
504
|
+
line("TX", tx);
|
|
505
|
+
line("Lock Until", new Date(lockUntil * 1000).toISOString());
|
|
506
|
+
line("Days", String(days));
|
|
507
|
+
console.log("");
|
|
508
|
+
}
|
|
509
|
+
async function cmdSpendingStatus() {
|
|
510
|
+
header("Spending Limit Status");
|
|
511
|
+
const [agent, maxSingle, maxPeriodic, cosignThreshold, ledgerCosigner, remaining, periodDuration, spendAsset,] = await Promise.all([
|
|
512
|
+
publicClient.readContract({
|
|
513
|
+
address: addresses.spendingLimit,
|
|
514
|
+
abi: SpendingLimitABI,
|
|
515
|
+
functionName: "agent",
|
|
516
|
+
}),
|
|
517
|
+
publicClient.readContract({
|
|
518
|
+
address: addresses.spendingLimit,
|
|
519
|
+
abi: SpendingLimitABI,
|
|
520
|
+
functionName: "maxSingleAction",
|
|
521
|
+
}),
|
|
522
|
+
publicClient.readContract({
|
|
523
|
+
address: addresses.spendingLimit,
|
|
524
|
+
abi: SpendingLimitABI,
|
|
525
|
+
functionName: "maxPeriodicLoss",
|
|
526
|
+
}),
|
|
527
|
+
publicClient.readContract({
|
|
528
|
+
address: addresses.spendingLimit,
|
|
529
|
+
abi: SpendingLimitABI,
|
|
530
|
+
functionName: "cosignThreshold",
|
|
531
|
+
}),
|
|
532
|
+
publicClient.readContract({
|
|
533
|
+
address: addresses.spendingLimit,
|
|
534
|
+
abi: SpendingLimitABI,
|
|
535
|
+
functionName: "ledgerCosigner",
|
|
536
|
+
}),
|
|
537
|
+
publicClient.readContract({
|
|
538
|
+
address: addresses.spendingLimit,
|
|
539
|
+
abi: SpendingLimitABI,
|
|
540
|
+
functionName: "getRemainingAllowance",
|
|
541
|
+
}),
|
|
542
|
+
publicClient.readContract({
|
|
543
|
+
address: addresses.spendingLimit,
|
|
544
|
+
abi: SpendingLimitABI,
|
|
545
|
+
functionName: "periodDuration",
|
|
546
|
+
}),
|
|
547
|
+
publicClient.readContract({
|
|
548
|
+
address: addresses.spendingLimit,
|
|
549
|
+
abi: SpendingLimitABI,
|
|
550
|
+
functionName: "spendAsset",
|
|
551
|
+
}),
|
|
552
|
+
]);
|
|
553
|
+
const [spent, limit, periodEnd] = await publicClient.readContract({
|
|
554
|
+
address: addresses.spendingLimit,
|
|
555
|
+
abi: SpendingLimitABI,
|
|
556
|
+
functionName: "getSpentInPeriod",
|
|
557
|
+
});
|
|
558
|
+
line("Contract", addresses.spendingLimit);
|
|
559
|
+
line("Agent", agent);
|
|
560
|
+
line("Ledger Cosigner", ledgerCosigner);
|
|
561
|
+
line("Spend Asset", spendAsset);
|
|
562
|
+
divider();
|
|
563
|
+
line("Max Single Action", `$${fmt(maxSingle)} USDC`);
|
|
564
|
+
line("Max Periodic Loss", `$${fmt(maxPeriodic)} USDC`);
|
|
565
|
+
line("Cosign Threshold", `$${fmt(cosignThreshold)} USDC`);
|
|
566
|
+
line("Period Duration", `${Number(periodDuration) / 3600} hours`);
|
|
567
|
+
divider();
|
|
568
|
+
line("Period Spent", `$${fmt(spent)} / $${fmt(limit)} USDC`);
|
|
569
|
+
line("Remaining Allowance", `$${fmt(remaining)} USDC`);
|
|
570
|
+
line("Period Ends", new Date(Number(periodEnd) * 1000).toISOString());
|
|
571
|
+
// Contract USDC balance
|
|
572
|
+
const contractBal = await publicClient.readContract({
|
|
573
|
+
address: addresses.usdc,
|
|
574
|
+
abi: ERC20_ABI,
|
|
575
|
+
functionName: "balanceOf",
|
|
576
|
+
args: [addresses.spendingLimit],
|
|
577
|
+
});
|
|
578
|
+
line("Contract USDC Balance", `$${fmt(contractBal)} USDC`);
|
|
579
|
+
console.log("");
|
|
580
|
+
}
|
|
581
|
+
async function cmdSpendingPay(to, amountStr) {
|
|
582
|
+
header("Execute Payment (Agent Only)");
|
|
583
|
+
const amount = parse(amountStr);
|
|
584
|
+
const agentWallet = getWalletClient(keys.agent);
|
|
585
|
+
console.log(` Sending $${amountStr} USDC to ${to}\n`);
|
|
586
|
+
const tx = await agentWallet.writeContract({
|
|
587
|
+
address: addresses.spendingLimit,
|
|
588
|
+
abi: SpendingLimitABI,
|
|
589
|
+
functionName: "execute",
|
|
590
|
+
args: [to, amount, "0x"],
|
|
591
|
+
});
|
|
592
|
+
await publicClient.waitForTransactionReceipt({ hash: tx });
|
|
593
|
+
const [spent, limit] = await publicClient.readContract({
|
|
594
|
+
address: addresses.spendingLimit,
|
|
595
|
+
abi: SpendingLimitABI,
|
|
596
|
+
functionName: "getSpentInPeriod",
|
|
597
|
+
});
|
|
598
|
+
line("TX", tx);
|
|
599
|
+
line("Amount", `$${amountStr} USDC`);
|
|
600
|
+
line("Period Spent", `$${fmt(spent)} / $${fmt(limit)} USDC`);
|
|
601
|
+
try {
|
|
602
|
+
await publishAgentTransaction(agentWallet.account.address, to, amountStr, false, fmt(spent), fmt(limit));
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
console.log(" [HCS] Event skipped");
|
|
606
|
+
}
|
|
607
|
+
console.log("");
|
|
608
|
+
}
|
|
609
|
+
async function cmdSpendingPayCosign(to, amountStr) {
|
|
610
|
+
header("Execute Payment (Ledger Co-Sign)");
|
|
611
|
+
const amount = parse(amountStr);
|
|
612
|
+
const agentWallet = getWalletClient(keys.agent);
|
|
613
|
+
console.log(` Sending $${amountStr} USDC to ${to} (with Ledger co-sign)\n`);
|
|
614
|
+
try {
|
|
615
|
+
const tx = await executeWithLedgerCosign(to, amount, addresses.spendingLimit);
|
|
616
|
+
const [spent, limit] = await publicClient.readContract({
|
|
617
|
+
address: addresses.spendingLimit,
|
|
618
|
+
abi: SpendingLimitABI,
|
|
619
|
+
functionName: "getSpentInPeriod",
|
|
620
|
+
});
|
|
621
|
+
line("TX", tx);
|
|
622
|
+
line("Amount", `$${amountStr} USDC`);
|
|
623
|
+
line("Period Spent", `$${fmt(spent)} / $${fmt(limit)} USDC`);
|
|
624
|
+
try {
|
|
625
|
+
await publishAgentTransaction(agentWallet.account.address, to, amountStr, true, fmt(spent), fmt(limit));
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
console.log(" [HCS] Event skipped");
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
console.log(" TRANSACTION BLOCKED");
|
|
633
|
+
console.log(` Reason: ${error.message?.slice(0, 120)}`);
|
|
634
|
+
try {
|
|
635
|
+
const [spent] = await publicClient.readContract({
|
|
636
|
+
address: addresses.spendingLimit,
|
|
637
|
+
abi: SpendingLimitABI,
|
|
638
|
+
functionName: "getSpentInPeriod",
|
|
639
|
+
});
|
|
640
|
+
await publishTransactionBlocked(agentWallet.account.address, amountStr, "EXCEEDS_LIMIT", fmt(spent), "50000");
|
|
641
|
+
}
|
|
642
|
+
catch { }
|
|
643
|
+
}
|
|
644
|
+
console.log("");
|
|
645
|
+
}
|
|
646
|
+
async function cmdAuditorStatus(auditorAddress) {
|
|
647
|
+
header("Auditor Status");
|
|
648
|
+
const addr = auditorAddress || privateKeyToAccount(keys.auditor).address;
|
|
649
|
+
const record = (await publicClient.readContract({
|
|
650
|
+
address: addresses.auditorStaking,
|
|
651
|
+
abi: AuditorStakingABI,
|
|
652
|
+
functionName: "getAuditorRecord",
|
|
653
|
+
args: [addr],
|
|
654
|
+
}));
|
|
655
|
+
const totalStaked = await publicClient.readContract({
|
|
656
|
+
address: addresses.auditorStaking,
|
|
657
|
+
abi: AuditorStakingABI,
|
|
658
|
+
functionName: "getTotalStaked",
|
|
659
|
+
args: [addr],
|
|
660
|
+
});
|
|
661
|
+
line("Auditor Address", addr);
|
|
662
|
+
divider();
|
|
663
|
+
line("Total Attestations", String(record.totalAttestations));
|
|
664
|
+
line("Successful Challenges", String(record.successfulChallenges));
|
|
665
|
+
line("Active Stake", `$${fmt(record.activeStake)} USDC`);
|
|
666
|
+
line("Total Staked", `$${fmt(totalStaked)} USDC`);
|
|
667
|
+
// USDC balance
|
|
668
|
+
const bal = await publicClient.readContract({
|
|
669
|
+
address: addresses.usdc,
|
|
670
|
+
abi: ERC20_ABI,
|
|
671
|
+
functionName: "balanceOf",
|
|
672
|
+
args: [addr],
|
|
673
|
+
});
|
|
674
|
+
line("USDC Balance", `$${fmt(bal)} USDC`);
|
|
675
|
+
console.log("");
|
|
676
|
+
}
|
|
677
|
+
async function cmdAuditorAudit() {
|
|
678
|
+
header("Containment Audit");
|
|
679
|
+
await auditContainment(addresses.spendingLimit, addresses.reserveVault, 50000000000n);
|
|
680
|
+
console.log("");
|
|
681
|
+
}
|
|
682
|
+
async function cmdAuditorAttest(certHashArg) {
|
|
683
|
+
header("Auditor Attestation");
|
|
684
|
+
const certHash = certHashArg;
|
|
685
|
+
const stakeAmount = 1500000000n; // $1.5k
|
|
686
|
+
const { signature, auditResult } = await attestCertificate(certHash, addresses.spendingLimit, addresses.reserveVault, 50000000000n, stakeAmount);
|
|
687
|
+
console.log(`\n Attestation complete.`);
|
|
688
|
+
line("Cert Hash", certHash);
|
|
689
|
+
line("Class", auditResult.certClass);
|
|
690
|
+
line("Signature", signature.slice(0, 30) + "...");
|
|
691
|
+
console.log("");
|
|
692
|
+
}
|
|
693
|
+
async function cmdChallengeGet(challengeIdStr) {
|
|
694
|
+
header("Challenge Details");
|
|
695
|
+
const challengeId = BigInt(challengeIdStr);
|
|
696
|
+
const challenge = (await publicClient.readContract({
|
|
697
|
+
address: addresses.challengeManager,
|
|
698
|
+
abi: ChallengeManagerABI,
|
|
699
|
+
functionName: "getChallenge",
|
|
700
|
+
args: [challengeId],
|
|
701
|
+
}));
|
|
702
|
+
line("Challenge ID", challengeIdStr);
|
|
703
|
+
line("Cert Hash", challenge.certHash);
|
|
704
|
+
line("Challenger", challenge.challenger);
|
|
705
|
+
line("Type", CHALLENGE_TYPE_NAMES[challenge.challengeType] || "Unknown");
|
|
706
|
+
line("Status", CHALLENGE_STATUS_NAMES[challenge.status] || "Unknown");
|
|
707
|
+
line("Bond", `$${fmt(challenge.bond)} USDC`);
|
|
708
|
+
line("Submitted At", new Date(Number(challenge.submittedAt) * 1000).toISOString());
|
|
709
|
+
if (Number(challenge.resolvedAt) > 0) {
|
|
710
|
+
line("Resolved At", new Date(Number(challenge.resolvedAt) * 1000).toISOString());
|
|
711
|
+
}
|
|
712
|
+
console.log("");
|
|
713
|
+
}
|
|
714
|
+
async function cmdChallengeList(certHash) {
|
|
715
|
+
header("Challenges for Certificate");
|
|
716
|
+
const ids = (await publicClient.readContract({
|
|
717
|
+
address: addresses.challengeManager,
|
|
718
|
+
abi: ChallengeManagerABI,
|
|
719
|
+
functionName: "getChallengesByCert",
|
|
720
|
+
args: [certHash],
|
|
721
|
+
}));
|
|
722
|
+
line("Cert Hash", certHash);
|
|
723
|
+
line("Total Challenges", String(ids.length));
|
|
724
|
+
if (ids.length > 0) {
|
|
725
|
+
divider();
|
|
726
|
+
for (const id of ids) {
|
|
727
|
+
const c = (await publicClient.readContract({
|
|
728
|
+
address: addresses.challengeManager,
|
|
729
|
+
abi: ChallengeManagerABI,
|
|
730
|
+
functionName: "getChallenge",
|
|
731
|
+
args: [id],
|
|
732
|
+
}));
|
|
733
|
+
console.log(` #${id} ${CHALLENGE_TYPE_NAMES[c.challengeType] || "?"} — ${CHALLENGE_STATUS_NAMES[c.status] || "?"} — bond: $${fmt(c.bond)} USDC`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
console.log("");
|
|
737
|
+
}
|
|
738
|
+
async function cmdHcsTimeline() {
|
|
739
|
+
if (!hederaConfig.hcsTopicId) {
|
|
740
|
+
console.log("\n No HCS_TOPIC_ID configured in .env\n");
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
await printEventTimeline(hederaConfig.hcsTopicId);
|
|
744
|
+
}
|
|
745
|
+
async function cmdHcsCreateTopic() {
|
|
746
|
+
header("Create HCS Topic");
|
|
747
|
+
const { createCCPTopic } = await import("./hcs/publisher.js");
|
|
748
|
+
const topicId = await createCCPTopic();
|
|
749
|
+
console.log(`\n Topic created: ${topicId}`);
|
|
750
|
+
console.log(` Add to .env: HCS_TOPIC_ID=${topicId}\n`);
|
|
751
|
+
}
|
|
752
|
+
async function cmdFund(targetName, amountStr) {
|
|
753
|
+
header("Fund Accounts (HBAR)");
|
|
754
|
+
const actors = getActorAddresses();
|
|
755
|
+
const operatorWallet = getWalletClient(keys.operator);
|
|
756
|
+
const amount = parseEther(amountStr || "50");
|
|
757
|
+
const hbarAmount = amountStr || "50";
|
|
758
|
+
// If a specific target is given, fund only that one
|
|
759
|
+
if (targetName) {
|
|
760
|
+
const targetAddr = actors[targetName.toLowerCase()] ||
|
|
761
|
+
(targetName.startsWith("0x") ? targetName : null);
|
|
762
|
+
if (!targetAddr) {
|
|
763
|
+
console.log(` Unknown target: ${targetName}`);
|
|
764
|
+
console.log(` Use: operator, auditor, agent, ledger, or an address\n`);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
const tx = await operatorWallet.sendTransaction({
|
|
768
|
+
to: targetAddr,
|
|
769
|
+
value: amount,
|
|
770
|
+
});
|
|
771
|
+
line("Sent", `${hbarAmount} HBAR to ${targetName}`);
|
|
772
|
+
line("TX", tx);
|
|
773
|
+
console.log("");
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
// Fund all non-operator actors
|
|
777
|
+
for (const [name, addr] of Object.entries(actors)) {
|
|
778
|
+
if (name === "operator")
|
|
779
|
+
continue;
|
|
780
|
+
try {
|
|
781
|
+
const tx = await operatorWallet.sendTransaction({
|
|
782
|
+
to: addr,
|
|
783
|
+
value: amount,
|
|
784
|
+
});
|
|
785
|
+
line(`${name}`, `${hbarAmount} HBAR sent — ${tx.slice(0, 22)}...`);
|
|
786
|
+
}
|
|
787
|
+
catch (e) {
|
|
788
|
+
line(`${name}`, `FAILED — ${e.message?.slice(0, 60)}`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
console.log("");
|
|
792
|
+
}
|
|
793
|
+
function cmdAddresses() {
|
|
794
|
+
header("Contract Addresses");
|
|
795
|
+
line("CCPRegistry", addresses.registry);
|
|
796
|
+
line("ReserveVault", addresses.reserveVault);
|
|
797
|
+
line("SpendingLimit", addresses.spendingLimit);
|
|
798
|
+
line("AuditorStaking", addresses.auditorStaking);
|
|
799
|
+
line("FeeEscrow", addresses.feeEscrow);
|
|
800
|
+
line("ChallengeManager", addresses.challengeManager);
|
|
801
|
+
line("USDC (Mock)", addresses.usdc);
|
|
802
|
+
divider();
|
|
803
|
+
line("RPC URL", hederaConfig.rpcUrl);
|
|
804
|
+
line("Chain ID", String(hederaConfig.chainId));
|
|
805
|
+
line("Network", hederaConfig.network);
|
|
806
|
+
if (hederaConfig.hcsTopicId) {
|
|
807
|
+
line("HCS Topic", hederaConfig.hcsTopicId);
|
|
808
|
+
}
|
|
809
|
+
console.log("");
|
|
810
|
+
}
|
|
811
|
+
function cmdActors() {
|
|
812
|
+
header("Actor Addresses");
|
|
813
|
+
const actors = getActorAddresses();
|
|
814
|
+
line("Operator", actors.operator);
|
|
815
|
+
line("Auditor", actors.auditor);
|
|
816
|
+
line("Agent", actors.agent);
|
|
817
|
+
line("Ledger", actors.ledger);
|
|
818
|
+
console.log("");
|
|
819
|
+
}
|
|
820
|
+
function cmdHelp() {
|
|
821
|
+
console.log(`
|
|
822
|
+
CCP — Containment Certificate Protocol
|
|
823
|
+
|
|
824
|
+
Usage: ccp <command> [args]
|
|
825
|
+
|
|
826
|
+
OVERVIEW
|
|
827
|
+
status Full system overview
|
|
828
|
+
addresses Show contract addresses
|
|
829
|
+
actors Show actor addresses
|
|
830
|
+
fund [target] [amount] Send HBAR for gas (default: 50 to all)
|
|
831
|
+
|
|
832
|
+
CERTIFICATES
|
|
833
|
+
cert:get <certHash> Get certificate details
|
|
834
|
+
cert:lookup <agentAddress> Lookup active cert by agent
|
|
835
|
+
cert:verify <agent> [minClass] [maxLoss]
|
|
836
|
+
Verify agent containment
|
|
837
|
+
cert:valid <certHash> Check certificate validity
|
|
838
|
+
cert:publish Publish new certificate (full flow)
|
|
839
|
+
cert:revoke <certHash> Revoke certificate
|
|
840
|
+
|
|
841
|
+
RESERVE VAULT
|
|
842
|
+
reserve:status Show reserve vault info
|
|
843
|
+
reserve:deposit <amount> Deposit USDC (e.g. 150000)
|
|
844
|
+
reserve:lock <days> Lock reserve for N days
|
|
845
|
+
|
|
846
|
+
SPENDING LIMITS
|
|
847
|
+
spending:status Show spending limit details
|
|
848
|
+
spending:pay <to> <amount> Pay (agent-only signature)
|
|
849
|
+
spending:pay:cosign <to> <amount> Pay with Ledger co-sign
|
|
850
|
+
|
|
851
|
+
AUDITOR
|
|
852
|
+
auditor:status [address] Show auditor record
|
|
853
|
+
auditor:audit Run containment audit checks
|
|
854
|
+
auditor:attest <certHash> Full attestation flow
|
|
855
|
+
|
|
856
|
+
CHALLENGES
|
|
857
|
+
challenge:get <id> Get challenge details
|
|
858
|
+
challenge:list <certHash> List challenges for cert
|
|
859
|
+
|
|
860
|
+
HCS (HEDERA CONSENSUS SERVICE)
|
|
861
|
+
hcs:timeline Show event timeline
|
|
862
|
+
hcs:create-topic Create new HCS topic
|
|
863
|
+
|
|
864
|
+
EXAMPLES
|
|
865
|
+
ccp status
|
|
866
|
+
ccp cert:lookup 0x89cFD052...
|
|
867
|
+
ccp spending:pay 0xdead... 500
|
|
868
|
+
ccp spending:pay:cosign 0xdead... 7000
|
|
869
|
+
ccp reserve:status
|
|
870
|
+
ccp hcs:timeline
|
|
871
|
+
|
|
872
|
+
INSTALL
|
|
873
|
+
npm install -g ccp-cli Install globally
|
|
874
|
+
npx ccp-cli status Run without installing
|
|
875
|
+
|
|
876
|
+
ENV
|
|
877
|
+
Copy .env.example to .env and fill in your Hedera testnet credentials.
|
|
878
|
+
The CLI reads from .env in the current working directory.
|
|
879
|
+
`);
|
|
880
|
+
}
|
|
881
|
+
// ─── Main ───
|
|
882
|
+
async function main() {
|
|
883
|
+
const args = process.argv.slice(2);
|
|
884
|
+
const command = args[0];
|
|
885
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
886
|
+
cmdHelp();
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
switch (command) {
|
|
891
|
+
case "status":
|
|
892
|
+
await cmdStatus();
|
|
893
|
+
break;
|
|
894
|
+
case "cert:get":
|
|
895
|
+
if (!args[1])
|
|
896
|
+
throw new Error("Usage: cert:get <certHash>");
|
|
897
|
+
await cmdCertGet(args[1]);
|
|
898
|
+
break;
|
|
899
|
+
case "cert:lookup":
|
|
900
|
+
if (!args[1])
|
|
901
|
+
throw new Error("Usage: cert:lookup <agentAddress>");
|
|
902
|
+
await cmdCertLookup(args[1]);
|
|
903
|
+
break;
|
|
904
|
+
case "cert:verify":
|
|
905
|
+
if (!args[1])
|
|
906
|
+
throw new Error("Usage: cert:verify <agentAddress> [minClass] [maxLoss]");
|
|
907
|
+
await cmdCertVerify(args[1], args[2] || "1", args[3] || "100000");
|
|
908
|
+
break;
|
|
909
|
+
case "cert:valid":
|
|
910
|
+
if (!args[1])
|
|
911
|
+
throw new Error("Usage: cert:valid <certHash>");
|
|
912
|
+
await cmdCertValid(args[1]);
|
|
913
|
+
break;
|
|
914
|
+
case "cert:publish":
|
|
915
|
+
await cmdCertPublish();
|
|
916
|
+
break;
|
|
917
|
+
case "cert:revoke":
|
|
918
|
+
if (!args[1])
|
|
919
|
+
throw new Error("Usage: cert:revoke <certHash>");
|
|
920
|
+
await cmdCertRevoke(args[1]);
|
|
921
|
+
break;
|
|
922
|
+
case "reserve:status":
|
|
923
|
+
await cmdReserveStatus();
|
|
924
|
+
break;
|
|
925
|
+
case "reserve:deposit":
|
|
926
|
+
if (!args[1])
|
|
927
|
+
throw new Error("Usage: reserve:deposit <amount>");
|
|
928
|
+
await cmdReserveDeposit(args[1]);
|
|
929
|
+
break;
|
|
930
|
+
case "reserve:lock":
|
|
931
|
+
if (!args[1])
|
|
932
|
+
throw new Error("Usage: reserve:lock <days>");
|
|
933
|
+
await cmdReserveLock(args[1]);
|
|
934
|
+
break;
|
|
935
|
+
case "spending:status":
|
|
936
|
+
await cmdSpendingStatus();
|
|
937
|
+
break;
|
|
938
|
+
case "spending:pay":
|
|
939
|
+
if (!args[1] || !args[2])
|
|
940
|
+
throw new Error("Usage: spending:pay <to> <amount>");
|
|
941
|
+
await cmdSpendingPay(args[1], args[2]);
|
|
942
|
+
break;
|
|
943
|
+
case "spending:pay:cosign":
|
|
944
|
+
if (!args[1] || !args[2])
|
|
945
|
+
throw new Error("Usage: spending:pay:cosign <to> <amount>");
|
|
946
|
+
await cmdSpendingPayCosign(args[1], args[2]);
|
|
947
|
+
break;
|
|
948
|
+
case "auditor:status":
|
|
949
|
+
await cmdAuditorStatus(args[1]);
|
|
950
|
+
break;
|
|
951
|
+
case "auditor:audit":
|
|
952
|
+
await cmdAuditorAudit();
|
|
953
|
+
break;
|
|
954
|
+
case "auditor:attest":
|
|
955
|
+
if (!args[1])
|
|
956
|
+
throw new Error("Usage: auditor:attest <certHash>");
|
|
957
|
+
await cmdAuditorAttest(args[1]);
|
|
958
|
+
break;
|
|
959
|
+
case "challenge:get":
|
|
960
|
+
if (!args[1])
|
|
961
|
+
throw new Error("Usage: challenge:get <challengeId>");
|
|
962
|
+
await cmdChallengeGet(args[1]);
|
|
963
|
+
break;
|
|
964
|
+
case "challenge:list":
|
|
965
|
+
if (!args[1])
|
|
966
|
+
throw new Error("Usage: challenge:list <certHash>");
|
|
967
|
+
await cmdChallengeList(args[1]);
|
|
968
|
+
break;
|
|
969
|
+
case "hcs:timeline":
|
|
970
|
+
await cmdHcsTimeline();
|
|
971
|
+
break;
|
|
972
|
+
case "hcs:create-topic":
|
|
973
|
+
await cmdHcsCreateTopic();
|
|
974
|
+
break;
|
|
975
|
+
case "fund":
|
|
976
|
+
await cmdFund(args[1], args[2]);
|
|
977
|
+
break;
|
|
978
|
+
case "mcp": {
|
|
979
|
+
// Launch MCP server (stdio transport)
|
|
980
|
+
const { execFileSync } = await import("child_process");
|
|
981
|
+
const { fileURLToPath } = await import("url");
|
|
982
|
+
const { dirname, join } = await import("path");
|
|
983
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
984
|
+
const mcpPath = join(__dirname, "mcp.js");
|
|
985
|
+
execFileSync("node", [mcpPath], { stdio: "inherit" });
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
case "addresses":
|
|
989
|
+
cmdAddresses();
|
|
990
|
+
break;
|
|
991
|
+
case "actors":
|
|
992
|
+
cmdActors();
|
|
993
|
+
break;
|
|
994
|
+
default:
|
|
995
|
+
console.error(` Unknown command: ${command}\n`);
|
|
996
|
+
cmdHelp();
|
|
997
|
+
process.exit(1);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
catch (error) {
|
|
1001
|
+
console.error(`\n Error: ${error.message}\n`);
|
|
1002
|
+
process.exit(1);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
main();
|
|
1006
|
+
//# sourceMappingURL=cli.js.map
|