@lifi/sdk 3.16.2 → 3.17.0-beta.0
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/package.json +1 -1
- package/src/_cjs/core/EVM/getEVMBalance.js +8 -1
- package/src/_cjs/core/EVM/getEVMBalance.js.map +1 -1
- package/src/_cjs/core/Solana/getSolanaBalance.js +21 -18
- package/src/_cjs/core/Solana/getSolanaBalance.js.map +1 -1
- package/src/_cjs/core/Sui/getSuiBalance.js +9 -11
- package/src/_cjs/core/Sui/getSuiBalance.js.map +1 -1
- package/src/_cjs/core/UTXO/getUTXOBalance.js +10 -3
- package/src/_cjs/core/UTXO/getUTXOBalance.js.map +1 -1
- package/src/_cjs/core/checkBalance.js +106 -23
- package/src/_cjs/core/checkBalance.js.map +1 -1
- package/src/_cjs/services/api.js +6 -3
- package/src/_cjs/services/api.js.map +1 -1
- package/src/_cjs/version.js +1 -1
- package/src/_cjs/version.js.map +1 -1
- package/src/_esm/core/EVM/getEVMBalance.js +10 -1
- package/src/_esm/core/EVM/getEVMBalance.js.map +1 -1
- package/src/_esm/core/Solana/getSolanaBalance.js +24 -18
- package/src/_esm/core/Solana/getSolanaBalance.js.map +1 -1
- package/src/_esm/core/Sui/getSuiBalance.js +11 -11
- package/src/_esm/core/Sui/getSuiBalance.js.map +1 -1
- package/src/_esm/core/UTXO/getUTXOBalance.js +12 -3
- package/src/_esm/core/UTXO/getUTXOBalance.js.map +1 -1
- package/src/_esm/core/checkBalance.js +126 -25
- package/src/_esm/core/checkBalance.js.map +1 -1
- package/src/_esm/services/api.js +6 -3
- package/src/_esm/services/api.js.map +1 -1
- package/src/_esm/version.js +1 -1
- package/src/_esm/version.js.map +1 -1
- package/src/_types/core/UTXO/getUTXOBalance.d.ts.map +1 -1
- package/src/_types/core/checkBalance.d.ts +13 -1
- package/src/_types/core/checkBalance.d.ts.map +1 -1
- package/src/_types/services/api.d.ts.map +1 -1
- package/src/_types/version.d.ts +1 -1
- package/src/_types/version.d.ts.map +1 -1
- package/src/core/EVM/getEVMBalance.ts +10 -1
- package/src/core/Solana/getSolanaBalance.ts +23 -16
- package/src/core/Sui/getSuiBalance.ts +10 -10
- package/src/core/UTXO/getUTXOBalance.ts +15 -3
- package/src/core/checkBalance.ts +165 -33
- package/src/services/api.ts +6 -3
- package/src/version.ts +1 -1
package/src/core/checkBalance.ts
CHANGED
|
@@ -1,45 +1,177 @@
|
|
|
1
|
-
import type { LiFiStep } from '@lifi/types'
|
|
2
|
-
import { formatUnits } from 'viem'
|
|
1
|
+
import type { LiFiStep, Token, TokenAmount } from '@lifi/types'
|
|
2
|
+
import { formatUnits, withTimeout } from 'viem'
|
|
3
|
+
import { config } from '../config.js'
|
|
3
4
|
import { BalanceError } from '../errors/errors.js'
|
|
4
|
-
import { getTokenBalance } from '../services/balance.js'
|
|
5
5
|
import { sleep } from '../utils/sleep.js'
|
|
6
6
|
|
|
7
|
+
const MAX_ATTEMPTS = 6
|
|
8
|
+
// Exponential backoff: 150, 300, 600, 1200, 2400 → ≈4.65s of sleep total.
|
|
9
|
+
const BACKOFF_BASE_MS = 150
|
|
10
|
+
const OVERALL_TIMEOUT_MS = 10_000
|
|
11
|
+
const SLIPPAGE_PRECISION = 1_000_000_000n
|
|
12
|
+
|
|
13
|
+
type Requirement = {
|
|
14
|
+
token: Token
|
|
15
|
+
sourcePart: bigint // 0n for pure overhead tokens
|
|
16
|
+
overheadPart: bigint // gas + non-included fees in this token
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Verifies that the wallet holds enough of every token required to execute
|
|
21
|
+
* the step on its source chain — the source-token amount, any gas costs, and
|
|
22
|
+
* any non-included fee costs. Reads all balances in one batched provider
|
|
23
|
+
* call, retries within a bounded budget to absorb transient RPC failures and
|
|
24
|
+
* post-confirmation propagation lag, and applies slippage to the source-token
|
|
25
|
+
* portion only as a last resort (overhead is never trimmed).
|
|
26
|
+
*
|
|
27
|
+
* Throws BalanceError("The balance is too low.") on a genuine shortfall, or
|
|
28
|
+
* BalanceError("Could not read wallet balance.") if the balance can't be read
|
|
29
|
+
* after retries.
|
|
30
|
+
*/
|
|
7
31
|
export const checkBalance = async (
|
|
8
32
|
walletAddress: string,
|
|
9
|
-
step: LiFiStep
|
|
10
|
-
depth = 0
|
|
33
|
+
step: LiFiStep
|
|
11
34
|
): Promise<void> => {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const fromChainId = step.action.fromChainId
|
|
36
|
+
const requirements = new Map<string, Requirement>()
|
|
37
|
+
const add = (token: Token, amount: bigint, source: boolean): void => {
|
|
38
|
+
if (token.chainId !== fromChainId || amount === 0n) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
const key = token.address.toLowerCase()
|
|
42
|
+
const req = requirements.get(key) ?? {
|
|
43
|
+
token,
|
|
44
|
+
sourcePart: 0n,
|
|
45
|
+
overheadPart: 0n,
|
|
46
|
+
}
|
|
47
|
+
if (source) {
|
|
48
|
+
req.sourcePart += amount
|
|
49
|
+
} else {
|
|
50
|
+
req.overheadPart += amount
|
|
51
|
+
}
|
|
52
|
+
requirements.set(key, req)
|
|
53
|
+
}
|
|
54
|
+
add(step.action.fromToken, BigInt(step.action.fromAmount), true)
|
|
55
|
+
for (const gas of step.estimate?.gasCosts ?? []) {
|
|
56
|
+
add(gas.token, BigInt(gas.amount), false)
|
|
57
|
+
}
|
|
58
|
+
for (const fee of step.estimate?.feeCosts ?? []) {
|
|
59
|
+
// Included fees are already part of fromAmount — don't count twice.
|
|
60
|
+
if (!fee.included) {
|
|
61
|
+
add(fee.token, BigInt(fee.amount), false)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (requirements.size === 0) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Provider is dispatched by wallet address; all requirements share the
|
|
69
|
+
// source chain, which matches this provider by virtue of the address.
|
|
70
|
+
const provider = config
|
|
71
|
+
.get()
|
|
72
|
+
.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))
|
|
82
|
+
)
|
|
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(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
|
|
36
101
|
}
|
|
37
102
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
new Error(errorMessage)
|
|
103
|
+
const balanceByAddress = new Map(
|
|
104
|
+
balances.map((b) => [b.address.toLowerCase(), b.amount] as const)
|
|
41
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)
|
|
42
170
|
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
timeout: OVERALL_TIMEOUT_MS,
|
|
174
|
+
errorInstance: new BalanceError('Could not read wallet balance.'),
|
|
43
175
|
}
|
|
44
|
-
|
|
176
|
+
)
|
|
45
177
|
}
|
package/src/services/api.ts
CHANGED
|
@@ -503,7 +503,8 @@ export const getChains = async (
|
|
|
503
503
|
): Promise<ExtendedChain[]> => {
|
|
504
504
|
if (params) {
|
|
505
505
|
for (const key of Object.keys(params)) {
|
|
506
|
-
|
|
506
|
+
const value = params[key as keyof ChainsRequest]
|
|
507
|
+
if (value === undefined || value === null) {
|
|
507
508
|
delete params[key as keyof ChainsRequest]
|
|
508
509
|
}
|
|
509
510
|
}
|
|
@@ -544,7 +545,8 @@ export async function getTokens(
|
|
|
544
545
|
): Promise<TokensResponse> {
|
|
545
546
|
if (params) {
|
|
546
547
|
for (const key of Object.keys(params)) {
|
|
547
|
-
|
|
548
|
+
const value = params[key as keyof TokensRequest]
|
|
549
|
+
if (value === undefined || value === null) {
|
|
548
550
|
delete params[key as keyof TokensRequest]
|
|
549
551
|
}
|
|
550
552
|
}
|
|
@@ -611,7 +613,8 @@ export const getTools = async (
|
|
|
611
613
|
): Promise<ToolsResponse> => {
|
|
612
614
|
if (params) {
|
|
613
615
|
for (const key of Object.keys(params)) {
|
|
614
|
-
|
|
616
|
+
const value = params[key as keyof ToolsRequest]
|
|
617
|
+
if (value === undefined || value === null) {
|
|
615
618
|
delete params[key as keyof ToolsRequest]
|
|
616
619
|
}
|
|
617
620
|
}
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const name = '@lifi/sdk'
|
|
2
|
-
export const version = '3.
|
|
2
|
+
export const version = '3.17.0-beta.0'
|