@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/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, pools] = await Promise.all([
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
- const rates = transformRates(pools);
459
- const prices = {};
460
- for (const [symbol, rate] of Object.entries(rates)) {
461
- prices[symbol] = rate.price;
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
- if (walletCoins && walletCoins.length > 0) {
467
- for (const coin of walletCoins) {
468
- const balance2 = Number(coin.totalBalance) / 10 ** coin.decimals;
469
- const price = prices[coin.symbol] ?? (STABLECOIN_SYMBOLS2.has(coin.symbol) ? 1 : 0);
470
- if (coin.symbol === "SUI" || coin.coinType === "0x2::sui::SUI") {
471
- const reserveAmount = Math.min(balance2, GAS_RESERVE_SUI2);
472
- gasReserveUsd2 = reserveAmount * price;
473
- availableUsd += (balance2 - reserveAmount) * price;
474
- } else {
475
- availableUsd += balance2 * price;
476
- if (STABLECOIN_SYMBOLS2.has(coin.symbol)) {
477
- stablesUsd += balance2 * price;
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
- } else {
482
- const mcpCoins = await callNavi(mgr, NaviTools.GET_COINS, { address }).catch(() => []);
483
- const coinArr = Array.isArray(mcpCoins) ? mcpCoins : [];
484
- for (const c of coinArr) {
485
- const symbol = c.symbol ?? "";
486
- const decimals = c.decimals ?? (symbol === "SUI" ? 9 : 6);
487
- const balance2 = Number(c.totalBalance ?? "0") / 10 ** decimals;
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 USDC into savings to earn yield. Always call balance_check first to know the available amount, then pass the exact number here. Returns tx hash, APY, fee, and updated savings balance.",
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 in USD to save (call balance_check first to get available 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 result = await agent.save({ amount: input.amount });
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 $${result.amount.toFixed(2)} at ${(result.apy * 100).toFixed(2)}% APY (tx: ${result.tx.slice(0, 8)}\u2026)`
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 USDC from savings back to wallet. Always call savings_info first to know the deposited amount, then pass the exact number here. Checks health factor to prevent liquidation if there is outstanding debt.",
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 in USD to withdraw (call savings_info first to get deposited 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({ amount: input.amount });
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 $${result.amount.toFixed(2)} (tx: ${result.tx.slice(0, 8)}\u2026)`
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 Audric, an AI assistant built on the Sui blockchain. You help users manage finances AND access 40+ paid API services via MPP (Machine Payment Protocol) micropayments.
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
- ## MPP Services (via pay_api tool)
1134
- When users ask for real-world data (weather, search, prices, news, etc.), use the pay_api tool. Each call costs a few cents in USDC, paid automatically on-chain. Common services:
1135
- - **Weather/forecast**: OpenWeather \u2014 current conditions, 5-day forecast
1136
- - **Web search**: Brave Search, Serper (Google), Perplexity (AI-powered)
1137
- - **News**: NewsAPI headlines and search
1138
- - **Crypto**: CoinGecko prices, markets, trending
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
- Always tell users the cost before calling a paid service. If they agree, use pay_api.
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
- ## Guidelines
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
- ### Before Acting
1153
- - **ALWAYS call a read tool first** before any write tool \u2014 call balance_check before save/send/borrow, savings_info before withdraw, balance_check before repay. Never skip this step.
1154
- - Show real numbers from tool results \u2014 never fabricate rates, amounts, or balances
1155
- - For transactions that move funds, explain what will happen and confirm intent
1156
- - When the user says "all" or "idle" or an imprecise amount, call the relevant read tool first to get the exact number, then call the write tool with that specific amount
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
- ### Tool Usage
1159
- - Use any available tools to help the user \u2014 don't refuse requests you can handle
1160
- - For real-world questions (weather, search, news, prices), use pay_api with the appropriate MPP endpoint
1161
- - Use multiple read-only tools in parallel when you need several data points
1162
- - Present amounts as currency ($1,234.56) and rates as percentages (4.86% APY)
1163
- - If a tool errors, explain the issue clearly and suggest alternatives
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
- ### Communication Style
1166
- - Be concise and direct \u2014 lead with results, follow with context
1167
- - Use short sentences. Avoid hedging language.
1168
- - When presenting positions or balances, use a structured format
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
- return `Save $${input.amount} into savings`;
1618
- case "withdraw":
1619
- return `Withdraw $${input.amount} from savings`;
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
- return `Pay for API call to ${input.url}${input.maxPrice ? ` (max $${input.maxPrice})` : ""}`;
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