@lifi/sdk 4.0.0-beta.5 → 4.0.0-beta.7
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/cjs/client/getClientStorage.js +2 -1
- package/dist/cjs/client/getClientStorage.js.map +1 -1
- package/dist/cjs/core/tasks/helpers/checkBalance.d.ts +13 -1
- package/dist/cjs/core/tasks/helpers/checkBalance.js +87 -17
- package/dist/cjs/core/tasks/helpers/checkBalance.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/utils/checkPackageUpdates.js +1 -1
- package/dist/cjs/utils/withTimeout.d.ts +26 -0
- package/dist/cjs/utils/withTimeout.js +35 -0
- package/dist/cjs/utils/withTimeout.js.map +1 -0
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/version.js.map +1 -1
- package/dist/esm/client/getClientStorage.js +2 -1
- package/dist/esm/client/getClientStorage.js.map +1 -1
- package/dist/esm/core/tasks/helpers/checkBalance.d.ts +13 -1
- package/dist/esm/core/tasks/helpers/checkBalance.d.ts.map +1 -1
- package/dist/esm/core/tasks/helpers/checkBalance.js +87 -17
- package/dist/esm/core/tasks/helpers/checkBalance.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/utils/checkPackageUpdates.js +1 -1
- package/dist/esm/utils/withTimeout.d.ts +26 -0
- package/dist/esm/utils/withTimeout.d.ts.map +1 -0
- package/dist/esm/utils/withTimeout.js +34 -0
- package/dist/esm/utils/withTimeout.js.map +1 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/package.json +2 -2
- package/src/client/getClientStorage.ts +1 -0
- package/src/core/tasks/helpers/checkBalance.ts +161 -35
- package/src/index.ts +1 -0
- package/src/utils/withTimeout.ts +50 -0
- package/src/version.ts +1 -1
|
@@ -28,7 +28,8 @@ const getClientStorage = (config) => {
|
|
|
28
28
|
_lifi_types.ChainType.EVM,
|
|
29
29
|
_lifi_types.ChainType.SVM,
|
|
30
30
|
_lifi_types.ChainType.UTXO,
|
|
31
|
-
_lifi_types.ChainType.MVM
|
|
31
|
+
_lifi_types.ChainType.MVM,
|
|
32
|
+
_lifi_types.ChainType.TVM
|
|
32
33
|
] });
|
|
33
34
|
_chainsUpdatedAt = Date.now();
|
|
34
35
|
updateRpcUrls();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getClientStorage.js","names":["getRpcUrlsFromChains","ChainId","_getChains","ChainType"],"sources":["../../../src/client/getClientStorage.ts"],"sourcesContent":["import { ChainId, ChainType, type ExtendedChain } from '@lifi/types'\nimport { _getChains } from '../actions/getChains.js'\nimport { getRpcUrlsFromChains } from '../core/utils.js'\nimport type { RPCUrls, SDKBaseConfig } from '../types/core.js'\n\n// 6 hours in milliseconds\nconst chainsRefreshInterval = 1000 * 60 * 60 * 6\n\nexport interface ClientStorage {\n readonly needReset: boolean\n setChains(chains: ExtendedChain[]): void\n getChains(): Promise<ExtendedChain[]>\n getRpcUrls(): Promise<RPCUrls>\n}\n\nexport const getClientStorage = (config: SDKBaseConfig): ClientStorage => {\n let _chains = [] as ExtendedChain[]\n let _rpcUrls = { ...config.rpcUrls } as RPCUrls\n let _chainsUpdatedAt: number | undefined\n\n const updateRpcUrls = () => {\n _rpcUrls = { ...config.rpcUrls }\n _rpcUrls = getRpcUrlsFromChains(_rpcUrls, _chains, [ChainId.SOL])\n }\n\n return {\n get needReset() {\n return (\n !_chainsUpdatedAt ||\n Date.now() - _chainsUpdatedAt >= chainsRefreshInterval\n )\n },\n setChains(chains: ExtendedChain[]) {\n _chains = chains\n _chainsUpdatedAt = Date.now()\n updateRpcUrls()\n },\n async getChains() {\n // When preloadChains is false, SDK does not auto-fetch chains\n // External consumer is responsible for calling setChains\n if (!config.preloadChains) {\n return _chains\n }\n\n if (this.needReset || !_chains.length) {\n _chains = await _getChains(config, {\n chainTypes: [\n ChainType.EVM,\n ChainType.SVM,\n ChainType.UTXO,\n ChainType.MVM,\n ],\n })\n _chainsUpdatedAt = Date.now()\n updateRpcUrls()\n }\n return _chains\n },\n async getRpcUrls() {\n await this.getChains() // _rpcUrls is updated when needed\n return _rpcUrls\n },\n }\n}\n"],"mappings":";;;;;AAMA,MAAM,wBAAwB,MAAO,KAAK,KAAK;AAS/C,MAAa,oBAAoB,WAAyC;CACxE,IAAI,UAAU,EAAE;CAChB,IAAI,WAAW,EAAE,GAAG,OAAO,SAAS;CACpC,IAAI;CAEJ,MAAM,sBAAsB;AAC1B,aAAW,EAAE,GAAG,OAAO,SAAS;AAChC,aAAWA,mBAAAA,qBAAqB,UAAU,SAAS,CAACC,YAAAA,QAAQ,IAAI,CAAC;;AAGnE,QAAO;EACL,IAAI,YAAY;AACd,UACE,CAAC,oBACD,KAAK,KAAK,GAAG,oBAAoB;;EAGrC,UAAU,QAAyB;AACjC,aAAU;AACV,sBAAmB,KAAK,KAAK;AAC7B,kBAAe;;EAEjB,MAAM,YAAY;AAGhB,OAAI,CAAC,OAAO,cACV,QAAO;AAGT,OAAI,KAAK,aAAa,CAAC,QAAQ,QAAQ;AACrC,cAAU,MAAMC,0BAAAA,WAAW,QAAQ,EACjC,YAAY;KACVC,YAAAA,UAAU;KACVA,YAAAA,UAAU;KACVA,YAAAA,UAAU;KACVA,YAAAA,UAAU;KACX,EACF,CAAC;AACF,uBAAmB,KAAK,KAAK;AAC7B,mBAAe;;AAEjB,UAAO;;EAET,MAAM,aAAa;AACjB,SAAM,KAAK,WAAW;AACtB,UAAO;;EAEV"}
|
|
1
|
+
{"version":3,"file":"getClientStorage.js","names":["getRpcUrlsFromChains","ChainId","_getChains","ChainType"],"sources":["../../../src/client/getClientStorage.ts"],"sourcesContent":["import { ChainId, ChainType, type ExtendedChain } from '@lifi/types'\nimport { _getChains } from '../actions/getChains.js'\nimport { getRpcUrlsFromChains } from '../core/utils.js'\nimport type { RPCUrls, SDKBaseConfig } from '../types/core.js'\n\n// 6 hours in milliseconds\nconst chainsRefreshInterval = 1000 * 60 * 60 * 6\n\nexport interface ClientStorage {\n readonly needReset: boolean\n setChains(chains: ExtendedChain[]): void\n getChains(): Promise<ExtendedChain[]>\n getRpcUrls(): Promise<RPCUrls>\n}\n\nexport const getClientStorage = (config: SDKBaseConfig): ClientStorage => {\n let _chains = [] as ExtendedChain[]\n let _rpcUrls = { ...config.rpcUrls } as RPCUrls\n let _chainsUpdatedAt: number | undefined\n\n const updateRpcUrls = () => {\n _rpcUrls = { ...config.rpcUrls }\n _rpcUrls = getRpcUrlsFromChains(_rpcUrls, _chains, [ChainId.SOL])\n }\n\n return {\n get needReset() {\n return (\n !_chainsUpdatedAt ||\n Date.now() - _chainsUpdatedAt >= chainsRefreshInterval\n )\n },\n setChains(chains: ExtendedChain[]) {\n _chains = chains\n _chainsUpdatedAt = Date.now()\n updateRpcUrls()\n },\n async getChains() {\n // When preloadChains is false, SDK does not auto-fetch chains\n // External consumer is responsible for calling setChains\n if (!config.preloadChains) {\n return _chains\n }\n\n if (this.needReset || !_chains.length) {\n _chains = await _getChains(config, {\n chainTypes: [\n ChainType.EVM,\n ChainType.SVM,\n ChainType.UTXO,\n ChainType.MVM,\n ChainType.TVM,\n ],\n })\n _chainsUpdatedAt = Date.now()\n updateRpcUrls()\n }\n return _chains\n },\n async getRpcUrls() {\n await this.getChains() // _rpcUrls is updated when needed\n return _rpcUrls\n },\n }\n}\n"],"mappings":";;;;;AAMA,MAAM,wBAAwB,MAAO,KAAK,KAAK;AAS/C,MAAa,oBAAoB,WAAyC;CACxE,IAAI,UAAU,EAAE;CAChB,IAAI,WAAW,EAAE,GAAG,OAAO,SAAS;CACpC,IAAI;CAEJ,MAAM,sBAAsB;AAC1B,aAAW,EAAE,GAAG,OAAO,SAAS;AAChC,aAAWA,mBAAAA,qBAAqB,UAAU,SAAS,CAACC,YAAAA,QAAQ,IAAI,CAAC;;AAGnE,QAAO;EACL,IAAI,YAAY;AACd,UACE,CAAC,oBACD,KAAK,KAAK,GAAG,oBAAoB;;EAGrC,UAAU,QAAyB;AACjC,aAAU;AACV,sBAAmB,KAAK,KAAK;AAC7B,kBAAe;;EAEjB,MAAM,YAAY;AAGhB,OAAI,CAAC,OAAO,cACV,QAAO;AAGT,OAAI,KAAK,aAAa,CAAC,QAAQ,QAAQ;AACrC,cAAU,MAAMC,0BAAAA,WAAW,QAAQ,EACjC,YAAY;KACVC,YAAAA,UAAU;KACVA,YAAAA,UAAU;KACVA,YAAAA,UAAU;KACVA,YAAAA,UAAU;KACVA,YAAAA,UAAU;KACX,EACF,CAAC;AACF,uBAAmB,KAAK,KAAK;AAC7B,mBAAe;;AAEjB,UAAO;;EAET,MAAM,aAAa;AACjB,SAAM,KAAK,WAAW;AACtB,UAAO;;EAEV"}
|
|
@@ -2,7 +2,19 @@ import { SDKClient } from "../../../types/core.js";
|
|
|
2
2
|
import { LiFiStep } from "@lifi/types";
|
|
3
3
|
|
|
4
4
|
//#region src/core/tasks/helpers/checkBalance.d.ts
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Verifies that the wallet holds enough of every token required to execute
|
|
7
|
+
* the step on its source chain — the source-token amount, any gas costs, and
|
|
8
|
+
* any non-included fee costs. Reads all balances in one batched provider
|
|
9
|
+
* call, retries within a bounded budget to absorb transient RPC failures and
|
|
10
|
+
* post-confirmation propagation lag, and applies slippage to the source-token
|
|
11
|
+
* portion only as a last resort (overhead is never trimmed).
|
|
12
|
+
*
|
|
13
|
+
* Throws BalanceError("The balance is too low.") on a genuine shortfall, or
|
|
14
|
+
* BalanceError("Could not read wallet balance.") if the balance can't be read
|
|
15
|
+
* after retries.
|
|
16
|
+
*/
|
|
17
|
+
declare const checkBalance: (client: SDKClient, walletAddress: string, step: LiFiStep) => Promise<void>;
|
|
6
18
|
//#endregion
|
|
7
19
|
export { checkBalance };
|
|
8
20
|
//# sourceMappingURL=checkBalance.d.ts.map
|
|
@@ -1,26 +1,96 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_errors_errors = require("../../../errors/errors.js");
|
|
3
3
|
const require_utils_sleep = require("../../../utils/sleep.js");
|
|
4
|
-
const require_actions_getTokenBalance = require("../../../actions/getTokenBalance.js");
|
|
5
4
|
const require_utils_formatUnits = require("../../../utils/formatUnits.js");
|
|
5
|
+
const require_utils_withTimeout = require("../../../utils/withTimeout.js");
|
|
6
6
|
//#region src/core/tasks/helpers/checkBalance.ts
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
7
|
+
const MAX_ATTEMPTS = 6;
|
|
8
|
+
const BACKOFF_BASE_MS = 150;
|
|
9
|
+
const OVERALL_TIMEOUT_MS = 1e4;
|
|
10
|
+
const SLIPPAGE_PRECISION = 1000000000n;
|
|
11
|
+
/**
|
|
12
|
+
* Verifies that the wallet holds enough of every token required to execute
|
|
13
|
+
* the step on its source chain — the source-token amount, any gas costs, and
|
|
14
|
+
* any non-included fee costs. Reads all balances in one batched provider
|
|
15
|
+
* call, retries within a bounded budget to absorb transient RPC failures and
|
|
16
|
+
* post-confirmation propagation lag, and applies slippage to the source-token
|
|
17
|
+
* portion only as a last resort (overhead is never trimmed).
|
|
18
|
+
*
|
|
19
|
+
* Throws BalanceError("The balance is too low.") on a genuine shortfall, or
|
|
20
|
+
* BalanceError("Could not read wallet balance.") if the balance can't be read
|
|
21
|
+
* after retries.
|
|
22
|
+
*/
|
|
23
|
+
const checkBalance = async (client, walletAddress, step) => {
|
|
24
|
+
const fromChainId = step.action.fromChainId;
|
|
25
|
+
const requirements = /* @__PURE__ */ new Map();
|
|
26
|
+
const add = (token, amount, source) => {
|
|
27
|
+
if (token.chainId !== fromChainId || amount === 0n) return;
|
|
28
|
+
const key = token.address.toLowerCase();
|
|
29
|
+
const req = requirements.get(key) ?? {
|
|
30
|
+
token,
|
|
31
|
+
sourcePart: 0n,
|
|
32
|
+
overheadPart: 0n
|
|
33
|
+
};
|
|
34
|
+
if (source) req.sourcePart += amount;
|
|
35
|
+
else req.overheadPart += amount;
|
|
36
|
+
requirements.set(key, req);
|
|
37
|
+
};
|
|
38
|
+
add(step.action.fromToken, BigInt(step.action.fromAmount), true);
|
|
39
|
+
for (const gas of step.estimate?.gasCosts ?? []) add(gas.token, BigInt(gas.amount), false);
|
|
40
|
+
for (const fee of step.estimate?.feeCosts ?? []) if (!fee.included) add(fee.token, BigInt(fee.amount), false);
|
|
41
|
+
if (requirements.size === 0) return;
|
|
42
|
+
const provider = client.providers.find((p) => p.isAddress(walletAddress));
|
|
43
|
+
if (!provider) throw new Error(`SDK Token Provider for ${walletAddress} is not found.`);
|
|
44
|
+
const reqs = Array.from(requirements.values());
|
|
45
|
+
const tokens = reqs.map((r) => r.token);
|
|
46
|
+
const slippage = step.action.slippage ?? 0;
|
|
47
|
+
const slippageScaled = BigInt(Math.floor((1 - slippage) * Number(SLIPPAGE_PRECISION)));
|
|
48
|
+
await require_utils_withTimeout.withTimeout(async () => {
|
|
49
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
50
|
+
const isFinal = attempt === MAX_ATTEMPTS - 1;
|
|
51
|
+
let balances;
|
|
52
|
+
try {
|
|
53
|
+
balances = await provider.getBalance(client, walletAddress, tokens);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (isFinal) throw new require_errors_errors.BalanceError("Could not read wallet balance.", error);
|
|
56
|
+
await require_utils_sleep.sleep(BACKOFF_BASE_MS * 2 ** attempt);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const balanceByAddress = new Map(balances.map((b) => [b.address.toLowerCase(), b.amount]));
|
|
60
|
+
const unknown = [];
|
|
61
|
+
const insufficient = [];
|
|
62
|
+
for (const req of reqs) {
|
|
63
|
+
const have = balanceByAddress.get(req.token.address.toLowerCase());
|
|
64
|
+
if (have === void 0) unknown.push(req.token);
|
|
65
|
+
else if (have < req.sourcePart + req.overheadPart) insufficient.push({
|
|
66
|
+
req,
|
|
67
|
+
have
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (unknown.length === 0 && insufficient.length === 0) return;
|
|
71
|
+
if (isFinal && unknown.length === 0 && insufficient.length === 1 && insufficient[0].req.sourcePart > 0n) {
|
|
72
|
+
const { req, have } = insufficient[0];
|
|
73
|
+
if (have >= req.sourcePart * slippageScaled / SLIPPAGE_PRECISION + req.overheadPart) {
|
|
74
|
+
step.action.fromAmount = (have - req.overheadPart).toString();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (isFinal) {
|
|
79
|
+
if (unknown.length > 0) throw new require_errors_errors.BalanceError("Could not read wallet balance.", /* @__PURE__ */ new Error(`Could not read balance for: ${unknown.map((t) => t.symbol || t.address).join(", ")}.`));
|
|
80
|
+
const lines = insufficient.map(({ req, have }) => {
|
|
81
|
+
const needed = require_utils_formatUnits.formatUnits(req.sourcePart + req.overheadPart, req.token.decimals);
|
|
82
|
+
const current = require_utils_formatUnits.formatUnits(have, req.token.decimals);
|
|
83
|
+
const symbol = req.token.symbol;
|
|
84
|
+
return req.sourcePart > 0n ? `Your ${symbol} balance is too low, you try to transfer ${needed} ${symbol}, but your wallet only holds ${current} ${symbol}.` : `Insufficient ${symbol} for fees: need ${needed} ${symbol}, have ${current} ${symbol}.`;
|
|
85
|
+
});
|
|
86
|
+
throw new require_errors_errors.BalanceError("The balance is too low.", /* @__PURE__ */ new Error(`${lines.join(" ")} No funds have been sent.`));
|
|
87
|
+
}
|
|
88
|
+
await require_utils_sleep.sleep(BACKOFF_BASE_MS * 2 ** attempt);
|
|
22
89
|
}
|
|
23
|
-
}
|
|
90
|
+
}, {
|
|
91
|
+
timeout: OVERALL_TIMEOUT_MS,
|
|
92
|
+
errorInstance: new require_errors_errors.BalanceError("Could not read wallet balance.")
|
|
93
|
+
});
|
|
24
94
|
};
|
|
25
95
|
//#endregion
|
|
26
96
|
exports.checkBalance = checkBalance;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkBalance.js","names":["
|
|
1
|
+
{"version":3,"file":"checkBalance.js","names":["withTimeout","BalanceError","sleep","formatUnits"],"sources":["../../../../../src/core/tasks/helpers/checkBalance.ts"],"sourcesContent":["import type { LiFiStep, Token, TokenAmount } from '@lifi/types'\nimport { BalanceError } from '../../../errors/errors.js'\nimport type { SDKClient } from '../../../types/core.js'\nimport { formatUnits } from '../../../utils/formatUnits.js'\nimport { sleep } from '../../../utils/sleep.js'\nimport { withTimeout } from '../../../utils/withTimeout.js'\n\nconst MAX_ATTEMPTS = 6\n// Exponential backoff: 150, 300, 600, 1200, 2400 → ≈4.65s of sleep total.\nconst BACKOFF_BASE_MS = 150\nconst OVERALL_TIMEOUT_MS = 10_000\nconst SLIPPAGE_PRECISION = 1_000_000_000n\n\ntype Requirement = {\n token: Token\n sourcePart: bigint // 0n for pure overhead tokens\n overheadPart: bigint // gas + non-included fees in this token\n}\n\n/**\n * Verifies that the wallet holds enough of every token required to execute\n * the step on its source chain — the source-token amount, any gas costs, and\n * any non-included fee costs. Reads all balances in one batched provider\n * call, retries within a bounded budget to absorb transient RPC failures and\n * post-confirmation propagation lag, and applies slippage to the source-token\n * portion only as a last resort (overhead is never trimmed).\n *\n * Throws BalanceError(\"The balance is too low.\") on a genuine shortfall, or\n * BalanceError(\"Could not read wallet balance.\") if the balance can't be read\n * after retries.\n */\nexport const checkBalance = async (\n client: SDKClient,\n walletAddress: string,\n step: LiFiStep\n): Promise<void> => {\n const fromChainId = step.action.fromChainId\n const requirements = new Map<string, Requirement>()\n const add = (token: Token, amount: bigint, source: boolean): void => {\n if (token.chainId !== fromChainId || amount === 0n) {\n return\n }\n const key = token.address.toLowerCase()\n const req = requirements.get(key) ?? {\n token,\n sourcePart: 0n,\n overheadPart: 0n,\n }\n if (source) {\n req.sourcePart += amount\n } else {\n req.overheadPart += amount\n }\n requirements.set(key, req)\n }\n add(step.action.fromToken, BigInt(step.action.fromAmount), true)\n for (const gas of step.estimate?.gasCosts ?? []) {\n add(gas.token, BigInt(gas.amount), false)\n }\n for (const fee of step.estimate?.feeCosts ?? []) {\n // Included fees are already part of fromAmount — don't count twice.\n if (!fee.included) {\n add(fee.token, BigInt(fee.amount), false)\n }\n }\n if (requirements.size === 0) {\n return\n }\n\n // Provider is dispatched by wallet address; all requirements share the\n // source chain, which matches this provider by virtue of the address.\n const provider = client.providers.find((p) => p.isAddress(walletAddress))\n if (!provider) {\n throw new Error(`SDK Token Provider for ${walletAddress} is not found.`)\n }\n\n const reqs = Array.from(requirements.values())\n const tokens = reqs.map((r) => r.token)\n const slippage = step.action.slippage ?? 0\n const slippageScaled = BigInt(\n Math.floor((1 - slippage) * Number(SLIPPAGE_PRECISION))\n )\n\n await withTimeout(\n async () => {\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {\n const isFinal = attempt === MAX_ATTEMPTS - 1\n\n let balances: TokenAmount[]\n try {\n balances = await provider.getBalance(client, walletAddress, tokens)\n } catch (error) {\n if (isFinal) {\n throw new BalanceError(\n 'Could not read wallet balance.',\n error as Error\n )\n }\n await sleep(BACKOFF_BASE_MS * 2 ** attempt)\n continue\n }\n\n const balanceByAddress = new Map(\n balances.map((b) => [b.address.toLowerCase(), b.amount] as const)\n )\n\n const unknown: Token[] = []\n const insufficient: { req: Requirement; have: bigint }[] = []\n for (const req of reqs) {\n const have = balanceByAddress.get(req.token.address.toLowerCase())\n if (have === undefined) {\n unknown.push(req.token)\n } else if (have < req.sourcePart + req.overheadPart) {\n insufficient.push({ req, have })\n }\n }\n\n if (unknown.length === 0 && insufficient.length === 0) {\n return\n }\n\n // Final-attempt slippage rescue: only when the sole shortfall is the\n // source-token portion. Trim source down to (balance − overhead) so\n // the overhead reserve is preserved.\n if (\n isFinal &&\n unknown.length === 0 &&\n insufficient.length === 1 &&\n insufficient[0].req.sourcePart > 0n\n ) {\n const { req, have } = insufficient[0]\n const minAcceptable =\n (req.sourcePart * slippageScaled) / SLIPPAGE_PRECISION +\n req.overheadPart\n if (have >= minAcceptable) {\n step.action.fromAmount = (have - req.overheadPart).toString()\n return\n }\n }\n\n if (isFinal) {\n if (unknown.length > 0) {\n throw new BalanceError(\n 'Could not read wallet balance.',\n new Error(\n `Could not read balance for: ${unknown\n .map((t) => t.symbol || t.address)\n .join(', ')}.`\n )\n )\n }\n const lines = insufficient.map(({ req, have }) => {\n const needed = formatUnits(\n req.sourcePart + req.overheadPart,\n req.token.decimals\n )\n const current = formatUnits(have, req.token.decimals)\n const symbol = req.token.symbol\n return req.sourcePart > 0n\n ? `Your ${symbol} balance is too low, you try to transfer ${needed} ${symbol}, but your wallet only holds ${current} ${symbol}.`\n : `Insufficient ${symbol} for fees: need ${needed} ${symbol}, have ${current} ${symbol}.`\n })\n throw new BalanceError(\n 'The balance is too low.',\n new Error(`${lines.join(' ')} No funds have been sent.`)\n )\n }\n\n await sleep(BACKOFF_BASE_MS * 2 ** attempt)\n }\n },\n {\n timeout: OVERALL_TIMEOUT_MS,\n errorInstance: new BalanceError('Could not read wallet balance.'),\n }\n )\n}\n"],"mappings":";;;;;;AAOA,MAAM,eAAe;AAErB,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;;;;;;;;;;;;;AAoB3B,MAAa,eAAe,OAC1B,QACA,eACA,SACkB;CAClB,MAAM,cAAc,KAAK,OAAO;CAChC,MAAM,+BAAe,IAAI,KAA0B;CACnD,MAAM,OAAO,OAAc,QAAgB,WAA0B;AACnE,MAAI,MAAM,YAAY,eAAe,WAAW,GAC9C;EAEF,MAAM,MAAM,MAAM,QAAQ,aAAa;EACvC,MAAM,MAAM,aAAa,IAAI,IAAI,IAAI;GACnC;GACA,YAAY;GACZ,cAAc;GACf;AACD,MAAI,OACF,KAAI,cAAc;MAElB,KAAI,gBAAgB;AAEtB,eAAa,IAAI,KAAK,IAAI;;AAE5B,KAAI,KAAK,OAAO,WAAW,OAAO,KAAK,OAAO,WAAW,EAAE,KAAK;AAChE,MAAK,MAAM,OAAO,KAAK,UAAU,YAAY,EAAE,CAC7C,KAAI,IAAI,OAAO,OAAO,IAAI,OAAO,EAAE,MAAM;AAE3C,MAAK,MAAM,OAAO,KAAK,UAAU,YAAY,EAAE,CAE7C,KAAI,CAAC,IAAI,SACP,KAAI,IAAI,OAAO,OAAO,IAAI,OAAO,EAAE,MAAM;AAG7C,KAAI,aAAa,SAAS,EACxB;CAKF,MAAM,WAAW,OAAO,UAAU,MAAM,MAAM,EAAE,UAAU,cAAc,CAAC;AACzE,KAAI,CAAC,SACH,OAAM,IAAI,MAAM,0BAA0B,cAAc,gBAAgB;CAG1E,MAAM,OAAO,MAAM,KAAK,aAAa,QAAQ,CAAC;CAC9C,MAAM,SAAS,KAAK,KAAK,MAAM,EAAE,MAAM;CACvC,MAAM,WAAW,KAAK,OAAO,YAAY;CACzC,MAAM,iBAAiB,OACrB,KAAK,OAAO,IAAI,YAAY,OAAO,mBAAmB,CAAC,CACxD;AAED,OAAMA,0BAAAA,YACJ,YAAY;AACV,OAAK,IAAI,UAAU,GAAG,UAAU,cAAc,WAAW;GACvD,MAAM,UAAU,YAAY,eAAe;GAE3C,IAAI;AACJ,OAAI;AACF,eAAW,MAAM,SAAS,WAAW,QAAQ,eAAe,OAAO;YAC5D,OAAO;AACd,QAAI,QACF,OAAM,IAAIC,sBAAAA,aACR,kCACA,MACD;AAEH,UAAMC,oBAAAA,MAAM,kBAAkB,KAAK,QAAQ;AAC3C;;GAGF,MAAM,mBAAmB,IAAI,IAC3B,SAAS,KAAK,MAAM,CAAC,EAAE,QAAQ,aAAa,EAAE,EAAE,OAAO,CAAU,CAClE;GAED,MAAM,UAAmB,EAAE;GAC3B,MAAM,eAAqD,EAAE;AAC7D,QAAK,MAAM,OAAO,MAAM;IACtB,MAAM,OAAO,iBAAiB,IAAI,IAAI,MAAM,QAAQ,aAAa,CAAC;AAClE,QAAI,SAAS,KAAA,EACX,SAAQ,KAAK,IAAI,MAAM;aACd,OAAO,IAAI,aAAa,IAAI,aACrC,cAAa,KAAK;KAAE;KAAK;KAAM,CAAC;;AAIpC,OAAI,QAAQ,WAAW,KAAK,aAAa,WAAW,EAClD;AAMF,OACE,WACA,QAAQ,WAAW,KACnB,aAAa,WAAW,KACxB,aAAa,GAAG,IAAI,aAAa,IACjC;IACA,MAAM,EAAE,KAAK,SAAS,aAAa;AAInC,QAAI,QAFD,IAAI,aAAa,iBAAkB,qBACpC,IAAI,cACqB;AACzB,UAAK,OAAO,cAAc,OAAO,IAAI,cAAc,UAAU;AAC7D;;;AAIJ,OAAI,SAAS;AACX,QAAI,QAAQ,SAAS,EACnB,OAAM,IAAID,sBAAAA,aACR,kDACA,IAAI,MACF,+BAA+B,QAC5B,KAAK,MAAM,EAAE,UAAU,EAAE,QAAQ,CACjC,KAAK,KAAK,CAAC,GACf,CACF;IAEH,MAAM,QAAQ,aAAa,KAAK,EAAE,KAAK,WAAW;KAChD,MAAM,SAASE,0BAAAA,YACb,IAAI,aAAa,IAAI,cACrB,IAAI,MAAM,SACX;KACD,MAAM,UAAUA,0BAAAA,YAAY,MAAM,IAAI,MAAM,SAAS;KACrD,MAAM,SAAS,IAAI,MAAM;AACzB,YAAO,IAAI,aAAa,KACpB,QAAQ,OAAO,2CAA2C,OAAO,GAAG,OAAO,+BAA+B,QAAQ,GAAG,OAAO,KAC5H,gBAAgB,OAAO,kBAAkB,OAAO,GAAG,OAAO,SAAS,QAAQ,GAAG,OAAO;MACzF;AACF,UAAM,IAAIF,sBAAAA,aACR,2CACA,IAAI,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC,2BAA2B,CACzD;;AAGH,SAAMC,oBAAAA,MAAM,kBAAkB,KAAK,QAAQ;;IAG/C;EACE,SAAS;EACT,eAAe,IAAID,sBAAAA,aAAa,iCAAiC;EAClE,CACF"}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -50,5 +50,6 @@ import { parseUnits } from "./utils/parseUnits.js";
|
|
|
50
50
|
import { sleep } from "./utils/sleep.js";
|
|
51
51
|
import { waitForResult } from "./utils/waitForResult.js";
|
|
52
52
|
import { LruMap, withDedupe } from "./utils/withDedupe.js";
|
|
53
|
+
import { withTimeout } from "./utils/withTimeout.js";
|
|
53
54
|
export * from "@lifi/types";
|
|
54
|
-
export { type AcceptExchangeRateUpdateHook, type AcceptSlippageUpdateHook, type AcceptSlippageUpdateHookParams, BalanceError, BaseError, BaseStepExecutionTask, BaseStepExecutor, CheckBalanceTask, type ContractCallParams, type ContractTool, type ErrorCode, ErrorMessage, ErrorName, type ExchangeRateUpdateParams, ExecuteStepRetryError, type ExecuteStepRetryParams, type Execution, type ExecutionAction, type ExecutionActionStatus, type ExecutionActionType, type ExecutionOptions, type ExecutionStatus, type GetContractCallsHook, type GetContractCallsResult, HTTPError, InMemoryStorage, type InteractionSettings, LiFiErrorCode, type LiFiStepExtended, LocalStorageAdapter, LruMap, PrepareTransactionTask, ProviderError, RPCError, type RPCUrls, type RequestInterceptor, type RouteExecutionData, type RouteExecutionDataDictionary, type RouteExecutionDictionary, type RouteExtended, type SDKBaseConfig, type SDKClient, type SDKConfig, SDKError, type SDKProvider, type SDKStorage, ServerError, StatusManager, type StepExecutor, type StepExecutorBaseContext, type StepExecutorContext, type StepExecutorOptions, type StepExtended, TaskPipeline, type TaskResult, type TaskStatus, TransactionError, type TransactionMethodType, type TransactionParameters, type TransactionRequestParameters, type TransactionRequestUpdateHook, UnknownError, type UpdateRouteHook, ValidationError, WaitForTransactionStatusTask, actions, checkBalance, checkPackageUpdates, convertQuoteToRoute, createClient, createDefaultStorage, executeRoute, fetchTxErrorDetails, formatUnits, getActionMessage, getActiveRoute, getActiveRoutes, getChains, getConnections, getContractCallsQuote, getGasRecommendation, getNameServiceAddress, getQuote, getRelayedTransactionStatus, getRelayerQuote, getRoutes, getStatus, getStepTransaction, getSubstatusMessage, getToken, getTokenBalance, getTokenBalances, getTokenBalancesByChain, getTokens, getTools, getTransactionHistory, getTransactionRequestData, getWalletBalances, isHex, parseUnits, patchContractCalls, relayTransaction, resumeRoute, sleep, stepComparison, stopRouteExecution, updateRouteExecution, waitForResult, withDedupe };
|
|
55
|
+
export { type AcceptExchangeRateUpdateHook, type AcceptSlippageUpdateHook, type AcceptSlippageUpdateHookParams, BalanceError, BaseError, BaseStepExecutionTask, BaseStepExecutor, CheckBalanceTask, type ContractCallParams, type ContractTool, type ErrorCode, ErrorMessage, ErrorName, type ExchangeRateUpdateParams, ExecuteStepRetryError, type ExecuteStepRetryParams, type Execution, type ExecutionAction, type ExecutionActionStatus, type ExecutionActionType, type ExecutionOptions, type ExecutionStatus, type GetContractCallsHook, type GetContractCallsResult, HTTPError, InMemoryStorage, type InteractionSettings, LiFiErrorCode, type LiFiStepExtended, LocalStorageAdapter, LruMap, PrepareTransactionTask, ProviderError, RPCError, type RPCUrls, type RequestInterceptor, type RouteExecutionData, type RouteExecutionDataDictionary, type RouteExecutionDictionary, type RouteExtended, type SDKBaseConfig, type SDKClient, type SDKConfig, SDKError, type SDKProvider, type SDKStorage, ServerError, StatusManager, type StepExecutor, type StepExecutorBaseContext, type StepExecutorContext, type StepExecutorOptions, type StepExtended, TaskPipeline, type TaskResult, type TaskStatus, TransactionError, type TransactionMethodType, type TransactionParameters, type TransactionRequestParameters, type TransactionRequestUpdateHook, UnknownError, type UpdateRouteHook, ValidationError, WaitForTransactionStatusTask, actions, checkBalance, checkPackageUpdates, convertQuoteToRoute, createClient, createDefaultStorage, executeRoute, fetchTxErrorDetails, formatUnits, getActionMessage, getActiveRoute, getActiveRoutes, getChains, getConnections, getContractCallsQuote, getGasRecommendation, getNameServiceAddress, getQuote, getRelayedTransactionStatus, getRelayerQuote, getRoutes, getStatus, getStepTransaction, getSubstatusMessage, getToken, getTokenBalance, getTokenBalances, getTokenBalancesByChain, getTokens, getTools, getTransactionHistory, getTransactionRequestData, getWalletBalances, isHex, parseUnits, patchContractCalls, relayTransaction, resumeRoute, sleep, stepComparison, stopRouteExecution, updateRouteExecution, waitForResult, withDedupe, withTimeout };
|
package/dist/cjs/index.js
CHANGED
|
@@ -38,6 +38,7 @@ const require_core_execution = require("./core/execution.js");
|
|
|
38
38
|
const require_core_storage = require("./core/storage.js");
|
|
39
39
|
const require_core_TaskPipeline = require("./core/TaskPipeline.js");
|
|
40
40
|
const require_utils_formatUnits = require("./utils/formatUnits.js");
|
|
41
|
+
const require_utils_withTimeout = require("./utils/withTimeout.js");
|
|
41
42
|
const require_core_tasks_helpers_checkBalance = require("./core/tasks/helpers/checkBalance.js");
|
|
42
43
|
const require_core_tasks_CheckBalanceTask = require("./core/tasks/CheckBalanceTask.js");
|
|
43
44
|
const require_core_tasks_helpers_getTransactionRequestData = require("./core/tasks/helpers/getTransactionRequestData.js");
|
|
@@ -117,6 +118,7 @@ exports.stopRouteExecution = require_core_execution.stopRouteExecution;
|
|
|
117
118
|
exports.updateRouteExecution = require_core_execution.updateRouteExecution;
|
|
118
119
|
exports.waitForResult = require_utils_waitForResult.waitForResult;
|
|
119
120
|
exports.withDedupe = require_utils_withDedupe.withDedupe;
|
|
121
|
+
exports.withTimeout = require_utils_withTimeout.withTimeout;
|
|
120
122
|
var _lifi_types = require("@lifi/types");
|
|
121
123
|
Object.keys(_lifi_types).forEach(function(k) {
|
|
122
124
|
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
@@ -5,7 +5,7 @@ const checkPackageUpdates = async (packageName, packageVersion) => {
|
|
|
5
5
|
try {
|
|
6
6
|
const pkgName = packageName ?? "@lifi/sdk";
|
|
7
7
|
const latestVersion = (await (await fetch(`https://registry.npmjs.org/${pkgName}/latest`)).json()).version;
|
|
8
|
-
const currentVersion = packageVersion ?? "4.0.0-beta.
|
|
8
|
+
const currentVersion = packageVersion ?? "4.0.0-beta.7";
|
|
9
9
|
if (latestVersion > currentVersion) console.warn(`${pkgName}: new package version is available. Please update as soon as possible to enjoy the newest features. Current version: ${currentVersion}. Latest version: ${latestVersion}.`);
|
|
10
10
|
} catch (_error) {}
|
|
11
11
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/utils/withTimeout.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a function in a timeout.
|
|
4
|
+
* Based on viem's withTimeout implementation.
|
|
5
|
+
* @param fn - The function to wrap.
|
|
6
|
+
* @param timeout - The timeout in milliseconds.
|
|
7
|
+
* @param errorInstance - The error instance to throw when the timeout is reached.
|
|
8
|
+
* @param signal - Whether or not the timeout should use an abort signal.
|
|
9
|
+
* @returns The result of the function.
|
|
10
|
+
*/
|
|
11
|
+
declare function withTimeout<T>(fn: ({
|
|
12
|
+
signal
|
|
13
|
+
}: {
|
|
14
|
+
signal: AbortController["signal"] | null;
|
|
15
|
+
}) => Promise<T>, {
|
|
16
|
+
errorInstance,
|
|
17
|
+
timeout,
|
|
18
|
+
signal
|
|
19
|
+
}: {
|
|
20
|
+
errorInstance?: Error | undefined;
|
|
21
|
+
timeout: number;
|
|
22
|
+
signal?: boolean | undefined;
|
|
23
|
+
}): Promise<T>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { withTimeout };
|
|
26
|
+
//# sourceMappingURL=withTimeout.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/utils/withTimeout.ts
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a function in a timeout.
|
|
5
|
+
* Based on viem's withTimeout implementation.
|
|
6
|
+
* @param fn - The function to wrap.
|
|
7
|
+
* @param timeout - The timeout in milliseconds.
|
|
8
|
+
* @param errorInstance - The error instance to throw when the timeout is reached.
|
|
9
|
+
* @param signal - Whether or not the timeout should use an abort signal.
|
|
10
|
+
* @returns The result of the function.
|
|
11
|
+
*/
|
|
12
|
+
function withTimeout(fn, { errorInstance = /* @__PURE__ */ new Error("Timed out after waiting for too long."), timeout, signal }) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
(async () => {
|
|
15
|
+
let timeoutId;
|
|
16
|
+
try {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
if (timeout > 0) timeoutId = setTimeout(() => {
|
|
19
|
+
if (signal) controller.abort();
|
|
20
|
+
else reject(errorInstance);
|
|
21
|
+
}, timeout);
|
|
22
|
+
resolve(await fn({ signal: controller?.signal || null }));
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (err?.name === "AbortError") reject(errorInstance);
|
|
25
|
+
reject(err);
|
|
26
|
+
} finally {
|
|
27
|
+
clearTimeout(timeoutId);
|
|
28
|
+
}
|
|
29
|
+
})();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
exports.withTimeout = withTimeout;
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=withTimeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withTimeout.js","names":[],"sources":["../../../src/utils/withTimeout.ts"],"sourcesContent":["/**\n * Wraps a function in a timeout.\n * Based on viem's withTimeout implementation.\n * @param fn - The function to wrap.\n * @param timeout - The timeout in milliseconds.\n * @param errorInstance - The error instance to throw when the timeout is reached.\n * @param signal - Whether or not the timeout should use an abort signal.\n * @returns The result of the function.\n */\nexport function withTimeout<T>(\n fn: ({ signal }: { signal: AbortController['signal'] | null }) => Promise<T>,\n {\n errorInstance = new Error('Timed out after waiting for too long.'),\n timeout,\n signal,\n }: {\n // The error instance to throw when the timeout is reached.\n errorInstance?: Error | undefined\n // The timeout (in ms).\n timeout: number\n // Whether or not the timeout should use an abort signal.\n signal?: boolean | undefined\n }\n): Promise<T> {\n return new Promise((resolve, reject) => {\n ;(async () => {\n let timeoutId!: NodeJS.Timeout\n try {\n const controller = new AbortController()\n if (timeout > 0) {\n timeoutId = setTimeout(() => {\n if (signal) {\n controller.abort()\n } else {\n reject(errorInstance)\n }\n }, timeout) as NodeJS.Timeout // need to cast because bun globals.d.ts overrides @types/node\n }\n resolve(await fn({ signal: controller?.signal || null }))\n } catch (err) {\n if ((err as Error)?.name === 'AbortError') {\n reject(errorInstance)\n }\n reject(err)\n } finally {\n clearTimeout(timeoutId)\n }\n })()\n })\n}\n"],"mappings":";;;;;;;;;;;AASA,SAAgB,YACd,IACA,EACE,gCAAgB,IAAI,MAAM,wCAAwC,EAClE,SACA,UASU;AACZ,QAAO,IAAI,SAAS,SAAS,WAAW;AACrC,GAAC,YAAY;GACZ,IAAI;AACJ,OAAI;IACF,MAAM,aAAa,IAAI,iBAAiB;AACxC,QAAI,UAAU,EACZ,aAAY,iBAAiB;AAC3B,SAAI,OACF,YAAW,OAAO;SAElB,QAAO,cAAc;OAEtB,QAAQ;AAEb,YAAQ,MAAM,GAAG,EAAE,QAAQ,YAAY,UAAU,MAAM,CAAC,CAAC;YAClD,KAAK;AACZ,QAAK,KAAe,SAAS,aAC3B,QAAO,cAAc;AAEvB,WAAO,IAAI;aACH;AACR,iBAAa,UAAU;;MAEvB;GACJ"}
|
package/dist/cjs/version.d.ts
CHANGED
package/dist/cjs/version.js
CHANGED
package/dist/cjs/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","names":[],"sources":["../../src/version.ts"],"sourcesContent":["export const name = '@lifi/sdk'\nexport const version = '4.0.0-beta.
|
|
1
|
+
{"version":3,"file":"version.js","names":[],"sources":["../../src/version.ts"],"sourcesContent":["export const name = '@lifi/sdk'\nexport const version = '4.0.0-beta.7'\n"],"mappings":";;AAAA,MAAa,OAAO;AACpB,MAAa,UAAU"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getClientStorage.js","names":[],"sources":["../../../src/client/getClientStorage.ts"],"sourcesContent":["import { ChainId, ChainType, type ExtendedChain } from '@lifi/types'\nimport { _getChains } from '../actions/getChains.js'\nimport { getRpcUrlsFromChains } from '../core/utils.js'\nimport type { RPCUrls, SDKBaseConfig } from '../types/core.js'\n\n// 6 hours in milliseconds\nconst chainsRefreshInterval = 1000 * 60 * 60 * 6\n\nexport interface ClientStorage {\n readonly needReset: boolean\n setChains(chains: ExtendedChain[]): void\n getChains(): Promise<ExtendedChain[]>\n getRpcUrls(): Promise<RPCUrls>\n}\n\nexport const getClientStorage = (config: SDKBaseConfig): ClientStorage => {\n let _chains = [] as ExtendedChain[]\n let _rpcUrls = { ...config.rpcUrls } as RPCUrls\n let _chainsUpdatedAt: number | undefined\n\n const updateRpcUrls = () => {\n _rpcUrls = { ...config.rpcUrls }\n _rpcUrls = getRpcUrlsFromChains(_rpcUrls, _chains, [ChainId.SOL])\n }\n\n return {\n get needReset() {\n return (\n !_chainsUpdatedAt ||\n Date.now() - _chainsUpdatedAt >= chainsRefreshInterval\n )\n },\n setChains(chains: ExtendedChain[]) {\n _chains = chains\n _chainsUpdatedAt = Date.now()\n updateRpcUrls()\n },\n async getChains() {\n // When preloadChains is false, SDK does not auto-fetch chains\n // External consumer is responsible for calling setChains\n if (!config.preloadChains) {\n return _chains\n }\n\n if (this.needReset || !_chains.length) {\n _chains = await _getChains(config, {\n chainTypes: [\n ChainType.EVM,\n ChainType.SVM,\n ChainType.UTXO,\n ChainType.MVM,\n ],\n })\n _chainsUpdatedAt = Date.now()\n updateRpcUrls()\n }\n return _chains\n },\n async getRpcUrls() {\n await this.getChains() // _rpcUrls is updated when needed\n return _rpcUrls\n },\n }\n}\n"],"mappings":";;;;AAMA,MAAM,wBAAwB,MAAO,KAAK,KAAK;AAS/C,MAAa,oBAAoB,WAAyC;CACxE,IAAI,UAAU,EAAE;CAChB,IAAI,WAAW,EAAE,GAAG,OAAO,SAAS;CACpC,IAAI;CAEJ,MAAM,sBAAsB;AAC1B,aAAW,EAAE,GAAG,OAAO,SAAS;AAChC,aAAW,qBAAqB,UAAU,SAAS,CAAC,QAAQ,IAAI,CAAC;;AAGnE,QAAO;EACL,IAAI,YAAY;AACd,UACE,CAAC,oBACD,KAAK,KAAK,GAAG,oBAAoB;;EAGrC,UAAU,QAAyB;AACjC,aAAU;AACV,sBAAmB,KAAK,KAAK;AAC7B,kBAAe;;EAEjB,MAAM,YAAY;AAGhB,OAAI,CAAC,OAAO,cACV,QAAO;AAGT,OAAI,KAAK,aAAa,CAAC,QAAQ,QAAQ;AACrC,cAAU,MAAM,WAAW,QAAQ,EACjC,YAAY;KACV,UAAU;KACV,UAAU;KACV,UAAU;KACV,UAAU;KACX,EACF,CAAC;AACF,uBAAmB,KAAK,KAAK;AAC7B,mBAAe;;AAEjB,UAAO;;EAET,MAAM,aAAa;AACjB,SAAM,KAAK,WAAW;AACtB,UAAO;;EAEV"}
|
|
1
|
+
{"version":3,"file":"getClientStorage.js","names":[],"sources":["../../../src/client/getClientStorage.ts"],"sourcesContent":["import { ChainId, ChainType, type ExtendedChain } from '@lifi/types'\nimport { _getChains } from '../actions/getChains.js'\nimport { getRpcUrlsFromChains } from '../core/utils.js'\nimport type { RPCUrls, SDKBaseConfig } from '../types/core.js'\n\n// 6 hours in milliseconds\nconst chainsRefreshInterval = 1000 * 60 * 60 * 6\n\nexport interface ClientStorage {\n readonly needReset: boolean\n setChains(chains: ExtendedChain[]): void\n getChains(): Promise<ExtendedChain[]>\n getRpcUrls(): Promise<RPCUrls>\n}\n\nexport const getClientStorage = (config: SDKBaseConfig): ClientStorage => {\n let _chains = [] as ExtendedChain[]\n let _rpcUrls = { ...config.rpcUrls } as RPCUrls\n let _chainsUpdatedAt: number | undefined\n\n const updateRpcUrls = () => {\n _rpcUrls = { ...config.rpcUrls }\n _rpcUrls = getRpcUrlsFromChains(_rpcUrls, _chains, [ChainId.SOL])\n }\n\n return {\n get needReset() {\n return (\n !_chainsUpdatedAt ||\n Date.now() - _chainsUpdatedAt >= chainsRefreshInterval\n )\n },\n setChains(chains: ExtendedChain[]) {\n _chains = chains\n _chainsUpdatedAt = Date.now()\n updateRpcUrls()\n },\n async getChains() {\n // When preloadChains is false, SDK does not auto-fetch chains\n // External consumer is responsible for calling setChains\n if (!config.preloadChains) {\n return _chains\n }\n\n if (this.needReset || !_chains.length) {\n _chains = await _getChains(config, {\n chainTypes: [\n ChainType.EVM,\n ChainType.SVM,\n ChainType.UTXO,\n ChainType.MVM,\n ChainType.TVM,\n ],\n })\n _chainsUpdatedAt = Date.now()\n updateRpcUrls()\n }\n return _chains\n },\n async getRpcUrls() {\n await this.getChains() // _rpcUrls is updated when needed\n return _rpcUrls\n },\n }\n}\n"],"mappings":";;;;AAMA,MAAM,wBAAwB,MAAO,KAAK,KAAK;AAS/C,MAAa,oBAAoB,WAAyC;CACxE,IAAI,UAAU,EAAE;CAChB,IAAI,WAAW,EAAE,GAAG,OAAO,SAAS;CACpC,IAAI;CAEJ,MAAM,sBAAsB;AAC1B,aAAW,EAAE,GAAG,OAAO,SAAS;AAChC,aAAW,qBAAqB,UAAU,SAAS,CAAC,QAAQ,IAAI,CAAC;;AAGnE,QAAO;EACL,IAAI,YAAY;AACd,UACE,CAAC,oBACD,KAAK,KAAK,GAAG,oBAAoB;;EAGrC,UAAU,QAAyB;AACjC,aAAU;AACV,sBAAmB,KAAK,KAAK;AAC7B,kBAAe;;EAEjB,MAAM,YAAY;AAGhB,OAAI,CAAC,OAAO,cACV,QAAO;AAGT,OAAI,KAAK,aAAa,CAAC,QAAQ,QAAQ;AACrC,cAAU,MAAM,WAAW,QAAQ,EACjC,YAAY;KACV,UAAU;KACV,UAAU;KACV,UAAU;KACV,UAAU;KACV,UAAU;KACX,EACF,CAAC;AACF,uBAAmB,KAAK,KAAK;AAC7B,mBAAe;;AAEjB,UAAO;;EAET,MAAM,aAAa;AACjB,SAAM,KAAK,WAAW;AACtB,UAAO;;EAEV"}
|
|
@@ -2,7 +2,19 @@ import { SDKClient } from "../../../types/core.js";
|
|
|
2
2
|
import { LiFiStep } from "@lifi/types";
|
|
3
3
|
|
|
4
4
|
//#region src/core/tasks/helpers/checkBalance.d.ts
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Verifies that the wallet holds enough of every token required to execute
|
|
7
|
+
* the step on its source chain — the source-token amount, any gas costs, and
|
|
8
|
+
* any non-included fee costs. Reads all balances in one batched provider
|
|
9
|
+
* call, retries within a bounded budget to absorb transient RPC failures and
|
|
10
|
+
* post-confirmation propagation lag, and applies slippage to the source-token
|
|
11
|
+
* portion only as a last resort (overhead is never trimmed).
|
|
12
|
+
*
|
|
13
|
+
* Throws BalanceError("The balance is too low.") on a genuine shortfall, or
|
|
14
|
+
* BalanceError("Could not read wallet balance.") if the balance can't be read
|
|
15
|
+
* after retries.
|
|
16
|
+
*/
|
|
17
|
+
declare const checkBalance: (client: SDKClient, walletAddress: string, step: LiFiStep) => Promise<void>;
|
|
6
18
|
//#endregion
|
|
7
19
|
export { checkBalance };
|
|
8
20
|
//# sourceMappingURL=checkBalance.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkBalance.d.ts","names":[],"sources":["../../../../../src/core/tasks/helpers/checkBalance.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"checkBalance.d.ts","names":[],"sources":["../../../../../src/core/tasks/helpers/checkBalance.ts"],"mappings":";;;;;;AA+BA;;;;;;;;;;cAAa,YAAA,GACX,MAAA,EAAQ,SAAA,EACR,aAAA,UACA,IAAA,EAAM,QAAA,KACL,OAAA"}
|
|
@@ -1,25 +1,95 @@
|
|
|
1
1
|
import { BalanceError } from "../../../errors/errors.js";
|
|
2
2
|
import { sleep } from "../../../utils/sleep.js";
|
|
3
|
-
import { getTokenBalance } from "../../../actions/getTokenBalance.js";
|
|
4
3
|
import { formatUnits } from "../../../utils/formatUnits.js";
|
|
4
|
+
import { withTimeout } from "../../../utils/withTimeout.js";
|
|
5
5
|
//#region src/core/tasks/helpers/checkBalance.ts
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
const MAX_ATTEMPTS = 6;
|
|
7
|
+
const BACKOFF_BASE_MS = 150;
|
|
8
|
+
const OVERALL_TIMEOUT_MS = 1e4;
|
|
9
|
+
const SLIPPAGE_PRECISION = 1000000000n;
|
|
10
|
+
/**
|
|
11
|
+
* Verifies that the wallet holds enough of every token required to execute
|
|
12
|
+
* the step on its source chain — the source-token amount, any gas costs, and
|
|
13
|
+
* any non-included fee costs. Reads all balances in one batched provider
|
|
14
|
+
* call, retries within a bounded budget to absorb transient RPC failures and
|
|
15
|
+
* post-confirmation propagation lag, and applies slippage to the source-token
|
|
16
|
+
* portion only as a last resort (overhead is never trimmed).
|
|
17
|
+
*
|
|
18
|
+
* Throws BalanceError("The balance is too low.") on a genuine shortfall, or
|
|
19
|
+
* BalanceError("Could not read wallet balance.") if the balance can't be read
|
|
20
|
+
* after retries.
|
|
21
|
+
*/
|
|
22
|
+
const checkBalance = async (client, walletAddress, step) => {
|
|
23
|
+
const fromChainId = step.action.fromChainId;
|
|
24
|
+
const requirements = /* @__PURE__ */ new Map();
|
|
25
|
+
const add = (token, amount, source) => {
|
|
26
|
+
if (token.chainId !== fromChainId || amount === 0n) return;
|
|
27
|
+
const key = token.address.toLowerCase();
|
|
28
|
+
const req = requirements.get(key) ?? {
|
|
29
|
+
token,
|
|
30
|
+
sourcePart: 0n,
|
|
31
|
+
overheadPart: 0n
|
|
32
|
+
};
|
|
33
|
+
if (source) req.sourcePart += amount;
|
|
34
|
+
else req.overheadPart += amount;
|
|
35
|
+
requirements.set(key, req);
|
|
36
|
+
};
|
|
37
|
+
add(step.action.fromToken, BigInt(step.action.fromAmount), true);
|
|
38
|
+
for (const gas of step.estimate?.gasCosts ?? []) add(gas.token, BigInt(gas.amount), false);
|
|
39
|
+
for (const fee of step.estimate?.feeCosts ?? []) if (!fee.included) add(fee.token, BigInt(fee.amount), false);
|
|
40
|
+
if (requirements.size === 0) return;
|
|
41
|
+
const provider = client.providers.find((p) => p.isAddress(walletAddress));
|
|
42
|
+
if (!provider) throw new Error(`SDK Token Provider for ${walletAddress} is not found.`);
|
|
43
|
+
const reqs = Array.from(requirements.values());
|
|
44
|
+
const tokens = reqs.map((r) => r.token);
|
|
45
|
+
const slippage = step.action.slippage ?? 0;
|
|
46
|
+
const slippageScaled = BigInt(Math.floor((1 - slippage) * Number(SLIPPAGE_PRECISION)));
|
|
47
|
+
await withTimeout(async () => {
|
|
48
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
49
|
+
const isFinal = attempt === MAX_ATTEMPTS - 1;
|
|
50
|
+
let balances;
|
|
51
|
+
try {
|
|
52
|
+
balances = await provider.getBalance(client, walletAddress, tokens);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (isFinal) throw new BalanceError("Could not read wallet balance.", error);
|
|
55
|
+
await sleep(BACKOFF_BASE_MS * 2 ** attempt);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const balanceByAddress = new Map(balances.map((b) => [b.address.toLowerCase(), b.amount]));
|
|
59
|
+
const unknown = [];
|
|
60
|
+
const insufficient = [];
|
|
61
|
+
for (const req of reqs) {
|
|
62
|
+
const have = balanceByAddress.get(req.token.address.toLowerCase());
|
|
63
|
+
if (have === void 0) unknown.push(req.token);
|
|
64
|
+
else if (have < req.sourcePart + req.overheadPart) insufficient.push({
|
|
65
|
+
req,
|
|
66
|
+
have
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (unknown.length === 0 && insufficient.length === 0) return;
|
|
70
|
+
if (isFinal && unknown.length === 0 && insufficient.length === 1 && insufficient[0].req.sourcePart > 0n) {
|
|
71
|
+
const { req, have } = insufficient[0];
|
|
72
|
+
if (have >= req.sourcePart * slippageScaled / SLIPPAGE_PRECISION + req.overheadPart) {
|
|
73
|
+
step.action.fromAmount = (have - req.overheadPart).toString();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (isFinal) {
|
|
78
|
+
if (unknown.length > 0) throw new BalanceError("Could not read wallet balance.", /* @__PURE__ */ new Error(`Could not read balance for: ${unknown.map((t) => t.symbol || t.address).join(", ")}.`));
|
|
79
|
+
const lines = insufficient.map(({ req, have }) => {
|
|
80
|
+
const needed = formatUnits(req.sourcePart + req.overheadPart, req.token.decimals);
|
|
81
|
+
const current = formatUnits(have, req.token.decimals);
|
|
82
|
+
const symbol = req.token.symbol;
|
|
83
|
+
return req.sourcePart > 0n ? `Your ${symbol} balance is too low, you try to transfer ${needed} ${symbol}, but your wallet only holds ${current} ${symbol}.` : `Insufficient ${symbol} for fees: need ${needed} ${symbol}, have ${current} ${symbol}.`;
|
|
84
|
+
});
|
|
85
|
+
throw new BalanceError("The balance is too low.", /* @__PURE__ */ new Error(`${lines.join(" ")} No funds have been sent.`));
|
|
86
|
+
}
|
|
87
|
+
await sleep(BACKOFF_BASE_MS * 2 ** attempt);
|
|
21
88
|
}
|
|
22
|
-
}
|
|
89
|
+
}, {
|
|
90
|
+
timeout: OVERALL_TIMEOUT_MS,
|
|
91
|
+
errorInstance: new BalanceError("Could not read wallet balance.")
|
|
92
|
+
});
|
|
23
93
|
};
|
|
24
94
|
//#endregion
|
|
25
95
|
export { checkBalance };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkBalance.js","names":[],"sources":["../../../../../src/core/tasks/helpers/checkBalance.ts"],"sourcesContent":["import type { LiFiStep } from '@lifi/types'\nimport {
|
|
1
|
+
{"version":3,"file":"checkBalance.js","names":[],"sources":["../../../../../src/core/tasks/helpers/checkBalance.ts"],"sourcesContent":["import type { LiFiStep, Token, TokenAmount } from '@lifi/types'\nimport { BalanceError } from '../../../errors/errors.js'\nimport type { SDKClient } from '../../../types/core.js'\nimport { formatUnits } from '../../../utils/formatUnits.js'\nimport { sleep } from '../../../utils/sleep.js'\nimport { withTimeout } from '../../../utils/withTimeout.js'\n\nconst MAX_ATTEMPTS = 6\n// Exponential backoff: 150, 300, 600, 1200, 2400 → ≈4.65s of sleep total.\nconst BACKOFF_BASE_MS = 150\nconst OVERALL_TIMEOUT_MS = 10_000\nconst SLIPPAGE_PRECISION = 1_000_000_000n\n\ntype Requirement = {\n token: Token\n sourcePart: bigint // 0n for pure overhead tokens\n overheadPart: bigint // gas + non-included fees in this token\n}\n\n/**\n * Verifies that the wallet holds enough of every token required to execute\n * the step on its source chain — the source-token amount, any gas costs, and\n * any non-included fee costs. Reads all balances in one batched provider\n * call, retries within a bounded budget to absorb transient RPC failures and\n * post-confirmation propagation lag, and applies slippage to the source-token\n * portion only as a last resort (overhead is never trimmed).\n *\n * Throws BalanceError(\"The balance is too low.\") on a genuine shortfall, or\n * BalanceError(\"Could not read wallet balance.\") if the balance can't be read\n * after retries.\n */\nexport const checkBalance = async (\n client: SDKClient,\n walletAddress: string,\n step: LiFiStep\n): Promise<void> => {\n const fromChainId = step.action.fromChainId\n const requirements = new Map<string, Requirement>()\n const add = (token: Token, amount: bigint, source: boolean): void => {\n if (token.chainId !== fromChainId || amount === 0n) {\n return\n }\n const key = token.address.toLowerCase()\n const req = requirements.get(key) ?? {\n token,\n sourcePart: 0n,\n overheadPart: 0n,\n }\n if (source) {\n req.sourcePart += amount\n } else {\n req.overheadPart += amount\n }\n requirements.set(key, req)\n }\n add(step.action.fromToken, BigInt(step.action.fromAmount), true)\n for (const gas of step.estimate?.gasCosts ?? []) {\n add(gas.token, BigInt(gas.amount), false)\n }\n for (const fee of step.estimate?.feeCosts ?? []) {\n // Included fees are already part of fromAmount — don't count twice.\n if (!fee.included) {\n add(fee.token, BigInt(fee.amount), false)\n }\n }\n if (requirements.size === 0) {\n return\n }\n\n // Provider is dispatched by wallet address; all requirements share the\n // source chain, which matches this provider by virtue of the address.\n const provider = client.providers.find((p) => p.isAddress(walletAddress))\n if (!provider) {\n throw new Error(`SDK Token Provider for ${walletAddress} is not found.`)\n }\n\n const reqs = Array.from(requirements.values())\n const tokens = reqs.map((r) => r.token)\n const slippage = step.action.slippage ?? 0\n const slippageScaled = BigInt(\n Math.floor((1 - slippage) * Number(SLIPPAGE_PRECISION))\n )\n\n await withTimeout(\n async () => {\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {\n const isFinal = attempt === MAX_ATTEMPTS - 1\n\n let balances: TokenAmount[]\n try {\n balances = await provider.getBalance(client, walletAddress, tokens)\n } catch (error) {\n if (isFinal) {\n throw new BalanceError(\n 'Could not read wallet balance.',\n error as Error\n )\n }\n await sleep(BACKOFF_BASE_MS * 2 ** attempt)\n continue\n }\n\n const balanceByAddress = new Map(\n balances.map((b) => [b.address.toLowerCase(), b.amount] as const)\n )\n\n const unknown: Token[] = []\n const insufficient: { req: Requirement; have: bigint }[] = []\n for (const req of reqs) {\n const have = balanceByAddress.get(req.token.address.toLowerCase())\n if (have === undefined) {\n unknown.push(req.token)\n } else if (have < req.sourcePart + req.overheadPart) {\n insufficient.push({ req, have })\n }\n }\n\n if (unknown.length === 0 && insufficient.length === 0) {\n return\n }\n\n // Final-attempt slippage rescue: only when the sole shortfall is the\n // source-token portion. Trim source down to (balance − overhead) so\n // the overhead reserve is preserved.\n if (\n isFinal &&\n unknown.length === 0 &&\n insufficient.length === 1 &&\n insufficient[0].req.sourcePart > 0n\n ) {\n const { req, have } = insufficient[0]\n const minAcceptable =\n (req.sourcePart * slippageScaled) / SLIPPAGE_PRECISION +\n req.overheadPart\n if (have >= minAcceptable) {\n step.action.fromAmount = (have - req.overheadPart).toString()\n return\n }\n }\n\n if (isFinal) {\n if (unknown.length > 0) {\n throw new BalanceError(\n 'Could not read wallet balance.',\n new Error(\n `Could not read balance for: ${unknown\n .map((t) => t.symbol || t.address)\n .join(', ')}.`\n )\n )\n }\n const lines = insufficient.map(({ req, have }) => {\n const needed = formatUnits(\n req.sourcePart + req.overheadPart,\n req.token.decimals\n )\n const current = formatUnits(have, req.token.decimals)\n const symbol = req.token.symbol\n return req.sourcePart > 0n\n ? `Your ${symbol} balance is too low, you try to transfer ${needed} ${symbol}, but your wallet only holds ${current} ${symbol}.`\n : `Insufficient ${symbol} for fees: need ${needed} ${symbol}, have ${current} ${symbol}.`\n })\n throw new BalanceError(\n 'The balance is too low.',\n new Error(`${lines.join(' ')} No funds have been sent.`)\n )\n }\n\n await sleep(BACKOFF_BASE_MS * 2 ** attempt)\n }\n },\n {\n timeout: OVERALL_TIMEOUT_MS,\n errorInstance: new BalanceError('Could not read wallet balance.'),\n }\n )\n}\n"],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -50,5 +50,6 @@ import { parseUnits } from "./utils/parseUnits.js";
|
|
|
50
50
|
import { sleep } from "./utils/sleep.js";
|
|
51
51
|
import { waitForResult } from "./utils/waitForResult.js";
|
|
52
52
|
import { LruMap, withDedupe } from "./utils/withDedupe.js";
|
|
53
|
+
import { withTimeout } from "./utils/withTimeout.js";
|
|
53
54
|
export * from "@lifi/types";
|
|
54
|
-
export { type AcceptExchangeRateUpdateHook, type AcceptSlippageUpdateHook, type AcceptSlippageUpdateHookParams, BalanceError, BaseError, BaseStepExecutionTask, BaseStepExecutor, CheckBalanceTask, type ContractCallParams, type ContractTool, type ErrorCode, ErrorMessage, ErrorName, type ExchangeRateUpdateParams, ExecuteStepRetryError, type ExecuteStepRetryParams, type Execution, type ExecutionAction, type ExecutionActionStatus, type ExecutionActionType, type ExecutionOptions, type ExecutionStatus, type GetContractCallsHook, type GetContractCallsResult, HTTPError, InMemoryStorage, type InteractionSettings, LiFiErrorCode, type LiFiStepExtended, LocalStorageAdapter, LruMap, PrepareTransactionTask, ProviderError, RPCError, type RPCUrls, type RequestInterceptor, type RouteExecutionData, type RouteExecutionDataDictionary, type RouteExecutionDictionary, type RouteExtended, type SDKBaseConfig, type SDKClient, type SDKConfig, SDKError, type SDKProvider, type SDKStorage, ServerError, StatusManager, type StepExecutor, type StepExecutorBaseContext, type StepExecutorContext, type StepExecutorOptions, type StepExtended, TaskPipeline, type TaskResult, type TaskStatus, TransactionError, type TransactionMethodType, type TransactionParameters, type TransactionRequestParameters, type TransactionRequestUpdateHook, UnknownError, type UpdateRouteHook, ValidationError, WaitForTransactionStatusTask, actions, checkBalance, checkPackageUpdates, convertQuoteToRoute, createClient, createDefaultStorage, executeRoute, fetchTxErrorDetails, formatUnits, getActionMessage, getActiveRoute, getActiveRoutes, getChains, getConnections, getContractCallsQuote, getGasRecommendation, getNameServiceAddress, getQuote, getRelayedTransactionStatus, getRelayerQuote, getRoutes, getStatus, getStepTransaction, getSubstatusMessage, getToken, getTokenBalance, getTokenBalances, getTokenBalancesByChain, getTokens, getTools, getTransactionHistory, getTransactionRequestData, getWalletBalances, isHex, parseUnits, patchContractCalls, relayTransaction, resumeRoute, sleep, stepComparison, stopRouteExecution, updateRouteExecution, waitForResult, withDedupe };
|
|
55
|
+
export { type AcceptExchangeRateUpdateHook, type AcceptSlippageUpdateHook, type AcceptSlippageUpdateHookParams, BalanceError, BaseError, BaseStepExecutionTask, BaseStepExecutor, CheckBalanceTask, type ContractCallParams, type ContractTool, type ErrorCode, ErrorMessage, ErrorName, type ExchangeRateUpdateParams, ExecuteStepRetryError, type ExecuteStepRetryParams, type Execution, type ExecutionAction, type ExecutionActionStatus, type ExecutionActionType, type ExecutionOptions, type ExecutionStatus, type GetContractCallsHook, type GetContractCallsResult, HTTPError, InMemoryStorage, type InteractionSettings, LiFiErrorCode, type LiFiStepExtended, LocalStorageAdapter, LruMap, PrepareTransactionTask, ProviderError, RPCError, type RPCUrls, type RequestInterceptor, type RouteExecutionData, type RouteExecutionDataDictionary, type RouteExecutionDictionary, type RouteExtended, type SDKBaseConfig, type SDKClient, type SDKConfig, SDKError, type SDKProvider, type SDKStorage, ServerError, StatusManager, type StepExecutor, type StepExecutorBaseContext, type StepExecutorContext, type StepExecutorOptions, type StepExtended, TaskPipeline, type TaskResult, type TaskStatus, TransactionError, type TransactionMethodType, type TransactionParameters, type TransactionRequestParameters, type TransactionRequestUpdateHook, UnknownError, type UpdateRouteHook, ValidationError, WaitForTransactionStatusTask, actions, checkBalance, checkPackageUpdates, convertQuoteToRoute, createClient, createDefaultStorage, executeRoute, fetchTxErrorDetails, formatUnits, getActionMessage, getActiveRoute, getActiveRoutes, getChains, getConnections, getContractCallsQuote, getGasRecommendation, getNameServiceAddress, getQuote, getRelayedTransactionStatus, getRelayerQuote, getRoutes, getStatus, getStepTransaction, getSubstatusMessage, getToken, getTokenBalance, getTokenBalances, getTokenBalancesByChain, getTokens, getTools, getTransactionHistory, getTransactionRequestData, getWalletBalances, isHex, parseUnits, patchContractCalls, relayTransaction, resumeRoute, sleep, stepComparison, stopRouteExecution, updateRouteExecution, waitForResult, withDedupe, withTimeout };
|
package/dist/esm/index.js
CHANGED
|
@@ -37,6 +37,7 @@ import { executeRoute, getActiveRoute, getActiveRoutes, resumeRoute, stopRouteEx
|
|
|
37
37
|
import { InMemoryStorage, LocalStorageAdapter, createDefaultStorage } from "./core/storage.js";
|
|
38
38
|
import { TaskPipeline } from "./core/TaskPipeline.js";
|
|
39
39
|
import { formatUnits } from "./utils/formatUnits.js";
|
|
40
|
+
import { withTimeout } from "./utils/withTimeout.js";
|
|
40
41
|
import { checkBalance } from "./core/tasks/helpers/checkBalance.js";
|
|
41
42
|
import { CheckBalanceTask } from "./core/tasks/CheckBalanceTask.js";
|
|
42
43
|
import { getTransactionRequestData } from "./core/tasks/helpers/getTransactionRequestData.js";
|
|
@@ -49,4 +50,4 @@ import { fetchTxErrorDetails } from "./utils/fetchTxErrorDetails.js";
|
|
|
49
50
|
import { isHex } from "./utils/isHex.js";
|
|
50
51
|
import { parseUnits } from "./utils/parseUnits.js";
|
|
51
52
|
export * from "@lifi/types";
|
|
52
|
-
export { BalanceError, BaseError, BaseStepExecutionTask, BaseStepExecutor, CheckBalanceTask, ErrorMessage, ErrorName, ExecuteStepRetryError, HTTPError, InMemoryStorage, LiFiErrorCode, LocalStorageAdapter, LruMap, PrepareTransactionTask, ProviderError, RPCError, SDKError, ServerError, StatusManager, TaskPipeline, TransactionError, UnknownError, ValidationError, WaitForTransactionStatusTask, actions, checkBalance, checkPackageUpdates, convertQuoteToRoute, createClient, createDefaultStorage, executeRoute, fetchTxErrorDetails, formatUnits, getActionMessage, getActiveRoute, getActiveRoutes, getChains, getConnections, getContractCallsQuote, getGasRecommendation, getNameServiceAddress, getQuote, getRelayedTransactionStatus, getRelayerQuote, getRoutes, getStatus, getStepTransaction, getSubstatusMessage, getToken, getTokenBalance, getTokenBalances, getTokenBalancesByChain, getTokens, getTools, getTransactionHistory, getTransactionRequestData, getWalletBalances, isHex, parseUnits, patchContractCalls, relayTransaction, resumeRoute, sleep, stepComparison, stopRouteExecution, updateRouteExecution, waitForResult, withDedupe };
|
|
53
|
+
export { BalanceError, BaseError, BaseStepExecutionTask, BaseStepExecutor, CheckBalanceTask, ErrorMessage, ErrorName, ExecuteStepRetryError, HTTPError, InMemoryStorage, LiFiErrorCode, LocalStorageAdapter, LruMap, PrepareTransactionTask, ProviderError, RPCError, SDKError, ServerError, StatusManager, TaskPipeline, TransactionError, UnknownError, ValidationError, WaitForTransactionStatusTask, actions, checkBalance, checkPackageUpdates, convertQuoteToRoute, createClient, createDefaultStorage, executeRoute, fetchTxErrorDetails, formatUnits, getActionMessage, getActiveRoute, getActiveRoutes, getChains, getConnections, getContractCallsQuote, getGasRecommendation, getNameServiceAddress, getQuote, getRelayedTransactionStatus, getRelayerQuote, getRoutes, getStatus, getStepTransaction, getSubstatusMessage, getToken, getTokenBalance, getTokenBalances, getTokenBalancesByChain, getTokens, getTools, getTransactionHistory, getTransactionRequestData, getWalletBalances, isHex, parseUnits, patchContractCalls, relayTransaction, resumeRoute, sleep, stepComparison, stopRouteExecution, updateRouteExecution, waitForResult, withDedupe, withTimeout };
|
|
@@ -4,7 +4,7 @@ const checkPackageUpdates = async (packageName, packageVersion) => {
|
|
|
4
4
|
try {
|
|
5
5
|
const pkgName = packageName ?? "@lifi/sdk";
|
|
6
6
|
const latestVersion = (await (await fetch(`https://registry.npmjs.org/${pkgName}/latest`)).json()).version;
|
|
7
|
-
const currentVersion = packageVersion ?? "4.0.0-beta.
|
|
7
|
+
const currentVersion = packageVersion ?? "4.0.0-beta.7";
|
|
8
8
|
if (latestVersion > currentVersion) console.warn(`${pkgName}: new package version is available. Please update as soon as possible to enjoy the newest features. Current version: ${currentVersion}. Latest version: ${latestVersion}.`);
|
|
9
9
|
} catch (_error) {}
|
|
10
10
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/utils/withTimeout.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a function in a timeout.
|
|
4
|
+
* Based on viem's withTimeout implementation.
|
|
5
|
+
* @param fn - The function to wrap.
|
|
6
|
+
* @param timeout - The timeout in milliseconds.
|
|
7
|
+
* @param errorInstance - The error instance to throw when the timeout is reached.
|
|
8
|
+
* @param signal - Whether or not the timeout should use an abort signal.
|
|
9
|
+
* @returns The result of the function.
|
|
10
|
+
*/
|
|
11
|
+
declare function withTimeout<T>(fn: ({
|
|
12
|
+
signal
|
|
13
|
+
}: {
|
|
14
|
+
signal: AbortController["signal"] | null;
|
|
15
|
+
}) => Promise<T>, {
|
|
16
|
+
errorInstance,
|
|
17
|
+
timeout,
|
|
18
|
+
signal
|
|
19
|
+
}: {
|
|
20
|
+
errorInstance?: Error | undefined;
|
|
21
|
+
timeout: number;
|
|
22
|
+
signal?: boolean | undefined;
|
|
23
|
+
}): Promise<T>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { withTimeout };
|
|
26
|
+
//# sourceMappingURL=withTimeout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withTimeout.d.ts","names":[],"sources":["../../../src/utils/withTimeout.ts"],"mappings":";;AASA;;;;;;;;iBAAgB,WAAA,GAAA,CACd,EAAA;EAAO;AAAA;EAAY,MAAA,EAAQ,eAAA;AAAA,MAAuC,OAAA,CAAQ,CAAA;EAExE,aAAA;EACA,OAAA;EACA;AAAA;EAGA,aAAA,GAAgB,KAAA;EAEhB,OAAA;EAEA,MAAA;AAAA,IAED,OAAA,CAAQ,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/utils/withTimeout.ts
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a function in a timeout.
|
|
4
|
+
* Based on viem's withTimeout implementation.
|
|
5
|
+
* @param fn - The function to wrap.
|
|
6
|
+
* @param timeout - The timeout in milliseconds.
|
|
7
|
+
* @param errorInstance - The error instance to throw when the timeout is reached.
|
|
8
|
+
* @param signal - Whether or not the timeout should use an abort signal.
|
|
9
|
+
* @returns The result of the function.
|
|
10
|
+
*/
|
|
11
|
+
function withTimeout(fn, { errorInstance = /* @__PURE__ */ new Error("Timed out after waiting for too long."), timeout, signal }) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
(async () => {
|
|
14
|
+
let timeoutId;
|
|
15
|
+
try {
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
if (timeout > 0) timeoutId = setTimeout(() => {
|
|
18
|
+
if (signal) controller.abort();
|
|
19
|
+
else reject(errorInstance);
|
|
20
|
+
}, timeout);
|
|
21
|
+
resolve(await fn({ signal: controller?.signal || null }));
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (err?.name === "AbortError") reject(errorInstance);
|
|
24
|
+
reject(err);
|
|
25
|
+
} finally {
|
|
26
|
+
clearTimeout(timeoutId);
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { withTimeout };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=withTimeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withTimeout.js","names":[],"sources":["../../../src/utils/withTimeout.ts"],"sourcesContent":["/**\n * Wraps a function in a timeout.\n * Based on viem's withTimeout implementation.\n * @param fn - The function to wrap.\n * @param timeout - The timeout in milliseconds.\n * @param errorInstance - The error instance to throw when the timeout is reached.\n * @param signal - Whether or not the timeout should use an abort signal.\n * @returns The result of the function.\n */\nexport function withTimeout<T>(\n fn: ({ signal }: { signal: AbortController['signal'] | null }) => Promise<T>,\n {\n errorInstance = new Error('Timed out after waiting for too long.'),\n timeout,\n signal,\n }: {\n // The error instance to throw when the timeout is reached.\n errorInstance?: Error | undefined\n // The timeout (in ms).\n timeout: number\n // Whether or not the timeout should use an abort signal.\n signal?: boolean | undefined\n }\n): Promise<T> {\n return new Promise((resolve, reject) => {\n ;(async () => {\n let timeoutId!: NodeJS.Timeout\n try {\n const controller = new AbortController()\n if (timeout > 0) {\n timeoutId = setTimeout(() => {\n if (signal) {\n controller.abort()\n } else {\n reject(errorInstance)\n }\n }, timeout) as NodeJS.Timeout // need to cast because bun globals.d.ts overrides @types/node\n }\n resolve(await fn({ signal: controller?.signal || null }))\n } catch (err) {\n if ((err as Error)?.name === 'AbortError') {\n reject(errorInstance)\n }\n reject(err)\n } finally {\n clearTimeout(timeoutId)\n }\n })()\n })\n}\n"],"mappings":";;;;;;;;;;AASA,SAAgB,YACd,IACA,EACE,gCAAgB,IAAI,MAAM,wCAAwC,EAClE,SACA,UASU;AACZ,QAAO,IAAI,SAAS,SAAS,WAAW;AACrC,GAAC,YAAY;GACZ,IAAI;AACJ,OAAI;IACF,MAAM,aAAa,IAAI,iBAAiB;AACxC,QAAI,UAAU,EACZ,aAAY,iBAAiB;AAC3B,SAAI,OACF,YAAW,OAAO;SAElB,QAAO,cAAc;OAEtB,QAAQ;AAEb,YAAQ,MAAM,GAAG,EAAE,QAAQ,YAAY,UAAU,MAAM,CAAC,CAAC;YAClD,KAAK;AACZ,QAAK,KAAe,SAAS,aAC3B,QAAO,cAAc;AAEvB,WAAO,IAAI;aACH;AACR,iBAAa,UAAU;;MAEvB;GACJ"}
|
package/dist/esm/version.d.ts
CHANGED
package/dist/esm/version.js
CHANGED
package/dist/esm/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","names":[],"sources":["../../src/version.ts"],"sourcesContent":["export const name = '@lifi/sdk'\nexport const version = '4.0.0-beta.
|
|
1
|
+
{"version":3,"file":"version.js","names":[],"sources":["../../src/version.ts"],"sourcesContent":["export const name = '@lifi/sdk'\nexport const version = '4.0.0-beta.7'\n"],"mappings":";AAAA,MAAa,OAAO;AACpB,MAAa,UAAU"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifi/sdk",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.7",
|
|
4
4
|
"description": "LI.FI SDK for Any-to-Any Cross-Chain-Swap",
|
|
5
5
|
"homepage": "https://github.com/lifinance/sdk",
|
|
6
6
|
"bugs": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"./package.json": "./package.json"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@lifi/types": "17.
|
|
31
|
+
"@lifi/types": "17.76.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|
|
@@ -1,51 +1,177 @@
|
|
|
1
|
-
import type { LiFiStep } from '@lifi/types'
|
|
2
|
-
import { getTokenBalance } from '../../../actions/getTokenBalance.js'
|
|
1
|
+
import type { LiFiStep, Token, TokenAmount } from '@lifi/types'
|
|
3
2
|
import { BalanceError } from '../../../errors/errors.js'
|
|
4
3
|
import type { SDKClient } from '../../../types/core.js'
|
|
5
4
|
import { formatUnits } from '../../../utils/formatUnits.js'
|
|
6
5
|
import { sleep } from '../../../utils/sleep.js'
|
|
6
|
+
import { withTimeout } from '../../../utils/withTimeout.js'
|
|
7
7
|
|
|
8
|
+
const MAX_ATTEMPTS = 6
|
|
9
|
+
// Exponential backoff: 150, 300, 600, 1200, 2400 → ≈4.65s of sleep total.
|
|
10
|
+
const BACKOFF_BASE_MS = 150
|
|
11
|
+
const OVERALL_TIMEOUT_MS = 10_000
|
|
12
|
+
const SLIPPAGE_PRECISION = 1_000_000_000n
|
|
13
|
+
|
|
14
|
+
type Requirement = {
|
|
15
|
+
token: Token
|
|
16
|
+
sourcePart: bigint // 0n for pure overhead tokens
|
|
17
|
+
overheadPart: bigint // gas + non-included fees in this token
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Verifies that the wallet holds enough of every token required to execute
|
|
22
|
+
* the step on its source chain — the source-token amount, any gas costs, and
|
|
23
|
+
* any non-included fee costs. Reads all balances in one batched provider
|
|
24
|
+
* call, retries within a bounded budget to absorb transient RPC failures and
|
|
25
|
+
* post-confirmation propagation lag, and applies slippage to the source-token
|
|
26
|
+
* portion only as a last resort (overhead is never trimmed).
|
|
27
|
+
*
|
|
28
|
+
* Throws BalanceError("The balance is too low.") on a genuine shortfall, or
|
|
29
|
+
* BalanceError("Could not read wallet balance.") if the balance can't be read
|
|
30
|
+
* after retries.
|
|
31
|
+
*/
|
|
8
32
|
export const checkBalance = async (
|
|
9
33
|
client: SDKClient,
|
|
10
34
|
walletAddress: string,
|
|
11
|
-
step: LiFiStep
|
|
12
|
-
depth = 0
|
|
35
|
+
step: LiFiStep
|
|
13
36
|
): Promise<void> => {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
const fromChainId = step.action.fromChainId
|
|
38
|
+
const requirements = new Map<string, Requirement>()
|
|
39
|
+
const add = (token: Token, amount: bigint, source: boolean): void => {
|
|
40
|
+
if (token.chainId !== fromChainId || amount === 0n) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
const key = token.address.toLowerCase()
|
|
44
|
+
const req = requirements.get(key) ?? {
|
|
45
|
+
token,
|
|
46
|
+
sourcePart: 0n,
|
|
47
|
+
overheadPart: 0n,
|
|
48
|
+
}
|
|
49
|
+
if (source) {
|
|
50
|
+
req.sourcePart += amount
|
|
51
|
+
} else {
|
|
52
|
+
req.overheadPart += amount
|
|
53
|
+
}
|
|
54
|
+
requirements.set(key, req)
|
|
55
|
+
}
|
|
56
|
+
add(step.action.fromToken, BigInt(step.action.fromAmount), true)
|
|
57
|
+
for (const gas of step.estimate?.gasCosts ?? []) {
|
|
58
|
+
add(gas.token, BigInt(gas.amount), false)
|
|
59
|
+
}
|
|
60
|
+
for (const fee of step.estimate?.feeCosts ?? []) {
|
|
61
|
+
// Included fees are already part of fromAmount — don't count twice.
|
|
62
|
+
if (!fee.included) {
|
|
63
|
+
add(fee.token, BigInt(fee.amount), false)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (requirements.size === 0) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Provider is dispatched by wallet address; all requirements share the
|
|
71
|
+
// source chain, which matches this provider by virtue of the address.
|
|
72
|
+
const provider = client.providers.find((p) => p.isAddress(walletAddress))
|
|
73
|
+
if (!provider) {
|
|
74
|
+
throw new Error(`SDK Token Provider for ${walletAddress} is not found.`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const reqs = Array.from(requirements.values())
|
|
78
|
+
const tokens = reqs.map((r) => r.token)
|
|
79
|
+
const slippage = step.action.slippage ?? 0
|
|
80
|
+
const slippageScaled = BigInt(
|
|
81
|
+
Math.floor((1 - slippage) * Number(SLIPPAGE_PRECISION))
|
|
18
82
|
)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const current = formatUnits(currentBalance, token.decimals)
|
|
38
|
-
let errorMessage = `Your ${token.symbol} balance is too low, you try to transfer ${needed} ${token.symbol}, but your wallet only holds ${current} ${token.symbol}. No funds have been sent.`
|
|
39
|
-
|
|
40
|
-
if (currentBalance !== 0n) {
|
|
41
|
-
errorMessage += `If the problem consists, please delete this transfer and start a new one with a maximum of ${current} ${token.symbol}.`
|
|
83
|
+
|
|
84
|
+
await withTimeout(
|
|
85
|
+
async () => {
|
|
86
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
87
|
+
const isFinal = attempt === MAX_ATTEMPTS - 1
|
|
88
|
+
|
|
89
|
+
let balances: TokenAmount[]
|
|
90
|
+
try {
|
|
91
|
+
balances = await provider.getBalance(client, walletAddress, tokens)
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (isFinal) {
|
|
94
|
+
throw new BalanceError(
|
|
95
|
+
'Could not read wallet balance.',
|
|
96
|
+
error as Error
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
await sleep(BACKOFF_BASE_MS * 2 ** attempt)
|
|
100
|
+
continue
|
|
42
101
|
}
|
|
43
102
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
new Error(errorMessage)
|
|
103
|
+
const balanceByAddress = new Map(
|
|
104
|
+
balances.map((b) => [b.address.toLowerCase(), b.amount] as const)
|
|
47
105
|
)
|
|
106
|
+
|
|
107
|
+
const unknown: Token[] = []
|
|
108
|
+
const insufficient: { req: Requirement; have: bigint }[] = []
|
|
109
|
+
for (const req of reqs) {
|
|
110
|
+
const have = balanceByAddress.get(req.token.address.toLowerCase())
|
|
111
|
+
if (have === undefined) {
|
|
112
|
+
unknown.push(req.token)
|
|
113
|
+
} else if (have < req.sourcePart + req.overheadPart) {
|
|
114
|
+
insufficient.push({ req, have })
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (unknown.length === 0 && insufficient.length === 0) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Final-attempt slippage rescue: only when the sole shortfall is the
|
|
123
|
+
// source-token portion. Trim source down to (balance − overhead) so
|
|
124
|
+
// the overhead reserve is preserved.
|
|
125
|
+
if (
|
|
126
|
+
isFinal &&
|
|
127
|
+
unknown.length === 0 &&
|
|
128
|
+
insufficient.length === 1 &&
|
|
129
|
+
insufficient[0].req.sourcePart > 0n
|
|
130
|
+
) {
|
|
131
|
+
const { req, have } = insufficient[0]
|
|
132
|
+
const minAcceptable =
|
|
133
|
+
(req.sourcePart * slippageScaled) / SLIPPAGE_PRECISION +
|
|
134
|
+
req.overheadPart
|
|
135
|
+
if (have >= minAcceptable) {
|
|
136
|
+
step.action.fromAmount = (have - req.overheadPart).toString()
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (isFinal) {
|
|
142
|
+
if (unknown.length > 0) {
|
|
143
|
+
throw new BalanceError(
|
|
144
|
+
'Could not read wallet balance.',
|
|
145
|
+
new Error(
|
|
146
|
+
`Could not read balance for: ${unknown
|
|
147
|
+
.map((t) => t.symbol || t.address)
|
|
148
|
+
.join(', ')}.`
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
const lines = insufficient.map(({ req, have }) => {
|
|
153
|
+
const needed = formatUnits(
|
|
154
|
+
req.sourcePart + req.overheadPart,
|
|
155
|
+
req.token.decimals
|
|
156
|
+
)
|
|
157
|
+
const current = formatUnits(have, req.token.decimals)
|
|
158
|
+
const symbol = req.token.symbol
|
|
159
|
+
return req.sourcePart > 0n
|
|
160
|
+
? `Your ${symbol} balance is too low, you try to transfer ${needed} ${symbol}, but your wallet only holds ${current} ${symbol}.`
|
|
161
|
+
: `Insufficient ${symbol} for fees: need ${needed} ${symbol}, have ${current} ${symbol}.`
|
|
162
|
+
})
|
|
163
|
+
throw new BalanceError(
|
|
164
|
+
'The balance is too low.',
|
|
165
|
+
new Error(`${lines.join(' ')} No funds have been sent.`)
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
await sleep(BACKOFF_BASE_MS * 2 ** attempt)
|
|
48
170
|
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
timeout: OVERALL_TIMEOUT_MS,
|
|
174
|
+
errorInstance: new BalanceError('Could not read wallet balance.'),
|
|
49
175
|
}
|
|
50
|
-
|
|
176
|
+
)
|
|
51
177
|
}
|
package/src/index.ts
CHANGED
|
@@ -116,3 +116,4 @@ export { parseUnits } from './utils/parseUnits.js'
|
|
|
116
116
|
export { sleep } from './utils/sleep.js'
|
|
117
117
|
export { waitForResult } from './utils/waitForResult.js'
|
|
118
118
|
export { LruMap, withDedupe } from './utils/withDedupe.js'
|
|
119
|
+
export { withTimeout } from './utils/withTimeout.js'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a function in a timeout.
|
|
3
|
+
* Based on viem's withTimeout implementation.
|
|
4
|
+
* @param fn - The function to wrap.
|
|
5
|
+
* @param timeout - The timeout in milliseconds.
|
|
6
|
+
* @param errorInstance - The error instance to throw when the timeout is reached.
|
|
7
|
+
* @param signal - Whether or not the timeout should use an abort signal.
|
|
8
|
+
* @returns The result of the function.
|
|
9
|
+
*/
|
|
10
|
+
export function withTimeout<T>(
|
|
11
|
+
fn: ({ signal }: { signal: AbortController['signal'] | null }) => Promise<T>,
|
|
12
|
+
{
|
|
13
|
+
errorInstance = new Error('Timed out after waiting for too long.'),
|
|
14
|
+
timeout,
|
|
15
|
+
signal,
|
|
16
|
+
}: {
|
|
17
|
+
// The error instance to throw when the timeout is reached.
|
|
18
|
+
errorInstance?: Error | undefined
|
|
19
|
+
// The timeout (in ms).
|
|
20
|
+
timeout: number
|
|
21
|
+
// Whether or not the timeout should use an abort signal.
|
|
22
|
+
signal?: boolean | undefined
|
|
23
|
+
}
|
|
24
|
+
): Promise<T> {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
;(async () => {
|
|
27
|
+
let timeoutId!: NodeJS.Timeout
|
|
28
|
+
try {
|
|
29
|
+
const controller = new AbortController()
|
|
30
|
+
if (timeout > 0) {
|
|
31
|
+
timeoutId = setTimeout(() => {
|
|
32
|
+
if (signal) {
|
|
33
|
+
controller.abort()
|
|
34
|
+
} else {
|
|
35
|
+
reject(errorInstance)
|
|
36
|
+
}
|
|
37
|
+
}, timeout) as NodeJS.Timeout // need to cast because bun globals.d.ts overrides @types/node
|
|
38
|
+
}
|
|
39
|
+
resolve(await fn({ signal: controller?.signal || null }))
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if ((err as Error)?.name === 'AbortError') {
|
|
42
|
+
reject(errorInstance)
|
|
43
|
+
}
|
|
44
|
+
reject(err)
|
|
45
|
+
} finally {
|
|
46
|
+
clearTimeout(timeoutId)
|
|
47
|
+
}
|
|
48
|
+
})()
|
|
49
|
+
})
|
|
50
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const name = '@lifi/sdk'
|
|
2
|
-
export const version = '4.0.0-beta.
|
|
2
|
+
export const version = '4.0.0-beta.7'
|