@t2000/engine 0.49.0 → 0.50.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.d.ts CHANGED
@@ -1962,6 +1962,9 @@ declare const balanceCheckTool: Tool<{
1962
1962
  debt: number;
1963
1963
  pendingRewards: number;
1964
1964
  gasReserve: number;
1965
+ defi: number;
1966
+ defiByProtocol: Partial<Record<"aftermath" | "alphafi" | "alphalend" | "bluefin" | "bluemove" | "bucket" | "bucket2" | "cetus" | "deepbook" | "ember" | "ferra" | "flowx" | "haedal" | "kai" | "kriya" | "magma" | "momentum" | "r25" | "scallop" | "steamm" | "suilend" | "suins-staking" | "suistake" | "turbos" | "typus" | "unihouse" | "walrus", number>>;
1967
+ defiSource: "blockvision" | "partial" | "degraded";
1965
1968
  total: number;
1966
1969
  stables: number;
1967
1970
  holdings: any[];
package/dist/index.js CHANGED
@@ -711,6 +711,417 @@ function parseNumberOrNull(input) {
711
711
  const n = Number(input);
712
712
  return Number.isFinite(n) ? n : null;
713
713
  }
714
+ var DEFI_PORTFOLIO_TIMEOUT_MS = 4e3;
715
+ var DEFI_CACHE_TTL_MS = 6e4;
716
+ var DEFI_PROTOCOLS = [
717
+ "aftermath",
718
+ "alphafi",
719
+ "alphalend",
720
+ "bluefin",
721
+ "bluemove",
722
+ "bucket",
723
+ "bucket2",
724
+ "cetus",
725
+ "deepbook",
726
+ "ember",
727
+ "ferra",
728
+ "flowx",
729
+ "haedal",
730
+ "kai",
731
+ "kriya",
732
+ "magma",
733
+ "momentum",
734
+ "r25",
735
+ "scallop",
736
+ "steamm",
737
+ "suilend",
738
+ "suins-staking",
739
+ "suistake",
740
+ "turbos",
741
+ "typus",
742
+ "unihouse",
743
+ "walrus"
744
+ ];
745
+ var SUI_TYPE_FULL = "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI";
746
+ var USDC_TYPE_FULL = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
747
+ var BLUE_TYPE_FULL = "0xe1b45a0e641b9955a20aa0ad1c1f4ad86aad8afb07296d4085e349a50e90bdca::blue::BLUE";
748
+ var WAL_TYPE_FULL = "0x356a26eb9e012a68958082340d4c4116e7f55615cf27affcff209cf0ae544f59::wal::WAL";
749
+ var NS_TYPE_FULL = "0x5145494a5f5100e645e4b0aa950fa6b68f614e8c59e17bc5ded3495123a79178::ns::NS";
750
+ var defiCache = /* @__PURE__ */ new Map();
751
+ var defiInflight = /* @__PURE__ */ new Map();
752
+ async function fetchAddressDefiPortfolio(address, apiKey, priceHints = {}) {
753
+ if (!apiKey || apiKey.trim().length === 0) {
754
+ return { totalUsd: 0, perProtocol: {}, pricedAt: Date.now(), source: "degraded" };
755
+ }
756
+ const now = Date.now();
757
+ const cached = defiCache.get(address);
758
+ if (cached && now - cached.ts < DEFI_CACHE_TTL_MS) return cached.data;
759
+ let inflight = defiInflight.get(address);
760
+ if (inflight) return inflight;
761
+ inflight = (async () => {
762
+ try {
763
+ const settled = await Promise.allSettled(
764
+ DEFI_PROTOCOLS.map((p) => fetchOneDefiProtocol(address, p, apiKey))
765
+ );
766
+ const seen = /* @__PURE__ */ new Set();
767
+ for (const s of settled) {
768
+ if (s.status === "fulfilled" && s.value) collectCoinTypes(s.value, seen);
769
+ }
770
+ const normalizedHints = {};
771
+ for (const [k, v] of Object.entries(priceHints)) {
772
+ normalizedHints[normalizeCoinType(k)] = v;
773
+ }
774
+ const missing = Array.from(seen).filter((ct) => {
775
+ const norm = normalizeCoinType(ct);
776
+ return !normalizedHints[norm] && !STABLE_USD_PRICES[norm];
777
+ });
778
+ let fetchedPrices = {};
779
+ if (missing.length > 0) {
780
+ try {
781
+ fetchedPrices = await fetchTokenPrices(missing, apiKey);
782
+ } catch (err) {
783
+ console.warn("[defi] fill-missing-prices failed:", err);
784
+ }
785
+ }
786
+ const prices = { ...normalizedHints };
787
+ for (const [ct, v] of Object.entries(fetchedPrices)) {
788
+ prices[normalizeCoinType(ct)] ??= v.price;
789
+ }
790
+ for (const [ct, p] of Object.entries(STABLE_USD_PRICES)) {
791
+ prices[normalizeCoinType(ct)] ??= p;
792
+ }
793
+ let totalUsd = 0;
794
+ let failures = 0;
795
+ const perProtocol = {};
796
+ for (let i = 0; i < DEFI_PROTOCOLS.length; i++) {
797
+ const proto = DEFI_PROTOCOLS[i];
798
+ const s = settled[i];
799
+ if (s.status !== "fulfilled" || !s.value) {
800
+ failures++;
801
+ continue;
802
+ }
803
+ try {
804
+ const usd = normalizeProtocol(proto, s.value, prices);
805
+ if (Number.isFinite(usd) && usd !== 0) {
806
+ perProtocol[proto] = usd;
807
+ totalUsd += usd;
808
+ }
809
+ } catch (err) {
810
+ console.warn(`[defi] ${proto} normaliser threw:`, err);
811
+ failures++;
812
+ }
813
+ }
814
+ if (totalUsd < 0) totalUsd = 0;
815
+ const summary = {
816
+ totalUsd,
817
+ perProtocol,
818
+ pricedAt: Date.now(),
819
+ source: failures === DEFI_PROTOCOLS.length ? "degraded" : failures > 0 ? "partial" : "blockvision"
820
+ };
821
+ defiCache.set(address, { data: summary, ts: Date.now() });
822
+ return summary;
823
+ } finally {
824
+ defiInflight.delete(address);
825
+ }
826
+ })();
827
+ defiInflight.set(address, inflight);
828
+ return inflight;
829
+ }
830
+ async function fetchOneDefiProtocol(address, protocol, apiKey) {
831
+ const url = `${BLOCKVISION_BASE}/account/defiPortfolio?address=${encodeURIComponent(address)}&protocol=${protocol}`;
832
+ let res;
833
+ try {
834
+ res = await fetch(url, {
835
+ headers: { "x-api-key": apiKey, accept: "application/json" },
836
+ signal: AbortSignal.timeout(DEFI_PORTFOLIO_TIMEOUT_MS)
837
+ });
838
+ } catch (err) {
839
+ console.warn(`[defi] ${protocol} fetch threw:`, err);
840
+ return null;
841
+ }
842
+ if (!res.ok) {
843
+ console.warn(`[defi] ${protocol} HTTP ${res.status}`);
844
+ return null;
845
+ }
846
+ let json;
847
+ try {
848
+ json = await res.json();
849
+ } catch (err) {
850
+ console.warn(`[defi] ${protocol} JSON parse failed:`, err);
851
+ return null;
852
+ }
853
+ if (json.code !== 200 || !json.result) return null;
854
+ return json.result;
855
+ }
856
+ var PAIR_A_COIN_KEYS = ["coinTypeA", "coinTypeX", "tokenXType"];
857
+ var PAIR_B_COIN_KEYS = ["coinTypeB", "coinTypeY", "tokenYType"];
858
+ var PAIR_A_AMOUNT_KEYS = [
859
+ "balanceA",
860
+ "amountA",
861
+ "coinAmountA",
862
+ "coinAAmount",
863
+ "coinTypeAAmount",
864
+ "tokenXBalance",
865
+ "tokenXAmount",
866
+ "amountX",
867
+ "valueA"
868
+ ];
869
+ var PAIR_B_AMOUNT_KEYS = [
870
+ "balanceB",
871
+ "amountB",
872
+ "coinAmountB",
873
+ "coinBAmount",
874
+ "coinTypeBAmount",
875
+ "tokenYBalance",
876
+ "tokenYAmount",
877
+ "amountY",
878
+ "valueB"
879
+ ];
880
+ var PAIR_A_DECIMALS_KEYS = ["coinTypeADecimals", "tokenXDecimals", "decimalsA"];
881
+ var PAIR_B_DECIMALS_KEYS = ["coinTypeBDecimals", "tokenYDecimals", "decimalsB"];
882
+ var SINGLE_COIN_KEYS = ["coinType", "depositToken", "token"];
883
+ var SINGLE_AMOUNT_KEYS = ["amount", "balance", "value", "equity"];
884
+ var SINGLE_DECIMALS_KEYS = ["decimals", "decimal", "coinDecimals"];
885
+ var DEBT_KEYS = /* @__PURE__ */ new Set([
886
+ "borrow",
887
+ "borrows",
888
+ "debt",
889
+ "debts",
890
+ "borrowings",
891
+ "borrowedpools"
892
+ ]);
893
+ var SKIP_KEYS = /* @__PURE__ */ new Set([
894
+ "rewards",
895
+ "reward",
896
+ "fees",
897
+ "fee",
898
+ "pendingrewards",
899
+ "incentiveinfos",
900
+ "feereward",
901
+ "incentivereward"
902
+ ]);
903
+ function isCoinTypeString(v) {
904
+ if (typeof v !== "string" || !v.includes("::")) return false;
905
+ return v.startsWith("0x") || /^[0-9a-fA-F]/.test(v);
906
+ }
907
+ function ensure0xPrefix(coinType) {
908
+ return coinType.startsWith("0x") ? coinType : "0x" + coinType;
909
+ }
910
+ function isAmountValue(v) {
911
+ return typeof v === "string" && v.trim().length > 0 || typeof v === "number" && Number.isFinite(v);
912
+ }
913
+ function isFiniteNumber(v) {
914
+ return typeof v === "number" && Number.isFinite(v);
915
+ }
916
+ function pickField(obj, keys, predicate) {
917
+ for (const k of keys) {
918
+ const v = obj[k];
919
+ if (predicate(v)) return v;
920
+ }
921
+ return void 0;
922
+ }
923
+ function nestedDecimals(node) {
924
+ if (!node || typeof node !== "object") return void 0;
925
+ const obj = node;
926
+ if (typeof obj.decimals === "number") return obj.decimals;
927
+ return void 0;
928
+ }
929
+ function toHumanQuantity(raw, decimalsHint) {
930
+ if (typeof raw === "number") {
931
+ if (!Number.isFinite(raw)) return 0;
932
+ if (!Number.isInteger(raw)) return raw;
933
+ if (decimalsHint != null) return raw / 10 ** decimalsHint;
934
+ return raw;
935
+ }
936
+ const trimmed = raw.trim();
937
+ if (trimmed.length === 0) return 0;
938
+ if (trimmed.includes(".") || trimmed.includes("e") || trimmed.includes("E")) {
939
+ const n2 = Number(trimmed);
940
+ return Number.isFinite(n2) ? n2 : 0;
941
+ }
942
+ const n = Number(trimmed);
943
+ if (!Number.isFinite(n)) return 0;
944
+ const dec = decimalsHint ?? 9;
945
+ return n / 10 ** dec;
946
+ }
947
+ function priceFor(coinType, prices) {
948
+ const prefixed = ensure0xPrefix(coinType);
949
+ const norm = normalizeCoinType(prefixed);
950
+ return prices[norm] ?? prices[prefixed] ?? prices[coinType] ?? STABLE_USD_PRICES[norm] ?? 0;
951
+ }
952
+ function toUsd(coinType, raw, decimalsHint, prices) {
953
+ if (raw == null || typeof raw !== "string" && typeof raw !== "number") return 0;
954
+ if (typeof raw === "string" && raw.trim().length === 0) return 0;
955
+ const prefixed = ensure0xPrefix(coinType);
956
+ const decimals = typeof decimalsHint === "number" ? decimalsHint : getDecimalsForCoinType(prefixed);
957
+ const human = toHumanQuantity(raw, decimals);
958
+ if (!Number.isFinite(human)) return 0;
959
+ return human * priceFor(prefixed, prices);
960
+ }
961
+ function extractPair(obj) {
962
+ const coinTypeA = pickField(obj, PAIR_A_COIN_KEYS, isCoinTypeString);
963
+ const coinTypeB = pickField(obj, PAIR_B_COIN_KEYS, isCoinTypeString);
964
+ if (!coinTypeA || !coinTypeB) return null;
965
+ const amountA = pickField(obj, PAIR_A_AMOUNT_KEYS, isAmountValue);
966
+ const amountB = pickField(obj, PAIR_B_AMOUNT_KEYS, isAmountValue);
967
+ if (amountA == null || amountB == null) return null;
968
+ const decimalsA = pickField(obj, PAIR_A_DECIMALS_KEYS, isFiniteNumber) ?? nestedDecimals(obj.coinA);
969
+ const decimalsB = pickField(obj, PAIR_B_DECIMALS_KEYS, isFiniteNumber) ?? nestedDecimals(obj.coinB);
970
+ return { coinTypeA, amountA, decimalsA, coinTypeB, amountB, decimalsB };
971
+ }
972
+ function extractSingle(obj) {
973
+ const coinType = pickField(obj, SINGLE_COIN_KEYS, isCoinTypeString);
974
+ if (!coinType) return null;
975
+ const amount = pickField(obj, SINGLE_AMOUNT_KEYS, isAmountValue);
976
+ if (amount == null) return null;
977
+ const decimals = pickField(obj, SINGLE_DECIMALS_KEYS, isFiniteNumber);
978
+ const isBorrow = obj.type === "Borrow";
979
+ return { coinType, amount, decimals, isBorrow };
980
+ }
981
+ function walkProtocolResponse(result, prices) {
982
+ let total = 0;
983
+ walk(result, false);
984
+ return total;
985
+ function walk(node, debtSide) {
986
+ if (!node || typeof node !== "object") return;
987
+ if (Array.isArray(node)) {
988
+ for (const item of node) walk(item, debtSide);
989
+ return;
990
+ }
991
+ const obj = node;
992
+ if (typeof obj.totalSupplyValue === "number") total += obj.totalSupplyValue;
993
+ if (typeof obj.totalCollateralValue === "number") total += obj.totalCollateralValue;
994
+ if (typeof obj.totalLockedScaValue === "number") total += obj.totalLockedScaValue;
995
+ if (typeof obj.totalDebtValue === "number") total -= obj.totalDebtValue;
996
+ const pair = extractPair(obj);
997
+ if (pair) {
998
+ const a = toUsd(pair.coinTypeA, pair.amountA, pair.decimalsA, prices);
999
+ const b = toUsd(pair.coinTypeB, pair.amountB, pair.decimalsB, prices);
1000
+ total += debtSide ? -(a + b) : a + b;
1001
+ } else {
1002
+ const single = extractSingle(obj);
1003
+ if (single) {
1004
+ const usd = toUsd(single.coinType, single.amount, single.decimals, prices);
1005
+ total += debtSide || single.isBorrow ? -usd : usd;
1006
+ }
1007
+ }
1008
+ for (const [k, v] of Object.entries(obj)) {
1009
+ const lk = k.toLowerCase();
1010
+ if (SKIP_KEYS.has(lk)) continue;
1011
+ const childDebt = debtSide || DEBT_KEYS.has(lk);
1012
+ walk(v, childDebt);
1013
+ }
1014
+ }
1015
+ }
1016
+ function collectCoinTypes(obj, out) {
1017
+ if (!obj || typeof obj !== "object") return;
1018
+ if (Array.isArray(obj)) {
1019
+ for (const x of obj) collectCoinTypes(x, out);
1020
+ return;
1021
+ }
1022
+ for (const [k, v] of Object.entries(obj)) {
1023
+ if (typeof v === "string" && isCoinTypeString(v)) {
1024
+ const lk = k.toLowerCase();
1025
+ if (lk.includes("cointype") || lk === "tokenxtype" || lk === "tokenytype" || lk === "deposittoken" || lk === "rewardstoken" || lk === "token" || lk === "coinaddress" || lk === "phantomtype" || lk === "typename") {
1026
+ out.add(ensure0xPrefix(v));
1027
+ }
1028
+ } else if (typeof v === "object" && v !== null) {
1029
+ collectCoinTypes(v, out);
1030
+ }
1031
+ }
1032
+ }
1033
+ function normalizeBluefin(result, prices) {
1034
+ const data = result.bluefin ?? {};
1035
+ let total = 0;
1036
+ for (const lp of data.lps ?? []) {
1037
+ if (lp.coinTypeA && lp.coinAmountA != null) {
1038
+ total += toUsd(lp.coinTypeA, lp.coinAmountA, void 0, prices);
1039
+ }
1040
+ if (lp.coinTypeB && lp.coinAmountB != null) {
1041
+ total += toUsd(lp.coinTypeB, lp.coinAmountB, void 0, prices);
1042
+ }
1043
+ }
1044
+ if (data.usdcVault?.amount != null) {
1045
+ total += toUsd(USDC_TYPE_FULL, data.usdcVault.amount, 6, prices);
1046
+ }
1047
+ if (data.blueVault?.amount != null) {
1048
+ total += toUsd(BLUE_TYPE_FULL, data.blueVault.amount, 9, prices);
1049
+ }
1050
+ return total;
1051
+ }
1052
+ function normalizeHaedal(result, prices) {
1053
+ const data = result.haedal ?? {};
1054
+ let total = 0;
1055
+ for (const lp of data.lps ?? []) {
1056
+ if (lp.coinTypeA && lp.balanceA != null) {
1057
+ total += toUsd(lp.coinTypeA, lp.balanceA, void 0, prices);
1058
+ }
1059
+ if (lp.coinTypeB && lp.balanceB != null) {
1060
+ total += toUsd(lp.coinTypeB, lp.balanceB, void 0, prices);
1061
+ }
1062
+ }
1063
+ for (const stake of data.stakings ?? []) {
1064
+ if (stake.sui_amount != null) {
1065
+ total += toUsd(SUI_TYPE_FULL, stake.sui_amount, 9, prices);
1066
+ }
1067
+ }
1068
+ return total;
1069
+ }
1070
+ function normalizeKai(result, prices) {
1071
+ const data = result.kai ?? {};
1072
+ let total = 0;
1073
+ for (const v of data.vaults ?? []) {
1074
+ const coinType = v.coin?.p?.phantomType ?? v.coin?.p?.typeName;
1075
+ if (coinType && v.equity != null) {
1076
+ total += toUsd(coinType, v.equity, v.coin?.decimals, prices);
1077
+ }
1078
+ }
1079
+ for (const lp of data.lpVaults ?? []) {
1080
+ const coinTypeA = lp.coinA?.p?.phantomType ?? lp.coinA?.p?.typeName;
1081
+ const coinTypeB = lp.coinB?.p?.phantomType ?? lp.coinB?.p?.typeName;
1082
+ if (coinTypeA && lp.balanceA != null) {
1083
+ total += toUsd(coinTypeA, lp.balanceA, lp.coinA?.decimals, prices);
1084
+ }
1085
+ if (coinTypeB && lp.balanceB != null) {
1086
+ total += toUsd(coinTypeB, lp.balanceB, lp.coinB?.decimals, prices);
1087
+ }
1088
+ }
1089
+ return total;
1090
+ }
1091
+ function sumBareStakings(data, impliedCoinType, decimals, prices) {
1092
+ if (!data) return 0;
1093
+ let total = 0;
1094
+ for (const s of data.stakings ?? []) {
1095
+ const amt = s.sui_amount ?? s.amount;
1096
+ if (amt != null) total += toUsd(impliedCoinType, amt, decimals, prices);
1097
+ }
1098
+ return total;
1099
+ }
1100
+ function normalizeSuistake(result, prices) {
1101
+ const data = result.suistake;
1102
+ return sumBareStakings(data, SUI_TYPE_FULL, 9, prices);
1103
+ }
1104
+ function normalizeWalrus(result, prices) {
1105
+ const data = result.walrus;
1106
+ return sumBareStakings(data, WAL_TYPE_FULL, 9, prices);
1107
+ }
1108
+ function normalizeSuinsStaking(result, prices) {
1109
+ const data = result["suins-staking"] ?? result.suinsStaking ?? result.suins_staking;
1110
+ return sumBareStakings(data, NS_TYPE_FULL, 6, prices);
1111
+ }
1112
+ var BESPOKE_NORMALIZERS = {
1113
+ bluefin: normalizeBluefin,
1114
+ haedal: normalizeHaedal,
1115
+ kai: normalizeKai,
1116
+ suistake: normalizeSuistake,
1117
+ walrus: normalizeWalrus,
1118
+ "suins-staking": normalizeSuinsStaking
1119
+ };
1120
+ function normalizeProtocol(protocol, result, prices) {
1121
+ const bespoke = BESPOKE_NORMALIZERS[protocol];
1122
+ if (bespoke) return bespoke(result, prices);
1123
+ return walkProtocolResponse(result, prices);
1124
+ }
714
1125
  function clearPortfolioCache() {
715
1126
  portfolioCache.clear();
716
1127
  portfolioInflight.clear();
@@ -808,7 +1219,7 @@ var balanceCheckTool = buildTool({
808
1219
  const address = targetAddress;
809
1220
  const mgr = getMcpManager(context);
810
1221
  const hasPositionFetcher = !!context.positionFetcher;
811
- const [portfolio, positions, rewards, serverPositions] = await Promise.all([
1222
+ const [portfolio, positions, rewards, serverPositions, defiPortfolio] = await Promise.all([
812
1223
  loadPortfolio(
813
1224
  address,
814
1225
  context.blockvisionApiKey,
@@ -839,7 +1250,22 @@ var balanceCheckTool = buildTool({
839
1250
  hasPositionFetcher ? context.positionFetcher(address).catch((err) => {
840
1251
  console.warn("[balance_check] positionFetcher failed:", err);
841
1252
  return null;
842
- }) : Promise.resolve(null)
1253
+ }) : Promise.resolve(null),
1254
+ // [v0.50] DeFi leg — independent of NAVI (excluded) and the wallet
1255
+ // portfolio (which only has coin holdings). Failure here surfaces
1256
+ // as defi.totalUsd === 0 and `source: 'degraded'`, leaving the
1257
+ // rest of balance_check unaffected. The fetcher fills its own
1258
+ // prices via fetchTokenPrices for any coin types it discovers.
1259
+ fetchAddressDefiPortfolio(address, context.blockvisionApiKey).catch((err) => {
1260
+ console.warn("[balance_check] defi fetch failed:", err);
1261
+ const fallback = {
1262
+ totalUsd: 0,
1263
+ perProtocol: {},
1264
+ pricedAt: Date.now(),
1265
+ source: "degraded"
1266
+ };
1267
+ return fallback;
1268
+ })
843
1269
  ]);
844
1270
  await applyVsuiPriceFallback(portfolio);
845
1271
  let availableUsd = 0;
@@ -886,13 +1312,17 @@ var balanceCheckTool = buildTool({
886
1312
  const visibleHoldings = holdings.filter((h) => h.usdValue >= 0.01).sort((a, b) => b.usdValue - a.usdValue);
887
1313
  const usdcHolding2 = holdings.find((h) => h.symbol === "USDC");
888
1314
  const saveableUsdc = usdcHolding2 ? usdcHolding2.balance : 0;
1315
+ const defi2 = defiPortfolio;
889
1316
  const bal = {
890
1317
  available: availableUsd,
891
1318
  savings,
892
1319
  debt,
893
1320
  pendingRewards: pendingRewardsUsd,
894
1321
  gasReserve: gasReserveUsd2,
895
- total: availableUsd + savings + gasReserveUsd2 + pendingRewardsUsd - debt,
1322
+ defi: defi2.totalUsd,
1323
+ defiByProtocol: defi2.perProtocol,
1324
+ defiSource: defi2.source,
1325
+ total: availableUsd + savings + gasReserveUsd2 + pendingRewardsUsd + defi2.totalUsd - debt,
896
1326
  stables: stablesUsd,
897
1327
  holdings: visibleHoldings,
898
1328
  saveableUsdc,
@@ -902,9 +1332,10 @@ var balanceCheckTool = buildTool({
902
1332
  };
903
1333
  const holdingsList = visibleHoldings.map((h) => `${h.symbol}: ${h.balance < 1 ? h.balance.toFixed(6) : h.balance.toFixed(2)} ($${h.usdValue.toFixed(2)})`).join(", ");
904
1334
  const subjectPrefix = isSelfQuery ? "Balance" : `Balance for ${address.slice(0, 6)}\u2026${address.slice(-4)}`;
1335
+ const defiSummaryText = defi2.totalUsd > 0 ? ` Other DeFi positions (LPs/staking/lending across ${Object.keys(defi2.perProtocol).join("/")}): $${defi2.totalUsd.toFixed(2)}.` : "";
905
1336
  return {
906
1337
  data: bal,
907
- displayText: `${subjectPrefix}: $${bal.total.toFixed(2)} total. Wallet holdings (NOT savings): ${holdingsList || "none"}. NAVI savings deposits: $${bal.savings.toFixed(2)}. Saveable USDC (only USDC can be saved): ${saveableUsdc.toFixed(2)} USDC.`
1338
+ displayText: `${subjectPrefix}: $${bal.total.toFixed(2)} total. Wallet holdings (NOT savings): ${holdingsList || "none"}. NAVI savings deposits: $${bal.savings.toFixed(2)}.${defiSummaryText} Saveable USDC (only USDC can be saved): ${saveableUsdc.toFixed(2)} USDC.`
908
1339
  };
909
1340
  }
910
1341
  if (input.address && context.walletAddress && input.address.toLowerCase() !== context.walletAddress.toLowerCase()) {
@@ -913,13 +1344,28 @@ var balanceCheckTool = buildTool({
913
1344
  );
914
1345
  }
915
1346
  const agent = requireAgent(context);
916
- const balance = await agent.balance();
1347
+ const fetchAddress = targetAddress ?? context.walletAddress;
1348
+ const [balance, defi] = await Promise.all([
1349
+ agent.balance(),
1350
+ fetchAddressDefiPortfolio(fetchAddress, context.blockvisionApiKey).catch((err) => {
1351
+ console.warn("[balance_check] sdk-path defi fetch failed:", err);
1352
+ const fallback = {
1353
+ totalUsd: 0,
1354
+ perProtocol: {},
1355
+ pricedAt: Date.now(),
1356
+ source: "degraded"
1357
+ };
1358
+ return fallback;
1359
+ })
1360
+ ]);
917
1361
  const gasReserveUsd = typeof balance.gasReserve === "number" ? balance.gasReserve : balance.gasReserve.usdEquiv ?? 0;
918
1362
  const stablesTotal = typeof balance.stables === "number" ? balance.stables : Object.values(balance.stables).reduce((a, b) => a + b, 0);
919
1363
  const sdkHoldings = balance.holdings;
920
1364
  const holdingsArr = Array.isArray(sdkHoldings) ? sdkHoldings : [];
921
1365
  const usdcHolding = holdingsArr.find((h) => h.symbol === "USDC");
922
1366
  const sdkSaveableUsdc = usdcHolding ? usdcHolding.balance ?? 0 : 0;
1367
+ const sdkDefiSummaryText = defi.totalUsd > 0 ? ` Other DeFi positions (LPs/staking/lending across ${Object.keys(defi.perProtocol).join("/")}): $${defi.totalUsd.toFixed(2)}.` : "";
1368
+ const sdkTotal = balance.total + defi.totalUsd;
923
1369
  return {
924
1370
  data: {
925
1371
  available: balance.available,
@@ -927,14 +1373,17 @@ var balanceCheckTool = buildTool({
927
1373
  debt: balance.debt,
928
1374
  pendingRewards: balance.pendingRewards,
929
1375
  gasReserve: gasReserveUsd,
930
- total: balance.total,
1376
+ defi: defi.totalUsd,
1377
+ defiByProtocol: defi.perProtocol,
1378
+ defiSource: defi.source,
1379
+ total: sdkTotal,
931
1380
  stables: stablesTotal,
932
1381
  holdings: holdingsArr,
933
1382
  saveableUsdc: sdkSaveableUsdc,
934
1383
  address: targetAddress ?? "",
935
1384
  isSelfQuery: true
936
1385
  },
937
- displayText: `Balance: $${balance.total.toFixed(2)} total. Wallet: $${balance.available.toFixed(2)} available. NAVI savings deposits: $${balance.savings.toFixed(2)}. Saveable USDC (only USDC can be saved): ${sdkSaveableUsdc.toFixed(2)} USDC.`
1386
+ displayText: `Balance: $${sdkTotal.toFixed(2)} total. Wallet: $${balance.available.toFixed(2)} available. NAVI savings deposits: $${balance.savings.toFixed(2)}.${sdkDefiSummaryText} Saveable USDC (only USDC can be saved): ${sdkSaveableUsdc.toFixed(2)} USDC.`
938
1387
  };
939
1388
  }
940
1389
  });
@@ -1576,7 +2025,7 @@ var transactionHistoryTool = buildTool({
1576
2025
  const targetAddress = input.address ?? context.walletAddress;
1577
2026
  const isSelfQuery = !!targetAddress && !!context.walletAddress && targetAddress.toLowerCase() === context.walletAddress.toLowerCase();
1578
2027
  const prices = context.tokenPrices;
1579
- const priceFor = (sym) => {
2028
+ const priceFor2 = (sym) => {
1580
2029
  if (!sym || !prices) return void 0;
1581
2030
  return prices[sym.toUpperCase()] ?? prices[sym.toLowerCase()] ?? prices[sym];
1582
2031
  };
@@ -1597,8 +2046,8 @@ var transactionHistoryTool = buildTool({
1597
2046
  if (r.amount == null) return false;
1598
2047
  const sym = r.asset?.toUpperCase() ?? "";
1599
2048
  const isStableLike = sym === "USDC" || sym === "USDT" || sym === "WUSDC" || sym === "WUSDT" || sym === "SUIUSDT" || sym === "USDY" || sym === "USDSUI" || sym === "USDE" || sym === "AUSD" || sym === "FDUSD" || sym === "BUCK";
1600
- const usd = isStableLike ? r.amount : (priceFor(sym) ?? 0) * r.amount;
1601
- if (!isStableLike && priceFor(sym) == null) return true;
2049
+ const usd = isStableLike ? r.amount : (priceFor2(sym) ?? 0) * r.amount;
2050
+ if (!isStableLike && priceFor2(sym) == null) return true;
1602
2051
  return usd >= minUsd;
1603
2052
  });
1604
2053
  }