@1delta/data-sdk 0.0.46 → 0.0.48

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
@@ -1,3 +1,4 @@
1
1
  export * from './chains';
2
2
  export * from './lending';
3
+ export * from './remoteCache';
3
4
  export * from './tokens';
package/dist/index.js CHANGED
@@ -95,6 +95,8 @@ var LENDER_DATA_DEFAULTS = () => ({
95
95
  llamaLendMarkets: {},
96
96
  resupplyConfig: {},
97
97
  curvanceConfig: {},
98
+ twyneConfig: {},
99
+ twyneMarkets: {},
98
100
  fraxlendConfig: {},
99
101
  eulerConfigs: {},
100
102
  eulerVaults: {},
@@ -745,6 +747,60 @@ var curvancePositionManager = (lender, chainId, marketManager, kind) => {
745
747
  return pms.simplePositionManager ?? pms.vaultPositionManager ?? pms.nativeVaultPositionManager ?? pms.pendlePtPositionManager;
746
748
  };
747
749
 
750
+ // src/lending/twyne.ts
751
+ var twyneConfig = () => getGlobalData2()?.twyneConfig;
752
+ var twyneConfigFor = (lender, chainId) => getGlobalData2()?.twyneConfig?.[twyneBaseKey(lender)]?.[String(chainId)];
753
+ var twyneMarkets = () => getGlobalData2()?.twyneMarkets;
754
+ var twyneChainData = (lender, chainId) => getGlobalData2()?.twyneMarkets?.[twyneBaseKey(lender)]?.[String(chainId)];
755
+ var twyneBaseKey = (lender) => lender?.startsWith("TWYNE") ? "TWYNE" : lender;
756
+ var twyneMarketKey = (chainId, intermediateVault, targetAsset) => `TWYNE_${chainId}_${intermediateVault.replace(/^0x/i, "").toUpperCase()}_${targetAsset.replace(/^0x/i, "").toUpperCase()}`;
757
+ var assertTwyneKeysUnique = (markets, chainId) => {
758
+ const byKey = /* @__PURE__ */ new Map();
759
+ for (const m of markets) {
760
+ const key = twyneMarketKey(chainId, m.intermediateVault, m.targetAsset);
761
+ const triple = `${m.intermediateVault}|${m.targetVault}|${m.targetAsset}`.toLowerCase();
762
+ const seen = byKey.get(key);
763
+ if (seen && seen !== triple) {
764
+ throw new Error(
765
+ `assertTwyneKeysUnique: key ${key} maps to two triples (${seen} and ${triple}). The target vault must be added back into the key.`
766
+ );
767
+ }
768
+ byKey.set(key, triple);
769
+ }
770
+ };
771
+ var twyneMarketByKey = (lender, chainId) => {
772
+ const upper = lender?.toUpperCase();
773
+ return twyneChainData(lender, chainId)?.markets?.find((m) => m.key.toUpperCase() === upper);
774
+ };
775
+ var twyneMarketFor = (lender, chainId, intermediateVault, targetAsset) => {
776
+ const iv = intermediateVault?.toLowerCase();
777
+ const asset = targetAsset?.toLowerCase();
778
+ return twyneChainData(lender, chainId)?.markets?.find(
779
+ (m) => m.intermediateVault.toLowerCase() === iv && m.targetAsset.toLowerCase() === asset
780
+ );
781
+ };
782
+ var twyneMarketsForIntermediateVault = (lender, chainId, intermediateVault) => {
783
+ const iv = intermediateVault?.toLowerCase();
784
+ return (twyneChainData(lender, chainId)?.markets ?? []).filter((m) => m.intermediateVault.toLowerCase() === iv);
785
+ };
786
+ var twyneLendersByChain = (chainId) => {
787
+ const cfg = getGlobalData2()?.twyneConfig ?? {};
788
+ const chain = String(chainId);
789
+ return Object.keys(cfg).filter((lender) => !!cfg[lender]?.[chain]?.collateralVaultFactory);
790
+ };
791
+ var twyneOperator = (lender, chainId, vaultType, kind) => twyneConfigFor(lender, chainId)?.operators?.[vaultType]?.[kind];
792
+ var twyneBeaconFor = (lender, chainId, targetVault) => {
793
+ const map = twyneConfigFor(lender, chainId)?.beacons;
794
+ if (!map || !targetVault) return void 0;
795
+ const direct = map[targetVault];
796
+ if (direct) return direct;
797
+ const target = targetVault.toLowerCase();
798
+ for (const key of Object.keys(map)) {
799
+ if (key.toLowerCase() === target) return map[key];
800
+ }
801
+ return void 0;
802
+ };
803
+
748
804
  // src/lending/fraxlend.ts
749
805
  var FRAXLEND_ETHEREUM_CONFIG = {
750
806
  deployer: "0xD6E9D27C75Afd88ad24Cd5EdccdC76fd2fc3A751",
@@ -862,12 +918,143 @@ var parseFraxlendPairKey = (lender) => {
862
918
  return { chainId: m[1], pair: m[2].toLowerCase() };
863
919
  };
864
920
 
921
+ // src/remoteCache.ts
922
+ var FRESH_TTL_MS = 5 * 60 * 1e3;
923
+ var REMOTE_CACHE_STORE_TTL_S = 7 * 24 * 60 * 60;
924
+ var STATE_KEY = "__1delta_remote_cache__";
925
+ function getState() {
926
+ const g = globalThis;
927
+ if (!g[STATE_KEY]) g[STATE_KEY] = { memo: /* @__PURE__ */ new Map(), inflight: /* @__PURE__ */ new Map() };
928
+ return g[STATE_KEY];
929
+ }
930
+ function setRemoteCacheStore(next) {
931
+ getState().store = next;
932
+ }
933
+ function setRemoteCacheGithubToken(token) {
934
+ getState().githubToken = token;
935
+ }
936
+ function clearRemoteCache() {
937
+ const state = getState();
938
+ state.memo.clear();
939
+ state.inflight.clear();
940
+ }
941
+ var RAW_HOST = "https://raw.githubusercontent.com/";
942
+ function githubApiMirror(url) {
943
+ if (!url.startsWith(RAW_HOST)) return void 0;
944
+ const [owner, repo, ref, ...path] = url.slice(RAW_HOST.length).split("/");
945
+ if (!owner || !repo || !ref || path.length === 0) return void 0;
946
+ return `https://api.github.com/repos/${owner}/${repo}/contents/${path.join("/")}?ref=${ref}`;
947
+ }
948
+ var RemoteFetchError = class extends Error {
949
+ constructor(message, status) {
950
+ super(message);
951
+ this.status = status;
952
+ this.name = "RemoteFetchError";
953
+ }
954
+ /** 404/410 — re-fetching cannot change the answer. */
955
+ get isMissing() {
956
+ return this.status === 404 || this.status === 410;
957
+ }
958
+ };
959
+ async function fetchRemoteJson(url, label) {
960
+ const res = await fetch(url);
961
+ if (res.ok) return await res.json();
962
+ const mirror = githubApiMirror(url);
963
+ const worthRetrying = res.status === 429 || res.status >= 500;
964
+ if (!mirror || !worthRetrying) {
965
+ throw new RemoteFetchError(`${label} fetch failed: HTTP ${res.status} from ${new URL(url).host}`, res.status);
966
+ }
967
+ const headers = {
968
+ Accept: "application/vnd.github.raw",
969
+ // The GitHub API rejects a request that sends no User-Agent.
970
+ "User-Agent": "1delta-data-sdk"
971
+ };
972
+ const token = getState().githubToken;
973
+ if (token) headers.Authorization = `Bearer ${token}`;
974
+ const viaApi = await fetch(mirror, { headers });
975
+ if (!viaApi.ok) {
976
+ throw new RemoteFetchError(
977
+ `${label} fetch failed: HTTP ${res.status} from raw.githubusercontent.com, HTTP ${viaApi.status} from api.github.com`,
978
+ res.status
979
+ );
980
+ }
981
+ return await viaApi.json();
982
+ }
983
+ var storeKey = (key) => `remote-cache:v1:${key}`;
984
+ async function readStore(key, isValid) {
985
+ const { store } = getState();
986
+ if (!store) return void 0;
987
+ try {
988
+ const raw = await store.get(storeKey(key));
989
+ if (!raw) return void 0;
990
+ const parsed = JSON.parse(raw);
991
+ if (typeof parsed?.at !== "number" || !isValid(parsed.value)) return void 0;
992
+ return parsed;
993
+ } catch {
994
+ return void 0;
995
+ }
996
+ }
997
+ async function writeStore(key, entry) {
998
+ const { store } = getState();
999
+ if (!store) return;
1000
+ try {
1001
+ await store.put(storeKey(key), JSON.stringify(entry), REMOTE_CACHE_STORE_TTL_S);
1002
+ } catch {
1003
+ }
1004
+ }
1005
+ async function loadCached(key, load, isValid) {
1006
+ const { memo, inflight } = getState();
1007
+ const cached = memo.get(key);
1008
+ if (cached && Date.now() - cached.at < FRESH_TTL_MS) return cached.value;
1009
+ const pending = inflight.get(key);
1010
+ if (pending) return pending;
1011
+ const task = (async () => {
1012
+ const stored = await readStore(key, isValid);
1013
+ if (stored && Date.now() - stored.at < FRESH_TTL_MS) {
1014
+ memo.set(key, stored);
1015
+ return stored.value;
1016
+ }
1017
+ try {
1018
+ const value = await load();
1019
+ const entry = { at: Date.now(), value };
1020
+ memo.set(key, entry);
1021
+ await writeStore(key, entry);
1022
+ return value;
1023
+ } catch (error) {
1024
+ const stale = memo.get(key) ?? stored;
1025
+ if (!stale) throw error;
1026
+ memo.set(key, stale);
1027
+ return stale.value;
1028
+ }
1029
+ })().finally(() => {
1030
+ inflight.delete(key);
1031
+ });
1032
+ inflight.set(key, task);
1033
+ return task;
1034
+ }
1035
+ async function loadCachedJson(key, url, isValid, label = key) {
1036
+ return loadCached(key, () => fetchRemoteJson(url, label), isValid);
1037
+ }
1038
+
865
1039
  // src/tokens.ts
866
1040
  var getListUrl = (chainId) => `https://raw.githubusercontent.com/1delta-DAO/token-lists/main/${chainId}.json`;
1041
+ var MAIN_TOKENS_URL = "https://raw.githubusercontent.com/1delta-DAO/token-lists/main/main-tokens.json";
1042
+ var isTokenListFile = (value) => !!value?.list && typeof value.list === "object";
1043
+ async function loadTokenListFile(chainId) {
1044
+ return loadCached(
1045
+ `token-list:${chainId}`,
1046
+ async () => {
1047
+ const json = await fetchRemoteJson(getListUrl(chainId), `Token list for chain ${chainId}`);
1048
+ return {
1049
+ list: json.list,
1050
+ mainTokens: json.mainTokens ?? []
1051
+ };
1052
+ },
1053
+ isTokenListFile
1054
+ );
1055
+ }
867
1056
  async function fetchTokenList(chainId) {
868
- const data = await fetch(getListUrl(chainId));
869
- if (!data.ok) throw new Error(`HTTP error! status: ${data.status}`);
870
- return (await data.json()).list;
1057
+ return (await loadTokenListFile(chainId)).list;
871
1058
  }
872
1059
  async function fetchTokenLists(chainIds) {
873
1060
  const promises = chainIds.map(async (chainId) => {
@@ -878,25 +1065,27 @@ async function fetchTokenLists(chainIds) {
878
1065
  return Object.fromEntries(results);
879
1066
  }
880
1067
  async function fetchTokenListFile(chainId) {
881
- const data = await fetch(getListUrl(chainId));
882
- if (!data.ok) throw new Error(`HTTP error! status: ${data.status}`);
883
- const json = await data.json();
884
- return {
885
- list: json.list,
886
- mainTokens: json.mainTokens ?? []
887
- };
1068
+ const file = await loadTokenListFile(chainId);
1069
+ return { list: file.list, mainTokens: file.mainTokens };
888
1070
  }
889
- var MAIN_TOKENS_URL = "https://raw.githubusercontent.com/1delta-DAO/token-lists/main/main-tokens.json";
890
1071
  async function fetchMainTokensMap() {
891
- const data = await fetch(MAIN_TOKENS_URL);
892
- if (!data.ok) return void 0;
893
- return await data.json();
1072
+ try {
1073
+ return await loadCached(
1074
+ "main-tokens",
1075
+ () => fetchRemoteJson(MAIN_TOKENS_URL, "main-tokens"),
1076
+ (value) => !!value && typeof value === "object"
1077
+ );
1078
+ } catch {
1079
+ return void 0;
1080
+ }
894
1081
  }
895
1082
 
896
1083
  exports.CURVANCE_MONAD_CONFIG = CURVANCE_MONAD_CONFIG;
897
1084
  exports.FRAXLEND_ETHEREUM_CONFIG = FRAXLEND_ETHEREUM_CONFIG;
898
1085
  exports.GEARBOX_ACCOUNT_COMPRESSOR_V310 = GEARBOX_ACCOUNT_COMPRESSOR_V310;
1086
+ exports.REMOTE_CACHE_STORE_TTL_S = REMOTE_CACHE_STORE_TTL_S;
899
1087
  exports.RESUPPLY_ETHEREUM_CONFIG = RESUPPLY_ETHEREUM_CONFIG;
1088
+ exports.RemoteFetchError = RemoteFetchError;
900
1089
  exports.aaveOracles = aaveOracles;
901
1090
  exports.aaveOraclesConfig = aaveOraclesConfig;
902
1091
  exports.aavePools = aavePools;
@@ -907,8 +1096,10 @@ exports.aaveV4Peripherals = aaveV4Peripherals;
907
1096
  exports.aaveV4SpokeLenderKey = aaveV4SpokeLenderKey;
908
1097
  exports.aaveV4Spokes = aaveV4Spokes;
909
1098
  exports.aaveWethGateway = aaveWethGateway;
1099
+ exports.assertTwyneKeysUnique = assertTwyneKeysUnique;
910
1100
  exports.chainLogoURI = chainLogoURI;
911
1101
  exports.chains = chains;
1102
+ exports.clearRemoteCache = clearRemoteCache;
912
1103
  exports.compoundV2Oracles = compoundV2Oracles;
913
1104
  exports.compoundV2OraclesConfig = compoundV2OraclesConfig;
914
1105
  exports.compoundV2Pools = compoundV2Pools;
@@ -942,6 +1133,7 @@ exports.exactlyMarketByAsset = exactlyMarketByAsset;
942
1133
  exports.exactlyMarkets = exactlyMarkets;
943
1134
  exports.exactlyMarketsByChain = exactlyMarketsByChain;
944
1135
  exports.fetchMainTokensMap = fetchMainTokensMap;
1136
+ exports.fetchRemoteJson = fetchRemoteJson;
945
1137
  exports.fetchTokenList = fetchTokenList;
946
1138
  exports.fetchTokenListFile = fetchTokenListFile;
947
1139
  exports.fetchTokenLists = fetchTokenLists;
@@ -1011,6 +1203,8 @@ exports.llamaLendMarketByVault = llamaLendMarketByVault;
1011
1203
  exports.llamaLendMarkets = llamaLendMarkets;
1012
1204
  exports.llamaLendSupportsDelegation = llamaLendSupportsDelegation;
1013
1205
  exports.llamaLendSupportsLeverage = llamaLendSupportsLeverage;
1206
+ exports.loadCached = loadCached;
1207
+ exports.loadCachedJson = loadCachedJson;
1014
1208
  exports.midnightApiBaseOverride = midnightApiBaseOverride;
1015
1209
  exports.midnightConfig = midnightConfig;
1016
1210
  exports.midnightMarketById = midnightMarketById;
@@ -1041,6 +1235,8 @@ exports.riverLendersByChain = riverLendersByChain;
1041
1235
  exports.riverMarketByIndex = riverMarketByIndex;
1042
1236
  exports.riverMarkets = riverMarkets;
1043
1237
  exports.setMidnightApiBase = setMidnightApiBase;
1238
+ exports.setRemoteCacheGithubToken = setRemoteCacheGithubToken;
1239
+ exports.setRemoteCacheStore = setRemoteCacheStore;
1044
1240
  exports.setTermApiBase = setTermApiBase;
1045
1241
  exports.siloMarkets = siloMarkets;
1046
1242
  exports.siloMarketsV3 = siloMarketsV3;
@@ -1068,6 +1264,18 @@ exports.termMaxChainIds = termMaxChainIds;
1068
1264
  exports.termMaxConfig = termMaxConfig;
1069
1265
  exports.termMaxConfigByChain = termMaxConfigByChain;
1070
1266
  exports.termMaxLenderKey = termMaxLenderKey;
1267
+ exports.twyneBaseKey = twyneBaseKey;
1268
+ exports.twyneBeaconFor = twyneBeaconFor;
1269
+ exports.twyneChainData = twyneChainData;
1270
+ exports.twyneConfig = twyneConfig;
1271
+ exports.twyneConfigFor = twyneConfigFor;
1272
+ exports.twyneLendersByChain = twyneLendersByChain;
1273
+ exports.twyneMarketByKey = twyneMarketByKey;
1274
+ exports.twyneMarketFor = twyneMarketFor;
1275
+ exports.twyneMarketKey = twyneMarketKey;
1276
+ exports.twyneMarkets = twyneMarkets;
1277
+ exports.twyneMarketsForIntermediateVault = twyneMarketsForIntermediateVault;
1278
+ exports.twyneOperator = twyneOperator;
1071
1279
  exports.usddChainData = usddChainData;
1072
1280
  exports.usddConfig = usddConfig;
1073
1281
  exports.usddConfigFor = usddConfigFor;
package/dist/index.mjs CHANGED
@@ -93,6 +93,8 @@ var LENDER_DATA_DEFAULTS = () => ({
93
93
  llamaLendMarkets: {},
94
94
  resupplyConfig: {},
95
95
  curvanceConfig: {},
96
+ twyneConfig: {},
97
+ twyneMarkets: {},
96
98
  fraxlendConfig: {},
97
99
  eulerConfigs: {},
98
100
  eulerVaults: {},
@@ -743,6 +745,60 @@ var curvancePositionManager = (lender, chainId, marketManager, kind) => {
743
745
  return pms.simplePositionManager ?? pms.vaultPositionManager ?? pms.nativeVaultPositionManager ?? pms.pendlePtPositionManager;
744
746
  };
745
747
 
748
+ // src/lending/twyne.ts
749
+ var twyneConfig = () => getGlobalData2()?.twyneConfig;
750
+ var twyneConfigFor = (lender, chainId) => getGlobalData2()?.twyneConfig?.[twyneBaseKey(lender)]?.[String(chainId)];
751
+ var twyneMarkets = () => getGlobalData2()?.twyneMarkets;
752
+ var twyneChainData = (lender, chainId) => getGlobalData2()?.twyneMarkets?.[twyneBaseKey(lender)]?.[String(chainId)];
753
+ var twyneBaseKey = (lender) => lender?.startsWith("TWYNE") ? "TWYNE" : lender;
754
+ var twyneMarketKey = (chainId, intermediateVault, targetAsset) => `TWYNE_${chainId}_${intermediateVault.replace(/^0x/i, "").toUpperCase()}_${targetAsset.replace(/^0x/i, "").toUpperCase()}`;
755
+ var assertTwyneKeysUnique = (markets, chainId) => {
756
+ const byKey = /* @__PURE__ */ new Map();
757
+ for (const m of markets) {
758
+ const key = twyneMarketKey(chainId, m.intermediateVault, m.targetAsset);
759
+ const triple = `${m.intermediateVault}|${m.targetVault}|${m.targetAsset}`.toLowerCase();
760
+ const seen = byKey.get(key);
761
+ if (seen && seen !== triple) {
762
+ throw new Error(
763
+ `assertTwyneKeysUnique: key ${key} maps to two triples (${seen} and ${triple}). The target vault must be added back into the key.`
764
+ );
765
+ }
766
+ byKey.set(key, triple);
767
+ }
768
+ };
769
+ var twyneMarketByKey = (lender, chainId) => {
770
+ const upper = lender?.toUpperCase();
771
+ return twyneChainData(lender, chainId)?.markets?.find((m) => m.key.toUpperCase() === upper);
772
+ };
773
+ var twyneMarketFor = (lender, chainId, intermediateVault, targetAsset) => {
774
+ const iv = intermediateVault?.toLowerCase();
775
+ const asset = targetAsset?.toLowerCase();
776
+ return twyneChainData(lender, chainId)?.markets?.find(
777
+ (m) => m.intermediateVault.toLowerCase() === iv && m.targetAsset.toLowerCase() === asset
778
+ );
779
+ };
780
+ var twyneMarketsForIntermediateVault = (lender, chainId, intermediateVault) => {
781
+ const iv = intermediateVault?.toLowerCase();
782
+ return (twyneChainData(lender, chainId)?.markets ?? []).filter((m) => m.intermediateVault.toLowerCase() === iv);
783
+ };
784
+ var twyneLendersByChain = (chainId) => {
785
+ const cfg = getGlobalData2()?.twyneConfig ?? {};
786
+ const chain = String(chainId);
787
+ return Object.keys(cfg).filter((lender) => !!cfg[lender]?.[chain]?.collateralVaultFactory);
788
+ };
789
+ var twyneOperator = (lender, chainId, vaultType, kind) => twyneConfigFor(lender, chainId)?.operators?.[vaultType]?.[kind];
790
+ var twyneBeaconFor = (lender, chainId, targetVault) => {
791
+ const map = twyneConfigFor(lender, chainId)?.beacons;
792
+ if (!map || !targetVault) return void 0;
793
+ const direct = map[targetVault];
794
+ if (direct) return direct;
795
+ const target = targetVault.toLowerCase();
796
+ for (const key of Object.keys(map)) {
797
+ if (key.toLowerCase() === target) return map[key];
798
+ }
799
+ return void 0;
800
+ };
801
+
746
802
  // src/lending/fraxlend.ts
747
803
  var FRAXLEND_ETHEREUM_CONFIG = {
748
804
  deployer: "0xD6E9D27C75Afd88ad24Cd5EdccdC76fd2fc3A751",
@@ -860,12 +916,143 @@ var parseFraxlendPairKey = (lender) => {
860
916
  return { chainId: m[1], pair: m[2].toLowerCase() };
861
917
  };
862
918
 
919
+ // src/remoteCache.ts
920
+ var FRESH_TTL_MS = 5 * 60 * 1e3;
921
+ var REMOTE_CACHE_STORE_TTL_S = 7 * 24 * 60 * 60;
922
+ var STATE_KEY = "__1delta_remote_cache__";
923
+ function getState() {
924
+ const g = globalThis;
925
+ if (!g[STATE_KEY]) g[STATE_KEY] = { memo: /* @__PURE__ */ new Map(), inflight: /* @__PURE__ */ new Map() };
926
+ return g[STATE_KEY];
927
+ }
928
+ function setRemoteCacheStore(next) {
929
+ getState().store = next;
930
+ }
931
+ function setRemoteCacheGithubToken(token) {
932
+ getState().githubToken = token;
933
+ }
934
+ function clearRemoteCache() {
935
+ const state = getState();
936
+ state.memo.clear();
937
+ state.inflight.clear();
938
+ }
939
+ var RAW_HOST = "https://raw.githubusercontent.com/";
940
+ function githubApiMirror(url) {
941
+ if (!url.startsWith(RAW_HOST)) return void 0;
942
+ const [owner, repo, ref, ...path] = url.slice(RAW_HOST.length).split("/");
943
+ if (!owner || !repo || !ref || path.length === 0) return void 0;
944
+ return `https://api.github.com/repos/${owner}/${repo}/contents/${path.join("/")}?ref=${ref}`;
945
+ }
946
+ var RemoteFetchError = class extends Error {
947
+ constructor(message, status) {
948
+ super(message);
949
+ this.status = status;
950
+ this.name = "RemoteFetchError";
951
+ }
952
+ /** 404/410 — re-fetching cannot change the answer. */
953
+ get isMissing() {
954
+ return this.status === 404 || this.status === 410;
955
+ }
956
+ };
957
+ async function fetchRemoteJson(url, label) {
958
+ const res = await fetch(url);
959
+ if (res.ok) return await res.json();
960
+ const mirror = githubApiMirror(url);
961
+ const worthRetrying = res.status === 429 || res.status >= 500;
962
+ if (!mirror || !worthRetrying) {
963
+ throw new RemoteFetchError(`${label} fetch failed: HTTP ${res.status} from ${new URL(url).host}`, res.status);
964
+ }
965
+ const headers = {
966
+ Accept: "application/vnd.github.raw",
967
+ // The GitHub API rejects a request that sends no User-Agent.
968
+ "User-Agent": "1delta-data-sdk"
969
+ };
970
+ const token = getState().githubToken;
971
+ if (token) headers.Authorization = `Bearer ${token}`;
972
+ const viaApi = await fetch(mirror, { headers });
973
+ if (!viaApi.ok) {
974
+ throw new RemoteFetchError(
975
+ `${label} fetch failed: HTTP ${res.status} from raw.githubusercontent.com, HTTP ${viaApi.status} from api.github.com`,
976
+ res.status
977
+ );
978
+ }
979
+ return await viaApi.json();
980
+ }
981
+ var storeKey = (key) => `remote-cache:v1:${key}`;
982
+ async function readStore(key, isValid) {
983
+ const { store } = getState();
984
+ if (!store) return void 0;
985
+ try {
986
+ const raw = await store.get(storeKey(key));
987
+ if (!raw) return void 0;
988
+ const parsed = JSON.parse(raw);
989
+ if (typeof parsed?.at !== "number" || !isValid(parsed.value)) return void 0;
990
+ return parsed;
991
+ } catch {
992
+ return void 0;
993
+ }
994
+ }
995
+ async function writeStore(key, entry) {
996
+ const { store } = getState();
997
+ if (!store) return;
998
+ try {
999
+ await store.put(storeKey(key), JSON.stringify(entry), REMOTE_CACHE_STORE_TTL_S);
1000
+ } catch {
1001
+ }
1002
+ }
1003
+ async function loadCached(key, load, isValid) {
1004
+ const { memo, inflight } = getState();
1005
+ const cached = memo.get(key);
1006
+ if (cached && Date.now() - cached.at < FRESH_TTL_MS) return cached.value;
1007
+ const pending = inflight.get(key);
1008
+ if (pending) return pending;
1009
+ const task = (async () => {
1010
+ const stored = await readStore(key, isValid);
1011
+ if (stored && Date.now() - stored.at < FRESH_TTL_MS) {
1012
+ memo.set(key, stored);
1013
+ return stored.value;
1014
+ }
1015
+ try {
1016
+ const value = await load();
1017
+ const entry = { at: Date.now(), value };
1018
+ memo.set(key, entry);
1019
+ await writeStore(key, entry);
1020
+ return value;
1021
+ } catch (error) {
1022
+ const stale = memo.get(key) ?? stored;
1023
+ if (!stale) throw error;
1024
+ memo.set(key, stale);
1025
+ return stale.value;
1026
+ }
1027
+ })().finally(() => {
1028
+ inflight.delete(key);
1029
+ });
1030
+ inflight.set(key, task);
1031
+ return task;
1032
+ }
1033
+ async function loadCachedJson(key, url, isValid, label = key) {
1034
+ return loadCached(key, () => fetchRemoteJson(url, label), isValid);
1035
+ }
1036
+
863
1037
  // src/tokens.ts
864
1038
  var getListUrl = (chainId) => `https://raw.githubusercontent.com/1delta-DAO/token-lists/main/${chainId}.json`;
1039
+ var MAIN_TOKENS_URL = "https://raw.githubusercontent.com/1delta-DAO/token-lists/main/main-tokens.json";
1040
+ var isTokenListFile = (value) => !!value?.list && typeof value.list === "object";
1041
+ async function loadTokenListFile(chainId) {
1042
+ return loadCached(
1043
+ `token-list:${chainId}`,
1044
+ async () => {
1045
+ const json = await fetchRemoteJson(getListUrl(chainId), `Token list for chain ${chainId}`);
1046
+ return {
1047
+ list: json.list,
1048
+ mainTokens: json.mainTokens ?? []
1049
+ };
1050
+ },
1051
+ isTokenListFile
1052
+ );
1053
+ }
865
1054
  async function fetchTokenList(chainId) {
866
- const data = await fetch(getListUrl(chainId));
867
- if (!data.ok) throw new Error(`HTTP error! status: ${data.status}`);
868
- return (await data.json()).list;
1055
+ return (await loadTokenListFile(chainId)).list;
869
1056
  }
870
1057
  async function fetchTokenLists(chainIds) {
871
1058
  const promises = chainIds.map(async (chainId) => {
@@ -876,19 +1063,19 @@ async function fetchTokenLists(chainIds) {
876
1063
  return Object.fromEntries(results);
877
1064
  }
878
1065
  async function fetchTokenListFile(chainId) {
879
- const data = await fetch(getListUrl(chainId));
880
- if (!data.ok) throw new Error(`HTTP error! status: ${data.status}`);
881
- const json = await data.json();
882
- return {
883
- list: json.list,
884
- mainTokens: json.mainTokens ?? []
885
- };
1066
+ const file = await loadTokenListFile(chainId);
1067
+ return { list: file.list, mainTokens: file.mainTokens };
886
1068
  }
887
- var MAIN_TOKENS_URL = "https://raw.githubusercontent.com/1delta-DAO/token-lists/main/main-tokens.json";
888
1069
  async function fetchMainTokensMap() {
889
- const data = await fetch(MAIN_TOKENS_URL);
890
- if (!data.ok) return void 0;
891
- return await data.json();
1070
+ try {
1071
+ return await loadCached(
1072
+ "main-tokens",
1073
+ () => fetchRemoteJson(MAIN_TOKENS_URL, "main-tokens"),
1074
+ (value) => !!value && typeof value === "object"
1075
+ );
1076
+ } catch {
1077
+ return void 0;
1078
+ }
892
1079
  }
893
1080
 
894
- export { CURVANCE_MONAD_CONFIG, FRAXLEND_ETHEREUM_CONFIG, GEARBOX_ACCOUNT_COMPRESSOR_V310, RESUPPLY_ETHEREUM_CONFIG, aaveOracles, aaveOraclesConfig, aavePools, aaveReserves, aaveTokens, aaveV4Oracles, aaveV4Peripherals, aaveV4SpokeLenderKey, aaveV4Spokes, aaveWethGateway, chainLogoURI, chains, compoundV2Oracles, compoundV2OraclesConfig, compoundV2Pools, compoundV2Reserves, compoundV2TokenArray, compoundV2Tokens, compoundV3BaseData, compoundV3Bulker, compoundV3OraclesData, compoundV3Pools, compoundV3Reserves, curvanceConfig, curvanceConfigFor, curvanceLendersByChain, curvancePositionManager, curvancePositionManagersFor, dolomiteChains, dolomiteConfigs, dolomiteEmode, dolomiteMarketIdByToken, dssChainData, dssConfigFor, dssDebtJoin, dssDebtToken, dssMarketByIlk, eulerConfigs, eulerVaults, exactlyConfig, exactlyMarketByAddress, exactlyMarketByAsset, exactlyMarkets, exactlyMarketsByChain, fetchMainTokensMap, fetchTokenList, fetchTokenListFile, fetchTokenLists, findAaveV4PositionManager, fluidChains, fluidResolvers, fluidVaultEntries, fluidVaults, frankencoinChainData, frankencoinConfig, frankencoinConfigFor, frankencoinLendersByChain, frankencoinMarketByPosition, frankencoinMarkets, fraxlendConfig, fraxlendConfigFor, fraxlendLendersByChain, fraxlendPairKey, fraxlendPairsFor, gearboxAccountCompressor, gearboxAddressProviderV310, gearboxBotListV3, gearboxChains, gearboxMarketCompressor, gearboxMarketConfigurators, gearboxResolvers, gearboxV3LenderKey, getAaveV4GatewayAddresses, getAaveV4PositionManagers, getAaveV4SpokeAccess, getAaveV4SpokeEntry, getAaveV4SpokePeripherals, getChainEntries, getSiloV2MarketEntry, getSiloV3MarketEntry, initConfig, initializeChainData, initializeLenderData, inverseChainData, inverseConfig, inverseConfigFor, inverseLendersByChain, inverseMarketByAddress, inverseMarkets, isAaveV4OperationRestricted, isListaSmartLpAsset, liquityBranchByIndex, liquityBranchByTroveManager, liquityBranchesByChain, liquityConfig, liquityConfigFor, liquityLendersByChain, liquityMarkets, listaCollateralProvider, listaCollateralProviders, listaNativeProvider, listaSmartLpByCollateral, llamaLendApiBaseUrl, llamaLendChainData, llamaLendConfig, llamaLendConfigFor, llamaLendLenderKey, llamaLendLendersByChain, llamaLendLeverageZap, llamaLendMarketByController, llamaLendMarketByVault, llamaLendMarkets, llamaLendSupportsDelegation, llamaLendSupportsLeverage, midnightApiBaseOverride, midnightConfig, midnightMarketById, midnightMarkets, midnightMarketsByChain, morphoBundler3, morphoOracles, morphoPools, morphoTypeMarkets, morphoTypeOracles, morphoTypeVaults, parseAaveV4SpokeLenderKey, parseFraxlendPairKey, parseGearboxV3LenderKey, parseLlamaLendLenderKey, parseSiloV2LenderKey, parseSiloV3LenderKey, parseTermMaxLenderKey, resolveMidnightApiBase, resolveTermApiBase, resupplyConfig, resupplyConfigFor, resupplyLendersByChain, riverChainData, riverConfig, riverConfigFor, riverLendersByChain, riverMarketByIndex, riverMarkets, setMidnightApiBase, setTermApiBase, siloMarkets, siloMarketsV3, siloPeripherals, siloPeripheralsV3, siloV2LenderKey, siloV3LenderKey, skyChainData, skyConfig, skyConfigFor, skyLendersByChain, skyMarkets, tellerConfig, tellerConfigByChain, tellerPoolByAddress, tellerPools, tellerPoolsByChain, termApiBaseOverride, termConfig, termMarketById, termMarkets, termMarketsByChain, termMaxApiBaseUrl, termMaxChainIds, termMaxConfig, termMaxConfigByChain, termMaxLenderKey, usddChainData, usddConfig, usddConfigFor, usddLendersByChain, usddMarketByIlk, usddMarkets };
1081
+ export { CURVANCE_MONAD_CONFIG, FRAXLEND_ETHEREUM_CONFIG, GEARBOX_ACCOUNT_COMPRESSOR_V310, REMOTE_CACHE_STORE_TTL_S, RESUPPLY_ETHEREUM_CONFIG, RemoteFetchError, aaveOracles, aaveOraclesConfig, aavePools, aaveReserves, aaveTokens, aaveV4Oracles, aaveV4Peripherals, aaveV4SpokeLenderKey, aaveV4Spokes, aaveWethGateway, assertTwyneKeysUnique, chainLogoURI, chains, clearRemoteCache, compoundV2Oracles, compoundV2OraclesConfig, compoundV2Pools, compoundV2Reserves, compoundV2TokenArray, compoundV2Tokens, compoundV3BaseData, compoundV3Bulker, compoundV3OraclesData, compoundV3Pools, compoundV3Reserves, curvanceConfig, curvanceConfigFor, curvanceLendersByChain, curvancePositionManager, curvancePositionManagersFor, dolomiteChains, dolomiteConfigs, dolomiteEmode, dolomiteMarketIdByToken, dssChainData, dssConfigFor, dssDebtJoin, dssDebtToken, dssMarketByIlk, eulerConfigs, eulerVaults, exactlyConfig, exactlyMarketByAddress, exactlyMarketByAsset, exactlyMarkets, exactlyMarketsByChain, fetchMainTokensMap, fetchRemoteJson, fetchTokenList, fetchTokenListFile, fetchTokenLists, findAaveV4PositionManager, fluidChains, fluidResolvers, fluidVaultEntries, fluidVaults, frankencoinChainData, frankencoinConfig, frankencoinConfigFor, frankencoinLendersByChain, frankencoinMarketByPosition, frankencoinMarkets, fraxlendConfig, fraxlendConfigFor, fraxlendLendersByChain, fraxlendPairKey, fraxlendPairsFor, gearboxAccountCompressor, gearboxAddressProviderV310, gearboxBotListV3, gearboxChains, gearboxMarketCompressor, gearboxMarketConfigurators, gearboxResolvers, gearboxV3LenderKey, getAaveV4GatewayAddresses, getAaveV4PositionManagers, getAaveV4SpokeAccess, getAaveV4SpokeEntry, getAaveV4SpokePeripherals, getChainEntries, getSiloV2MarketEntry, getSiloV3MarketEntry, initConfig, initializeChainData, initializeLenderData, inverseChainData, inverseConfig, inverseConfigFor, inverseLendersByChain, inverseMarketByAddress, inverseMarkets, isAaveV4OperationRestricted, isListaSmartLpAsset, liquityBranchByIndex, liquityBranchByTroveManager, liquityBranchesByChain, liquityConfig, liquityConfigFor, liquityLendersByChain, liquityMarkets, listaCollateralProvider, listaCollateralProviders, listaNativeProvider, listaSmartLpByCollateral, llamaLendApiBaseUrl, llamaLendChainData, llamaLendConfig, llamaLendConfigFor, llamaLendLenderKey, llamaLendLendersByChain, llamaLendLeverageZap, llamaLendMarketByController, llamaLendMarketByVault, llamaLendMarkets, llamaLendSupportsDelegation, llamaLendSupportsLeverage, loadCached, loadCachedJson, midnightApiBaseOverride, midnightConfig, midnightMarketById, midnightMarkets, midnightMarketsByChain, morphoBundler3, morphoOracles, morphoPools, morphoTypeMarkets, morphoTypeOracles, morphoTypeVaults, parseAaveV4SpokeLenderKey, parseFraxlendPairKey, parseGearboxV3LenderKey, parseLlamaLendLenderKey, parseSiloV2LenderKey, parseSiloV3LenderKey, parseTermMaxLenderKey, resolveMidnightApiBase, resolveTermApiBase, resupplyConfig, resupplyConfigFor, resupplyLendersByChain, riverChainData, riverConfig, riverConfigFor, riverLendersByChain, riverMarketByIndex, riverMarkets, setMidnightApiBase, setRemoteCacheGithubToken, setRemoteCacheStore, setTermApiBase, siloMarkets, siloMarketsV3, siloPeripherals, siloPeripheralsV3, siloV2LenderKey, siloV3LenderKey, skyChainData, skyConfig, skyConfigFor, skyLendersByChain, skyMarkets, tellerConfig, tellerConfigByChain, tellerPoolByAddress, tellerPools, tellerPoolsByChain, termApiBaseOverride, termConfig, termMarketById, termMarkets, termMarketsByChain, termMaxApiBaseUrl, termMaxChainIds, termMaxConfig, termMaxConfigByChain, termMaxLenderKey, twyneBaseKey, twyneBeaconFor, twyneChainData, twyneConfig, twyneConfigFor, twyneLendersByChain, twyneMarketByKey, twyneMarketFor, twyneMarketKey, twyneMarkets, twyneMarketsForIntermediateVault, twyneOperator, usddChainData, usddConfig, usddConfigFor, usddLendersByChain, usddMarketByIlk, usddMarkets };
@@ -37,4 +37,5 @@ export * from './dss';
37
37
  export * from './llamalend';
38
38
  export * from './resupply';
39
39
  export * from './curvance';
40
+ export * from './twyne';
40
41
  export * from './fraxlend';
@@ -53,6 +53,13 @@ export type MorphoTypeVaultEntry = {
53
53
  underlying: string;
54
54
  /** Curated display name (e.g. `Lista USD1 Vault`). Optional. */
55
55
  name?: string;
56
+ /** Human curator/brand name (e.g. `Steakhouse Financial`). Filled by
57
+ * lender-metadata's `update:vault-curators` job (Morpho curator roster
58
+ * joined on the on-chain `curator()`/`owner()` address, with a
59
+ * conservative name-derived fallback) — the chains in this registry have
60
+ * no Morpho-API coverage, so the on-chain fetcher cannot learn the
61
+ * curator any other way. */
62
+ curatorName?: string;
56
63
  /** Vault interface version. `v1` = MetaMorpho (withdraw-queue → markets);
57
64
  * `v2` = Vaults V2 (adapter-based). Drives the on-chain fetcher's
58
65
  * allocation walk. Absent ⇒ the fetcher auto-detects on-chain
@@ -24,6 +24,7 @@ import type { GeneralReservesMap, OracleMap } from './common';
24
24
  import type { CompoundV2OraclesConfigType, CompoundV2PoolsType, CompoundV2TokenArrayType, CompoundV2TokensType } from './compoundV2';
25
25
  import type { CompoundV3BaseDataType, CompoundV3BulkerType, CompoundV3OracleDataMap, CompoundV3PoolsType } from './compoundV3';
26
26
  import type { CurvanceConfigType } from './curvance';
27
+ import type { TwyneConfigType, TwyneMarketsType } from './twyne';
27
28
  import type { DolomiteConfigsType, DolomiteEmodeType } from './dolomite';
28
29
  import type { UsddConfigType, UsddMarketsType } from './dss';
29
30
  import type { EulerConfigsType, EulerVaultsType } from './euler';
@@ -105,6 +106,8 @@ export interface GlobalLenderDataRegistry {
105
106
  llamaLendMarkets: LlamaLendMarketsType;
106
107
  resupplyConfig: ResupplyConfigType;
107
108
  curvanceConfig: CurvanceConfigType;
109
+ twyneConfig: TwyneConfigType;
110
+ twyneMarkets: TwyneMarketsType;
108
111
  fraxlendConfig: FraxlendConfigType;
109
112
  eulerConfigs: EulerConfigsType;
110
113
  eulerVaults: EulerVaultsType;
@@ -0,0 +1,210 @@
1
+ /** The two external protocols Twyne integrates. Mirrors the on-chain enum. */
2
+ export type TwyneVaultType = 'EULER_V2' | 'AAVE_V3';
3
+ /**
4
+ * The 1-click operators for one `VaultType`. Each is EVC-authenticated
5
+ * (`_msgSender() == collateralVault.borrower()`) and runs a Morpho Blue flash
6
+ * loan plus, where it swaps, Euler's `Swapper` — whose GENERIC handler executes
7
+ * arbitrary calldata on an arbitrary target, so any aggregator we support works
8
+ * without an allowlist.
9
+ */
10
+ export type TwyneOperators = {
11
+ /** `executeLeverage(collateralVault, underlyingAmt, collateralAmt, flashAmt, minOut, deadline, swapData)` */
12
+ leverage?: string;
13
+ /** `executeDeleverage(collateralVault, flashAmt, maxDebt, withdrawAmt, swapData)` */
14
+ deleverage?: string;
15
+ /**
16
+ * `executeTeleport(collateralVault, aTokenAmount, debtAmount)` — migrate an
17
+ * existing external position IN.
18
+ *
19
+ * AAVE_V3 ONLY, and it carries NO swapper (its constructor takes
20
+ * `evc, morpho, factory, permit2, aavePool` and nothing else), so it handles
21
+ * the IDENTITY CASE ONLY: the user's aToken must be this market's aToken and
22
+ * their VARIABLE debt asset must be this market's `targetAsset`. Anything
23
+ * cross-asset has to fall back to the generic migrate. There is no
24
+ * teleport-OUT.
25
+ */
26
+ teleport?: string;
27
+ };
28
+ /**
29
+ * The BeaconProxy beacon for one target vault, and OPTIONALLY a cached
30
+ * init-code hash for the CREATE2 address predictor.
31
+ *
32
+ * `CollateralVaultFactory.createCollateralVault` deploys
33
+ * `new BeaconProxy{salt: keccak256(abi.encodePacked(borrower, nonce))}(beacon, "")`,
34
+ * so a position's address IS predictable — but the init-code hash is a property
35
+ * of the FACTORY IMPLEMENTATION, not of the protocol. That factory is a UUPS
36
+ * proxy which has already been upgraded four times, and the embedded BeaconProxy
37
+ * creation code is not identical across those implementations.
38
+ *
39
+ * So `evc.batchSimulation` is the source of truth for a new vault's address, and
40
+ * a cached hash here is a FAST PATH ONLY. It is valid exactly while
41
+ * `factoryImplementation` still matches the live ERC-1967 implementation slot
42
+ * (`0x360894…382bbc`) — one `eth_getStorageAt` to check. On a mismatch, fall
43
+ * back; never guess, because a stale hash yields a plausible EMPTY address and
44
+ * the leverage batch would then `skim`/`borrow` on nothing.
45
+ *
46
+ * See TWYNE_BUILD_PLAN.md D3.
47
+ */
48
+ export type TwyneBeacon = {
49
+ beacon: string;
50
+ /** `keccak256(BeaconProxy.creationCode ++ abi.encode(beacon, "0x"))`. */
51
+ initCodeHash?: string;
52
+ /** The factory implementation `initCodeHash` was derived from. A cached hash
53
+ * is only usable while this still matches the live implementation slot. */
54
+ factoryImplementation?: string;
55
+ };
56
+ /** Per-chain, protocol-level (evergreen) Twyne addresses. */
57
+ export type TwyneConfigChain = {
58
+ /**
59
+ * Twyne's OWN EVC — `0xef39D649…` on Ethereum, NOT Euler's canonical
60
+ * `0x0C9a3dd6…`. Every collateral-vault write is authenticated through it
61
+ * (`EVCUtil._msgSender()`), which is also what lets an EVC account operator
62
+ * drive a position. Reusing the Euler address here is a silent failure.
63
+ */
64
+ evc: string;
65
+ /** `CollateralVaultFactory` — `createCollateralVault`, `getCollateralVaults`,
66
+ * `isCollateralVault`, `nonce`, `collateralVaultBeacon`, `paused`. */
67
+ collateralVaultFactory: string;
68
+ /** `VaultManager` — the whitelists and the LTV bounds
69
+ * (`maxTwyneLTVs`, `externalLiqBuffers`, `isAllowedTargetAssets`). */
70
+ vaultManager: string;
71
+ /** Euler-style oracle router used to price collateral vaults. */
72
+ oracleRouter?: string;
73
+ /** Morpho Blue — the (free) flash source every operator uses. */
74
+ morpho?: string;
75
+ /** Euler periphery `Swapper` (GENERIC handler = no target allowlist). */
76
+ swapper?: string;
77
+ /** Euler periphery `SwapVerifier` — used by the Euler leverage operator. */
78
+ swapVerifier?: string;
79
+ /** Permit2, as the operators pull user funds through `SafeERC20Lib`. */
80
+ permit2?: string;
81
+ /** 1-click operators per external protocol. */
82
+ operators?: Partial<Record<TwyneVaultType, TwyneOperators>>;
83
+ /**
84
+ * Collateral-vault beacons keyed by TARGET VAULT (the factory keys its
85
+ * beacon map that way), for the CREATE2 address predictor.
86
+ */
87
+ beacons?: {
88
+ [targetVault: string]: TwyneBeacon;
89
+ };
90
+ };
91
+ export type TwyneConfigType = {
92
+ [lender: string]: {
93
+ [chainId: string]: TwyneConfigChain;
94
+ };
95
+ };
96
+ /**
97
+ * One whitelisted Twyne market.
98
+ *
99
+ * Numeric risk fields are strings/numbers in the protocol's own scale (1e4 for
100
+ * LTVs and the buffer) because they are sourced from JSON metadata; live values
101
+ * are re-read on every refresh, since the external liquidation threshold moves
102
+ * — Aave's PT eMode thresholds ramp down as a PT ages, and Twyne's LOWER bound
103
+ * is derived from it.
104
+ */
105
+ export type TwyneMarketConfig = {
106
+ /** `TWYNE_<chainId>_<INTERMEDIATE_VAULT>_<TARGET_ASSET>`, upper-cased hex. */
107
+ key: string;
108
+ vaultType: TwyneVaultType;
109
+ /** The EVK credit vault. Also the market's identity for LTV bounds. */
110
+ intermediateVault: string;
111
+ /** Euler: the debt eVault. Aave: the Pool. */
112
+ targetVault: string;
113
+ /** The borrowed asset (e.g. USDe, WETH). */
114
+ targetAsset: string;
115
+ /** The intermediate vault's asset — an `eToken` or a Twyne aToken wrapper.
116
+ * This is what the collateral vault actually holds. */
117
+ collateralAsset: string;
118
+ /** What the user thinks they deposited (WETH / wstETH / a Pendle PT).
119
+ * `depositUnderlying` / `redeemUnderlying` take this. */
120
+ underlyingAsset: string;
121
+ /** Aave only: the aToken the wrapper wraps. */
122
+ aToken?: string;
123
+ /** Aave only: the eMode category the collateral vault is initialized with.
124
+ * The external liquidation threshold must be read from THIS category when
125
+ * the reserve is in its collateral bitmap — never the plain reserve LT. */
126
+ categoryId?: number;
127
+ /** Governance ceiling on a position's chosen liq LTV, 1e4. */
128
+ maxTwyneLiqLTV?: number;
129
+ /** Safety margin below the external threshold, 1e4 — the LOWER bound is
130
+ * `extLiqLTV * externalLiqBuffer / 1e4`. */
131
+ externalLiqBuffer?: number;
132
+ /** Maturity of the collateral, unix seconds, when the collateral is a
133
+ * fixed-term asset (every live PT market). Absent for wstETH/WETH. */
134
+ collateralMaturity?: number;
135
+ /** The credit vault's `IRMTwyneCurve` — the join key into the `irm` repo's
136
+ * `twyne.json`, where the fitted `gamma` lives. */
137
+ creditIrm?: string;
138
+ /** The collateral vault BeaconProxy beacon for this market's target vault.
139
+ * Recorded for the CREATE2 predictor's fast path; the init-code hash is
140
+ * deliberately NOT pinned (see TWYNE_BUILD_PLAN.md D3). */
141
+ beacon?: string;
142
+ /** Display only. */
143
+ symbol?: string;
144
+ name?: string;
145
+ };
146
+ export type TwyneChainData = {
147
+ markets: TwyneMarketConfig[];
148
+ };
149
+ export type TwyneMarketsType = {
150
+ [lender: string]: {
151
+ [chainId: string]: TwyneChainData;
152
+ };
153
+ };
154
+ export declare const twyneConfig: () => TwyneConfigType;
155
+ /** Deployment seed for one Twyne lender on a chain (undefined if unloaded). */
156
+ export declare const twyneConfigFor: (lender: string, chainId: string | number) => TwyneConfigChain | undefined;
157
+ export declare const twyneMarkets: () => TwyneMarketsType;
158
+ /** Generated chain data (curated market roster; undefined if unloaded). */
159
+ export declare const twyneChainData: (lender: string, chainId: string | number) => TwyneChainData | undefined;
160
+ /**
161
+ * The bare lender key a per-market key belongs to. Per-market keys are
162
+ * `TWYNE_<chainId>_<iv>_<targetAsset>`, so the base is simply the first
163
+ * segment — kept as a function because callers pass both forms
164
+ * interchangeably.
165
+ */
166
+ export declare const twyneBaseKey: (lender: string) => string;
167
+ /**
168
+ * Build the per-market lender key for a triple.
169
+ *
170
+ * The TARGET VAULT is deliberately NOT in the key: it is redundant on every
171
+ * live market (implied by the asset on Euler, always the Pool on Aave), and
172
+ * every other fan-out lender in this registry uses a one-or-two-identifier key.
173
+ * `assertTwyneKeysUnique` guards the day that assumption breaks.
174
+ */
175
+ export declare const twyneMarketKey: (chainId: string | number, intermediateVault: string, targetAsset: string) => string;
176
+ /**
177
+ * Throw if two distinct `(intermediateVault, targetVault, targetAsset)` triples
178
+ * collapse onto one key.
179
+ *
180
+ * This cannot happen with the eight live markets, but it is exactly the failure
181
+ * that would silently MERGE two markets — the same lender key resolving to two
182
+ * different debt venues — so the roster is validated at load rather than
183
+ * trusted. Called by the generator and by the margin-fetcher on ingest.
184
+ */
185
+ export declare const assertTwyneKeysUnique: (markets: TwyneMarketConfig[], chainId: string | number) => void;
186
+ /** Find one Twyne market by its per-market lender key. */
187
+ export declare const twyneMarketByKey: (lender: string, chainId: string | number) => TwyneMarketConfig | undefined;
188
+ /** Find one Twyne market by its (intermediate vault, target asset) pair. */
189
+ export declare const twyneMarketFor: (lender: string, chainId: string | number, intermediateVault: string, targetAsset: string) => TwyneMarketConfig | undefined;
190
+ /** Every market that reserves credit from one intermediate (credit) vault. */
191
+ export declare const twyneMarketsForIntermediateVault: (lender: string, chainId: string | number, intermediateVault: string) => TwyneMarketConfig[];
192
+ /**
193
+ * All Twyne lender keys with config on a chain (drives getLendersForChain).
194
+ * Gated on the collateral vault FACTORY — the one address every flow needs, and
195
+ * the bootstrap for position discovery.
196
+ */
197
+ export declare const twyneLendersByChain: (chainId: string | number) => string[];
198
+ /**
199
+ * The operator to drive for a market's `VaultType`, or `undefined` when Twyne
200
+ * has not deployed one — a real state, not a gap: there is no Euler-side
201
+ * teleport at all, and the docs' address table omits deployed operators, so
202
+ * absence here must be reported rather than defaulted.
203
+ */
204
+ export declare const twyneOperator: (lender: string, chainId: string | number, vaultType: TwyneVaultType, kind: keyof TwyneOperators) => string | undefined;
205
+ /**
206
+ * Beacon + init-code hash for a target vault, for the CREATE2 predictor.
207
+ * Lookup is case-insensitive because target-vault addresses arrive from config
208
+ * and from chain in mixed checksum forms.
209
+ */
210
+ export declare const twyneBeaconFor: (lender: string, chainId: string | number, targetVault: string) => TwyneBeacon | undefined;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Cache for the static JSON this SDK reads from GitHub (token lists, the chain
3
+ * registry).
4
+ *
5
+ * raw.githubusercontent.com rate-limits per EGRESS IP, and Cloudflare Workers
6
+ * share theirs — so an uncached fetch per request 429s in bursts. Before this
7
+ * existed that surfaced as a hard `INTERNAL_ERROR` 500 on every route needing a
8
+ * token list, and as a silently EMPTY chain registry (every chain rendered as
9
+ * "Chain 8453") that a 10-minute init cache then pinned in place.
10
+ *
11
+ * These files change on the order of days, so serving a stale copy is strictly
12
+ * better than failing — hence the stale-on-error fallback, which is the part
13
+ * that actually survives an outage.
14
+ */
15
+ /** How long a persisted copy is kept around purely as the stale fallback. */
16
+ export declare const REMOTE_CACHE_STORE_TTL_S: number;
17
+ /**
18
+ * Durable backing store — a Cloudflare KV namespace in the workers, absent
19
+ * everywhere else. Without one the cache is per-process, which still collapses
20
+ * N requests into one upstream fetch but cannot help a cold start; with one, a
21
+ * fresh isolate never touches GitHub at all.
22
+ */
23
+ export interface RemoteCacheStore {
24
+ get(key: string): Promise<string | null>;
25
+ put(key: string, value: string, ttlSeconds: number): Promise<void>;
26
+ }
27
+ /** Installs (or with `undefined`, removes) the durable store. */
28
+ export declare function setRemoteCacheStore(next: RemoteCacheStore | undefined): void;
29
+ /**
30
+ * Optional GitHub token for the API mirror below. Unauthenticated it allows 60
31
+ * requests/hour per IP, which is thin for a shared Worker egress; a token
32
+ * raises that to 5,000. Only ever used on the fallback path.
33
+ */
34
+ export declare function setRemoteCacheGithubToken(token: string | undefined): void;
35
+ /** Drops every cached value. Intended for tests. */
36
+ export declare function clearRemoteCache(): void;
37
+ /**
38
+ * A non-2xx from the remote. Carries the ORIGIN status so a caller can tell a
39
+ * settled answer (404/410 — the file is not published) from a transient one
40
+ * (429/5xx — the same URL may work a second later). Callers that collapse the
41
+ * two re-fetch known-missing files forever, or treat a rate limit as proof that
42
+ * a lender has no config.
43
+ */
44
+ export declare class RemoteFetchError extends Error {
45
+ /** Status from the ORIGIN host, not from the mirror. */
46
+ readonly status: number;
47
+ constructor(message: string,
48
+ /** Status from the ORIGIN host, not from the mirror. */
49
+ status: number);
50
+ /** 404/410 — re-fetching cannot change the answer. */
51
+ get isMissing(): boolean;
52
+ }
53
+ /** Fetches JSON from `url`, retrying via the GitHub API when raw rate-limits. */
54
+ export declare function fetchRemoteJson<T>(url: string, label: string): Promise<T>;
55
+ /**
56
+ * Serves `key` from memory, then the durable store, then `load()` — and on a
57
+ * failed `load()` falls back to whatever stale copy either layer holds, only
58
+ * rethrowing when there is nothing to fall back to.
59
+ *
60
+ * Concurrent callers for the same key share one in-flight load.
61
+ *
62
+ * `isValid` guards the durable layer: a truncated or shape-changed entry is
63
+ * treated as absent rather than served, since a bad token list silently
64
+ * mis-decimals every balance built from it.
65
+ */
66
+ export declare function loadCached<T>(key: string, load: () => Promise<T>, isValid: (value: any) => boolean): Promise<T>;
67
+ /** `loadCached` over a JSON URL, failing on any non-2xx so stale can take over. */
68
+ export declare function loadCachedJson<T>(key: string, url: string, isValid: (value: any) => boolean, label?: string): Promise<T>;
package/dist/tokens.d.ts CHANGED
@@ -13,10 +13,6 @@ export interface TokenListEntry {
13
13
  type TokenList = {
14
14
  [addr: string]: TokenListEntry;
15
15
  };
16
- export declare function fetchTokenList(chainId: string): Promise<TokenList>;
17
- export declare function fetchTokenLists(chainIds: string[]): Promise<{
18
- [chainId: string]: TokenList;
19
- }>;
20
16
  export interface TokenListFileData {
21
17
  list: TokenList;
22
18
  /**
@@ -26,7 +22,18 @@ export interface TokenListFileData {
26
22
  */
27
23
  mainTokens: string[];
28
24
  }
29
- /** Like {@link fetchTokenList} but keeps the `mainTokens` curation. */
25
+ /**
26
+ * NOTE: cached — the returned `list` is a SHARED object. Treat it as read-only;
27
+ * mutating it corrupts the copy every other caller sees.
28
+ */
29
+ export declare function fetchTokenList(chainId: string): Promise<TokenList>;
30
+ export declare function fetchTokenLists(chainIds: string[]): Promise<{
31
+ [chainId: string]: TokenList;
32
+ }>;
33
+ /**
34
+ * Like {@link fetchTokenList} but keeps the `mainTokens` curation. Same
35
+ * read-only caveat on `list`.
36
+ */
30
37
  export declare function fetchTokenListFile(chainId: string): Promise<TokenListFileData>;
31
38
  /**
32
39
  * Aggregated chainId → mainTokens map (one small file for all chains, so
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.0.46",
7
+ "version": "0.0.48",
8
8
  "description": "Hold and initialize lending protocol data across a stack",
9
9
  "files": [
10
10
  "dist"