@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 +3 -0
- package/dist/index.js +459 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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)}
|
|
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
|
|
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
|
-
|
|
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: $${
|
|
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
|
|
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 : (
|
|
1601
|
-
if (!isStableLike &&
|
|
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
|
}
|