@t2000/engine 0.4.6 → 0.5.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/README.md +25 -6
- package/dist/index.d.ts +130 -2
- package/dist/index.js +691 -117
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { getSwapQuote } from '@t2000/sdk';
|
|
2
3
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
4
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
5
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
@@ -411,18 +412,53 @@ function parseMcpJson(content) {
|
|
|
411
412
|
}
|
|
412
413
|
}
|
|
413
414
|
|
|
415
|
+
// src/defillama-prices.ts
|
|
416
|
+
var DEFILLAMA_PRICES_URL = "https://coins.llama.fi/prices/current";
|
|
417
|
+
var CACHE_TTL = 6e4;
|
|
418
|
+
var cache = null;
|
|
419
|
+
var pendingRequest = null;
|
|
420
|
+
async function fetchTokenPrices(coinTypes) {
|
|
421
|
+
if (coinTypes.length === 0) return {};
|
|
422
|
+
if (cache && Date.now() - cache.ts < CACHE_TTL) {
|
|
423
|
+
const allHit = coinTypes.every((ct) => ct in cache.prices);
|
|
424
|
+
if (allHit) return cache.prices;
|
|
425
|
+
}
|
|
426
|
+
if (pendingRequest) {
|
|
427
|
+
return pendingRequest;
|
|
428
|
+
}
|
|
429
|
+
pendingRequest = doFetch(coinTypes);
|
|
430
|
+
try {
|
|
431
|
+
return await pendingRequest;
|
|
432
|
+
} finally {
|
|
433
|
+
pendingRequest = null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function doFetch(coinTypes) {
|
|
437
|
+
const coins = coinTypes.map((ct) => `sui:${ct}`).join(",");
|
|
438
|
+
const url = `${DEFILLAMA_PRICES_URL}/${encodeURIComponent(coins)}`;
|
|
439
|
+
const res = await fetch(url, {
|
|
440
|
+
signal: AbortSignal.timeout(1e4)
|
|
441
|
+
});
|
|
442
|
+
if (!res.ok) {
|
|
443
|
+
console.warn(`[defillama-prices] HTTP ${res.status} from ${DEFILLAMA_PRICES_URL}`);
|
|
444
|
+
return cache?.prices ?? {};
|
|
445
|
+
}
|
|
446
|
+
const json = await res.json();
|
|
447
|
+
const prices = {};
|
|
448
|
+
if (json.coins) {
|
|
449
|
+
for (const [key, val] of Object.entries(json.coins)) {
|
|
450
|
+
const coinType = key.replace(/^sui:/, "");
|
|
451
|
+
prices[coinType] = val.price;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
cache = { prices, ts: Date.now() };
|
|
455
|
+
return prices;
|
|
456
|
+
}
|
|
457
|
+
function clearPriceCache() {
|
|
458
|
+
cache = null;
|
|
459
|
+
}
|
|
460
|
+
|
|
414
461
|
// src/tools/balance.ts
|
|
415
|
-
var STABLECOIN_SYMBOLS2 = /* @__PURE__ */ new Set([
|
|
416
|
-
"USDC",
|
|
417
|
-
"USDT",
|
|
418
|
-
"wUSDC",
|
|
419
|
-
"wUSDT",
|
|
420
|
-
"FDUSD",
|
|
421
|
-
"AUSD",
|
|
422
|
-
"BUCK",
|
|
423
|
-
"suiUSDe",
|
|
424
|
-
"USDSUI"
|
|
425
|
-
]);
|
|
426
462
|
var GAS_RESERVE_SUI2 = 0.05;
|
|
427
463
|
async function callNavi(manager, tool, args = {}) {
|
|
428
464
|
const result = await manager.callTool(NAVI_SERVER_NAME, tool, args);
|
|
@@ -442,7 +478,7 @@ var balanceCheckTool = buildTool({
|
|
|
442
478
|
if (hasNaviMcp(context)) {
|
|
443
479
|
const address = getWalletAddress(context);
|
|
444
480
|
const mgr = getMcpManager(context);
|
|
445
|
-
const [walletCoins, positions, rewards
|
|
481
|
+
const [walletCoins, positions, rewards] = await Promise.all([
|
|
446
482
|
fetchWalletCoins(address, context.suiRpcUrl).catch((err) => {
|
|
447
483
|
console.warn("[balance_check] Sui RPC coin fetch failed, falling back to MCP:", err);
|
|
448
484
|
return null;
|
|
@@ -452,50 +488,68 @@ var balanceCheckTool = buildTool({
|
|
|
452
488
|
protocols: "navi",
|
|
453
489
|
format: "json"
|
|
454
490
|
}),
|
|
455
|
-
callNavi(mgr, NaviTools.GET_AVAILABLE_REWARDS, { address })
|
|
456
|
-
callNavi(mgr, NaviTools.GET_POOLS, {})
|
|
491
|
+
callNavi(mgr, NaviTools.GET_AVAILABLE_REWARDS, { address })
|
|
457
492
|
]);
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
493
|
+
let coins = walletCoins;
|
|
494
|
+
if (!coins || coins.length === 0) {
|
|
495
|
+
const mcpCoins = await callNavi(mgr, NaviTools.GET_COINS, { address }).catch(() => []);
|
|
496
|
+
const coinArr = Array.isArray(mcpCoins) ? mcpCoins : [];
|
|
497
|
+
coins = coinArr.map((c) => ({
|
|
498
|
+
coinType: c.coinType ?? "",
|
|
499
|
+
symbol: c.symbol ?? "",
|
|
500
|
+
decimals: c.decimals ?? (c.symbol === "SUI" ? 9 : 6),
|
|
501
|
+
totalBalance: c.totalBalance ?? "0",
|
|
502
|
+
coinObjectCount: 0
|
|
503
|
+
}));
|
|
504
|
+
}
|
|
505
|
+
const VSUI_COIN_TYPE = "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT";
|
|
506
|
+
const coinTypes = coins.map((c) => c.coinType).filter(Boolean);
|
|
507
|
+
const prices = await fetchTokenPrices(coinTypes).catch((err) => {
|
|
508
|
+
console.warn("[balance_check] DefiLlama price fetch failed:", err);
|
|
509
|
+
return {};
|
|
510
|
+
});
|
|
511
|
+
if (coins.some((c) => c.coinType === VSUI_COIN_TYPE) && !prices[VSUI_COIN_TYPE]) {
|
|
512
|
+
try {
|
|
513
|
+
const statsRes = await fetch("https://open-api.naviprotocol.io/api/volo/stats", {
|
|
514
|
+
signal: AbortSignal.timeout(5e3)
|
|
515
|
+
});
|
|
516
|
+
if (statsRes.ok) {
|
|
517
|
+
const statsJson = await statsRes.json();
|
|
518
|
+
const d = statsJson.data ?? statsJson;
|
|
519
|
+
const rate = d.exchange_rate ?? d.exchangeRate ?? 1.05;
|
|
520
|
+
const suiPrice = prices["0x2::sui::SUI"] ?? 0;
|
|
521
|
+
prices[VSUI_COIN_TYPE] = rate * suiPrice;
|
|
522
|
+
}
|
|
523
|
+
} catch {
|
|
524
|
+
const suiPrice = prices["0x2::sui::SUI"] ?? 0;
|
|
525
|
+
prices[VSUI_COIN_TYPE] = suiPrice * 1.05;
|
|
526
|
+
}
|
|
462
527
|
}
|
|
463
528
|
let availableUsd = 0;
|
|
464
529
|
let stablesUsd = 0;
|
|
465
530
|
let gasReserveUsd2 = 0;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
531
|
+
const STABLE_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USDT", "wUSDC", "wUSDT", "FDUSD", "AUSD", "BUCK"]);
|
|
532
|
+
const holdings = [];
|
|
533
|
+
for (const coin of coins) {
|
|
534
|
+
const balance2 = Number(coin.totalBalance) / 10 ** coin.decimals;
|
|
535
|
+
const price = prices[coin.coinType] ?? 0;
|
|
536
|
+
if (coin.symbol === "SUI" || coin.coinType === "0x2::sui::SUI") {
|
|
537
|
+
const reserveAmount = Math.min(balance2, GAS_RESERVE_SUI2);
|
|
538
|
+
gasReserveUsd2 = reserveAmount * price;
|
|
539
|
+
availableUsd += (balance2 - reserveAmount) * price;
|
|
540
|
+
} else {
|
|
541
|
+
availableUsd += balance2 * price;
|
|
542
|
+
if (STABLE_SYMBOLS.has(coin.symbol)) {
|
|
543
|
+
stablesUsd += balance2 * price;
|
|
479
544
|
}
|
|
480
545
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const price = prices[symbol] ?? (STABLECOIN_SYMBOLS2.has(symbol) ? 1 : 0);
|
|
489
|
-
if (symbol === "SUI" || c.coinType === "0x2::sui::SUI") {
|
|
490
|
-
const reserveAmount = Math.min(balance2, GAS_RESERVE_SUI2);
|
|
491
|
-
gasReserveUsd2 = reserveAmount * price;
|
|
492
|
-
availableUsd += (balance2 - reserveAmount) * price;
|
|
493
|
-
} else {
|
|
494
|
-
availableUsd += balance2 * price;
|
|
495
|
-
if (STABLECOIN_SYMBOLS2.has(symbol)) {
|
|
496
|
-
stablesUsd += balance2 * price;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
546
|
+
if (balance2 > 0) {
|
|
547
|
+
holdings.push({
|
|
548
|
+
symbol: coin.symbol || coin.coinType.split("::").pop() || coin.coinType,
|
|
549
|
+
coinType: coin.coinType,
|
|
550
|
+
balance: balance2,
|
|
551
|
+
usdValue: balance2 * price
|
|
552
|
+
});
|
|
499
553
|
}
|
|
500
554
|
}
|
|
501
555
|
const sp = context.serverPositions;
|
|
@@ -520,7 +574,8 @@ var balanceCheckTool = buildTool({
|
|
|
520
574
|
pendingRewards: pendingRewardsUsd,
|
|
521
575
|
gasReserve: gasReserveUsd2,
|
|
522
576
|
total: availableUsd + savings + gasReserveUsd2 + pendingRewardsUsd - debt,
|
|
523
|
-
stables: stablesUsd
|
|
577
|
+
stables: stablesUsd,
|
|
578
|
+
holdings: holdings.sort((a, b) => b.usdValue - a.usdValue)
|
|
524
579
|
};
|
|
525
580
|
return {
|
|
526
581
|
data: bal,
|
|
@@ -531,6 +586,8 @@ var balanceCheckTool = buildTool({
|
|
|
531
586
|
const balance = await agent.balance();
|
|
532
587
|
const gasReserveUsd = typeof balance.gasReserve === "number" ? balance.gasReserve : balance.gasReserve.usdEquiv ?? 0;
|
|
533
588
|
const stablesTotal = typeof balance.stables === "number" ? balance.stables : Object.values(balance.stables).reduce((a, b) => a + b, 0);
|
|
589
|
+
const sdkHoldings = balance.holdings;
|
|
590
|
+
const holdingsArr = Array.isArray(sdkHoldings) ? sdkHoldings : [];
|
|
534
591
|
return {
|
|
535
592
|
data: {
|
|
536
593
|
available: balance.available,
|
|
@@ -539,7 +596,8 @@ var balanceCheckTool = buildTool({
|
|
|
539
596
|
pendingRewards: balance.pendingRewards,
|
|
540
597
|
gasReserve: gasReserveUsd,
|
|
541
598
|
total: balance.total,
|
|
542
|
-
stables: stablesTotal
|
|
599
|
+
stables: stablesTotal,
|
|
600
|
+
holdings: holdingsArr
|
|
543
601
|
},
|
|
544
602
|
displayText: `Balance: $${balance.total.toFixed(2)} (Available: $${balance.available.toFixed(2)}, Savings: $${balance.savings.toFixed(2)})`
|
|
545
603
|
};
|
|
@@ -838,15 +896,20 @@ var transactionHistoryTool = buildTool({
|
|
|
838
896
|
});
|
|
839
897
|
var saveDepositTool = buildTool({
|
|
840
898
|
name: "save_deposit",
|
|
841
|
-
description: "Deposit
|
|
899
|
+
description: "Deposit into NAVI lending to earn yield. Supports USDC (default), USDT, SUI, and other NAVI-supported assets. Always call balance_check first to know the available amount.",
|
|
842
900
|
inputSchema: z.object({
|
|
843
|
-
amount: z.number().positive()
|
|
901
|
+
amount: z.number().positive(),
|
|
902
|
+
asset: z.string().optional().describe("Asset to deposit (default: USDC). Options: USDC, USDT, SUI, USDe, USDsui")
|
|
844
903
|
}),
|
|
845
904
|
jsonSchema: {
|
|
846
905
|
type: "object",
|
|
847
906
|
properties: {
|
|
848
907
|
amount: {
|
|
849
|
-
description: "Exact amount
|
|
908
|
+
description: "Exact amount to save (call balance_check first to get available amount)"
|
|
909
|
+
},
|
|
910
|
+
asset: {
|
|
911
|
+
type: "string",
|
|
912
|
+
description: "Asset to deposit (default: USDC). Options: USDC, USDT, SUI, USDe, USDsui"
|
|
850
913
|
}
|
|
851
914
|
},
|
|
852
915
|
required: ["amount"]
|
|
@@ -855,32 +918,39 @@ var saveDepositTool = buildTool({
|
|
|
855
918
|
permissionLevel: "confirm",
|
|
856
919
|
async call(input, context) {
|
|
857
920
|
const agent = requireAgent(context);
|
|
858
|
-
const
|
|
921
|
+
const asset = input.asset ?? "USDC";
|
|
922
|
+
const result = await agent.save({ amount: input.amount, asset });
|
|
859
923
|
return {
|
|
860
924
|
data: {
|
|
861
925
|
success: result.success,
|
|
862
926
|
tx: result.tx,
|
|
863
927
|
amount: result.amount,
|
|
928
|
+
asset,
|
|
864
929
|
apy: result.apy,
|
|
865
930
|
fee: result.fee,
|
|
866
931
|
gasCost: result.gasCost,
|
|
867
932
|
savingsBalance: result.savingsBalance
|
|
868
933
|
},
|
|
869
|
-
displayText: `Saved
|
|
934
|
+
displayText: `Saved ${result.amount.toFixed(2)} ${asset} at ${(result.apy * 100).toFixed(2)}% APY (tx: ${result.tx.slice(0, 8)}\u2026)`
|
|
870
935
|
};
|
|
871
936
|
}
|
|
872
937
|
});
|
|
873
938
|
var withdrawTool = buildTool({
|
|
874
939
|
name: "withdraw",
|
|
875
|
-
description: "Withdraw
|
|
940
|
+
description: "Withdraw from NAVI lending back to wallet. Supports any deposited asset (USDC, USDT, SUI, etc). Always call savings_info first. Checks health factor to prevent liquidation if there is outstanding debt.",
|
|
876
941
|
inputSchema: z.object({
|
|
877
|
-
amount: z.number().positive()
|
|
942
|
+
amount: z.number().positive(),
|
|
943
|
+
asset: z.string().optional().describe("Asset to withdraw (default: picks largest position). Options: USDC, USDT, SUI, USDe, USDsui")
|
|
878
944
|
}),
|
|
879
945
|
jsonSchema: {
|
|
880
946
|
type: "object",
|
|
881
947
|
properties: {
|
|
882
948
|
amount: {
|
|
883
|
-
description: "Exact amount
|
|
949
|
+
description: "Exact amount to withdraw (call savings_info first to get deposited amount)"
|
|
950
|
+
},
|
|
951
|
+
asset: {
|
|
952
|
+
type: "string",
|
|
953
|
+
description: "Asset to withdraw (default: picks largest position). Options: USDC, USDT, SUI, USDe, USDsui"
|
|
884
954
|
}
|
|
885
955
|
},
|
|
886
956
|
required: ["amount"]
|
|
@@ -889,7 +959,10 @@ var withdrawTool = buildTool({
|
|
|
889
959
|
permissionLevel: "confirm",
|
|
890
960
|
async call(input, context) {
|
|
891
961
|
const agent = requireAgent(context);
|
|
892
|
-
const result = await agent.withdraw({
|
|
962
|
+
const result = await agent.withdraw({
|
|
963
|
+
amount: input.amount,
|
|
964
|
+
asset: input.asset
|
|
965
|
+
});
|
|
893
966
|
return {
|
|
894
967
|
data: {
|
|
895
968
|
success: result.success,
|
|
@@ -897,7 +970,7 @@ var withdrawTool = buildTool({
|
|
|
897
970
|
amount: result.amount,
|
|
898
971
|
gasCost: result.gasCost
|
|
899
972
|
},
|
|
900
|
-
displayText: `Withdrew
|
|
973
|
+
displayText: `Withdrew ${result.amount.toFixed(2)}${input.asset ? " " + input.asset : ""} (tx: ${result.tx.slice(0, 8)}\u2026)`
|
|
901
974
|
};
|
|
902
975
|
}
|
|
903
976
|
});
|
|
@@ -1031,6 +1104,29 @@ var claimRewardsTool = buildTool({
|
|
|
1031
1104
|
}
|
|
1032
1105
|
});
|
|
1033
1106
|
var MPP_GATEWAY = "https://mpp.t2000.ai";
|
|
1107
|
+
var SERVICE_PRICES = [
|
|
1108
|
+
[/\/fal\//, 0.03],
|
|
1109
|
+
[/\/googlemaps\//, 0.01],
|
|
1110
|
+
[/\/perplexity\//, 0.01],
|
|
1111
|
+
[/\/firecrawl\//, 0.01],
|
|
1112
|
+
[/\/serpapi\//, 0.01],
|
|
1113
|
+
[/\/openweather\//, 5e-3],
|
|
1114
|
+
[/\/brave\//, 5e-3],
|
|
1115
|
+
[/\/serper\//, 5e-3],
|
|
1116
|
+
[/\/newsapi\//, 5e-3],
|
|
1117
|
+
[/\/coingecko\//, 5e-3],
|
|
1118
|
+
[/\/alphavantage\//, 5e-3],
|
|
1119
|
+
[/\/exchangerate\//, 5e-3],
|
|
1120
|
+
[/\/deepl\//, 5e-3],
|
|
1121
|
+
[/\/jina\//, 5e-3],
|
|
1122
|
+
[/\/resend\//, 5e-3]
|
|
1123
|
+
];
|
|
1124
|
+
function estimatePayApiCost(url) {
|
|
1125
|
+
for (const [pattern, price] of SERVICE_PRICES) {
|
|
1126
|
+
if (pattern.test(url)) return price;
|
|
1127
|
+
}
|
|
1128
|
+
return 5e-3;
|
|
1129
|
+
}
|
|
1034
1130
|
var payApiTool = buildTool({
|
|
1035
1131
|
name: "pay_api",
|
|
1036
1132
|
description: `Call any MPP (Machine Payment Protocol) service via on-chain USDC micropayment. The gateway at ${MPP_GATEWAY} hosts 40+ services (88 endpoints). All endpoints accept POST with JSON body. Payment is handled automatically.
|
|
@@ -1097,6 +1193,436 @@ Always use POST. Construct the URL from the gateway base + path. Pass parameters
|
|
|
1097
1193
|
};
|
|
1098
1194
|
}
|
|
1099
1195
|
});
|
|
1196
|
+
var swapExecuteTool = buildTool({
|
|
1197
|
+
name: "swap_execute",
|
|
1198
|
+
description: "Swap tokens on Sui via Cetus Aggregator (20+ DEXs). Supports any token pair with liquidity. Use user-friendly names (SUI, USDC, CETUS, DEEP, etc.) or full coin types.",
|
|
1199
|
+
inputSchema: z.object({
|
|
1200
|
+
from: z.string().describe('Source token (e.g. "SUI", "USDC", or full coin type)'),
|
|
1201
|
+
to: z.string().describe('Target token (e.g. "USDC", "CETUS", or full coin type)'),
|
|
1202
|
+
amount: z.number().positive().describe("Amount to swap"),
|
|
1203
|
+
byAmountIn: z.boolean().optional().describe("true = fixed input amount (default), false = fixed output amount"),
|
|
1204
|
+
slippage: z.number().min(1e-3).max(0.05).optional().describe("Max slippage (default 0.01 = 1%, max 5%)")
|
|
1205
|
+
}),
|
|
1206
|
+
jsonSchema: {
|
|
1207
|
+
type: "object",
|
|
1208
|
+
properties: {
|
|
1209
|
+
from: { type: "string", description: "Source token name or coin type" },
|
|
1210
|
+
to: { type: "string", description: "Target token name or coin type" },
|
|
1211
|
+
amount: { type: "number", description: "Amount to swap" },
|
|
1212
|
+
byAmountIn: { type: "boolean", description: "true = fixed input (default), false = fixed output" },
|
|
1213
|
+
slippage: { type: "number", description: "Max slippage (0.01 = 1%)" }
|
|
1214
|
+
},
|
|
1215
|
+
required: ["from", "to", "amount"]
|
|
1216
|
+
},
|
|
1217
|
+
isReadOnly: false,
|
|
1218
|
+
permissionLevel: "confirm",
|
|
1219
|
+
async call(input, context) {
|
|
1220
|
+
const agent = requireAgent(context);
|
|
1221
|
+
const result = await agent.swap({
|
|
1222
|
+
from: input.from,
|
|
1223
|
+
to: input.to,
|
|
1224
|
+
amount: input.amount,
|
|
1225
|
+
byAmountIn: input.byAmountIn,
|
|
1226
|
+
slippage: input.slippage
|
|
1227
|
+
});
|
|
1228
|
+
return {
|
|
1229
|
+
data: {
|
|
1230
|
+
tx: result.tx,
|
|
1231
|
+
fromToken: result.fromToken,
|
|
1232
|
+
toToken: result.toToken,
|
|
1233
|
+
fromAmount: result.fromAmount,
|
|
1234
|
+
toAmount: result.toAmount,
|
|
1235
|
+
priceImpact: result.priceImpact,
|
|
1236
|
+
route: result.route,
|
|
1237
|
+
gasCost: result.gasCost
|
|
1238
|
+
},
|
|
1239
|
+
displayText: `Swapped ${result.fromAmount} ${result.fromToken} for ${result.toAmount.toFixed(4)} ${result.toToken} (tx: ${result.tx.slice(0, 8)}...)`
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
var swapQuoteTool = buildTool({
|
|
1244
|
+
name: "swap_quote",
|
|
1245
|
+
description: "Get a swap quote without executing. Shows expected output amount, price impact, and route. Use before swap_execute to preview a trade.",
|
|
1246
|
+
inputSchema: z.object({
|
|
1247
|
+
from: z.string().describe('Source token (e.g. "SUI", "USDC", or full coin type)'),
|
|
1248
|
+
to: z.string().describe('Target token (e.g. "USDC", "CETUS", or full coin type)'),
|
|
1249
|
+
amount: z.number().positive().describe("Amount to swap"),
|
|
1250
|
+
byAmountIn: z.boolean().optional().describe("true = fixed input (default), false = fixed output")
|
|
1251
|
+
}),
|
|
1252
|
+
jsonSchema: {
|
|
1253
|
+
type: "object",
|
|
1254
|
+
properties: {
|
|
1255
|
+
from: { type: "string", description: "Source token name or coin type" },
|
|
1256
|
+
to: { type: "string", description: "Target token name or coin type" },
|
|
1257
|
+
amount: { type: "number", description: "Amount to swap" },
|
|
1258
|
+
byAmountIn: { type: "boolean", description: "true = fixed input (default), false = fixed output" }
|
|
1259
|
+
},
|
|
1260
|
+
required: ["from", "to", "amount"]
|
|
1261
|
+
},
|
|
1262
|
+
isReadOnly: true,
|
|
1263
|
+
async call(input, context) {
|
|
1264
|
+
const walletAddress = context.agent ? context.agent.address() : getWalletAddress(context);
|
|
1265
|
+
const result = await getSwapQuote({
|
|
1266
|
+
walletAddress,
|
|
1267
|
+
from: input.from,
|
|
1268
|
+
to: input.to,
|
|
1269
|
+
amount: input.amount,
|
|
1270
|
+
byAmountIn: input.byAmountIn
|
|
1271
|
+
});
|
|
1272
|
+
return {
|
|
1273
|
+
data: result,
|
|
1274
|
+
displayText: `${result.fromAmount} ${result.fromToken} \u2192 ${result.toAmount.toFixed(4)} ${result.toToken} (impact: ${(result.priceImpact * 100).toFixed(2)}%, via ${result.route})`
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
var voloStakeTool = buildTool({
|
|
1279
|
+
name: "volo_stake",
|
|
1280
|
+
description: "Stake SUI for vSUI via VOLO liquid staking. Earn ~3-5% APY. Rewards compound automatically via exchange rate \u2014 no claiming needed. Minimum 1 SUI.",
|
|
1281
|
+
inputSchema: z.object({
|
|
1282
|
+
amount: z.number().min(1).describe("Amount of SUI to stake (minimum 1)")
|
|
1283
|
+
}),
|
|
1284
|
+
jsonSchema: {
|
|
1285
|
+
type: "object",
|
|
1286
|
+
properties: {
|
|
1287
|
+
amount: { type: "number", description: "Amount of SUI to stake" }
|
|
1288
|
+
},
|
|
1289
|
+
required: ["amount"]
|
|
1290
|
+
},
|
|
1291
|
+
isReadOnly: false,
|
|
1292
|
+
permissionLevel: "confirm",
|
|
1293
|
+
async call(input, context) {
|
|
1294
|
+
const agent = requireAgent(context);
|
|
1295
|
+
const result = await agent.stakeVSui({ amount: input.amount });
|
|
1296
|
+
return {
|
|
1297
|
+
data: {
|
|
1298
|
+
tx: result.tx,
|
|
1299
|
+
amountSui: result.amountSui,
|
|
1300
|
+
vSuiReceived: result.vSuiReceived,
|
|
1301
|
+
apy: result.apy,
|
|
1302
|
+
gasCost: result.gasCost
|
|
1303
|
+
},
|
|
1304
|
+
displayText: `Staked ${result.amountSui} SUI for ${result.vSuiReceived.toFixed(4)} vSUI at ${(result.apy * 100).toFixed(2)}% APY (tx: ${result.tx.slice(0, 8)}...)`
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
var voloUnstakeTool = buildTool({
|
|
1309
|
+
name: "volo_unstake",
|
|
1310
|
+
description: 'Unstake vSUI back to SUI. Returns SUI including accumulated yield. Use amount in vSUI units or "all" to unstake entire position.',
|
|
1311
|
+
inputSchema: z.object({
|
|
1312
|
+
amount: z.union([z.number().positive(), z.literal("all")]).describe('Amount of vSUI to unstake, or "all"')
|
|
1313
|
+
}),
|
|
1314
|
+
jsonSchema: {
|
|
1315
|
+
type: "object",
|
|
1316
|
+
properties: {
|
|
1317
|
+
amount: { description: 'Amount of vSUI to unstake, or the string "all"' }
|
|
1318
|
+
},
|
|
1319
|
+
required: ["amount"]
|
|
1320
|
+
},
|
|
1321
|
+
isReadOnly: false,
|
|
1322
|
+
permissionLevel: "confirm",
|
|
1323
|
+
async call(input, context) {
|
|
1324
|
+
const agent = requireAgent(context);
|
|
1325
|
+
const result = await agent.unstakeVSui({ amount: input.amount });
|
|
1326
|
+
return {
|
|
1327
|
+
data: {
|
|
1328
|
+
tx: result.tx,
|
|
1329
|
+
vSuiAmount: result.vSuiAmount,
|
|
1330
|
+
suiReceived: result.suiReceived,
|
|
1331
|
+
gasCost: result.gasCost
|
|
1332
|
+
},
|
|
1333
|
+
displayText: `Unstaked ${result.vSuiAmount.toFixed(4)} vSUI, received ${result.suiReceived.toFixed(4)} SUI (tx: ${result.tx.slice(0, 8)}...)`
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
var VOLO_STATS_URL = "https://open-api.naviprotocol.io/api/volo/stats";
|
|
1338
|
+
var voloStatsTool = buildTool({
|
|
1339
|
+
name: "volo_stats",
|
|
1340
|
+
description: "Get current VOLO liquid staking stats: vSUI APY, exchange rate, total staked SUI, and total vSUI supply.",
|
|
1341
|
+
inputSchema: z.object({}),
|
|
1342
|
+
jsonSchema: { type: "object", properties: {} },
|
|
1343
|
+
isReadOnly: true,
|
|
1344
|
+
async call() {
|
|
1345
|
+
const res = await fetch(VOLO_STATS_URL);
|
|
1346
|
+
if (!res.ok) throw new Error(`VOLO API returned ${res.status}`);
|
|
1347
|
+
const json = await res.json();
|
|
1348
|
+
const data = json.data ?? json;
|
|
1349
|
+
const stats = {
|
|
1350
|
+
apy: data.apy ?? 0,
|
|
1351
|
+
exchangeRate: data.exchange_rate ?? data.exchangeRate ?? 0,
|
|
1352
|
+
totalStaked: data.total_staked ?? data.totalStaked ?? 0,
|
|
1353
|
+
totalVSui: data.total_vsui ?? data.totalVSui ?? 0
|
|
1354
|
+
};
|
|
1355
|
+
return {
|
|
1356
|
+
data: stats,
|
|
1357
|
+
displayText: `vSUI APY: ${(stats.apy * 100).toFixed(2)}%, Rate: 1 SUI = ${(1 / stats.exchangeRate).toFixed(4)} vSUI, Total staked: ${stats.totalStaked.toLocaleString()} SUI`
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1361
|
+
var LLAMA_API = "https://api.llama.fi";
|
|
1362
|
+
var YIELDS_API = "https://yields.llama.fi";
|
|
1363
|
+
var COINS_API = "https://coins.llama.fi";
|
|
1364
|
+
var CACHE_TTL2 = 6e4;
|
|
1365
|
+
var apiCache = /* @__PURE__ */ new Map();
|
|
1366
|
+
async function cachedFetch(url) {
|
|
1367
|
+
const hit = apiCache.get(url);
|
|
1368
|
+
if (hit && Date.now() - hit.ts < CACHE_TTL2) return hit.data;
|
|
1369
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(15e3) });
|
|
1370
|
+
if (!res.ok) throw new Error(`DefiLlama API error: HTTP ${res.status}`);
|
|
1371
|
+
const data = await res.json();
|
|
1372
|
+
apiCache.set(url, { data, ts: Date.now() });
|
|
1373
|
+
return data;
|
|
1374
|
+
}
|
|
1375
|
+
var defillamaYieldPoolsTool = buildTool({
|
|
1376
|
+
name: "defillama_yield_pools",
|
|
1377
|
+
description: 'Get top DeFi yield pools across all protocols. Filter by chain (e.g. "Sui") and sort by APY. Shows pool name, protocol, TVL, and APY breakdown.',
|
|
1378
|
+
inputSchema: z.object({
|
|
1379
|
+
chain: z.string().optional().describe('Filter by chain name (e.g. "Sui", "Ethereum")'),
|
|
1380
|
+
limit: z.number().min(1).max(20).optional().describe("Max results (default 5)")
|
|
1381
|
+
}),
|
|
1382
|
+
jsonSchema: {
|
|
1383
|
+
type: "object",
|
|
1384
|
+
properties: {
|
|
1385
|
+
chain: { type: "string", description: "Filter by chain name" },
|
|
1386
|
+
limit: { type: "number", description: "Max results (default 5)" }
|
|
1387
|
+
},
|
|
1388
|
+
required: []
|
|
1389
|
+
},
|
|
1390
|
+
isReadOnly: true,
|
|
1391
|
+
async call(input) {
|
|
1392
|
+
const data = await cachedFetch(`${YIELDS_API}/pools`);
|
|
1393
|
+
let pools = data.data ?? [];
|
|
1394
|
+
if (input.chain) {
|
|
1395
|
+
const chain = input.chain.toLowerCase();
|
|
1396
|
+
pools = pools.filter((p) => p.chain.toLowerCase() === chain);
|
|
1397
|
+
}
|
|
1398
|
+
pools.sort((a, b) => b.apy - a.apy);
|
|
1399
|
+
const limit = input.limit ?? 5;
|
|
1400
|
+
const top = pools.slice(0, limit);
|
|
1401
|
+
const results = top.map((p) => ({
|
|
1402
|
+
pool: p.symbol,
|
|
1403
|
+
protocol: p.project,
|
|
1404
|
+
chain: p.chain,
|
|
1405
|
+
apy: Math.round(p.apy * 100) / 100,
|
|
1406
|
+
apyBase: p.apyBase != null ? Math.round(p.apyBase * 100) / 100 : void 0,
|
|
1407
|
+
apyReward: p.apyReward != null ? Math.round(p.apyReward * 100) / 100 : void 0,
|
|
1408
|
+
tvl: Math.round(p.tvlUsd)
|
|
1409
|
+
}));
|
|
1410
|
+
return {
|
|
1411
|
+
data: results,
|
|
1412
|
+
displayText: results.map((r) => `${r.pool} (${r.protocol}): ${r.apy}% APY, $${(r.tvl / 1e6).toFixed(1)}M TVL`).join("\n")
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
var defillamaProtocolInfoTool = buildTool({
|
|
1417
|
+
name: "defillama_protocol_info",
|
|
1418
|
+
description: 'Get detailed info about a DeFi protocol: TVL, category, chains it operates on, and TVL changes. Use for "Is this protocol safe?" or "Tell me about NAVI."',
|
|
1419
|
+
inputSchema: z.object({
|
|
1420
|
+
name: z.string().describe('Protocol name (e.g. "navi-lending", "cetus")')
|
|
1421
|
+
}),
|
|
1422
|
+
jsonSchema: {
|
|
1423
|
+
type: "object",
|
|
1424
|
+
properties: {
|
|
1425
|
+
name: { type: "string", description: 'Protocol slug (e.g. "navi-lending")' }
|
|
1426
|
+
},
|
|
1427
|
+
required: ["name"]
|
|
1428
|
+
},
|
|
1429
|
+
isReadOnly: true,
|
|
1430
|
+
async call(input) {
|
|
1431
|
+
const data = await cachedFetch(`${LLAMA_API}/protocol/${encodeURIComponent(input.name)}`);
|
|
1432
|
+
const result = {
|
|
1433
|
+
name: data.name,
|
|
1434
|
+
category: data.category,
|
|
1435
|
+
chains: data.chains,
|
|
1436
|
+
tvl: Math.round(data.tvl),
|
|
1437
|
+
change1d: data.change_1d,
|
|
1438
|
+
change7d: data.change_7d,
|
|
1439
|
+
url: data.url,
|
|
1440
|
+
description: data.description
|
|
1441
|
+
};
|
|
1442
|
+
return {
|
|
1443
|
+
data: result,
|
|
1444
|
+
displayText: `${result.name}: $${(result.tvl / 1e6).toFixed(1)}M TVL (${result.category}) on ${result.chains.join(", ")}`
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
var defillamaTokenPricesTool = buildTool({
|
|
1449
|
+
name: "defillama_token_prices",
|
|
1450
|
+
description: 'Get current USD prices for Sui tokens. Accepts full coin type strings (e.g. "0x2::sui::SUI"). Returns price per token.',
|
|
1451
|
+
inputSchema: z.object({
|
|
1452
|
+
coinTypes: z.array(z.string()).min(1).max(10).describe("Array of Sui coin type strings")
|
|
1453
|
+
}),
|
|
1454
|
+
jsonSchema: {
|
|
1455
|
+
type: "object",
|
|
1456
|
+
properties: {
|
|
1457
|
+
coinTypes: { type: "array", items: { type: "string" }, description: "Sui coin type strings" }
|
|
1458
|
+
},
|
|
1459
|
+
required: ["coinTypes"]
|
|
1460
|
+
},
|
|
1461
|
+
isReadOnly: true,
|
|
1462
|
+
async call(input) {
|
|
1463
|
+
const prices = await fetchTokenPrices(input.coinTypes);
|
|
1464
|
+
const results = input.coinTypes.map((ct) => ({
|
|
1465
|
+
coinType: ct,
|
|
1466
|
+
symbol: ct.split("::").pop() ?? ct,
|
|
1467
|
+
price: prices[ct] ?? null
|
|
1468
|
+
}));
|
|
1469
|
+
return {
|
|
1470
|
+
data: results,
|
|
1471
|
+
displayText: results.map((r) => `${r.symbol}: ${r.price != null ? `$${r.price.toFixed(4)}` : "price unavailable"}`).join(", ")
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
var defillamaPriceChangeTool = buildTool({
|
|
1476
|
+
name: "defillama_price_change",
|
|
1477
|
+
description: "Get price change for a Sui token over a period. Shows current price and historical price to calculate % change.",
|
|
1478
|
+
inputSchema: z.object({
|
|
1479
|
+
coinType: z.string().describe('Sui coin type (e.g. "0x2::sui::SUI")'),
|
|
1480
|
+
period: z.enum(["1h", "24h", "7d", "30d"]).optional().describe('Period (default "24h")')
|
|
1481
|
+
}),
|
|
1482
|
+
jsonSchema: {
|
|
1483
|
+
type: "object",
|
|
1484
|
+
properties: {
|
|
1485
|
+
coinType: { type: "string", description: "Sui coin type string" },
|
|
1486
|
+
period: { type: "string", description: "Period: 1h, 24h, 7d, 30d" }
|
|
1487
|
+
},
|
|
1488
|
+
required: ["coinType"]
|
|
1489
|
+
},
|
|
1490
|
+
isReadOnly: true,
|
|
1491
|
+
async call(input) {
|
|
1492
|
+
const period = input.period ?? "24h";
|
|
1493
|
+
const hoursMap = { "1h": 1, "24h": 24, "7d": 168, "30d": 720 };
|
|
1494
|
+
const hours = hoursMap[period] ?? 24;
|
|
1495
|
+
const historicalTs = Math.floor(Date.now() / 1e3) - hours * 3600;
|
|
1496
|
+
const coinKey = `sui:${input.coinType}`;
|
|
1497
|
+
const [current, historical] = await Promise.all([
|
|
1498
|
+
cachedFetch(
|
|
1499
|
+
`${COINS_API}/prices/current/${encodeURIComponent(coinKey)}`
|
|
1500
|
+
),
|
|
1501
|
+
cachedFetch(
|
|
1502
|
+
`${COINS_API}/prices/historical/${historicalTs}/${encodeURIComponent(coinKey)}`
|
|
1503
|
+
)
|
|
1504
|
+
]);
|
|
1505
|
+
const currentPrice = current.coins[coinKey]?.price;
|
|
1506
|
+
const historicalPrice = historical.coins[coinKey]?.price;
|
|
1507
|
+
const symbol = input.coinType.split("::").pop() ?? input.coinType;
|
|
1508
|
+
if (currentPrice == null) {
|
|
1509
|
+
return {
|
|
1510
|
+
data: { symbol, currentPrice: 0, historicalPrice: null, change: null, period },
|
|
1511
|
+
displayText: "Token price not available on DefiLlama."
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
const change = historicalPrice ? Math.round((currentPrice - historicalPrice) / historicalPrice * 1e4) / 100 : null;
|
|
1515
|
+
return {
|
|
1516
|
+
data: {
|
|
1517
|
+
symbol,
|
|
1518
|
+
currentPrice,
|
|
1519
|
+
historicalPrice: historicalPrice ?? null,
|
|
1520
|
+
change,
|
|
1521
|
+
period
|
|
1522
|
+
},
|
|
1523
|
+
displayText: change != null ? `${symbol}: $${currentPrice.toFixed(4)} (${change >= 0 ? "+" : ""}${change.toFixed(2)}% over ${period})` : `${symbol}: $${currentPrice.toFixed(4)}`
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
var defillamaChainTvlTool = buildTool({
|
|
1528
|
+
name: "defillama_chain_tvl",
|
|
1529
|
+
description: 'Get chain TVL rankings. Shows top chains by total value locked. Use for "How big is Sui?" or "Compare chains."',
|
|
1530
|
+
inputSchema: z.object({
|
|
1531
|
+
limit: z.number().min(1).max(20).optional().describe("Max results (default 10)")
|
|
1532
|
+
}),
|
|
1533
|
+
jsonSchema: {
|
|
1534
|
+
type: "object",
|
|
1535
|
+
properties: {
|
|
1536
|
+
limit: { type: "number", description: "Max results (default 10)" }
|
|
1537
|
+
},
|
|
1538
|
+
required: []
|
|
1539
|
+
},
|
|
1540
|
+
isReadOnly: true,
|
|
1541
|
+
async call(input) {
|
|
1542
|
+
const data = await cachedFetch(`${LLAMA_API}/v2/chains`);
|
|
1543
|
+
const sorted = [...data].sort((a, b) => b.tvl - a.tvl);
|
|
1544
|
+
const limit = input.limit ?? 10;
|
|
1545
|
+
const top = sorted.slice(0, limit);
|
|
1546
|
+
const results = top.map((c, i) => ({
|
|
1547
|
+
rank: i + 1,
|
|
1548
|
+
chain: c.name,
|
|
1549
|
+
tvl: Math.round(c.tvl)
|
|
1550
|
+
}));
|
|
1551
|
+
return {
|
|
1552
|
+
data: results,
|
|
1553
|
+
displayText: results.map((r) => `#${r.rank} ${r.chain}: $${(r.tvl / 1e9).toFixed(2)}B`).join("\n")
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
var defillamaProtocolFeesTool = buildTool({
|
|
1558
|
+
name: "defillama_protocol_fees",
|
|
1559
|
+
description: 'Get protocol fee/revenue rankings. Shows which protocols earn the most in fees. Use for "Which protocols are most profitable?"',
|
|
1560
|
+
inputSchema: z.object({
|
|
1561
|
+
chain: z.string().optional().describe("Filter by chain"),
|
|
1562
|
+
limit: z.number().min(1).max(20).optional().describe("Max results (default 5)")
|
|
1563
|
+
}),
|
|
1564
|
+
jsonSchema: {
|
|
1565
|
+
type: "object",
|
|
1566
|
+
properties: {
|
|
1567
|
+
chain: { type: "string", description: "Filter by chain" },
|
|
1568
|
+
limit: { type: "number", description: "Max results (default 5)" }
|
|
1569
|
+
},
|
|
1570
|
+
required: []
|
|
1571
|
+
},
|
|
1572
|
+
isReadOnly: true,
|
|
1573
|
+
async call(input) {
|
|
1574
|
+
const data = await cachedFetch(`${LLAMA_API}/overview/fees`);
|
|
1575
|
+
let protocols = data.protocols ?? [];
|
|
1576
|
+
if (input.chain) {
|
|
1577
|
+
const chain = input.chain.toLowerCase();
|
|
1578
|
+
protocols = protocols.filter(
|
|
1579
|
+
(p) => p.chains?.some((c) => c.toLowerCase() === chain)
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
protocols.sort((a, b) => (b.total24h ?? 0) - (a.total24h ?? 0));
|
|
1583
|
+
const limit = input.limit ?? 5;
|
|
1584
|
+
const top = protocols.slice(0, limit);
|
|
1585
|
+
const results = top.map((p) => ({
|
|
1586
|
+
name: p.name,
|
|
1587
|
+
fees24h: p.total24h != null ? Math.round(p.total24h) : null,
|
|
1588
|
+
fees7d: p.total7d != null ? Math.round(p.total7d) : null,
|
|
1589
|
+
category: p.category
|
|
1590
|
+
}));
|
|
1591
|
+
return {
|
|
1592
|
+
data: results,
|
|
1593
|
+
displayText: results.map((r) => `${r.name}: $${r.fees24h != null ? (r.fees24h / 1e3).toFixed(1) + "K" : "?"}/day`).join("\n")
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
var defillamaSuiProtocolsTool = buildTool({
|
|
1598
|
+
name: "defillama_sui_protocols",
|
|
1599
|
+
description: "List top DeFi protocols on Sui by TVL. Shows name, TVL, category, and slug for each protocol. Use to discover protocols before calling defillama_protocol_info.",
|
|
1600
|
+
inputSchema: z.object({
|
|
1601
|
+
limit: z.number().int().min(1).max(50).optional().describe("Max protocols to return (default 10)")
|
|
1602
|
+
}),
|
|
1603
|
+
jsonSchema: {
|
|
1604
|
+
type: "object",
|
|
1605
|
+
properties: {
|
|
1606
|
+
limit: { type: "number", description: "Max protocols to return (default 10)" }
|
|
1607
|
+
}
|
|
1608
|
+
},
|
|
1609
|
+
isReadOnly: true,
|
|
1610
|
+
async call(input) {
|
|
1611
|
+
const limit = input.limit ?? 10;
|
|
1612
|
+
const data = await cachedFetch(`${LLAMA_API}/protocols`);
|
|
1613
|
+
const suiProtocols = data.filter((p) => p.chains?.includes("Sui") && p.tvl > 0).sort((a, b) => b.tvl - a.tvl).slice(0, limit);
|
|
1614
|
+
const results = suiProtocols.map((p) => ({
|
|
1615
|
+
name: p.name,
|
|
1616
|
+
slug: p.slug,
|
|
1617
|
+
tvl: Math.round(p.tvl),
|
|
1618
|
+
category: p.category
|
|
1619
|
+
}));
|
|
1620
|
+
return {
|
|
1621
|
+
data: results,
|
|
1622
|
+
displayText: results.map((r, i) => `${i + 1}. ${r.name} ($${(r.tvl / 1e6).toFixed(1)}M TVL, ${r.category})`).join("\n")
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1100
1626
|
|
|
1101
1627
|
// src/tools/index.ts
|
|
1102
1628
|
var READ_TOOLS = [
|
|
@@ -1104,7 +1630,16 @@ var READ_TOOLS = [
|
|
|
1104
1630
|
savingsInfoTool,
|
|
1105
1631
|
healthCheckTool,
|
|
1106
1632
|
ratesInfoTool,
|
|
1107
|
-
transactionHistoryTool
|
|
1633
|
+
transactionHistoryTool,
|
|
1634
|
+
swapQuoteTool,
|
|
1635
|
+
voloStatsTool,
|
|
1636
|
+
defillamaYieldPoolsTool,
|
|
1637
|
+
defillamaProtocolInfoTool,
|
|
1638
|
+
defillamaTokenPricesTool,
|
|
1639
|
+
defillamaPriceChangeTool,
|
|
1640
|
+
defillamaChainTvlTool,
|
|
1641
|
+
defillamaProtocolFeesTool,
|
|
1642
|
+
defillamaSuiProtocolsTool
|
|
1108
1643
|
];
|
|
1109
1644
|
var WRITE_TOOLS = [
|
|
1110
1645
|
saveDepositTool,
|
|
@@ -1113,66 +1648,54 @@ var WRITE_TOOLS = [
|
|
|
1113
1648
|
borrowTool,
|
|
1114
1649
|
repayDebtTool,
|
|
1115
1650
|
claimRewardsTool,
|
|
1116
|
-
payApiTool
|
|
1651
|
+
payApiTool,
|
|
1652
|
+
swapExecuteTool,
|
|
1653
|
+
voloStakeTool,
|
|
1654
|
+
voloUnstakeTool
|
|
1117
1655
|
];
|
|
1118
1656
|
function getDefaultTools() {
|
|
1119
1657
|
return [...READ_TOOLS, ...WRITE_TOOLS];
|
|
1120
1658
|
}
|
|
1121
1659
|
|
|
1122
1660
|
// src/prompt.ts
|
|
1123
|
-
var DEFAULT_SYSTEM_PROMPT = `You are
|
|
1124
|
-
|
|
1125
|
-
## Core Capabilities
|
|
1126
|
-
- Check balances, savings positions, health factors, and interest rates
|
|
1127
|
-
- Execute deposits, withdrawals, transfers, borrows, and repayments
|
|
1128
|
-
- Track transaction history and earnings
|
|
1129
|
-
- Look up swap quotes, bridge options, and token information via NAVI
|
|
1130
|
-
- **Access any MPP service** \u2014 weather, web search, news, crypto prices, stock quotes, translations, image generation, maps, flights, and more via the pay_api tool
|
|
1131
|
-
- Answer general knowledge questions conversationally
|
|
1661
|
+
var DEFAULT_SYSTEM_PROMPT = `You are a financial agent on Sui. You manage money and access paid APIs via MPP micropayments.
|
|
1132
1662
|
|
|
1133
|
-
##
|
|
1134
|
-
|
|
1135
|
-
-
|
|
1136
|
-
-
|
|
1137
|
-
-
|
|
1138
|
-
-
|
|
1139
|
-
- **Stocks**: Alpha Vantage quotes, daily data
|
|
1140
|
-
- **Maps**: Google Maps geocode, places, directions
|
|
1141
|
-
- **Translation**: DeepL, Google Translate
|
|
1142
|
-
- **FX rates**: Exchange rate conversion
|
|
1143
|
-
- **Scraping**: Firecrawl, Jina Reader
|
|
1144
|
-
- **Flights**: SerpAPI Google Flights
|
|
1145
|
-
- **Image gen**: Flux, Stable Diffusion, DALL-E
|
|
1146
|
-
- **Email**: Resend
|
|
1663
|
+
## Response rules
|
|
1664
|
+
- 1-2 sentences max. No bullet lists unless asked. No preambles.
|
|
1665
|
+
- Never say "Would you like me to...", "Sure!", "Great question!", "Absolutely!" \u2014 just do it or say you can't.
|
|
1666
|
+
- Lead with the result. After tool calls, state the outcome with real numbers. Done.
|
|
1667
|
+
- Present amounts as $1,234.56 and rates as X.XX% APY.
|
|
1668
|
+
- Show top 3 results unless asked for more. Summarize totals in one line.
|
|
1147
1669
|
|
|
1148
|
-
|
|
1670
|
+
## Execution rule
|
|
1671
|
+
Only offer to execute actions you have tools for. If you retrieved a quote, data, or information but have no tool to act on it, give the user the result and tell them where to execute manually \u2014 in one sentence. Never say "Would you like me to proceed?" unless you have a tool that can actually proceed.
|
|
1149
1672
|
|
|
1150
|
-
##
|
|
1673
|
+
## Before acting
|
|
1674
|
+
- ALWAYS call a read tool first before any write tool \u2014 balance_check before save/send/borrow, savings_info before withdraw.
|
|
1675
|
+
- Show real numbers from tools \u2014 never fabricate rates, amounts, or balances.
|
|
1676
|
+
- When user says "all" or an imprecise amount, call the read tool first to get the exact number.
|
|
1151
1677
|
|
|
1152
|
-
|
|
1153
|
-
-
|
|
1154
|
-
-
|
|
1155
|
-
- For
|
|
1156
|
-
-
|
|
1678
|
+
## Tool usage
|
|
1679
|
+
- Use tools proactively \u2014 don't refuse requests you can handle.
|
|
1680
|
+
- For real-world questions (weather, search, news, prices), use pay_api. Tell the user the cost first.
|
|
1681
|
+
- For broad market data (yields across protocols, token prices, TVL, protocol comparisons), use defillama_* tools.
|
|
1682
|
+
- To discover Sui protocols, use defillama_sui_protocols first, then defillama_protocol_info with the slug.
|
|
1683
|
+
- Run multiple read-only tools in parallel when you need several data points.
|
|
1684
|
+
- If a tool errors, say what went wrong and what to try instead. One sentence.
|
|
1157
1685
|
|
|
1158
|
-
|
|
1159
|
-
-
|
|
1160
|
-
-
|
|
1161
|
-
-
|
|
1162
|
-
-
|
|
1163
|
-
-
|
|
1686
|
+
## Multi-step flows
|
|
1687
|
+
- "How much X for Y?": swap_quote first, then swap_execute if user confirms.
|
|
1688
|
+
- "Swap then save": swap_execute \u2192 balance_check \u2192 save_deposit. Confirm each step.
|
|
1689
|
+
- "Buy $X of token": defillama_token_prices \u2192 calculate amount \u2192 swap_execute.
|
|
1690
|
+
- "Best yield on SUI": compare rates_info (NAVI lending) + defillama_yield_pools (broader) + volo_stats.
|
|
1691
|
+
- save_deposit supports any NAVI asset: USDC (default), USDT, SUI, USDe, USDsui. Pass asset param for non-USDC.
|
|
1692
|
+
- "Deposit SUI to earn yield": save_deposit with asset="SUI" for NAVI lending, or volo_stake for liquid staking.
|
|
1693
|
+
- "What protocols are on Sui?": defillama_sui_protocols \u2192 defillama_protocol_info for details.
|
|
1164
1694
|
|
|
1165
|
-
|
|
1166
|
-
-
|
|
1167
|
-
-
|
|
1168
|
-
-
|
|
1169
|
-
- For non-financial questions, answer naturally and helpfully
|
|
1170
|
-
|
|
1171
|
-
### Safety
|
|
1172
|
-
- Never encourage risky financial behavior
|
|
1173
|
-
- Warn when health factor drops below 1.5
|
|
1174
|
-
- Remind users of gas costs for on-chain transactions
|
|
1175
|
-
- All amounts are in USDC unless explicitly stated otherwise`;
|
|
1695
|
+
## Safety
|
|
1696
|
+
- Never encourage risky financial behavior.
|
|
1697
|
+
- Warn when health factor < 1.5.
|
|
1698
|
+
- All amounts in USDC unless stated otherwise.`;
|
|
1176
1699
|
|
|
1177
1700
|
// src/cost.ts
|
|
1178
1701
|
var DEFAULT_INPUT_COST = 3 / 1e6;
|
|
@@ -1444,6 +1967,23 @@ ${summary.join("\n")}`);
|
|
|
1444
1967
|
break;
|
|
1445
1968
|
}
|
|
1446
1969
|
for await (const toolEvent of runTools(approved, this.tools, context, this.txMutex)) {
|
|
1970
|
+
if (toolEvent.type === "tool_result" && !toolEvent.isError) {
|
|
1971
|
+
const warning = flagSuspiciousResult(toolEvent.toolName, toolEvent.result);
|
|
1972
|
+
if (warning) {
|
|
1973
|
+
const flagged = {
|
|
1974
|
+
...toolEvent,
|
|
1975
|
+
result: typeof toolEvent.result === "object" && toolEvent.result ? { ...toolEvent.result, _warning: warning } : { data: toolEvent.result, _warning: warning }
|
|
1976
|
+
};
|
|
1977
|
+
yield flagged;
|
|
1978
|
+
toolResultBlocks.push({
|
|
1979
|
+
type: "tool_result",
|
|
1980
|
+
toolUseId: flagged.toolUseId,
|
|
1981
|
+
content: JSON.stringify(flagged.result),
|
|
1982
|
+
isError: flagged.isError
|
|
1983
|
+
});
|
|
1984
|
+
continue;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1447
1987
|
yield toolEvent;
|
|
1448
1988
|
if (toolEvent.type === "tool_result") {
|
|
1449
1989
|
toolResultBlocks.push({
|
|
@@ -1613,10 +2153,14 @@ function validateHistory(messages) {
|
|
|
1613
2153
|
function describeAction(tool, call) {
|
|
1614
2154
|
const input = call.input;
|
|
1615
2155
|
switch (tool.name) {
|
|
1616
|
-
case "save_deposit":
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
2156
|
+
case "save_deposit": {
|
|
2157
|
+
const asset = input.asset ?? "USDC";
|
|
2158
|
+
return `Save ${input.amount} ${asset} into lending`;
|
|
2159
|
+
}
|
|
2160
|
+
case "withdraw": {
|
|
2161
|
+
const wAsset = input.asset ?? "";
|
|
2162
|
+
return `Withdraw ${input.amount}${wAsset ? " " + wAsset : ""} from lending`;
|
|
2163
|
+
}
|
|
1620
2164
|
case "send_transfer":
|
|
1621
2165
|
return `Send $${input.amount} to ${input.to}`;
|
|
1622
2166
|
case "borrow":
|
|
@@ -1625,12 +2169,42 @@ function describeAction(tool, call) {
|
|
|
1625
2169
|
return `Repay $${input.amount} of outstanding debt`;
|
|
1626
2170
|
case "claim_rewards":
|
|
1627
2171
|
return "Claim all pending protocol rewards";
|
|
1628
|
-
case "pay_api":
|
|
1629
|
-
|
|
2172
|
+
case "pay_api": {
|
|
2173
|
+
const url = String(input.url ?? "");
|
|
2174
|
+
const cost = estimatePayApiCost(url);
|
|
2175
|
+
return `Pay for API call to ${url} (~$${cost})`;
|
|
2176
|
+
}
|
|
2177
|
+
case "swap_execute": {
|
|
2178
|
+
const from = input.from ?? "?";
|
|
2179
|
+
const to = input.to ?? "?";
|
|
2180
|
+
const amt = input.amount ?? "?";
|
|
2181
|
+
const slippagePct = (input.slippage ?? 0.01) * 100;
|
|
2182
|
+
return `Swap ${amt} ${from} for ${to} (${slippagePct}% max slippage)`;
|
|
2183
|
+
}
|
|
2184
|
+
case "volo_stake":
|
|
2185
|
+
return `Stake ${input.amount} SUI for vSUI`;
|
|
2186
|
+
case "volo_unstake":
|
|
2187
|
+
return `Unstake ${input.amount === "all" ? "all" : input.amount} vSUI`;
|
|
1630
2188
|
default:
|
|
1631
2189
|
return `Execute ${tool.name}`;
|
|
1632
2190
|
}
|
|
1633
2191
|
}
|
|
2192
|
+
function flagSuspiciousResult(toolName, result) {
|
|
2193
|
+
if (!result || typeof result !== "object") return null;
|
|
2194
|
+
const r = result;
|
|
2195
|
+
if (toolName === "swap_execute") {
|
|
2196
|
+
const outAmt = Number(r.toAmount ?? r.outputAmount ?? 0);
|
|
2197
|
+
const inAmt = Number(r.fromAmount ?? r.inputAmount ?? 1);
|
|
2198
|
+
if (inAmt > 0 && outAmt / inAmt > 1e6) {
|
|
2199
|
+
return "[Warning: This quote may contain inaccurate data. Verify on-chain before executing.]";
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
const apy = Number(r.apy ?? r.APY ?? NaN);
|
|
2203
|
+
if (!isNaN(apy) && apy < 0) {
|
|
2204
|
+
return "[Warning: Negative APY detected \u2014 data may be stale.]";
|
|
2205
|
+
}
|
|
2206
|
+
return null;
|
|
2207
|
+
}
|
|
1634
2208
|
|
|
1635
2209
|
// src/streaming.ts
|
|
1636
2210
|
function serializeSSE(event) {
|
|
@@ -2325,6 +2899,6 @@ function sanitizeAnthropicMessages(messages) {
|
|
|
2325
2899
|
return merged;
|
|
2326
2900
|
}
|
|
2327
2901
|
|
|
2328
|
-
export { AnthropicProvider, CostTracker, DEFAULT_SYSTEM_PROMPT, McpClientManager, McpResponseCache, MemorySessionStore, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_SERVER_NAME, NaviTools, QueryEngine, READ_TOOLS, TxMutex, WRITE_TOOLS, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, balanceCheckTool, borrowTool, buildMcpTools, buildTool, claimRewardsTool, compactMessages, engineToSSE, estimateTokens, extractMcpText, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, fetchWalletCoins, findTool, getDefaultTools, getMcpManager, getWalletAddress, hasNaviMcp, healthCheckTool, parseMcpJson, parseSSE, payApiTool, ratesInfoTool, registerEngineTools, repayDebtTool, requireAgent, runTools, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, validateHistory, withdrawTool };
|
|
2902
|
+
export { AnthropicProvider, CostTracker, DEFAULT_SYSTEM_PROMPT, McpClientManager, McpResponseCache, MemorySessionStore, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_SERVER_NAME, NaviTools, QueryEngine, READ_TOOLS, TxMutex, WRITE_TOOLS, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, balanceCheckTool, borrowTool, buildMcpTools, buildTool, claimRewardsTool, clearPriceCache, compactMessages, defillamaChainTvlTool, defillamaPriceChangeTool, defillamaProtocolFeesTool, defillamaProtocolInfoTool, defillamaSuiProtocolsTool, defillamaTokenPricesTool, defillamaYieldPoolsTool, engineToSSE, estimateTokens, extractMcpText, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, fetchTokenPrices, fetchWalletCoins, findTool, getDefaultTools, getMcpManager, getWalletAddress, hasNaviMcp, healthCheckTool, parseMcpJson, parseSSE, payApiTool, ratesInfoTool, registerEngineTools, repayDebtTool, requireAgent, runTools, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, swapExecuteTool, swapQuoteTool, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, withdrawTool };
|
|
2329
2903
|
//# sourceMappingURL=index.js.map
|
|
2330
2904
|
//# sourceMappingURL=index.js.map
|