shll-skills 5.1.1 → 5.2.1
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/mcp.js +94 -21
- package/dist/mcp.mjs +94 -21
- package/package.json +1 -1
- package/src/mcp.ts +82 -21
package/dist/mcp.js
CHANGED
|
@@ -641,9 +641,51 @@ function createClients() {
|
|
|
641
641
|
});
|
|
642
642
|
return { account, publicClient, policyClient, config };
|
|
643
643
|
}
|
|
644
|
+
var AGENT_NFA_EXPIRY_ABI = [
|
|
645
|
+
{ name: "operatorExpiresOf", type: "function", stateMutability: "view", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
|
|
646
|
+
{ name: "userExpires", type: "function", stateMutability: "view", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] }
|
|
647
|
+
];
|
|
648
|
+
async function checkAgentExpiry(tokenId) {
|
|
649
|
+
const config = getConfig();
|
|
650
|
+
const pc = (0, import_viem2.createPublicClient)({ chain: import_chains2.bsc, transport: (0, import_viem2.http)(config.rpc) });
|
|
651
|
+
const [operatorExpires, userExpires] = await Promise.all([
|
|
652
|
+
pc.readContract({ address: config.nfa, abi: AGENT_NFA_EXPIRY_ABI, functionName: "operatorExpiresOf", args: [tokenId] }),
|
|
653
|
+
pc.readContract({ address: config.nfa, abi: AGENT_NFA_EXPIRY_ABI, functionName: "userExpires", args: [tokenId] })
|
|
654
|
+
]);
|
|
655
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
656
|
+
if (now > operatorExpires) {
|
|
657
|
+
return {
|
|
658
|
+
expired: true,
|
|
659
|
+
content: [{
|
|
660
|
+
type: "text",
|
|
661
|
+
text: JSON.stringify({
|
|
662
|
+
status: "error",
|
|
663
|
+
message: `Agent token-id ${tokenId} operator authorization has EXPIRED (expired at ${new Date(Number(operatorExpires) * 1e3).toISOString()}). Please renew at https://shll.run/me or use a different token-id.`,
|
|
664
|
+
expiredAt: new Date(Number(operatorExpires) * 1e3).toISOString(),
|
|
665
|
+
action: "renew"
|
|
666
|
+
})
|
|
667
|
+
}]
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
if (now > userExpires) {
|
|
671
|
+
return {
|
|
672
|
+
expired: true,
|
|
673
|
+
content: [{
|
|
674
|
+
type: "text",
|
|
675
|
+
text: JSON.stringify({
|
|
676
|
+
status: "error",
|
|
677
|
+
message: `Agent token-id ${tokenId} rental has EXPIRED (expired at ${new Date(Number(userExpires) * 1e3).toISOString()}). Please renew at https://shll.run/me or use a different token-id.`,
|
|
678
|
+
expiredAt: new Date(Number(userExpires) * 1e3).toISOString(),
|
|
679
|
+
action: "renew"
|
|
680
|
+
})
|
|
681
|
+
}]
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
return { expired: false };
|
|
685
|
+
}
|
|
644
686
|
var server = new import_mcp.McpServer({
|
|
645
687
|
name: "shll-defi",
|
|
646
|
-
version: "5.
|
|
688
|
+
version: "5.2.0"
|
|
647
689
|
});
|
|
648
690
|
server.tool(
|
|
649
691
|
"portfolio",
|
|
@@ -741,6 +783,8 @@ server.tool(
|
|
|
741
783
|
async ({ token_id, from, to, amount, dex, slippage }) => {
|
|
742
784
|
const { publicClient, policyClient } = createClients();
|
|
743
785
|
const tokenId = BigInt(token_id);
|
|
786
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
787
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
744
788
|
const vault = await policyClient.getVault(tokenId);
|
|
745
789
|
const fromToken = resolveToken(from);
|
|
746
790
|
const toToken = resolveToken(to);
|
|
@@ -834,6 +878,8 @@ server.tool(
|
|
|
834
878
|
async ({ token_id, token, amount }) => {
|
|
835
879
|
const { publicClient, policyClient } = createClients();
|
|
836
880
|
const tokenId = BigInt(token_id);
|
|
881
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
882
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
837
883
|
const symbol = token.toUpperCase();
|
|
838
884
|
const vTokenAddr = VENUS_VTOKENS[symbol];
|
|
839
885
|
if (!vTokenAddr) return { content: [{ type: "text", text: JSON.stringify({ error: `Unsupported: ${symbol}. Use: ${Object.keys(VENUS_VTOKENS).join(", ")}` }) }] };
|
|
@@ -870,6 +916,8 @@ server.tool(
|
|
|
870
916
|
async ({ token_id, token, amount }) => {
|
|
871
917
|
const { policyClient } = createClients();
|
|
872
918
|
const tokenId = BigInt(token_id);
|
|
919
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
920
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
873
921
|
const symbol = token.toUpperCase();
|
|
874
922
|
const vTokenAddr = VENUS_VTOKENS[symbol];
|
|
875
923
|
if (!vTokenAddr) return { content: [{ type: "text", text: JSON.stringify({ error: `Unsupported: ${symbol}` }) }] };
|
|
@@ -926,6 +974,8 @@ server.tool(
|
|
|
926
974
|
async ({ token_id, token, amount, to }) => {
|
|
927
975
|
const { policyClient } = createClients();
|
|
928
976
|
const tokenId = BigInt(token_id);
|
|
977
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
978
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
929
979
|
const tokenInfo = resolveToken(token);
|
|
930
980
|
const amt = parseAmount(amount, tokenInfo.decimals);
|
|
931
981
|
const recipient = to;
|
|
@@ -946,17 +996,14 @@ server.tool(
|
|
|
946
996
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", hash: result.hash, token, amount, to: recipient }) }] };
|
|
947
997
|
}
|
|
948
998
|
);
|
|
949
|
-
var
|
|
950
|
-
type: "function",
|
|
951
|
-
name: "
|
|
952
|
-
|
|
953
|
-
outputs: [{ name: "", type: "address" }],
|
|
954
|
-
stateMutability: "view"
|
|
955
|
-
}];
|
|
999
|
+
var MY_AGENTS_ABI = [
|
|
1000
|
+
{ type: "function", name: "operatorOf", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "address" }], stateMutability: "view" },
|
|
1001
|
+
{ type: "function", name: "operatorExpiresOf", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "uint256" }], stateMutability: "view" }
|
|
1002
|
+
];
|
|
956
1003
|
var DEFAULT_INDEXER = "https://indexer-mainnet.shll.run";
|
|
957
1004
|
server.tool(
|
|
958
1005
|
"my_agents",
|
|
959
|
-
"List all agents where the current operator key is authorized. Returns
|
|
1006
|
+
"List all agents where the current operator key is or was authorized. Returns active agents and expired agents that need renewal.",
|
|
960
1007
|
{},
|
|
961
1008
|
async () => {
|
|
962
1009
|
const { account, publicClient, config } = createClients();
|
|
@@ -973,18 +1020,34 @@ server.tool(
|
|
|
973
1020
|
agents.map(async (a) => {
|
|
974
1021
|
const tokenId = BigInt(a.tokenId);
|
|
975
1022
|
try {
|
|
976
|
-
const op = await
|
|
977
|
-
address: nfaAddr,
|
|
978
|
-
abi:
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1023
|
+
const [op, opExpires] = await Promise.all([
|
|
1024
|
+
publicClient.readContract({ address: nfaAddr, abi: MY_AGENTS_ABI, functionName: "operatorOf", args: [tokenId] }),
|
|
1025
|
+
publicClient.readContract({ address: nfaAddr, abi: MY_AGENTS_ABI, functionName: "operatorExpiresOf", args: [tokenId] })
|
|
1026
|
+
]);
|
|
1027
|
+
const isActive = op.toLowerCase() === operator;
|
|
1028
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
1029
|
+
const isExpired = !isActive && Number(opExpires) > 0 && now > opExpires;
|
|
1030
|
+
if (isActive) {
|
|
1031
|
+
return {
|
|
1032
|
+
tokenId: tokenId.toString(),
|
|
1033
|
+
vault: a.account || "",
|
|
1034
|
+
owner: a.owner || "",
|
|
1035
|
+
agentType: a.agentType || "unknown",
|
|
1036
|
+
status: "active",
|
|
1037
|
+
operatorExpires: new Date(Number(opExpires) * 1e3).toISOString()
|
|
1038
|
+
};
|
|
1039
|
+
} else if (isExpired) {
|
|
1040
|
+
return {
|
|
1041
|
+
tokenId: tokenId.toString(),
|
|
1042
|
+
vault: a.account || "",
|
|
1043
|
+
owner: a.owner || "",
|
|
1044
|
+
agentType: a.agentType || "unknown",
|
|
1045
|
+
status: "expired",
|
|
1046
|
+
operatorExpires: new Date(Number(opExpires) * 1e3).toISOString(),
|
|
1047
|
+
note: "Operator authorization expired. Renew at https://shll.run/me"
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
return null;
|
|
988
1051
|
} catch {
|
|
989
1052
|
return null;
|
|
990
1053
|
}
|
|
@@ -1009,6 +1072,8 @@ server.tool(
|
|
|
1009
1072
|
async ({ token_id, amount }) => {
|
|
1010
1073
|
const { policyClient } = createClients();
|
|
1011
1074
|
const tokenId = BigInt(token_id);
|
|
1075
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
1076
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
1012
1077
|
const amt = (0, import_viem2.parseEther)(amount);
|
|
1013
1078
|
const data = (0, import_viem2.encodeFunctionData)({ abi: WBNB_ABI, functionName: "deposit" });
|
|
1014
1079
|
const action = { target: WBNB, value: amt, data };
|
|
@@ -1028,6 +1093,8 @@ server.tool(
|
|
|
1028
1093
|
async ({ token_id, amount }) => {
|
|
1029
1094
|
const { policyClient } = createClients();
|
|
1030
1095
|
const tokenId = BigInt(token_id);
|
|
1096
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
1097
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
1031
1098
|
const amt = (0, import_viem2.parseEther)(amount);
|
|
1032
1099
|
const data = (0, import_viem2.encodeFunctionData)({ abi: WBNB_ABI, functionName: "withdraw", args: [amt] });
|
|
1033
1100
|
const action = { target: WBNB, value: 0n, data };
|
|
@@ -1224,6 +1291,8 @@ server.tool(
|
|
|
1224
1291
|
}
|
|
1225
1292
|
const { account, publicClient, policyClient, config } = createClients();
|
|
1226
1293
|
const tokenId = BigInt(token_id);
|
|
1294
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
1295
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
1227
1296
|
const walletClient = (0, import_viem2.createWalletClient)({ account, chain: import_chains2.bsc, transport: (0, import_viem2.http)(config.rpc) });
|
|
1228
1297
|
const policies = await policyClient.getPolicies(tokenId);
|
|
1229
1298
|
const results = [];
|
|
@@ -1326,6 +1395,8 @@ server.tool(
|
|
|
1326
1395
|
async ({ token_id, target, data, value }) => {
|
|
1327
1396
|
const { policyClient } = createClients();
|
|
1328
1397
|
const tokenId = BigInt(token_id);
|
|
1398
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
1399
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
1329
1400
|
const action = {
|
|
1330
1401
|
target,
|
|
1331
1402
|
value: BigInt(value),
|
|
@@ -1371,6 +1442,8 @@ server.tool(
|
|
|
1371
1442
|
async ({ token_id, actions: rawActions }) => {
|
|
1372
1443
|
const { policyClient } = createClients();
|
|
1373
1444
|
const tokenId = BigInt(token_id);
|
|
1445
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
1446
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
1374
1447
|
const actions = rawActions.map((a) => ({
|
|
1375
1448
|
target: a.target,
|
|
1376
1449
|
value: BigInt(a.value || "0"),
|
package/dist/mcp.mjs
CHANGED
|
@@ -152,9 +152,51 @@ function createClients() {
|
|
|
152
152
|
});
|
|
153
153
|
return { account, publicClient, policyClient, config };
|
|
154
154
|
}
|
|
155
|
+
var AGENT_NFA_EXPIRY_ABI = [
|
|
156
|
+
{ name: "operatorExpiresOf", type: "function", stateMutability: "view", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
|
|
157
|
+
{ name: "userExpires", type: "function", stateMutability: "view", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] }
|
|
158
|
+
];
|
|
159
|
+
async function checkAgentExpiry(tokenId) {
|
|
160
|
+
const config = getConfig();
|
|
161
|
+
const pc = createPublicClient({ chain: bsc, transport: http(config.rpc) });
|
|
162
|
+
const [operatorExpires, userExpires] = await Promise.all([
|
|
163
|
+
pc.readContract({ address: config.nfa, abi: AGENT_NFA_EXPIRY_ABI, functionName: "operatorExpiresOf", args: [tokenId] }),
|
|
164
|
+
pc.readContract({ address: config.nfa, abi: AGENT_NFA_EXPIRY_ABI, functionName: "userExpires", args: [tokenId] })
|
|
165
|
+
]);
|
|
166
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
167
|
+
if (now > operatorExpires) {
|
|
168
|
+
return {
|
|
169
|
+
expired: true,
|
|
170
|
+
content: [{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: JSON.stringify({
|
|
173
|
+
status: "error",
|
|
174
|
+
message: `Agent token-id ${tokenId} operator authorization has EXPIRED (expired at ${new Date(Number(operatorExpires) * 1e3).toISOString()}). Please renew at https://shll.run/me or use a different token-id.`,
|
|
175
|
+
expiredAt: new Date(Number(operatorExpires) * 1e3).toISOString(),
|
|
176
|
+
action: "renew"
|
|
177
|
+
})
|
|
178
|
+
}]
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (now > userExpires) {
|
|
182
|
+
return {
|
|
183
|
+
expired: true,
|
|
184
|
+
content: [{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: JSON.stringify({
|
|
187
|
+
status: "error",
|
|
188
|
+
message: `Agent token-id ${tokenId} rental has EXPIRED (expired at ${new Date(Number(userExpires) * 1e3).toISOString()}). Please renew at https://shll.run/me or use a different token-id.`,
|
|
189
|
+
expiredAt: new Date(Number(userExpires) * 1e3).toISOString(),
|
|
190
|
+
action: "renew"
|
|
191
|
+
})
|
|
192
|
+
}]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return { expired: false };
|
|
196
|
+
}
|
|
155
197
|
var server = new McpServer({
|
|
156
198
|
name: "shll-defi",
|
|
157
|
-
version: "5.
|
|
199
|
+
version: "5.2.0"
|
|
158
200
|
});
|
|
159
201
|
server.tool(
|
|
160
202
|
"portfolio",
|
|
@@ -252,6 +294,8 @@ server.tool(
|
|
|
252
294
|
async ({ token_id, from, to, amount, dex, slippage }) => {
|
|
253
295
|
const { publicClient, policyClient } = createClients();
|
|
254
296
|
const tokenId = BigInt(token_id);
|
|
297
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
298
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
255
299
|
const vault = await policyClient.getVault(tokenId);
|
|
256
300
|
const fromToken = resolveToken(from);
|
|
257
301
|
const toToken = resolveToken(to);
|
|
@@ -345,6 +389,8 @@ server.tool(
|
|
|
345
389
|
async ({ token_id, token, amount }) => {
|
|
346
390
|
const { publicClient, policyClient } = createClients();
|
|
347
391
|
const tokenId = BigInt(token_id);
|
|
392
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
393
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
348
394
|
const symbol = token.toUpperCase();
|
|
349
395
|
const vTokenAddr = VENUS_VTOKENS[symbol];
|
|
350
396
|
if (!vTokenAddr) return { content: [{ type: "text", text: JSON.stringify({ error: `Unsupported: ${symbol}. Use: ${Object.keys(VENUS_VTOKENS).join(", ")}` }) }] };
|
|
@@ -381,6 +427,8 @@ server.tool(
|
|
|
381
427
|
async ({ token_id, token, amount }) => {
|
|
382
428
|
const { policyClient } = createClients();
|
|
383
429
|
const tokenId = BigInt(token_id);
|
|
430
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
431
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
384
432
|
const symbol = token.toUpperCase();
|
|
385
433
|
const vTokenAddr = VENUS_VTOKENS[symbol];
|
|
386
434
|
if (!vTokenAddr) return { content: [{ type: "text", text: JSON.stringify({ error: `Unsupported: ${symbol}` }) }] };
|
|
@@ -437,6 +485,8 @@ server.tool(
|
|
|
437
485
|
async ({ token_id, token, amount, to }) => {
|
|
438
486
|
const { policyClient } = createClients();
|
|
439
487
|
const tokenId = BigInt(token_id);
|
|
488
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
489
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
440
490
|
const tokenInfo = resolveToken(token);
|
|
441
491
|
const amt = parseAmount(amount, tokenInfo.decimals);
|
|
442
492
|
const recipient = to;
|
|
@@ -457,17 +507,14 @@ server.tool(
|
|
|
457
507
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", hash: result.hash, token, amount, to: recipient }) }] };
|
|
458
508
|
}
|
|
459
509
|
);
|
|
460
|
-
var
|
|
461
|
-
type: "function",
|
|
462
|
-
name: "
|
|
463
|
-
|
|
464
|
-
outputs: [{ name: "", type: "address" }],
|
|
465
|
-
stateMutability: "view"
|
|
466
|
-
}];
|
|
510
|
+
var MY_AGENTS_ABI = [
|
|
511
|
+
{ type: "function", name: "operatorOf", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "address" }], stateMutability: "view" },
|
|
512
|
+
{ type: "function", name: "operatorExpiresOf", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "uint256" }], stateMutability: "view" }
|
|
513
|
+
];
|
|
467
514
|
var DEFAULT_INDEXER = "https://indexer-mainnet.shll.run";
|
|
468
515
|
server.tool(
|
|
469
516
|
"my_agents",
|
|
470
|
-
"List all agents where the current operator key is authorized. Returns
|
|
517
|
+
"List all agents where the current operator key is or was authorized. Returns active agents and expired agents that need renewal.",
|
|
471
518
|
{},
|
|
472
519
|
async () => {
|
|
473
520
|
const { account, publicClient, config } = createClients();
|
|
@@ -484,18 +531,34 @@ server.tool(
|
|
|
484
531
|
agents.map(async (a) => {
|
|
485
532
|
const tokenId = BigInt(a.tokenId);
|
|
486
533
|
try {
|
|
487
|
-
const op = await
|
|
488
|
-
address: nfaAddr,
|
|
489
|
-
abi:
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
534
|
+
const [op, opExpires] = await Promise.all([
|
|
535
|
+
publicClient.readContract({ address: nfaAddr, abi: MY_AGENTS_ABI, functionName: "operatorOf", args: [tokenId] }),
|
|
536
|
+
publicClient.readContract({ address: nfaAddr, abi: MY_AGENTS_ABI, functionName: "operatorExpiresOf", args: [tokenId] })
|
|
537
|
+
]);
|
|
538
|
+
const isActive = op.toLowerCase() === operator;
|
|
539
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
540
|
+
const isExpired = !isActive && Number(opExpires) > 0 && now > opExpires;
|
|
541
|
+
if (isActive) {
|
|
542
|
+
return {
|
|
543
|
+
tokenId: tokenId.toString(),
|
|
544
|
+
vault: a.account || "",
|
|
545
|
+
owner: a.owner || "",
|
|
546
|
+
agentType: a.agentType || "unknown",
|
|
547
|
+
status: "active",
|
|
548
|
+
operatorExpires: new Date(Number(opExpires) * 1e3).toISOString()
|
|
549
|
+
};
|
|
550
|
+
} else if (isExpired) {
|
|
551
|
+
return {
|
|
552
|
+
tokenId: tokenId.toString(),
|
|
553
|
+
vault: a.account || "",
|
|
554
|
+
owner: a.owner || "",
|
|
555
|
+
agentType: a.agentType || "unknown",
|
|
556
|
+
status: "expired",
|
|
557
|
+
operatorExpires: new Date(Number(opExpires) * 1e3).toISOString(),
|
|
558
|
+
note: "Operator authorization expired. Renew at https://shll.run/me"
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
499
562
|
} catch {
|
|
500
563
|
return null;
|
|
501
564
|
}
|
|
@@ -520,6 +583,8 @@ server.tool(
|
|
|
520
583
|
async ({ token_id, amount }) => {
|
|
521
584
|
const { policyClient } = createClients();
|
|
522
585
|
const tokenId = BigInt(token_id);
|
|
586
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
587
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
523
588
|
const amt = parseEther(amount);
|
|
524
589
|
const data = encodeFunctionData({ abi: WBNB_ABI, functionName: "deposit" });
|
|
525
590
|
const action = { target: WBNB, value: amt, data };
|
|
@@ -539,6 +604,8 @@ server.tool(
|
|
|
539
604
|
async ({ token_id, amount }) => {
|
|
540
605
|
const { policyClient } = createClients();
|
|
541
606
|
const tokenId = BigInt(token_id);
|
|
607
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
608
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
542
609
|
const amt = parseEther(amount);
|
|
543
610
|
const data = encodeFunctionData({ abi: WBNB_ABI, functionName: "withdraw", args: [amt] });
|
|
544
611
|
const action = { target: WBNB, value: 0n, data };
|
|
@@ -735,6 +802,8 @@ server.tool(
|
|
|
735
802
|
}
|
|
736
803
|
const { account, publicClient, policyClient, config } = createClients();
|
|
737
804
|
const tokenId = BigInt(token_id);
|
|
805
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
806
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
738
807
|
const walletClient = createWalletClient({ account, chain: bsc, transport: http(config.rpc) });
|
|
739
808
|
const policies = await policyClient.getPolicies(tokenId);
|
|
740
809
|
const results = [];
|
|
@@ -837,6 +906,8 @@ server.tool(
|
|
|
837
906
|
async ({ token_id, target, data, value }) => {
|
|
838
907
|
const { policyClient } = createClients();
|
|
839
908
|
const tokenId = BigInt(token_id);
|
|
909
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
910
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
840
911
|
const action = {
|
|
841
912
|
target,
|
|
842
913
|
value: BigInt(value),
|
|
@@ -882,6 +953,8 @@ server.tool(
|
|
|
882
953
|
async ({ token_id, actions: rawActions }) => {
|
|
883
954
|
const { policyClient } = createClients();
|
|
884
955
|
const tokenId = BigInt(token_id);
|
|
956
|
+
const expiryCheck = await checkAgentExpiry(tokenId);
|
|
957
|
+
if (expiryCheck.expired) return { content: expiryCheck.content };
|
|
885
958
|
const actions = rawActions.map((a) => ({
|
|
886
959
|
target: a.target,
|
|
887
960
|
value: BigInt(a.value || "0"),
|
package/package.json
CHANGED
package/src/mcp.ts
CHANGED
|
@@ -191,13 +191,56 @@ function createClients() {
|
|
|
191
191
|
return { account, publicClient, policyClient, config };
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
// Expiry pre-check: prevents write operations on expired agents with clear error
|
|
195
|
+
const AGENT_NFA_EXPIRY_ABI = [
|
|
196
|
+
{ name: "operatorExpiresOf", type: "function" as const, stateMutability: "view" as const, inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
|
|
197
|
+
{ name: "userExpires", type: "function" as const, stateMutability: "view" as const, inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
|
|
198
|
+
] as const;
|
|
199
|
+
|
|
200
|
+
async function checkAgentExpiry(tokenId: bigint) {
|
|
201
|
+
const config = getConfig();
|
|
202
|
+
const pc = createPublicClient({ chain: bsc, transport: http(config.rpc) });
|
|
203
|
+
const [operatorExpires, userExpires] = await Promise.all([
|
|
204
|
+
pc.readContract({ address: config.nfa as Address, abi: AGENT_NFA_EXPIRY_ABI, functionName: "operatorExpiresOf", args: [tokenId] }) as Promise<bigint>,
|
|
205
|
+
pc.readContract({ address: config.nfa as Address, abi: AGENT_NFA_EXPIRY_ABI, functionName: "userExpires", args: [tokenId] }) as Promise<bigint>,
|
|
206
|
+
]);
|
|
207
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
208
|
+
if (now > operatorExpires) {
|
|
209
|
+
return {
|
|
210
|
+
expired: true,
|
|
211
|
+
content: [{
|
|
212
|
+
type: "text" as const, text: JSON.stringify({
|
|
213
|
+
status: "error",
|
|
214
|
+
message: `Agent token-id ${tokenId} operator authorization has EXPIRED (expired at ${new Date(Number(operatorExpires) * 1000).toISOString()}). Please renew at https://shll.run/me or use a different token-id.`,
|
|
215
|
+
expiredAt: new Date(Number(operatorExpires) * 1000).toISOString(),
|
|
216
|
+
action: "renew",
|
|
217
|
+
})
|
|
218
|
+
}],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (now > userExpires) {
|
|
222
|
+
return {
|
|
223
|
+
expired: true,
|
|
224
|
+
content: [{
|
|
225
|
+
type: "text" as const, text: JSON.stringify({
|
|
226
|
+
status: "error",
|
|
227
|
+
message: `Agent token-id ${tokenId} rental has EXPIRED (expired at ${new Date(Number(userExpires) * 1000).toISOString()}). Please renew at https://shll.run/me or use a different token-id.`,
|
|
228
|
+
expiredAt: new Date(Number(userExpires) * 1000).toISOString(),
|
|
229
|
+
action: "renew",
|
|
230
|
+
})
|
|
231
|
+
}],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return { expired: false };
|
|
235
|
+
}
|
|
236
|
+
|
|
194
237
|
// ═══════════════════════════════════════════════════════
|
|
195
238
|
// MCP Server
|
|
196
239
|
// ═══════════════════════════════════════════════════════
|
|
197
240
|
|
|
198
241
|
const server = new McpServer({
|
|
199
242
|
name: "shll-defi",
|
|
200
|
-
version: "5.
|
|
243
|
+
version: "5.2.0",
|
|
201
244
|
});
|
|
202
245
|
|
|
203
246
|
// ── Tool: portfolio ─────────────────────────────────────
|
|
@@ -306,6 +349,7 @@ server.tool(
|
|
|
306
349
|
async ({ token_id, from, to, amount, dex, slippage }) => {
|
|
307
350
|
const { publicClient, policyClient } = createClients();
|
|
308
351
|
const tokenId = BigInt(token_id);
|
|
352
|
+
const expiryCheck = await checkAgentExpiry(tokenId); if (expiryCheck.expired) return { content: expiryCheck.content! };
|
|
309
353
|
const vault = await policyClient.getVault(tokenId);
|
|
310
354
|
|
|
311
355
|
const fromToken = resolveToken(from);
|
|
@@ -410,6 +454,7 @@ server.tool(
|
|
|
410
454
|
async ({ token_id, token, amount }) => {
|
|
411
455
|
const { publicClient, policyClient } = createClients();
|
|
412
456
|
const tokenId = BigInt(token_id);
|
|
457
|
+
const expiryCheck = await checkAgentExpiry(tokenId); if (expiryCheck.expired) return { content: expiryCheck.content! };
|
|
413
458
|
const symbol = token.toUpperCase();
|
|
414
459
|
const vTokenAddr = VENUS_VTOKENS[symbol];
|
|
415
460
|
if (!vTokenAddr) return { content: [{ type: "text" as const, text: JSON.stringify({ error: `Unsupported: ${symbol}. Use: ${Object.keys(VENUS_VTOKENS).join(", ")}` }) }] };
|
|
@@ -455,6 +500,7 @@ server.tool(
|
|
|
455
500
|
async ({ token_id, token, amount }) => {
|
|
456
501
|
const { policyClient } = createClients();
|
|
457
502
|
const tokenId = BigInt(token_id);
|
|
503
|
+
const expiryCheck = await checkAgentExpiry(tokenId); if (expiryCheck.expired) return { content: expiryCheck.content! };
|
|
458
504
|
const symbol = token.toUpperCase();
|
|
459
505
|
const vTokenAddr = VENUS_VTOKENS[symbol];
|
|
460
506
|
if (!vTokenAddr) return { content: [{ type: "text" as const, text: JSON.stringify({ error: `Unsupported: ${symbol}` }) }] };
|
|
@@ -519,6 +565,7 @@ server.tool(
|
|
|
519
565
|
async ({ token_id, token, amount, to }) => {
|
|
520
566
|
const { policyClient } = createClients();
|
|
521
567
|
const tokenId = BigInt(token_id);
|
|
568
|
+
const expiryCheck = await checkAgentExpiry(tokenId); if (expiryCheck.expired) return { content: expiryCheck.content! };
|
|
522
569
|
const tokenInfo = resolveToken(token);
|
|
523
570
|
const amt = parseAmount(amount, tokenInfo.decimals);
|
|
524
571
|
const recipient = to as Address;
|
|
@@ -546,18 +593,16 @@ server.tool(
|
|
|
546
593
|
);
|
|
547
594
|
|
|
548
595
|
// ── Tool: my_agents ─────────────────────────────────────
|
|
549
|
-
const
|
|
550
|
-
type: "function" as const, name: "operatorOf",
|
|
551
|
-
inputs: [{ name: "tokenId", type: "uint256" }],
|
|
552
|
-
|
|
553
|
-
stateMutability: "view" as const,
|
|
554
|
-
}] as const;
|
|
596
|
+
const MY_AGENTS_ABI = [
|
|
597
|
+
{ type: "function" as const, name: "operatorOf", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "address" }], stateMutability: "view" as const },
|
|
598
|
+
{ type: "function" as const, name: "operatorExpiresOf", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "uint256" }], stateMutability: "view" as const },
|
|
599
|
+
] as const;
|
|
555
600
|
|
|
556
601
|
const DEFAULT_INDEXER = "https://indexer-mainnet.shll.run";
|
|
557
602
|
|
|
558
603
|
server.tool(
|
|
559
604
|
"my_agents",
|
|
560
|
-
"List all agents where the current operator key is authorized. Returns
|
|
605
|
+
"List all agents where the current operator key is or was authorized. Returns active agents and expired agents that need renewal.",
|
|
561
606
|
{},
|
|
562
607
|
async () => {
|
|
563
608
|
const { account, publicClient, config } = createClients();
|
|
@@ -574,23 +619,34 @@ server.tool(
|
|
|
574
619
|
return { content: [{ type: "text" as const, text: JSON.stringify({ operator, agents: [], count: 0 }) }] };
|
|
575
620
|
}
|
|
576
621
|
|
|
577
|
-
// 2.
|
|
622
|
+
// 2. Check operatorOf AND operatorExpiresOf for all agents
|
|
578
623
|
const checks = await Promise.all(
|
|
579
624
|
agents.map(async (a) => {
|
|
580
625
|
const tokenId = BigInt(a.tokenId!);
|
|
581
626
|
try {
|
|
582
|
-
const op = await
|
|
583
|
-
address: nfaAddr,
|
|
584
|
-
abi:
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
627
|
+
const [op, opExpires] = await Promise.all([
|
|
628
|
+
publicClient.readContract({ address: nfaAddr, abi: MY_AGENTS_ABI, functionName: "operatorOf", args: [tokenId] }) as Promise<string>,
|
|
629
|
+
publicClient.readContract({ address: nfaAddr, abi: MY_AGENTS_ABI, functionName: "operatorExpiresOf", args: [tokenId] }) as Promise<bigint>,
|
|
630
|
+
]);
|
|
631
|
+
const isActive = op.toLowerCase() === operator;
|
|
632
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
633
|
+
const isExpired = !isActive && Number(opExpires) > 0 && now > opExpires;
|
|
634
|
+
|
|
635
|
+
if (isActive) {
|
|
636
|
+
return {
|
|
637
|
+
tokenId: tokenId.toString(), vault: a.account || "", owner: a.owner || "",
|
|
638
|
+
agentType: a.agentType || "unknown", status: "active" as const,
|
|
639
|
+
operatorExpires: new Date(Number(opExpires) * 1000).toISOString(),
|
|
640
|
+
};
|
|
641
|
+
} else if (isExpired) {
|
|
642
|
+
return {
|
|
643
|
+
tokenId: tokenId.toString(), vault: a.account || "", owner: a.owner || "",
|
|
644
|
+
agentType: a.agentType || "unknown", status: "expired" as const,
|
|
645
|
+
operatorExpires: new Date(Number(opExpires) * 1000).toISOString(),
|
|
646
|
+
note: "Operator authorization expired. Renew at https://shll.run/me",
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
return null;
|
|
594
650
|
} catch { return null; }
|
|
595
651
|
})
|
|
596
652
|
);
|
|
@@ -616,6 +672,7 @@ server.tool(
|
|
|
616
672
|
async ({ token_id, amount }) => {
|
|
617
673
|
const { policyClient } = createClients();
|
|
618
674
|
const tokenId = BigInt(token_id);
|
|
675
|
+
const expiryCheck = await checkAgentExpiry(tokenId); if (expiryCheck.expired) return { content: expiryCheck.content! };
|
|
619
676
|
const amt = parseEther(amount);
|
|
620
677
|
const data = encodeFunctionData({ abi: WBNB_ABI, functionName: "deposit" });
|
|
621
678
|
const action: Action = { target: WBNB as Address, value: amt, data };
|
|
@@ -639,6 +696,7 @@ server.tool(
|
|
|
639
696
|
async ({ token_id, amount }) => {
|
|
640
697
|
const { policyClient } = createClients();
|
|
641
698
|
const tokenId = BigInt(token_id);
|
|
699
|
+
const expiryCheck = await checkAgentExpiry(tokenId); if (expiryCheck.expired) return { content: expiryCheck.content! };
|
|
642
700
|
const amt = parseEther(amount);
|
|
643
701
|
const data = encodeFunctionData({ abi: WBNB_ABI, functionName: "withdraw", args: [amt] });
|
|
644
702
|
const action: Action = { target: WBNB as Address, value: 0n, data };
|
|
@@ -862,6 +920,7 @@ server.tool(
|
|
|
862
920
|
|
|
863
921
|
const { account, publicClient, policyClient, config } = createClients();
|
|
864
922
|
const tokenId = BigInt(token_id);
|
|
923
|
+
const expiryCheck = await checkAgentExpiry(tokenId); if (expiryCheck.expired) return { content: expiryCheck.content! };
|
|
865
924
|
const walletClient = createWalletClient({ account, chain: bsc, transport: http(config.rpc) });
|
|
866
925
|
const policies = await policyClient.getPolicies(tokenId);
|
|
867
926
|
const results: string[] = [];
|
|
@@ -983,6 +1042,7 @@ server.tool(
|
|
|
983
1042
|
async ({ token_id, target, data, value }) => {
|
|
984
1043
|
const { policyClient } = createClients();
|
|
985
1044
|
const tokenId = BigInt(token_id);
|
|
1045
|
+
const expiryCheck = await checkAgentExpiry(tokenId); if (expiryCheck.expired) return { content: expiryCheck.content! };
|
|
986
1046
|
const action: Action = {
|
|
987
1047
|
target: target as Address,
|
|
988
1048
|
value: BigInt(value),
|
|
@@ -1031,6 +1091,7 @@ server.tool(
|
|
|
1031
1091
|
async ({ token_id, actions: rawActions }) => {
|
|
1032
1092
|
const { policyClient } = createClients();
|
|
1033
1093
|
const tokenId = BigInt(token_id);
|
|
1094
|
+
const expiryCheck = await checkAgentExpiry(tokenId); if (expiryCheck.expired) return { content: expiryCheck.content! };
|
|
1034
1095
|
const actions: Action[] = rawActions.map(a => ({
|
|
1035
1096
|
target: a.target as Address,
|
|
1036
1097
|
value: BigInt(a.value || "0"),
|