shll-skills 2.0.11 → 2.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/SKILL.md +5 -3
- package/dist/index.js +180 -5
- package/dist/index.mjs +180 -5
- package/package.json +2 -2
- package/src/index.ts +238 -5
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: shll-run
|
|
3
3
|
description: Execute DeFi transactions on BSC via SHLL AgentNFA. The AI handles all commands — users only need to chat.
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.1.0
|
|
5
5
|
author: SHLL Team
|
|
6
6
|
website: https://shll.run
|
|
7
7
|
twitter: https://twitter.com/shllrun
|
|
@@ -175,11 +175,13 @@ Examples:
|
|
|
175
175
|
| `shll-run search --query <TEXT>` | Search token by name |
|
|
176
176
|
| `shll-run tokens` | List known tokens |
|
|
177
177
|
|
|
178
|
-
### Risk Management
|
|
178
|
+
### Risk Management & Audit
|
|
179
179
|
| Command | What it does |
|
|
180
180
|
|---------|-------------|
|
|
181
|
-
| `shll-run policies -k <ID>` | View active policies |
|
|
181
|
+
| `shll-run policies -k <ID>` | View active policies + human-readable summary |
|
|
182
182
|
| `shll-run config -k <ID> --tx-limit <BNB> --daily-limit <BNB> --cooldown <SEC>` | Tighten risk limits |
|
|
183
|
+
| `shll-run status -k <ID>` | One-shot security overview (vault, operator, policies, activity) |
|
|
184
|
+
| `shll-run history -k <ID> [--limit N]` | Recent transactions + policy rejections |
|
|
183
185
|
|
|
184
186
|
**Supported tokens:** BNB, USDC, USDT, WBNB, CAKE, ETH, BTCB, DAI, BUSD, or any 0x address.
|
|
185
187
|
|
package/dist/index.js
CHANGED
|
@@ -1346,6 +1346,7 @@ policiesCmd.action(async (opts) => {
|
|
|
1346
1346
|
const publicClient = (0, import_viem2.createPublicClient)({ chain: import_chains2.bsc, transport: (0, import_viem2.http)(rpcUrl) });
|
|
1347
1347
|
const policies = await client.getPolicies(tokenId);
|
|
1348
1348
|
const enriched = [];
|
|
1349
|
+
const summaryParts = [];
|
|
1349
1350
|
for (const p of policies) {
|
|
1350
1351
|
const entry = {
|
|
1351
1352
|
name: p.policyTypeName,
|
|
@@ -1361,13 +1362,16 @@ policiesCmd.action(async (opts) => {
|
|
|
1361
1362
|
args: [tokenId]
|
|
1362
1363
|
});
|
|
1363
1364
|
const [maxPerTx, maxPerDay, maxSlippageBps] = limits;
|
|
1365
|
+
const txBnb = (Number(maxPerTx) / 1e18).toFixed(4);
|
|
1366
|
+
const dayBnb = (Number(maxPerDay) / 1e18).toFixed(4);
|
|
1364
1367
|
entry.currentConfig = {
|
|
1365
1368
|
maxPerTx: maxPerTx.toString(),
|
|
1366
|
-
maxPerTxBnb:
|
|
1369
|
+
maxPerTxBnb: txBnb,
|
|
1367
1370
|
maxPerDay: maxPerDay.toString(),
|
|
1368
|
-
maxPerDayBnb:
|
|
1371
|
+
maxPerDayBnb: dayBnb,
|
|
1369
1372
|
maxSlippageBps: maxSlippageBps.toString()
|
|
1370
1373
|
};
|
|
1374
|
+
summaryParts.push(`Max ${txBnb} BNB/tx, ${dayBnb} BNB/day, slippage ${maxSlippageBps}bps`);
|
|
1371
1375
|
} catch {
|
|
1372
1376
|
}
|
|
1373
1377
|
}
|
|
@@ -1379,13 +1383,34 @@ policiesCmd.action(async (opts) => {
|
|
|
1379
1383
|
functionName: "cooldownSeconds",
|
|
1380
1384
|
args: [tokenId]
|
|
1381
1385
|
});
|
|
1382
|
-
|
|
1386
|
+
const secs = Number(cd);
|
|
1387
|
+
entry.currentConfig = { cooldownSeconds: secs.toString() };
|
|
1388
|
+
summaryParts.push(`Cooldown ${secs}s between transactions`);
|
|
1383
1389
|
} catch {
|
|
1384
1390
|
}
|
|
1385
1391
|
}
|
|
1392
|
+
if (p.policyTypeName === "receiver_guard") {
|
|
1393
|
+
summaryParts.push("Outbound transfers restricted (ReceiverGuard)");
|
|
1394
|
+
}
|
|
1395
|
+
if (p.policyTypeName === "dex_whitelist") {
|
|
1396
|
+
summaryParts.push("Only whitelisted DEXs allowed");
|
|
1397
|
+
}
|
|
1398
|
+
if (p.policyTypeName === "token_whitelist") {
|
|
1399
|
+
summaryParts.push("Only whitelisted tokens allowed");
|
|
1400
|
+
}
|
|
1401
|
+
if (p.policyTypeName === "defi_guard") {
|
|
1402
|
+
summaryParts.push("DeFi interactions validated by DeFiGuard");
|
|
1403
|
+
}
|
|
1386
1404
|
enriched.push(entry);
|
|
1387
1405
|
}
|
|
1388
|
-
|
|
1406
|
+
const humanSummary = summaryParts.length > 0 ? summaryParts.join(" | ") : "No configurable policies found";
|
|
1407
|
+
output({
|
|
1408
|
+
status: "success",
|
|
1409
|
+
tokenId: tokenId.toString(),
|
|
1410
|
+
humanSummary,
|
|
1411
|
+
securityNote: "Operator wallet CANNOT withdraw vault funds or transfer Agent NFT \u2014 only owner can.",
|
|
1412
|
+
policies: enriched
|
|
1413
|
+
});
|
|
1389
1414
|
} catch (error) {
|
|
1390
1415
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1391
1416
|
output({ status: "error", message });
|
|
@@ -1467,7 +1492,7 @@ configCmd.action(async (opts) => {
|
|
|
1467
1492
|
process.exit(1);
|
|
1468
1493
|
}
|
|
1469
1494
|
});
|
|
1470
|
-
var DEFAULT_INDEXER = "https://indexer.shll.run";
|
|
1495
|
+
var DEFAULT_INDEXER = "https://indexer-mainnet.shll.run";
|
|
1471
1496
|
var listingsCmd = new import_commander.Command("listings").description("List all available agent templates for rent").option("--indexer <url>", "Indexer API URL", DEFAULT_INDEXER).action(async (opts) => {
|
|
1472
1497
|
try {
|
|
1473
1498
|
const indexerUrl = opts.indexer || DEFAULT_INDEXER;
|
|
@@ -1649,6 +1674,154 @@ var balanceCmd = new import_commander.Command("balance").description("Check BNB
|
|
|
1649
1674
|
process.exit(1);
|
|
1650
1675
|
}
|
|
1651
1676
|
});
|
|
1677
|
+
var historyCmd = new import_commander.Command("history").description("Show recent transactions executed through the agent vault").requiredOption("-k, --token-id <number>", "Agent NFA Token ID").option("--limit <number>", "Number of transactions to show", "10").option("--indexer <url>", "Indexer API URL", DEFAULT_INDEXER);
|
|
1678
|
+
historyCmd.action(async (opts) => {
|
|
1679
|
+
try {
|
|
1680
|
+
const tokenId = opts.tokenId;
|
|
1681
|
+
const limit = Number(opts.limit) || 10;
|
|
1682
|
+
const indexerUrl = opts.indexer || DEFAULT_INDEXER;
|
|
1683
|
+
const activityRes = await fetch(`${indexerUrl}/api/activity/${tokenId}?limit=${limit}`, {
|
|
1684
|
+
signal: AbortSignal.timeout(1e4)
|
|
1685
|
+
});
|
|
1686
|
+
if (!activityRes.ok) {
|
|
1687
|
+
output({ status: "error", message: `Indexer returned ${activityRes.status}. Is the indexer running?` });
|
|
1688
|
+
process.exit(1);
|
|
1689
|
+
}
|
|
1690
|
+
const data = await activityRes.json();
|
|
1691
|
+
let failures = [];
|
|
1692
|
+
try {
|
|
1693
|
+
const failRes = await fetch(`${indexerUrl}/api/agents/${tokenId}/commit-failures?limit=5`, {
|
|
1694
|
+
signal: AbortSignal.timeout(8e3)
|
|
1695
|
+
});
|
|
1696
|
+
if (failRes.ok) {
|
|
1697
|
+
const failData = await failRes.json();
|
|
1698
|
+
failures = failData.items || [];
|
|
1699
|
+
}
|
|
1700
|
+
} catch {
|
|
1701
|
+
}
|
|
1702
|
+
const transactions = (data.items || []).map((tx) => {
|
|
1703
|
+
const date = new Date(Number(tx.timestamp) * 1e3);
|
|
1704
|
+
return {
|
|
1705
|
+
time: date.toISOString(),
|
|
1706
|
+
txHash: tx.txHash,
|
|
1707
|
+
target: tx.target,
|
|
1708
|
+
success: tx.success,
|
|
1709
|
+
bscscanUrl: `https://bscscan.com/tx/${tx.txHash}`
|
|
1710
|
+
};
|
|
1711
|
+
});
|
|
1712
|
+
output({
|
|
1713
|
+
status: "success",
|
|
1714
|
+
tokenId,
|
|
1715
|
+
transactions,
|
|
1716
|
+
totalShown: transactions.length,
|
|
1717
|
+
recentPolicyRejections: failures.length,
|
|
1718
|
+
policyRejections: failures.length > 0 ? failures : void 0
|
|
1719
|
+
});
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1722
|
+
output({ status: "error", message });
|
|
1723
|
+
process.exit(1);
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
var statusCmd = new import_commander.Command("status").description("Show a security overview: vault balance, operator status, policies, and recent activity").requiredOption("-k, --token-id <number>", "Agent NFA Token ID").option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC).option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA).option("--guard-address <address>", "PolicyGuard contract address", DEFAULT_GUARD).option("--indexer <url>", "Indexer API URL", DEFAULT_INDEXER);
|
|
1727
|
+
statusCmd.action(async (opts) => {
|
|
1728
|
+
try {
|
|
1729
|
+
const tokenId = BigInt(opts.tokenId);
|
|
1730
|
+
const rpcUrl = opts.rpc || DEFAULT_RPC;
|
|
1731
|
+
const nfaAddr = toHex(opts.nfaAddress || DEFAULT_NFA);
|
|
1732
|
+
const publicClient = (0, import_viem2.createPublicClient)({ chain: import_chains2.bsc, transport: (0, import_viem2.http)(rpcUrl) });
|
|
1733
|
+
const indexerUrl = opts.indexer || DEFAULT_INDEXER;
|
|
1734
|
+
const vault = await publicClient.readContract({
|
|
1735
|
+
address: nfaAddr,
|
|
1736
|
+
abi: AGENT_NFA_ABI,
|
|
1737
|
+
functionName: "accountOf",
|
|
1738
|
+
args: [tokenId]
|
|
1739
|
+
});
|
|
1740
|
+
const bnbBalance = await publicClient.getBalance({ address: vault });
|
|
1741
|
+
const bnbHuman = (Number(bnbBalance) / 1e18).toFixed(6);
|
|
1742
|
+
let operatorInfo = { configured: false };
|
|
1743
|
+
const pk = process.env.RUNNER_PRIVATE_KEY;
|
|
1744
|
+
if (pk) {
|
|
1745
|
+
const account = (0, import_accounts2.privateKeyToAccount)(toHex(pk));
|
|
1746
|
+
const opBalance = await publicClient.getBalance({ address: account.address });
|
|
1747
|
+
const opBnb = (Number(opBalance) / 1e18).toFixed(6);
|
|
1748
|
+
operatorInfo = {
|
|
1749
|
+
configured: true,
|
|
1750
|
+
address: account.address,
|
|
1751
|
+
gasBnb: opBnb,
|
|
1752
|
+
gasOk: Number(opBalance) > 1e15
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
const client = createPolicyClient(opts);
|
|
1756
|
+
const policies = await client.getPolicies(tokenId);
|
|
1757
|
+
const summaryParts = [];
|
|
1758
|
+
for (const p of policies) {
|
|
1759
|
+
if (p.policyTypeName === "spending_limit") {
|
|
1760
|
+
try {
|
|
1761
|
+
const limits = await publicClient.readContract({
|
|
1762
|
+
address: p.address,
|
|
1763
|
+
abi: SPENDING_LIMIT_ABI,
|
|
1764
|
+
functionName: "instanceLimits",
|
|
1765
|
+
args: [tokenId]
|
|
1766
|
+
});
|
|
1767
|
+
const [maxPerTx, maxPerDay] = limits;
|
|
1768
|
+
summaryParts.push(`Max ${(Number(maxPerTx) / 1e18).toFixed(4)} BNB/tx, ${(Number(maxPerDay) / 1e18).toFixed(4)} BNB/day`);
|
|
1769
|
+
} catch {
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
if (p.policyTypeName === "cooldown") {
|
|
1773
|
+
try {
|
|
1774
|
+
const cd = await publicClient.readContract({
|
|
1775
|
+
address: p.address,
|
|
1776
|
+
abi: COOLDOWN_ABI,
|
|
1777
|
+
functionName: "cooldownSeconds",
|
|
1778
|
+
args: [tokenId]
|
|
1779
|
+
});
|
|
1780
|
+
summaryParts.push(`Cooldown ${Number(cd)}s`);
|
|
1781
|
+
} catch {
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
if (p.policyTypeName === "receiver_guard") summaryParts.push("ReceiverGuard active");
|
|
1785
|
+
if (p.policyTypeName === "dex_whitelist") summaryParts.push("DEX whitelist active");
|
|
1786
|
+
if (p.policyTypeName === "token_whitelist") summaryParts.push("Token whitelist active");
|
|
1787
|
+
if (p.policyTypeName === "defi_guard") summaryParts.push("DeFiGuard active");
|
|
1788
|
+
}
|
|
1789
|
+
let activityStats = { available: false };
|
|
1790
|
+
try {
|
|
1791
|
+
const summaryRes = await fetch(`${indexerUrl}/api/agents/${opts.tokenId}/summary`, {
|
|
1792
|
+
signal: AbortSignal.timeout(8e3)
|
|
1793
|
+
});
|
|
1794
|
+
if (summaryRes.ok) {
|
|
1795
|
+
const summaryData = await summaryRes.json();
|
|
1796
|
+
activityStats = {
|
|
1797
|
+
available: true,
|
|
1798
|
+
totalExecutions: summaryData.totalExecutions,
|
|
1799
|
+
successRate: summaryData.totalExecutions > 0 ? `${(summaryData.successCount / summaryData.totalExecutions * 100).toFixed(1)}%` : "N/A",
|
|
1800
|
+
lastExecution: summaryData.lastExecution ? new Date(Number(summaryData.lastExecution) * 1e3).toISOString() : null
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
} catch {
|
|
1804
|
+
}
|
|
1805
|
+
output({
|
|
1806
|
+
status: "success",
|
|
1807
|
+
tokenId: tokenId.toString(),
|
|
1808
|
+
vault: {
|
|
1809
|
+
address: vault,
|
|
1810
|
+
bnbBalance: bnbHuman
|
|
1811
|
+
},
|
|
1812
|
+
operator: operatorInfo,
|
|
1813
|
+
securitySummary: summaryParts.length > 0 ? summaryParts.join(" | ") : "No policies found",
|
|
1814
|
+
policyCount: policies.length,
|
|
1815
|
+
activity: activityStats,
|
|
1816
|
+
securityNote: "Operator wallet CANNOT withdraw vault funds or transfer Agent NFT.",
|
|
1817
|
+
dashboardUrl: `https://shll.run/dashboard?tokenId=${tokenId}`
|
|
1818
|
+
});
|
|
1819
|
+
} catch (error) {
|
|
1820
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1821
|
+
output({ status: "error", message });
|
|
1822
|
+
process.exit(1);
|
|
1823
|
+
}
|
|
1824
|
+
});
|
|
1652
1825
|
program.addCommand(swapCmd);
|
|
1653
1826
|
program.addCommand(rawCmd);
|
|
1654
1827
|
program.addCommand(tokensCmd);
|
|
@@ -1665,4 +1838,6 @@ program.addCommand(setupGuideCmd);
|
|
|
1665
1838
|
program.addCommand(listingsCmd);
|
|
1666
1839
|
program.addCommand(genWalletCmd);
|
|
1667
1840
|
program.addCommand(balanceCmd);
|
|
1841
|
+
program.addCommand(historyCmd);
|
|
1842
|
+
program.addCommand(statusCmd);
|
|
1668
1843
|
program.parse(process.argv);
|
package/dist/index.mjs
CHANGED
|
@@ -1356,6 +1356,7 @@ policiesCmd.action(async (opts) => {
|
|
|
1356
1356
|
const publicClient = createPublicClient2({ chain: bsc2, transport: http2(rpcUrl) });
|
|
1357
1357
|
const policies = await client.getPolicies(tokenId);
|
|
1358
1358
|
const enriched = [];
|
|
1359
|
+
const summaryParts = [];
|
|
1359
1360
|
for (const p of policies) {
|
|
1360
1361
|
const entry = {
|
|
1361
1362
|
name: p.policyTypeName,
|
|
@@ -1371,13 +1372,16 @@ policiesCmd.action(async (opts) => {
|
|
|
1371
1372
|
args: [tokenId]
|
|
1372
1373
|
});
|
|
1373
1374
|
const [maxPerTx, maxPerDay, maxSlippageBps] = limits;
|
|
1375
|
+
const txBnb = (Number(maxPerTx) / 1e18).toFixed(4);
|
|
1376
|
+
const dayBnb = (Number(maxPerDay) / 1e18).toFixed(4);
|
|
1374
1377
|
entry.currentConfig = {
|
|
1375
1378
|
maxPerTx: maxPerTx.toString(),
|
|
1376
|
-
maxPerTxBnb:
|
|
1379
|
+
maxPerTxBnb: txBnb,
|
|
1377
1380
|
maxPerDay: maxPerDay.toString(),
|
|
1378
|
-
maxPerDayBnb:
|
|
1381
|
+
maxPerDayBnb: dayBnb,
|
|
1379
1382
|
maxSlippageBps: maxSlippageBps.toString()
|
|
1380
1383
|
};
|
|
1384
|
+
summaryParts.push(`Max ${txBnb} BNB/tx, ${dayBnb} BNB/day, slippage ${maxSlippageBps}bps`);
|
|
1381
1385
|
} catch {
|
|
1382
1386
|
}
|
|
1383
1387
|
}
|
|
@@ -1389,13 +1393,34 @@ policiesCmd.action(async (opts) => {
|
|
|
1389
1393
|
functionName: "cooldownSeconds",
|
|
1390
1394
|
args: [tokenId]
|
|
1391
1395
|
});
|
|
1392
|
-
|
|
1396
|
+
const secs = Number(cd);
|
|
1397
|
+
entry.currentConfig = { cooldownSeconds: secs.toString() };
|
|
1398
|
+
summaryParts.push(`Cooldown ${secs}s between transactions`);
|
|
1393
1399
|
} catch {
|
|
1394
1400
|
}
|
|
1395
1401
|
}
|
|
1402
|
+
if (p.policyTypeName === "receiver_guard") {
|
|
1403
|
+
summaryParts.push("Outbound transfers restricted (ReceiverGuard)");
|
|
1404
|
+
}
|
|
1405
|
+
if (p.policyTypeName === "dex_whitelist") {
|
|
1406
|
+
summaryParts.push("Only whitelisted DEXs allowed");
|
|
1407
|
+
}
|
|
1408
|
+
if (p.policyTypeName === "token_whitelist") {
|
|
1409
|
+
summaryParts.push("Only whitelisted tokens allowed");
|
|
1410
|
+
}
|
|
1411
|
+
if (p.policyTypeName === "defi_guard") {
|
|
1412
|
+
summaryParts.push("DeFi interactions validated by DeFiGuard");
|
|
1413
|
+
}
|
|
1396
1414
|
enriched.push(entry);
|
|
1397
1415
|
}
|
|
1398
|
-
|
|
1416
|
+
const humanSummary = summaryParts.length > 0 ? summaryParts.join(" | ") : "No configurable policies found";
|
|
1417
|
+
output({
|
|
1418
|
+
status: "success",
|
|
1419
|
+
tokenId: tokenId.toString(),
|
|
1420
|
+
humanSummary,
|
|
1421
|
+
securityNote: "Operator wallet CANNOT withdraw vault funds or transfer Agent NFT \u2014 only owner can.",
|
|
1422
|
+
policies: enriched
|
|
1423
|
+
});
|
|
1399
1424
|
} catch (error) {
|
|
1400
1425
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1401
1426
|
output({ status: "error", message });
|
|
@@ -1477,7 +1502,7 @@ configCmd.action(async (opts) => {
|
|
|
1477
1502
|
process.exit(1);
|
|
1478
1503
|
}
|
|
1479
1504
|
});
|
|
1480
|
-
var DEFAULT_INDEXER = "https://indexer.shll.run";
|
|
1505
|
+
var DEFAULT_INDEXER = "https://indexer-mainnet.shll.run";
|
|
1481
1506
|
var listingsCmd = new Command("listings").description("List all available agent templates for rent").option("--indexer <url>", "Indexer API URL", DEFAULT_INDEXER).action(async (opts) => {
|
|
1482
1507
|
try {
|
|
1483
1508
|
const indexerUrl = opts.indexer || DEFAULT_INDEXER;
|
|
@@ -1659,6 +1684,154 @@ var balanceCmd = new Command("balance").description("Check BNB balance of the ga
|
|
|
1659
1684
|
process.exit(1);
|
|
1660
1685
|
}
|
|
1661
1686
|
});
|
|
1687
|
+
var historyCmd = new Command("history").description("Show recent transactions executed through the agent vault").requiredOption("-k, --token-id <number>", "Agent NFA Token ID").option("--limit <number>", "Number of transactions to show", "10").option("--indexer <url>", "Indexer API URL", DEFAULT_INDEXER);
|
|
1688
|
+
historyCmd.action(async (opts) => {
|
|
1689
|
+
try {
|
|
1690
|
+
const tokenId = opts.tokenId;
|
|
1691
|
+
const limit = Number(opts.limit) || 10;
|
|
1692
|
+
const indexerUrl = opts.indexer || DEFAULT_INDEXER;
|
|
1693
|
+
const activityRes = await fetch(`${indexerUrl}/api/activity/${tokenId}?limit=${limit}`, {
|
|
1694
|
+
signal: AbortSignal.timeout(1e4)
|
|
1695
|
+
});
|
|
1696
|
+
if (!activityRes.ok) {
|
|
1697
|
+
output({ status: "error", message: `Indexer returned ${activityRes.status}. Is the indexer running?` });
|
|
1698
|
+
process.exit(1);
|
|
1699
|
+
}
|
|
1700
|
+
const data = await activityRes.json();
|
|
1701
|
+
let failures = [];
|
|
1702
|
+
try {
|
|
1703
|
+
const failRes = await fetch(`${indexerUrl}/api/agents/${tokenId}/commit-failures?limit=5`, {
|
|
1704
|
+
signal: AbortSignal.timeout(8e3)
|
|
1705
|
+
});
|
|
1706
|
+
if (failRes.ok) {
|
|
1707
|
+
const failData = await failRes.json();
|
|
1708
|
+
failures = failData.items || [];
|
|
1709
|
+
}
|
|
1710
|
+
} catch {
|
|
1711
|
+
}
|
|
1712
|
+
const transactions = (data.items || []).map((tx) => {
|
|
1713
|
+
const date = new Date(Number(tx.timestamp) * 1e3);
|
|
1714
|
+
return {
|
|
1715
|
+
time: date.toISOString(),
|
|
1716
|
+
txHash: tx.txHash,
|
|
1717
|
+
target: tx.target,
|
|
1718
|
+
success: tx.success,
|
|
1719
|
+
bscscanUrl: `https://bscscan.com/tx/${tx.txHash}`
|
|
1720
|
+
};
|
|
1721
|
+
});
|
|
1722
|
+
output({
|
|
1723
|
+
status: "success",
|
|
1724
|
+
tokenId,
|
|
1725
|
+
transactions,
|
|
1726
|
+
totalShown: transactions.length,
|
|
1727
|
+
recentPolicyRejections: failures.length,
|
|
1728
|
+
policyRejections: failures.length > 0 ? failures : void 0
|
|
1729
|
+
});
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1732
|
+
output({ status: "error", message });
|
|
1733
|
+
process.exit(1);
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
var statusCmd = new Command("status").description("Show a security overview: vault balance, operator status, policies, and recent activity").requiredOption("-k, --token-id <number>", "Agent NFA Token ID").option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC).option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA).option("--guard-address <address>", "PolicyGuard contract address", DEFAULT_GUARD).option("--indexer <url>", "Indexer API URL", DEFAULT_INDEXER);
|
|
1737
|
+
statusCmd.action(async (opts) => {
|
|
1738
|
+
try {
|
|
1739
|
+
const tokenId = BigInt(opts.tokenId);
|
|
1740
|
+
const rpcUrl = opts.rpc || DEFAULT_RPC;
|
|
1741
|
+
const nfaAddr = toHex(opts.nfaAddress || DEFAULT_NFA);
|
|
1742
|
+
const publicClient = createPublicClient2({ chain: bsc2, transport: http2(rpcUrl) });
|
|
1743
|
+
const indexerUrl = opts.indexer || DEFAULT_INDEXER;
|
|
1744
|
+
const vault = await publicClient.readContract({
|
|
1745
|
+
address: nfaAddr,
|
|
1746
|
+
abi: AGENT_NFA_ABI,
|
|
1747
|
+
functionName: "accountOf",
|
|
1748
|
+
args: [tokenId]
|
|
1749
|
+
});
|
|
1750
|
+
const bnbBalance = await publicClient.getBalance({ address: vault });
|
|
1751
|
+
const bnbHuman = (Number(bnbBalance) / 1e18).toFixed(6);
|
|
1752
|
+
let operatorInfo = { configured: false };
|
|
1753
|
+
const pk = process.env.RUNNER_PRIVATE_KEY;
|
|
1754
|
+
if (pk) {
|
|
1755
|
+
const account = privateKeyToAccount2(toHex(pk));
|
|
1756
|
+
const opBalance = await publicClient.getBalance({ address: account.address });
|
|
1757
|
+
const opBnb = (Number(opBalance) / 1e18).toFixed(6);
|
|
1758
|
+
operatorInfo = {
|
|
1759
|
+
configured: true,
|
|
1760
|
+
address: account.address,
|
|
1761
|
+
gasBnb: opBnb,
|
|
1762
|
+
gasOk: Number(opBalance) > 1e15
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
const client = createPolicyClient(opts);
|
|
1766
|
+
const policies = await client.getPolicies(tokenId);
|
|
1767
|
+
const summaryParts = [];
|
|
1768
|
+
for (const p of policies) {
|
|
1769
|
+
if (p.policyTypeName === "spending_limit") {
|
|
1770
|
+
try {
|
|
1771
|
+
const limits = await publicClient.readContract({
|
|
1772
|
+
address: p.address,
|
|
1773
|
+
abi: SPENDING_LIMIT_ABI,
|
|
1774
|
+
functionName: "instanceLimits",
|
|
1775
|
+
args: [tokenId]
|
|
1776
|
+
});
|
|
1777
|
+
const [maxPerTx, maxPerDay] = limits;
|
|
1778
|
+
summaryParts.push(`Max ${(Number(maxPerTx) / 1e18).toFixed(4)} BNB/tx, ${(Number(maxPerDay) / 1e18).toFixed(4)} BNB/day`);
|
|
1779
|
+
} catch {
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
if (p.policyTypeName === "cooldown") {
|
|
1783
|
+
try {
|
|
1784
|
+
const cd = await publicClient.readContract({
|
|
1785
|
+
address: p.address,
|
|
1786
|
+
abi: COOLDOWN_ABI,
|
|
1787
|
+
functionName: "cooldownSeconds",
|
|
1788
|
+
args: [tokenId]
|
|
1789
|
+
});
|
|
1790
|
+
summaryParts.push(`Cooldown ${Number(cd)}s`);
|
|
1791
|
+
} catch {
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
if (p.policyTypeName === "receiver_guard") summaryParts.push("ReceiverGuard active");
|
|
1795
|
+
if (p.policyTypeName === "dex_whitelist") summaryParts.push("DEX whitelist active");
|
|
1796
|
+
if (p.policyTypeName === "token_whitelist") summaryParts.push("Token whitelist active");
|
|
1797
|
+
if (p.policyTypeName === "defi_guard") summaryParts.push("DeFiGuard active");
|
|
1798
|
+
}
|
|
1799
|
+
let activityStats = { available: false };
|
|
1800
|
+
try {
|
|
1801
|
+
const summaryRes = await fetch(`${indexerUrl}/api/agents/${opts.tokenId}/summary`, {
|
|
1802
|
+
signal: AbortSignal.timeout(8e3)
|
|
1803
|
+
});
|
|
1804
|
+
if (summaryRes.ok) {
|
|
1805
|
+
const summaryData = await summaryRes.json();
|
|
1806
|
+
activityStats = {
|
|
1807
|
+
available: true,
|
|
1808
|
+
totalExecutions: summaryData.totalExecutions,
|
|
1809
|
+
successRate: summaryData.totalExecutions > 0 ? `${(summaryData.successCount / summaryData.totalExecutions * 100).toFixed(1)}%` : "N/A",
|
|
1810
|
+
lastExecution: summaryData.lastExecution ? new Date(Number(summaryData.lastExecution) * 1e3).toISOString() : null
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
} catch {
|
|
1814
|
+
}
|
|
1815
|
+
output({
|
|
1816
|
+
status: "success",
|
|
1817
|
+
tokenId: tokenId.toString(),
|
|
1818
|
+
vault: {
|
|
1819
|
+
address: vault,
|
|
1820
|
+
bnbBalance: bnbHuman
|
|
1821
|
+
},
|
|
1822
|
+
operator: operatorInfo,
|
|
1823
|
+
securitySummary: summaryParts.length > 0 ? summaryParts.join(" | ") : "No policies found",
|
|
1824
|
+
policyCount: policies.length,
|
|
1825
|
+
activity: activityStats,
|
|
1826
|
+
securityNote: "Operator wallet CANNOT withdraw vault funds or transfer Agent NFT.",
|
|
1827
|
+
dashboardUrl: `https://shll.run/dashboard?tokenId=${tokenId}`
|
|
1828
|
+
});
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1831
|
+
output({ status: "error", message });
|
|
1832
|
+
process.exit(1);
|
|
1833
|
+
}
|
|
1834
|
+
});
|
|
1662
1835
|
program.addCommand(swapCmd);
|
|
1663
1836
|
program.addCommand(rawCmd);
|
|
1664
1837
|
program.addCommand(tokensCmd);
|
|
@@ -1675,4 +1848,6 @@ program.addCommand(setupGuideCmd);
|
|
|
1675
1848
|
program.addCommand(listingsCmd);
|
|
1676
1849
|
program.addCommand(genWalletCmd);
|
|
1677
1850
|
program.addCommand(balanceCmd);
|
|
1851
|
+
program.addCommand(historyCmd);
|
|
1852
|
+
program.addCommand(statusCmd);
|
|
1678
1853
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shll-skills",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "SHLL Agent Runtime Skill for OpenClaw",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,4 +21,4 @@
|
|
|
21
21
|
"typescript": "^5.4.5",
|
|
22
22
|
"@types/node": "^20.12.7"
|
|
23
23
|
}
|
|
24
|
-
}
|
|
24
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1088,6 +1088,7 @@ policiesCmd.action(async (opts) => {
|
|
|
1088
1088
|
|
|
1089
1089
|
// Enrich with current config for configurable policies
|
|
1090
1090
|
const enriched = [];
|
|
1091
|
+
const summaryParts: string[] = [];
|
|
1091
1092
|
for (const p of policies) {
|
|
1092
1093
|
const entry: Record<string, unknown> = {
|
|
1093
1094
|
name: p.policyTypeName,
|
|
@@ -1104,13 +1105,16 @@ policiesCmd.action(async (opts) => {
|
|
|
1104
1105
|
args: [tokenId],
|
|
1105
1106
|
});
|
|
1106
1107
|
const [maxPerTx, maxPerDay, maxSlippageBps] = limits;
|
|
1108
|
+
const txBnb = (Number(maxPerTx) / 1e18).toFixed(4);
|
|
1109
|
+
const dayBnb = (Number(maxPerDay) / 1e18).toFixed(4);
|
|
1107
1110
|
entry.currentConfig = {
|
|
1108
1111
|
maxPerTx: maxPerTx.toString(),
|
|
1109
|
-
maxPerTxBnb:
|
|
1112
|
+
maxPerTxBnb: txBnb,
|
|
1110
1113
|
maxPerDay: maxPerDay.toString(),
|
|
1111
|
-
maxPerDayBnb:
|
|
1114
|
+
maxPerDayBnb: dayBnb,
|
|
1112
1115
|
maxSlippageBps: maxSlippageBps.toString(),
|
|
1113
1116
|
};
|
|
1117
|
+
summaryParts.push(`Max ${txBnb} BNB/tx, ${dayBnb} BNB/day, slippage ${maxSlippageBps}bps`);
|
|
1114
1118
|
} catch { /* policy read failed */ }
|
|
1115
1119
|
}
|
|
1116
1120
|
|
|
@@ -1122,14 +1126,39 @@ policiesCmd.action(async (opts) => {
|
|
|
1122
1126
|
functionName: "cooldownSeconds",
|
|
1123
1127
|
args: [tokenId],
|
|
1124
1128
|
});
|
|
1125
|
-
|
|
1129
|
+
const secs = Number(cd);
|
|
1130
|
+
entry.currentConfig = { cooldownSeconds: secs.toString() };
|
|
1131
|
+
summaryParts.push(`Cooldown ${secs}s between transactions`);
|
|
1126
1132
|
} catch { /* policy read failed */ }
|
|
1127
1133
|
}
|
|
1128
1134
|
|
|
1135
|
+
if (p.policyTypeName === "receiver_guard") {
|
|
1136
|
+
summaryParts.push("Outbound transfers restricted (ReceiverGuard)");
|
|
1137
|
+
}
|
|
1138
|
+
if (p.policyTypeName === "dex_whitelist") {
|
|
1139
|
+
summaryParts.push("Only whitelisted DEXs allowed");
|
|
1140
|
+
}
|
|
1141
|
+
if (p.policyTypeName === "token_whitelist") {
|
|
1142
|
+
summaryParts.push("Only whitelisted tokens allowed");
|
|
1143
|
+
}
|
|
1144
|
+
if (p.policyTypeName === "defi_guard") {
|
|
1145
|
+
summaryParts.push("DeFi interactions validated by DeFiGuard");
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1129
1148
|
enriched.push(entry);
|
|
1130
1149
|
}
|
|
1131
1150
|
|
|
1132
|
-
|
|
1151
|
+
const humanSummary = summaryParts.length > 0
|
|
1152
|
+
? summaryParts.join(" | ")
|
|
1153
|
+
: "No configurable policies found";
|
|
1154
|
+
|
|
1155
|
+
output({
|
|
1156
|
+
status: "success",
|
|
1157
|
+
tokenId: tokenId.toString(),
|
|
1158
|
+
humanSummary,
|
|
1159
|
+
securityNote: "Operator wallet CANNOT withdraw vault funds or transfer Agent NFT — only owner can.",
|
|
1160
|
+
policies: enriched,
|
|
1161
|
+
});
|
|
1133
1162
|
|
|
1134
1163
|
} catch (error: unknown) {
|
|
1135
1164
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -1242,7 +1271,7 @@ configCmd.action(async (opts) => {
|
|
|
1242
1271
|
});
|
|
1243
1272
|
|
|
1244
1273
|
// -- Subcommand: listings (query available agent templates) --------
|
|
1245
|
-
const DEFAULT_INDEXER = "https://indexer.shll.run";
|
|
1274
|
+
const DEFAULT_INDEXER = "https://indexer-mainnet.shll.run";
|
|
1246
1275
|
|
|
1247
1276
|
const listingsCmd = new Command("listings")
|
|
1248
1277
|
.description("List all available agent templates for rent")
|
|
@@ -1478,6 +1507,208 @@ const balanceCmd = new Command("balance")
|
|
|
1478
1507
|
}
|
|
1479
1508
|
});
|
|
1480
1509
|
|
|
1510
|
+
// -- Subcommand: history (recent vault transactions) ------------------
|
|
1511
|
+
const historyCmd = new Command("history")
|
|
1512
|
+
.description("Show recent transactions executed through the agent vault")
|
|
1513
|
+
.requiredOption("-k, --token-id <number>", "Agent NFA Token ID")
|
|
1514
|
+
.option("--limit <number>", "Number of transactions to show", "10")
|
|
1515
|
+
.option("--indexer <url>", "Indexer API URL", DEFAULT_INDEXER);
|
|
1516
|
+
|
|
1517
|
+
historyCmd.action(async (opts) => {
|
|
1518
|
+
try {
|
|
1519
|
+
const tokenId = opts.tokenId;
|
|
1520
|
+
const limit = Number(opts.limit) || 10;
|
|
1521
|
+
const indexerUrl = opts.indexer || DEFAULT_INDEXER;
|
|
1522
|
+
|
|
1523
|
+
// Fetch execution activity from indexer
|
|
1524
|
+
const activityRes = await fetch(`${indexerUrl}/api/activity/${tokenId}?limit=${limit}`, {
|
|
1525
|
+
signal: AbortSignal.timeout(10000),
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
if (!activityRes.ok) {
|
|
1529
|
+
output({ status: "error", message: `Indexer returned ${activityRes.status}. Is the indexer running?` });
|
|
1530
|
+
process.exit(1);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
const data = await activityRes.json() as {
|
|
1534
|
+
items: Array<{
|
|
1535
|
+
txHash: string;
|
|
1536
|
+
target: string;
|
|
1537
|
+
success: boolean;
|
|
1538
|
+
timestamp: string;
|
|
1539
|
+
blockNumber: string;
|
|
1540
|
+
action?: string;
|
|
1541
|
+
}>;
|
|
1542
|
+
count: number;
|
|
1543
|
+
};
|
|
1544
|
+
|
|
1545
|
+
// Also fetch commit failures (policy rejections)
|
|
1546
|
+
let failures: Array<{
|
|
1547
|
+
txHash: string;
|
|
1548
|
+
reason: string;
|
|
1549
|
+
timestamp: string;
|
|
1550
|
+
}> = [];
|
|
1551
|
+
try {
|
|
1552
|
+
const failRes = await fetch(`${indexerUrl}/api/agents/${tokenId}/commit-failures?limit=5`, {
|
|
1553
|
+
signal: AbortSignal.timeout(8000),
|
|
1554
|
+
});
|
|
1555
|
+
if (failRes.ok) {
|
|
1556
|
+
const failData = await failRes.json() as { items: typeof failures };
|
|
1557
|
+
failures = failData.items || [];
|
|
1558
|
+
}
|
|
1559
|
+
} catch { /* non-critical */ }
|
|
1560
|
+
|
|
1561
|
+
const transactions = (data.items || []).map((tx) => {
|
|
1562
|
+
const date = new Date(Number(tx.timestamp) * 1000);
|
|
1563
|
+
return {
|
|
1564
|
+
time: date.toISOString(),
|
|
1565
|
+
txHash: tx.txHash,
|
|
1566
|
+
target: tx.target,
|
|
1567
|
+
success: tx.success,
|
|
1568
|
+
bscscanUrl: `https://bscscan.com/tx/${tx.txHash}`,
|
|
1569
|
+
};
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
output({
|
|
1573
|
+
status: "success",
|
|
1574
|
+
tokenId,
|
|
1575
|
+
transactions,
|
|
1576
|
+
totalShown: transactions.length,
|
|
1577
|
+
recentPolicyRejections: failures.length,
|
|
1578
|
+
policyRejections: failures.length > 0 ? failures : undefined,
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
} catch (error: unknown) {
|
|
1582
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1583
|
+
output({ status: "error", message });
|
|
1584
|
+
process.exit(1);
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1588
|
+
// -- Subcommand: status (one-shot security overview) ------------------
|
|
1589
|
+
const statusCmd = new Command("status")
|
|
1590
|
+
.description("Show a security overview: vault balance, operator status, policies, and recent activity")
|
|
1591
|
+
.requiredOption("-k, --token-id <number>", "Agent NFA Token ID")
|
|
1592
|
+
.option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC)
|
|
1593
|
+
.option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA)
|
|
1594
|
+
.option("--guard-address <address>", "PolicyGuard contract address", DEFAULT_GUARD)
|
|
1595
|
+
.option("--indexer <url>", "Indexer API URL", DEFAULT_INDEXER);
|
|
1596
|
+
|
|
1597
|
+
statusCmd.action(async (opts) => {
|
|
1598
|
+
try {
|
|
1599
|
+
const tokenId = BigInt(opts.tokenId);
|
|
1600
|
+
const rpcUrl = opts.rpc || DEFAULT_RPC;
|
|
1601
|
+
const nfaAddr = toHex(opts.nfaAddress || DEFAULT_NFA) as Address;
|
|
1602
|
+
const publicClient = createPublicClient({ chain: bsc, transport: http(rpcUrl) });
|
|
1603
|
+
const indexerUrl = opts.indexer || DEFAULT_INDEXER;
|
|
1604
|
+
|
|
1605
|
+
// 1. Vault address and BNB balance
|
|
1606
|
+
const vault = await publicClient.readContract({
|
|
1607
|
+
address: nfaAddr,
|
|
1608
|
+
abi: AGENT_NFA_ABI,
|
|
1609
|
+
functionName: "accountOf",
|
|
1610
|
+
args: [tokenId],
|
|
1611
|
+
}) as Address;
|
|
1612
|
+
const bnbBalance = await publicClient.getBalance({ address: vault });
|
|
1613
|
+
const bnbHuman = (Number(bnbBalance) / 1e18).toFixed(6);
|
|
1614
|
+
|
|
1615
|
+
// 2. Operator wallet info
|
|
1616
|
+
let operatorInfo: Record<string, unknown> = { configured: false };
|
|
1617
|
+
const pk = process.env.RUNNER_PRIVATE_KEY;
|
|
1618
|
+
if (pk) {
|
|
1619
|
+
const account = privateKeyToAccount(toHex(pk) as Hex);
|
|
1620
|
+
const opBalance = await publicClient.getBalance({ address: account.address });
|
|
1621
|
+
const opBnb = (Number(opBalance) / 1e18).toFixed(6);
|
|
1622
|
+
operatorInfo = {
|
|
1623
|
+
configured: true,
|
|
1624
|
+
address: account.address,
|
|
1625
|
+
gasBnb: opBnb,
|
|
1626
|
+
gasOk: Number(opBalance) > 1e15,
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// 3. Policies summary
|
|
1631
|
+
const client = createPolicyClient(opts);
|
|
1632
|
+
const policies = await client.getPolicies(tokenId);
|
|
1633
|
+
const summaryParts: string[] = [];
|
|
1634
|
+
for (const p of policies) {
|
|
1635
|
+
if (p.policyTypeName === "spending_limit") {
|
|
1636
|
+
try {
|
|
1637
|
+
const limits = await publicClient.readContract({
|
|
1638
|
+
address: p.address,
|
|
1639
|
+
abi: SPENDING_LIMIT_ABI,
|
|
1640
|
+
functionName: "instanceLimits",
|
|
1641
|
+
args: [tokenId],
|
|
1642
|
+
});
|
|
1643
|
+
const [maxPerTx, maxPerDay] = limits;
|
|
1644
|
+
summaryParts.push(`Max ${(Number(maxPerTx) / 1e18).toFixed(4)} BNB/tx, ${(Number(maxPerDay) / 1e18).toFixed(4)} BNB/day`);
|
|
1645
|
+
} catch { /* skip */ }
|
|
1646
|
+
}
|
|
1647
|
+
if (p.policyTypeName === "cooldown") {
|
|
1648
|
+
try {
|
|
1649
|
+
const cd = await publicClient.readContract({
|
|
1650
|
+
address: p.address,
|
|
1651
|
+
abi: COOLDOWN_ABI,
|
|
1652
|
+
functionName: "cooldownSeconds",
|
|
1653
|
+
args: [tokenId],
|
|
1654
|
+
});
|
|
1655
|
+
summaryParts.push(`Cooldown ${Number(cd)}s`);
|
|
1656
|
+
} catch { /* skip */ }
|
|
1657
|
+
}
|
|
1658
|
+
if (p.policyTypeName === "receiver_guard") summaryParts.push("ReceiverGuard active");
|
|
1659
|
+
if (p.policyTypeName === "dex_whitelist") summaryParts.push("DEX whitelist active");
|
|
1660
|
+
if (p.policyTypeName === "token_whitelist") summaryParts.push("Token whitelist active");
|
|
1661
|
+
if (p.policyTypeName === "defi_guard") summaryParts.push("DeFiGuard active");
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
// 4. Recent activity stats from indexer
|
|
1665
|
+
let activityStats: Record<string, unknown> = { available: false };
|
|
1666
|
+
try {
|
|
1667
|
+
const summaryRes = await fetch(`${indexerUrl}/api/agents/${opts.tokenId}/summary`, {
|
|
1668
|
+
signal: AbortSignal.timeout(8000),
|
|
1669
|
+
});
|
|
1670
|
+
if (summaryRes.ok) {
|
|
1671
|
+
const summaryData = await summaryRes.json() as {
|
|
1672
|
+
totalExecutions: number;
|
|
1673
|
+
successCount: number;
|
|
1674
|
+
failCount: number;
|
|
1675
|
+
lastExecution: string | null;
|
|
1676
|
+
};
|
|
1677
|
+
activityStats = {
|
|
1678
|
+
available: true,
|
|
1679
|
+
totalExecutions: summaryData.totalExecutions,
|
|
1680
|
+
successRate: summaryData.totalExecutions > 0
|
|
1681
|
+
? `${((summaryData.successCount / summaryData.totalExecutions) * 100).toFixed(1)}%`
|
|
1682
|
+
: "N/A",
|
|
1683
|
+
lastExecution: summaryData.lastExecution
|
|
1684
|
+
? new Date(Number(summaryData.lastExecution) * 1000).toISOString()
|
|
1685
|
+
: null,
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
} catch { /* non-critical */ }
|
|
1689
|
+
|
|
1690
|
+
output({
|
|
1691
|
+
status: "success",
|
|
1692
|
+
tokenId: tokenId.toString(),
|
|
1693
|
+
vault: {
|
|
1694
|
+
address: vault,
|
|
1695
|
+
bnbBalance: bnbHuman,
|
|
1696
|
+
},
|
|
1697
|
+
operator: operatorInfo,
|
|
1698
|
+
securitySummary: summaryParts.length > 0 ? summaryParts.join(" | ") : "No policies found",
|
|
1699
|
+
policyCount: policies.length,
|
|
1700
|
+
activity: activityStats,
|
|
1701
|
+
securityNote: "Operator wallet CANNOT withdraw vault funds or transfer Agent NFT.",
|
|
1702
|
+
dashboardUrl: `https://shll.run/dashboard?tokenId=${tokenId}`,
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
} catch (error: unknown) {
|
|
1706
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1707
|
+
output({ status: "error", message });
|
|
1708
|
+
process.exit(1);
|
|
1709
|
+
}
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1481
1712
|
program.addCommand(swapCmd);
|
|
1482
1713
|
program.addCommand(rawCmd);
|
|
1483
1714
|
program.addCommand(tokensCmd);
|
|
@@ -1494,5 +1725,7 @@ program.addCommand(setupGuideCmd);
|
|
|
1494
1725
|
program.addCommand(listingsCmd);
|
|
1495
1726
|
program.addCommand(genWalletCmd);
|
|
1496
1727
|
program.addCommand(balanceCmd);
|
|
1728
|
+
program.addCommand(historyCmd);
|
|
1729
|
+
program.addCommand(statusCmd);
|
|
1497
1730
|
program.parse(process.argv);
|
|
1498
1731
|
|