shll-skills 4.0.1 → 5.0.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/dist/index.js CHANGED
@@ -2152,6 +2152,71 @@ lendingInfoCmd.action(async (opts) => {
2152
2152
  process.exit(1);
2153
2153
  }
2154
2154
  });
2155
+ var OPERATOR_OF_ABI = [{
2156
+ type: "function",
2157
+ name: "operatorOf",
2158
+ inputs: [{ name: "tokenId", type: "uint256" }],
2159
+ outputs: [{ name: "", type: "address" }],
2160
+ stateMutability: "view"
2161
+ }];
2162
+ var myAgentsCmd = new import_commander.Command("my-agents").description("List all agents where the current RUNNER_PRIVATE_KEY is the operator").option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC).option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA).option("--indexer <url>", "Indexer base URL", DEFAULT_INDEXER);
2163
+ myAgentsCmd.action(async (opts) => {
2164
+ try {
2165
+ if (!process.env.RUNNER_PRIVATE_KEY) {
2166
+ output({ status: "error", message: "RUNNER_PRIVATE_KEY environment variable is missing" });
2167
+ process.exit(1);
2168
+ }
2169
+ const operator = (0, import_accounts2.privateKeyToAccount)(toHex(process.env.RUNNER_PRIVATE_KEY)).address.toLowerCase();
2170
+ const rpcUrl = opts.rpc || DEFAULT_RPC;
2171
+ const nfaAddr = toHex(opts.nfaAddress || DEFAULT_NFA);
2172
+ const indexerUrl = (opts.indexer || DEFAULT_INDEXER).replace(/\/+$/, "");
2173
+ const publicClient = (0, import_viem2.createPublicClient)({ chain: import_chains2.bsc, transport: (0, import_viem2.http)(rpcUrl) });
2174
+ const res = await fetch(`${indexerUrl}/api/agents`);
2175
+ if (!res.ok) {
2176
+ output({ status: "error", message: `Indexer error: ${res.status}` });
2177
+ process.exit(1);
2178
+ }
2179
+ const json = await res.json();
2180
+ const agents = (json.items || []).filter((a) => !a.isTemplate && a.tokenId !== void 0);
2181
+ if (agents.length === 0) {
2182
+ output({ status: "success", agents: [], message: "No agents found in indexer" });
2183
+ return;
2184
+ }
2185
+ const checks = await Promise.all(
2186
+ agents.map(async (a) => {
2187
+ const tokenId = BigInt(a.tokenId);
2188
+ try {
2189
+ const op = await publicClient.readContract({
2190
+ address: nfaAddr,
2191
+ abi: OPERATOR_OF_ABI,
2192
+ functionName: "operatorOf",
2193
+ args: [tokenId]
2194
+ });
2195
+ return {
2196
+ tokenId: tokenId.toString(),
2197
+ vault: a.account || "",
2198
+ owner: a.owner || "",
2199
+ agentType: a.agentType || "unknown",
2200
+ isOperator: op.toLowerCase() === operator
2201
+ };
2202
+ } catch {
2203
+ return null;
2204
+ }
2205
+ })
2206
+ );
2207
+ const myAgents = checks.filter((c) => c !== null && c.isOperator);
2208
+ output({
2209
+ status: "success",
2210
+ operator,
2211
+ agents: myAgents,
2212
+ count: myAgents.length
2213
+ });
2214
+ } catch (error) {
2215
+ const message = error instanceof Error ? error.message : "Unknown error";
2216
+ output({ status: "error", message });
2217
+ process.exit(1);
2218
+ }
2219
+ });
2155
2220
  program.addCommand(swapCmd);
2156
2221
  program.addCommand(rawCmd);
2157
2222
  program.addCommand(tokensCmd);
@@ -2173,4 +2238,5 @@ program.addCommand(statusCmd);
2173
2238
  program.addCommand(lendCmd);
2174
2239
  program.addCommand(redeemCmd);
2175
2240
  program.addCommand(lendingInfoCmd);
2241
+ program.addCommand(myAgentsCmd);
2176
2242
  program.parse(process.argv);
package/dist/index.mjs CHANGED
@@ -1664,6 +1664,71 @@ lendingInfoCmd.action(async (opts) => {
1664
1664
  process.exit(1);
1665
1665
  }
1666
1666
  });
1667
+ var OPERATOR_OF_ABI = [{
1668
+ type: "function",
1669
+ name: "operatorOf",
1670
+ inputs: [{ name: "tokenId", type: "uint256" }],
1671
+ outputs: [{ name: "", type: "address" }],
1672
+ stateMutability: "view"
1673
+ }];
1674
+ var myAgentsCmd = new Command("my-agents").description("List all agents where the current RUNNER_PRIVATE_KEY is the operator").option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC).option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA).option("--indexer <url>", "Indexer base URL", DEFAULT_INDEXER);
1675
+ myAgentsCmd.action(async (opts) => {
1676
+ try {
1677
+ if (!process.env.RUNNER_PRIVATE_KEY) {
1678
+ output({ status: "error", message: "RUNNER_PRIVATE_KEY environment variable is missing" });
1679
+ process.exit(1);
1680
+ }
1681
+ const operator = privateKeyToAccount(toHex(process.env.RUNNER_PRIVATE_KEY)).address.toLowerCase();
1682
+ const rpcUrl = opts.rpc || DEFAULT_RPC;
1683
+ const nfaAddr = toHex(opts.nfaAddress || DEFAULT_NFA);
1684
+ const indexerUrl = (opts.indexer || DEFAULT_INDEXER).replace(/\/+$/, "");
1685
+ const publicClient = createPublicClient({ chain: bsc, transport: http(rpcUrl) });
1686
+ const res = await fetch(`${indexerUrl}/api/agents`);
1687
+ if (!res.ok) {
1688
+ output({ status: "error", message: `Indexer error: ${res.status}` });
1689
+ process.exit(1);
1690
+ }
1691
+ const json = await res.json();
1692
+ const agents = (json.items || []).filter((a) => !a.isTemplate && a.tokenId !== void 0);
1693
+ if (agents.length === 0) {
1694
+ output({ status: "success", agents: [], message: "No agents found in indexer" });
1695
+ return;
1696
+ }
1697
+ const checks = await Promise.all(
1698
+ agents.map(async (a) => {
1699
+ const tokenId = BigInt(a.tokenId);
1700
+ try {
1701
+ const op = await publicClient.readContract({
1702
+ address: nfaAddr,
1703
+ abi: OPERATOR_OF_ABI,
1704
+ functionName: "operatorOf",
1705
+ args: [tokenId]
1706
+ });
1707
+ return {
1708
+ tokenId: tokenId.toString(),
1709
+ vault: a.account || "",
1710
+ owner: a.owner || "",
1711
+ agentType: a.agentType || "unknown",
1712
+ isOperator: op.toLowerCase() === operator
1713
+ };
1714
+ } catch {
1715
+ return null;
1716
+ }
1717
+ })
1718
+ );
1719
+ const myAgents = checks.filter((c) => c !== null && c.isOperator);
1720
+ output({
1721
+ status: "success",
1722
+ operator,
1723
+ agents: myAgents,
1724
+ count: myAgents.length
1725
+ });
1726
+ } catch (error) {
1727
+ const message = error instanceof Error ? error.message : "Unknown error";
1728
+ output({ status: "error", message });
1729
+ process.exit(1);
1730
+ }
1731
+ });
1667
1732
  program.addCommand(swapCmd);
1668
1733
  program.addCommand(rawCmd);
1669
1734
  program.addCommand(tokensCmd);
@@ -1685,4 +1750,5 @@ program.addCommand(statusCmd);
1685
1750
  program.addCommand(lendCmd);
1686
1751
  program.addCommand(redeemCmd);
1687
1752
  program.addCommand(lendingInfoCmd);
1753
+ program.addCommand(myAgentsCmd);
1688
1754
  program.parse(process.argv);
package/dist/mcp.js CHANGED
@@ -508,6 +508,8 @@ var import_chains2 = require("viem/chains");
508
508
  var DEFAULT_NFA = "0xE98DCdbf370D7b52c9A2b88F79bEF514A5375a2b";
509
509
  var DEFAULT_GUARD = "0x25d17eA0e3Bcb8CA08a2BFE917E817AFc05dbBB3";
510
510
  var DEFAULT_RPC = "https://bsc-dataseed1.binance.org";
511
+ var DEFAULT_LISTING_MANAGER = "0x1f9CE85bD0FF75acc3D92eB79f1Eb472f0865071";
512
+ var DEFAULT_LISTING_ID = "0x733e9d959da5c1745fa507df6b47537f0945012eff3ceb4b684cd4482f2bc4d3";
511
513
  var PANCAKE_V2_ROUTER = "0x10ED43C718714eb63d5aA57B78B54704E256024E";
512
514
  var PANCAKE_V3_SMART_ROUTER = "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4";
513
515
  var WBNB = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c";
@@ -605,6 +607,21 @@ var VTOKEN_READ_ABI = [
605
607
  { type: "function", name: "supplyRatePerBlock", inputs: [], outputs: [{ name: "", type: "uint256" }], stateMutability: "view" }
606
608
  ];
607
609
  var VBNB_MINT_ABI = [{ type: "function", name: "mint", inputs: [], outputs: [], stateMutability: "payable" }];
610
+ var WBNB_ABI = [
611
+ { type: "function", name: "deposit", inputs: [], outputs: [], stateMutability: "payable" },
612
+ { type: "function", name: "withdraw", inputs: [{ name: "wad", type: "uint256" }], outputs: [], stateMutability: "nonpayable" }
613
+ ];
614
+ var SPENDING_LIMIT_ABI = [
615
+ { type: "function", name: "setLimits", inputs: [{ name: "instanceId", type: "uint256" }, { name: "maxPerTx", type: "uint256" }, { name: "maxPerDay", type: "uint256" }, { name: "maxSlippageBps", type: "uint256" }], outputs: [], stateMutability: "nonpayable" },
616
+ { type: "function", name: "instanceLimits", inputs: [{ name: "instanceId", type: "uint256" }], outputs: [{ name: "maxPerTx", type: "uint256" }, { name: "maxPerDay", type: "uint256" }, { name: "maxSlippageBps", type: "uint256" }], stateMutability: "view" }
617
+ ];
618
+ var COOLDOWN_ABI = [
619
+ { type: "function", name: "setCooldown", inputs: [{ name: "instanceId", type: "uint256" }, { name: "seconds_", type: "uint256" }], outputs: [], stateMutability: "nonpayable" },
620
+ { type: "function", name: "cooldownSeconds", inputs: [{ name: "instanceId", type: "uint256" }], outputs: [{ name: "", type: "uint256" }], stateMutability: "view" }
621
+ ];
622
+ var LISTING_MANAGER_ABI = [
623
+ { type: "function", name: "listings", inputs: [{ name: "listingId", type: "bytes32" }], outputs: [{ name: "nfa", type: "address" }, { name: "templateId", type: "uint256" }, { name: "owner", type: "address" }, { name: "pricePerDay", type: "uint256" }, { name: "minDays", type: "uint32" }, { name: "active", type: "bool" }], stateMutability: "view" }
624
+ ];
608
625
  function getConfig() {
609
626
  const privateKey = process.env.RUNNER_PRIVATE_KEY;
610
627
  if (!privateKey) throw new Error("RUNNER_PRIVATE_KEY environment variable is required");
@@ -626,7 +643,7 @@ function createClients() {
626
643
  }
627
644
  var server = new import_mcp.McpServer({
628
645
  name: "shll-defi",
629
- version: "1.0.0"
646
+ version: "5.0.0"
630
647
  });
631
648
  server.tool(
632
649
  "portfolio",
@@ -929,6 +946,395 @@ server.tool(
929
946
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", hash: result.hash, token, amount, to: recipient }) }] };
930
947
  }
931
948
  );
949
+ var OPERATOR_OF_ABI = [{
950
+ type: "function",
951
+ name: "operatorOf",
952
+ inputs: [{ name: "tokenId", type: "uint256" }],
953
+ outputs: [{ name: "", type: "address" }],
954
+ stateMutability: "view"
955
+ }];
956
+ var DEFAULT_INDEXER = "https://indexer-mainnet.shll.run";
957
+ server.tool(
958
+ "my_agents",
959
+ "List all agents where the current operator key is authorized. Returns token IDs, vault addresses, and agent types. Call this first if the user does not specify a token ID.",
960
+ {},
961
+ async () => {
962
+ const { account, publicClient, config } = createClients();
963
+ const operator = account.address.toLowerCase();
964
+ const nfaAddr = config.nfa;
965
+ const res = await fetch(`${DEFAULT_INDEXER}/api/agents`);
966
+ if (!res.ok) return { content: [{ type: "text", text: JSON.stringify({ error: `Indexer error: ${res.status}` }) }] };
967
+ const json = await res.json();
968
+ const agents = (json.items || []).filter((a) => !a.isTemplate && a.tokenId !== void 0);
969
+ if (agents.length === 0) {
970
+ return { content: [{ type: "text", text: JSON.stringify({ operator, agents: [], count: 0 }) }] };
971
+ }
972
+ const checks = await Promise.all(
973
+ agents.map(async (a) => {
974
+ const tokenId = BigInt(a.tokenId);
975
+ try {
976
+ const op = await publicClient.readContract({
977
+ address: nfaAddr,
978
+ abi: OPERATOR_OF_ABI,
979
+ functionName: "operatorOf",
980
+ args: [tokenId]
981
+ });
982
+ return op.toLowerCase() === operator ? {
983
+ tokenId: tokenId.toString(),
984
+ vault: a.account || "",
985
+ owner: a.owner || "",
986
+ agentType: a.agentType || "unknown"
987
+ } : null;
988
+ } catch {
989
+ return null;
990
+ }
991
+ })
992
+ );
993
+ const myAgents = checks.filter((c) => c !== null);
994
+ return {
995
+ content: [{
996
+ type: "text",
997
+ text: JSON.stringify({ operator, agents: myAgents, count: myAgents.length })
998
+ }]
999
+ };
1000
+ }
1001
+ );
1002
+ server.tool(
1003
+ "wrap",
1004
+ "Wrap BNB to WBNB in agent vault",
1005
+ {
1006
+ token_id: import_zod.z.string().describe("Agent NFA Token ID"),
1007
+ amount: import_zod.z.string().describe("BNB amount to wrap (human-readable, e.g. 0.1)")
1008
+ },
1009
+ async ({ token_id, amount }) => {
1010
+ const { policyClient } = createClients();
1011
+ const tokenId = BigInt(token_id);
1012
+ const amt = (0, import_viem2.parseEther)(amount);
1013
+ const data = (0, import_viem2.encodeFunctionData)({ abi: WBNB_ABI, functionName: "deposit" });
1014
+ const action = { target: WBNB, value: amt, data };
1015
+ const sim = await policyClient.validate(tokenId, action);
1016
+ if (!sim.ok) return { content: [{ type: "text", text: JSON.stringify({ status: "rejected", reason: sim.reason }) }] };
1017
+ const result = await policyClient.execute(tokenId, action, true);
1018
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", hash: result.hash, message: `Wrapped ${amount} BNB \u2192 WBNB` }) }] };
1019
+ }
1020
+ );
1021
+ server.tool(
1022
+ "unwrap",
1023
+ "Unwrap WBNB to BNB in agent vault",
1024
+ {
1025
+ token_id: import_zod.z.string().describe("Agent NFA Token ID"),
1026
+ amount: import_zod.z.string().describe("WBNB amount to unwrap (human-readable, e.g. 0.1)")
1027
+ },
1028
+ async ({ token_id, amount }) => {
1029
+ const { policyClient } = createClients();
1030
+ const tokenId = BigInt(token_id);
1031
+ const amt = (0, import_viem2.parseEther)(amount);
1032
+ const data = (0, import_viem2.encodeFunctionData)({ abi: WBNB_ABI, functionName: "withdraw", args: [amt] });
1033
+ const action = { target: WBNB, value: 0n, data };
1034
+ const sim = await policyClient.validate(tokenId, action);
1035
+ if (!sim.ok) return { content: [{ type: "text", text: JSON.stringify({ status: "rejected", reason: sim.reason }) }] };
1036
+ const result = await policyClient.execute(tokenId, action, true);
1037
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", hash: result.hash, message: `Unwrapped ${amount} WBNB \u2192 BNB` }) }] };
1038
+ }
1039
+ );
1040
+ server.tool(
1041
+ "search",
1042
+ "Search for a token by name or symbol on BSC via DexScreener",
1043
+ { query: import_zod.z.string().describe("Token name or symbol to search") },
1044
+ async ({ query }) => {
1045
+ const encoded = encodeURIComponent(query);
1046
+ const resp = await fetch(`https://api.dexscreener.com/latest/dex/search?q=${encoded}`, { signal: AbortSignal.timeout(8e3) });
1047
+ if (!resp.ok) return { content: [{ type: "text", text: JSON.stringify({ error: "DexScreener API error" }) }] };
1048
+ const data = await resp.json();
1049
+ const results = (data.pairs || []).filter((p) => p.chainId === "bsc").slice(0, 10).map((p) => ({
1050
+ symbol: p.baseToken.symbol,
1051
+ name: p.baseToken.name,
1052
+ address: p.baseToken.address,
1053
+ priceUsd: p.priceUsd,
1054
+ liquidity: p.liquidity?.usd || 0,
1055
+ volume24h: p.volume?.h24 || 0
1056
+ }));
1057
+ return { content: [{ type: "text", text: JSON.stringify({ results, count: results.length }) }] };
1058
+ }
1059
+ );
1060
+ server.tool(
1061
+ "tokens",
1062
+ "List all known token symbols and their BSC addresses",
1063
+ {},
1064
+ async () => {
1065
+ const tokens = Object.entries(TOKEN_LIST).map(([sym, info]) => ({
1066
+ symbol: sym,
1067
+ address: info.address,
1068
+ decimals: info.decimals
1069
+ }));
1070
+ return { content: [{ type: "text", text: JSON.stringify({ tokens, count: tokens.length }) }] };
1071
+ }
1072
+ );
1073
+ server.tool(
1074
+ "policies",
1075
+ "View all active policies and current risk settings for an agent",
1076
+ { token_id: import_zod.z.string().describe("Agent NFA Token ID") },
1077
+ async ({ token_id }) => {
1078
+ const { publicClient, policyClient } = createClients();
1079
+ const tokenId = BigInt(token_id);
1080
+ const policies = await policyClient.getPolicies(tokenId);
1081
+ const enriched = [];
1082
+ const summaryParts = [];
1083
+ for (const p of policies) {
1084
+ const entry = { name: p.policyTypeName, address: p.address, renterConfigurable: p.renterConfigurable };
1085
+ if (p.policyTypeName === "spending_limit") {
1086
+ try {
1087
+ const limits = await publicClient.readContract({ address: p.address, abi: SPENDING_LIMIT_ABI, functionName: "instanceLimits", args: [tokenId] });
1088
+ const [maxPerTx, maxPerDay, maxSlippageBps] = limits;
1089
+ const txBnb = (Number(maxPerTx) / 1e18).toFixed(4);
1090
+ const dayBnb = (Number(maxPerDay) / 1e18).toFixed(4);
1091
+ entry.currentConfig = { maxPerTx: maxPerTx.toString(), maxPerTxBnb: txBnb, maxPerDay: maxPerDay.toString(), maxPerDayBnb: dayBnb, maxSlippageBps: maxSlippageBps.toString() };
1092
+ summaryParts.push(`Max ${txBnb} BNB/tx, ${dayBnb} BNB/day, slippage ${maxSlippageBps}bps`);
1093
+ } catch {
1094
+ }
1095
+ }
1096
+ if (p.policyTypeName === "cooldown") {
1097
+ try {
1098
+ const cd = await publicClient.readContract({ address: p.address, abi: COOLDOWN_ABI, functionName: "cooldownSeconds", args: [tokenId] });
1099
+ const secs = Number(cd);
1100
+ entry.currentConfig = { cooldownSeconds: secs.toString() };
1101
+ summaryParts.push(`Cooldown ${secs}s between transactions`);
1102
+ } catch {
1103
+ }
1104
+ }
1105
+ if (p.policyTypeName === "receiver_guard") summaryParts.push("Outbound transfers restricted (ReceiverGuard)");
1106
+ if (p.policyTypeName === "dex_whitelist") summaryParts.push("Only whitelisted DEXs allowed");
1107
+ if (p.policyTypeName === "token_whitelist") summaryParts.push("Only whitelisted tokens allowed");
1108
+ if (p.policyTypeName === "defi_guard") summaryParts.push("DeFi interactions validated by DeFiGuard");
1109
+ enriched.push(entry);
1110
+ }
1111
+ const humanSummary = summaryParts.length > 0 ? summaryParts.join(" | ") : "No configurable policies found";
1112
+ return { content: [{ type: "text", text: JSON.stringify({ tokenId: token_id, humanSummary, securityNote: "Operator wallet CANNOT withdraw vault funds or transfer Agent NFT.", policies: enriched }) }] };
1113
+ }
1114
+ );
1115
+ server.tool(
1116
+ "status",
1117
+ "One-shot security overview: vault balance, operator status, policies, and recent activity",
1118
+ { token_id: import_zod.z.string().describe("Agent NFA Token ID") },
1119
+ async ({ token_id }) => {
1120
+ const { account, publicClient, policyClient } = createClients();
1121
+ const tokenId = BigInt(token_id);
1122
+ const vault = await policyClient.getVault(tokenId);
1123
+ const bnbBalance = await publicClient.getBalance({ address: vault });
1124
+ const bnbHuman = (Number(bnbBalance) / 1e18).toFixed(6);
1125
+ const opBalance = await publicClient.getBalance({ address: account.address });
1126
+ const opBnb = (Number(opBalance) / 1e18).toFixed(6);
1127
+ const operatorInfo = { address: account.address, gasBnb: opBnb, gasOk: Number(opBalance) > 1e15 };
1128
+ const policies = await policyClient.getPolicies(tokenId);
1129
+ const summaryParts = [];
1130
+ for (const p of policies) {
1131
+ if (p.policyTypeName === "spending_limit") {
1132
+ try {
1133
+ const limits = await publicClient.readContract({ address: p.address, abi: SPENDING_LIMIT_ABI, functionName: "instanceLimits", args: [tokenId] });
1134
+ const [maxPerTx, maxPerDay] = limits;
1135
+ summaryParts.push(`Max ${(Number(maxPerTx) / 1e18).toFixed(4)} BNB/tx, ${(Number(maxPerDay) / 1e18).toFixed(4)} BNB/day`);
1136
+ } catch {
1137
+ }
1138
+ }
1139
+ if (p.policyTypeName === "cooldown") {
1140
+ try {
1141
+ const cd = await publicClient.readContract({ address: p.address, abi: COOLDOWN_ABI, functionName: "cooldownSeconds", args: [tokenId] });
1142
+ summaryParts.push(`Cooldown ${Number(cd)}s`);
1143
+ } catch {
1144
+ }
1145
+ }
1146
+ if (p.policyTypeName === "receiver_guard") summaryParts.push("ReceiverGuard active");
1147
+ if (p.policyTypeName === "dex_whitelist") summaryParts.push("DEX whitelist active");
1148
+ if (p.policyTypeName === "token_whitelist") summaryParts.push("Token whitelist active");
1149
+ if (p.policyTypeName === "defi_guard") summaryParts.push("DeFiGuard active");
1150
+ }
1151
+ let activityStats = { available: false };
1152
+ try {
1153
+ const summaryRes = await fetch(`${DEFAULT_INDEXER}/api/agents/${token_id}/summary`, { signal: AbortSignal.timeout(8e3) });
1154
+ if (summaryRes.ok) {
1155
+ const summaryData = await summaryRes.json();
1156
+ activityStats = {
1157
+ available: true,
1158
+ totalExecutions: summaryData.totalExecutions,
1159
+ successRate: summaryData.totalExecutions > 0 ? `${(summaryData.successCount / summaryData.totalExecutions * 100).toFixed(1)}%` : "N/A",
1160
+ lastExecution: summaryData.lastExecution ? new Date(Number(summaryData.lastExecution) * 1e3).toISOString() : null
1161
+ };
1162
+ }
1163
+ } catch {
1164
+ }
1165
+ return {
1166
+ content: [{
1167
+ type: "text",
1168
+ text: JSON.stringify({
1169
+ tokenId: token_id,
1170
+ vault: { address: vault, bnbBalance: bnbHuman },
1171
+ operator: operatorInfo,
1172
+ securitySummary: summaryParts.length > 0 ? summaryParts.join(" | ") : "No policies found",
1173
+ policyCount: policies.length,
1174
+ activity: activityStats,
1175
+ securityNote: "Operator wallet CANNOT withdraw vault funds or transfer Agent NFT.",
1176
+ dashboardUrl: `https://shll.run/dashboard?tokenId=${token_id}`
1177
+ })
1178
+ }]
1179
+ };
1180
+ }
1181
+ );
1182
+ server.tool(
1183
+ "history",
1184
+ "Show recent transactions executed through the agent vault",
1185
+ {
1186
+ token_id: import_zod.z.string().describe("Agent NFA Token ID"),
1187
+ limit: import_zod.z.number().default(10).describe("Number of transactions to show")
1188
+ },
1189
+ async ({ token_id, limit }) => {
1190
+ const activityRes = await fetch(`${DEFAULT_INDEXER}/api/activity/${token_id}?limit=${limit}`, { signal: AbortSignal.timeout(1e4) });
1191
+ if (!activityRes.ok) return { content: [{ type: "text", text: JSON.stringify({ error: `Indexer returned ${activityRes.status}` }) }] };
1192
+ const data = await activityRes.json();
1193
+ let failures = [];
1194
+ try {
1195
+ const failRes = await fetch(`${DEFAULT_INDEXER}/api/agents/${token_id}/commit-failures?limit=5`, { signal: AbortSignal.timeout(8e3) });
1196
+ if (failRes.ok) {
1197
+ const failData = await failRes.json();
1198
+ failures = failData.items || [];
1199
+ }
1200
+ } catch {
1201
+ }
1202
+ const transactions = (data.items || []).map((tx) => ({
1203
+ time: new Date(Number(tx.timestamp) * 1e3).toISOString(),
1204
+ txHash: tx.txHash,
1205
+ target: tx.target,
1206
+ success: tx.success,
1207
+ bscscanUrl: `https://bscscan.com/tx/${tx.txHash}`
1208
+ }));
1209
+ return { content: [{ type: "text", text: JSON.stringify({ tokenId: token_id, transactions, totalShown: transactions.length, recentPolicyRejections: failures.length, policyRejections: failures.length > 0 ? failures : void 0 }) }] };
1210
+ }
1211
+ );
1212
+ server.tool(
1213
+ "config",
1214
+ "Configure risk parameters (spending limits, cooldown) for an agent. Only tightening is allowed.",
1215
+ {
1216
+ token_id: import_zod.z.string().describe("Agent NFA Token ID"),
1217
+ tx_limit: import_zod.z.string().optional().describe("Max BNB per transaction (human-readable, e.g. 0.5)"),
1218
+ daily_limit: import_zod.z.string().optional().describe("Max BNB per day (human-readable, e.g. 2.0)"),
1219
+ cooldown: import_zod.z.string().optional().describe("Minimum seconds between transactions (e.g. 60)")
1220
+ },
1221
+ async ({ token_id, tx_limit, daily_limit, cooldown }) => {
1222
+ if (!tx_limit && !daily_limit && !cooldown) {
1223
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Specify at least one: tx_limit, daily_limit, or cooldown" }) }] };
1224
+ }
1225
+ const { account, publicClient, policyClient, config } = createClients();
1226
+ const tokenId = BigInt(token_id);
1227
+ const walletClient = (0, import_viem2.createWalletClient)({ account, chain: import_chains2.bsc, transport: (0, import_viem2.http)(config.rpc) });
1228
+ const policies = await policyClient.getPolicies(tokenId);
1229
+ const results = [];
1230
+ if (tx_limit || daily_limit) {
1231
+ const spendingPolicy = policies.find((p) => p.policyTypeName === "spending_limit");
1232
+ if (!spendingPolicy) return { content: [{ type: "text", text: JSON.stringify({ error: "No SpendingLimitPolicy found" }) }] };
1233
+ const current = await publicClient.readContract({ address: spendingPolicy.address, abi: SPENDING_LIMIT_ABI, functionName: "instanceLimits", args: [tokenId] });
1234
+ const [curMaxPerTx, curMaxPerDay, curSlippage] = current;
1235
+ const newMaxPerTx = tx_limit ? (0, import_viem2.parseEther)(tx_limit) : curMaxPerTx;
1236
+ const newMaxPerDay = daily_limit ? (0, import_viem2.parseEther)(daily_limit) : curMaxPerDay;
1237
+ const hash = await walletClient.writeContract({ address: spendingPolicy.address, abi: SPENDING_LIMIT_ABI, functionName: "setLimits", args: [tokenId, newMaxPerTx, newMaxPerDay, curSlippage] });
1238
+ await publicClient.waitForTransactionReceipt({ hash });
1239
+ results.push(`SpendingLimit updated: ${hash}`);
1240
+ }
1241
+ if (cooldown) {
1242
+ const cooldownPolicy = policies.find((p) => p.policyTypeName === "cooldown");
1243
+ if (!cooldownPolicy) return { content: [{ type: "text", text: JSON.stringify({ error: "No CooldownPolicy found" }) }] };
1244
+ const seconds = BigInt(cooldown);
1245
+ const hash = await walletClient.writeContract({ address: cooldownPolicy.address, abi: COOLDOWN_ABI, functionName: "setCooldown", args: [tokenId, seconds] });
1246
+ await publicClient.waitForTransactionReceipt({ hash });
1247
+ results.push(`Cooldown updated: ${hash}`);
1248
+ }
1249
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Risk parameters updated", details: results }) }] };
1250
+ }
1251
+ );
1252
+ server.tool(
1253
+ "listings",
1254
+ "List all available agent templates for rent",
1255
+ {},
1256
+ async () => {
1257
+ const res = await fetch(`${DEFAULT_INDEXER}/api/listings`);
1258
+ if (!res.ok) return { content: [{ type: "text", text: JSON.stringify({ error: `Indexer returned ${res.status}` }) }] };
1259
+ const data = await res.json();
1260
+ const available = data.items.filter((l) => l.active);
1261
+ const listings = available.map((l) => ({
1262
+ listingId: l.id,
1263
+ name: l.agentName || "Unnamed Agent",
1264
+ type: l.agentType || "unknown",
1265
+ pricePerDayBNB: (Number(l.pricePerDay) / 1e18).toFixed(6),
1266
+ minDays: l.minDays,
1267
+ nfa: l.nfa
1268
+ }));
1269
+ return { content: [{ type: "text", text: JSON.stringify({ count: listings.length, listings, hint: "Use the listingId with setup_guide tool" }) }] };
1270
+ }
1271
+ );
1272
+ server.tool(
1273
+ "setup_guide",
1274
+ "Generate step-by-step dual-wallet onboarding instructions and shll.run/setup URL",
1275
+ {
1276
+ listing_id: import_zod.z.string().default(DEFAULT_LISTING_ID).describe("Template listing ID (bytes32 hex)"),
1277
+ days: import_zod.z.number().default(1).describe("Number of days to rent")
1278
+ },
1279
+ async ({ listing_id, days }) => {
1280
+ const { account, publicClient } = createClients();
1281
+ const operatorAddress = account.address;
1282
+ const listingId = listing_id;
1283
+ const daysToRent = days;
1284
+ let rentCost = "unknown";
1285
+ try {
1286
+ const listing = await publicClient.readContract({ address: DEFAULT_LISTING_MANAGER, abi: LISTING_MANAGER_ABI, functionName: "listings", args: [listingId] });
1287
+ const [, , , pricePerDay, minDays, active] = listing;
1288
+ if (!active) return { content: [{ type: "text", text: JSON.stringify({ error: "Listing is not active" }) }] };
1289
+ if (daysToRent < minDays) return { content: [{ type: "text", text: JSON.stringify({ error: `Minimum rental is ${minDays} days, you requested ${daysToRent}` }) }] };
1290
+ const totalRent = BigInt(pricePerDay) * BigInt(daysToRent);
1291
+ rentCost = `${(Number(totalRent) / 1e18).toFixed(6)} BNB`;
1292
+ } catch {
1293
+ rentCost = "unable to query \u2014 check listing_id";
1294
+ }
1295
+ const setupUrl = `https://shll.run/setup?operator=${operatorAddress}&listing=${encodeURIComponent(listingId)}&days=${daysToRent}`;
1296
+ return {
1297
+ content: [{
1298
+ type: "text",
1299
+ text: JSON.stringify({
1300
+ status: "guide",
1301
+ securityModel: "DUAL-WALLET: Your wallet (owner) stays offline. AI only uses the operator wallet, which CANNOT withdraw vault funds.",
1302
+ operatorAddress,
1303
+ setupUrl,
1304
+ rentCost,
1305
+ steps: [
1306
+ { step: 1, title: "Open SHLL Setup Page", action: `Open ${setupUrl} in your browser`, note: "Connect YOUR wallet (MetaMask). This is your owner wallet." },
1307
+ { step: 2, title: "Rent Agent", action: "Click 'Rent Agent' and confirm the transaction" },
1308
+ { step: 3, title: "Authorize Operator", action: "Click 'Authorize Operator'", note: `Operator address: ${operatorAddress}` },
1309
+ { step: 4, title: "Fund Vault (optional)", action: "Deposit BNB into the vault for trading" },
1310
+ { step: 5, title: "Tell AI your token-id", action: "Come back and tell the AI your token-id number." }
1311
+ ]
1312
+ })
1313
+ }]
1314
+ };
1315
+ }
1316
+ );
1317
+ server.tool(
1318
+ "generate_wallet",
1319
+ "Generate a new operator wallet (address + private key) for AI to use. This is a HOT wallet for trading only.",
1320
+ {},
1321
+ async () => {
1322
+ const pk = (0, import_accounts2.generatePrivateKey)();
1323
+ const account = (0, import_accounts2.privateKeyToAccount)(pk);
1324
+ return {
1325
+ content: [{
1326
+ type: "text",
1327
+ text: JSON.stringify({
1328
+ status: "success",
1329
+ address: account.address,
1330
+ privateKey: pk,
1331
+ note: "SAVE THIS PRIVATE KEY SECURELY. This is the OPERATOR wallet \u2014 it can only trade within PolicyGuard limits. It CANNOT withdraw vault funds. Send ~$1 of BNB here for gas fees, then set RUNNER_PRIVATE_KEY.",
1332
+ securityReminder: "Use a SEPARATE wallet as the owner to rent the agent and fund the vault. Use setup_guide tool for step-by-step instructions."
1333
+ })
1334
+ }]
1335
+ };
1336
+ }
1337
+ );
932
1338
  async function main() {
933
1339
  const transport = new import_stdio.StdioServerTransport();
934
1340
  await server.connect(transport);