@lifi/sdk 3.0.2-beta.0 → 3.1.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.
Files changed (201) hide show
  1. package/package.json +3 -3
  2. package/src/_cjs/core/EVM/EVMStepExecutor.js +12 -11
  3. package/src/_cjs/core/EVM/EVMStepExecutor.js.map +1 -1
  4. package/src/_cjs/core/EVM/checkAllowance.js +3 -4
  5. package/src/_cjs/core/EVM/checkAllowance.js.map +1 -1
  6. package/src/_cjs/core/EVM/multisig.js +4 -3
  7. package/src/_cjs/core/EVM/multisig.js.map +1 -1
  8. package/src/_cjs/core/EVM/parseEVMErrors.js +38 -0
  9. package/src/_cjs/core/EVM/parseEVMErrors.js.map +1 -0
  10. package/src/_cjs/core/EVM/publicClient.js +8 -3
  11. package/src/_cjs/core/EVM/publicClient.js.map +1 -1
  12. package/src/_cjs/core/EVM/switchChain.js +4 -3
  13. package/src/_cjs/core/EVM/switchChain.js.map +1 -1
  14. package/src/_cjs/core/Solana/SolanaStepExecutor.js +42 -24
  15. package/src/_cjs/core/Solana/SolanaStepExecutor.js.map +1 -1
  16. package/src/_cjs/core/Solana/connection.js.map +1 -1
  17. package/src/_cjs/core/Solana/parseSolanaErrors.js +33 -0
  18. package/src/_cjs/core/Solana/parseSolanaErrors.js.map +1 -0
  19. package/src/_cjs/core/checkBalance.js +2 -2
  20. package/src/_cjs/core/checkBalance.js.map +1 -1
  21. package/src/_cjs/core/stepComparison.js +3 -3
  22. package/src/_cjs/core/stepComparison.js.map +1 -1
  23. package/src/_cjs/core/waitForReceivingTransaction.js +1 -1
  24. package/src/_cjs/core/waitForReceivingTransaction.js.map +1 -1
  25. package/src/_cjs/errors/SDKError.js +48 -0
  26. package/src/_cjs/errors/SDKError.js.map +1 -0
  27. package/src/_cjs/errors/baseError.js +30 -0
  28. package/src/_cjs/errors/baseError.js.map +1 -0
  29. package/src/_cjs/errors/constants.js +48 -0
  30. package/src/_cjs/errors/constants.js.map +1 -0
  31. package/src/_cjs/errors/errors.js +48 -0
  32. package/src/_cjs/errors/errors.js.map +1 -0
  33. package/src/_cjs/errors/httpError.js +101 -0
  34. package/src/_cjs/errors/httpError.js.map +1 -0
  35. package/src/_cjs/errors/index.js +11 -0
  36. package/src/_cjs/errors/index.js.map +1 -0
  37. package/src/_cjs/errors/utils/baseErrorRootCause.js +21 -0
  38. package/src/_cjs/errors/utils/baseErrorRootCause.js.map +1 -0
  39. package/src/_cjs/errors/utils/rootCause.js +12 -0
  40. package/src/_cjs/errors/utils/rootCause.js.map +1 -0
  41. package/src/_cjs/helpers.js +13 -8
  42. package/src/_cjs/helpers.js.map +1 -1
  43. package/src/_cjs/index.js +2 -4
  44. package/src/_cjs/index.js.map +1 -1
  45. package/src/_cjs/request.js +37 -34
  46. package/src/_cjs/request.js.map +1 -1
  47. package/src/_cjs/services/api.js +61 -132
  48. package/src/_cjs/services/api.js.map +1 -1
  49. package/src/_cjs/services/balance.js +1 -1
  50. package/src/_cjs/services/balance.js.map +1 -1
  51. package/src/_cjs/types/request.js +3 -0
  52. package/src/_cjs/types/request.js.map +1 -0
  53. package/src/_cjs/utils/errors.js +0 -183
  54. package/src/_cjs/utils/errors.js.map +1 -1
  55. package/src/_cjs/utils/index.js +1 -2
  56. package/src/_cjs/utils/index.js.map +1 -1
  57. package/src/_cjs/version.js +1 -1
  58. package/src/_esm/core/EVM/EVMStepExecutor.js +8 -7
  59. package/src/_esm/core/EVM/EVMStepExecutor.js.map +1 -1
  60. package/src/_esm/core/EVM/checkAllowance.js +3 -4
  61. package/src/_esm/core/EVM/checkAllowance.js.map +1 -1
  62. package/src/_esm/core/EVM/multisig.js +2 -1
  63. package/src/_esm/core/EVM/multisig.js.map +1 -1
  64. package/src/_esm/core/EVM/parseEVMErrors.js +35 -0
  65. package/src/_esm/core/EVM/parseEVMErrors.js.map +1 -0
  66. package/src/_esm/core/EVM/publicClient.js +9 -4
  67. package/src/_esm/core/EVM/publicClient.js.map +1 -1
  68. package/src/_esm/core/EVM/switchChain.js +2 -1
  69. package/src/_esm/core/EVM/switchChain.js.map +1 -1
  70. package/src/_esm/core/Solana/SolanaStepExecutor.js +52 -25
  71. package/src/_esm/core/Solana/SolanaStepExecutor.js.map +1 -1
  72. package/src/_esm/core/Solana/connection.js +2 -3
  73. package/src/_esm/core/Solana/connection.js.map +1 -1
  74. package/src/_esm/core/Solana/parseSolanaErrors.js +29 -0
  75. package/src/_esm/core/Solana/parseSolanaErrors.js.map +1 -0
  76. package/src/_esm/core/checkBalance.js +2 -2
  77. package/src/_esm/core/checkBalance.js.map +1 -1
  78. package/src/_esm/core/stepComparison.js +3 -3
  79. package/src/_esm/core/stepComparison.js.map +1 -1
  80. package/src/_esm/core/waitForReceivingTransaction.js +1 -1
  81. package/src/_esm/core/waitForReceivingTransaction.js.map +1 -1
  82. package/src/_esm/errors/SDKError.js +47 -0
  83. package/src/_esm/errors/SDKError.js.map +1 -0
  84. package/src/_esm/errors/baseError.js +28 -0
  85. package/src/_esm/errors/baseError.js.map +1 -0
  86. package/src/_esm/errors/constants.js +45 -0
  87. package/src/_esm/errors/constants.js.map +1 -0
  88. package/src/_esm/errors/errors.js +38 -0
  89. package/src/_esm/errors/errors.js.map +1 -0
  90. package/src/_esm/errors/httpError.js +97 -0
  91. package/src/_esm/errors/httpError.js.map +1 -0
  92. package/src/_esm/errors/index.js +8 -0
  93. package/src/_esm/errors/index.js.map +1 -0
  94. package/src/_esm/errors/utils/baseErrorRootCause.js +16 -0
  95. package/src/_esm/errors/utils/baseErrorRootCause.js.map +1 -0
  96. package/src/_esm/errors/utils/rootCause.js +8 -0
  97. package/src/_esm/errors/utils/rootCause.js.map +1 -0
  98. package/src/_esm/helpers.js +16 -9
  99. package/src/_esm/helpers.js.map +1 -1
  100. package/src/_esm/index.js +2 -2
  101. package/src/_esm/index.js.map +1 -1
  102. package/src/_esm/request.js +38 -35
  103. package/src/_esm/request.js.map +1 -1
  104. package/src/_esm/services/api.js +61 -133
  105. package/src/_esm/services/api.js.map +1 -1
  106. package/src/_esm/services/balance.js +4 -4
  107. package/src/_esm/services/balance.js.map +1 -1
  108. package/src/_esm/types/request.js +2 -0
  109. package/src/_esm/types/request.js.map +1 -0
  110. package/src/_esm/utils/errors.js +1 -173
  111. package/src/_esm/utils/errors.js.map +1 -1
  112. package/src/_esm/utils/index.js +1 -2
  113. package/src/_esm/utils/index.js.map +1 -1
  114. package/src/_esm/version.js +1 -1
  115. package/src/_types/core/EVM/EVMStepExecutor.d.ts.map +1 -1
  116. package/src/_types/core/EVM/checkAllowance.d.ts.map +1 -1
  117. package/src/_types/core/EVM/multisig.d.ts.map +1 -1
  118. package/src/_types/core/EVM/parseEVMErrors.d.ts +4 -0
  119. package/src/_types/core/EVM/parseEVMErrors.d.ts.map +1 -0
  120. package/src/_types/core/EVM/publicClient.d.ts.map +1 -1
  121. package/src/_types/core/EVM/switchChain.d.ts.map +1 -1
  122. package/src/_types/core/Solana/SolanaStepExecutor.d.ts.map +1 -1
  123. package/src/_types/core/Solana/connection.d.ts +2 -3
  124. package/src/_types/core/Solana/connection.d.ts.map +1 -1
  125. package/src/_types/core/Solana/parseSolanaErrors.d.ts +4 -0
  126. package/src/_types/core/Solana/parseSolanaErrors.d.ts.map +1 -0
  127. package/src/_types/core/stepComparison.d.ts.map +1 -1
  128. package/src/_types/errors/SDKError.d.ts +12 -0
  129. package/src/_types/errors/SDKError.d.ts.map +1 -0
  130. package/src/_types/errors/baseError.d.ts +7 -0
  131. package/src/_types/errors/baseError.d.ts.map +1 -0
  132. package/src/_types/errors/constants.d.ts +43 -0
  133. package/src/_types/errors/constants.d.ts.map +1 -0
  134. package/src/_types/errors/errors.d.ts +24 -0
  135. package/src/_types/errors/errors.d.ts.map +1 -0
  136. package/src/_types/errors/httpError.d.ts +21 -0
  137. package/src/_types/errors/httpError.d.ts.map +1 -0
  138. package/src/_types/errors/index.d.ts +8 -0
  139. package/src/_types/errors/index.d.ts.map +1 -0
  140. package/src/_types/errors/utils/baseErrorRootCause.d.ts +4 -0
  141. package/src/_types/errors/utils/baseErrorRootCause.d.ts.map +1 -0
  142. package/src/_types/errors/utils/rootCause.d.ts +2 -0
  143. package/src/_types/errors/utils/rootCause.d.ts.map +1 -0
  144. package/src/_types/helpers.d.ts +5 -4
  145. package/src/_types/helpers.d.ts.map +1 -1
  146. package/src/_types/index.d.ts +2 -2
  147. package/src/_types/index.d.ts.map +1 -1
  148. package/src/_types/request.d.ts +1 -5
  149. package/src/_types/request.d.ts.map +1 -1
  150. package/src/_types/services/api.d.ts.map +1 -1
  151. package/src/_types/services/balance.d.ts +3 -3
  152. package/src/_types/types/request.d.ts +4 -0
  153. package/src/_types/types/request.d.ts.map +1 -0
  154. package/src/_types/utils/errors.d.ts +0 -109
  155. package/src/_types/utils/errors.d.ts.map +1 -1
  156. package/src/_types/utils/index.d.ts +1 -2
  157. package/src/_types/utils/index.d.ts.map +1 -1
  158. package/src/_types/version.d.ts +1 -1
  159. package/src/core/EVM/EVMStepExecutor.ts +14 -12
  160. package/src/core/EVM/checkAllowance.ts +3 -4
  161. package/src/core/EVM/multisig.ts +2 -1
  162. package/src/core/EVM/parseEVMErrors.ts +60 -0
  163. package/src/core/EVM/publicClient.ts +9 -4
  164. package/src/core/EVM/switchChain.ts +2 -1
  165. package/src/core/Solana/SolanaStepExecutor.ts +67 -34
  166. package/src/core/Solana/connection.ts +2 -3
  167. package/src/core/Solana/parseSolanaErrors.ts +45 -0
  168. package/src/core/checkBalance.ts +2 -2
  169. package/src/core/stepComparison.ts +3 -4
  170. package/src/core/waitForReceivingTransaction.ts +1 -1
  171. package/src/errors/SDKError.ts +25 -0
  172. package/src/errors/baseError.ts +22 -0
  173. package/src/errors/constants.ts +45 -0
  174. package/src/errors/errors.ts +44 -0
  175. package/src/errors/httpError.ts +93 -0
  176. package/src/errors/index.ts +7 -0
  177. package/src/errors/utils/baseErrorRootCause.ts +18 -0
  178. package/src/errors/utils/rootCause.ts +7 -0
  179. package/src/helpers.ts +23 -16
  180. package/src/index.ts +2 -2
  181. package/src/request.ts +52 -38
  182. package/src/services/api.ts +109 -157
  183. package/src/services/balance.ts +4 -4
  184. package/src/types/request.ts +3 -0
  185. package/src/utils/errors.ts +0 -233
  186. package/src/utils/index.ts +1 -2
  187. package/src/version.ts +1 -1
  188. package/src/_cjs/utils/parseBackendError.js +0 -27
  189. package/src/_cjs/utils/parseBackendError.js.map +0 -1
  190. package/src/_cjs/utils/parseError.js +0 -69
  191. package/src/_cjs/utils/parseError.js.map +0 -1
  192. package/src/_esm/utils/parseBackendError.js +0 -24
  193. package/src/_esm/utils/parseBackendError.js.map +0 -1
  194. package/src/_esm/utils/parseError.js +0 -100
  195. package/src/_esm/utils/parseError.js.map +0 -1
  196. package/src/_types/utils/parseBackendError.d.ts +0 -3
  197. package/src/_types/utils/parseBackendError.d.ts.map +0 -1
  198. package/src/_types/utils/parseError.d.ts +0 -35
  199. package/src/_types/utils/parseError.d.ts.map +0 -1
  200. package/src/utils/parseBackendError.ts +0 -50
  201. package/src/utils/parseError.ts +0 -210
@@ -0,0 +1,60 @@
1
+ import { type LiFiStep, type Process } from '@lifi/types'
2
+ import { TransactionError, UnknownError } from '../../errors/errors.js'
3
+ import { SDKError } from '../../errors/SDKError.js'
4
+ import { ErrorMessage, LiFiErrorCode } from '../../errors/constants.js'
5
+ import { BaseError } from '../../utils/index.js'
6
+ import { fetchTxErrorDetails } from '../../helpers.js'
7
+
8
+ export const parseEVMErrors = async (
9
+ e: Error,
10
+ step?: LiFiStep,
11
+ process?: Process
12
+ ): Promise<SDKError> => {
13
+ if (e instanceof SDKError) {
14
+ e.step = e.step ?? step
15
+ e.process = e.process ?? process
16
+ return e
17
+ }
18
+
19
+ const baseError = await handleSpecificErrors(e, step, process)
20
+
21
+ return new SDKError(baseError, step, process)
22
+ }
23
+
24
+ const handleSpecificErrors = async (
25
+ e: any,
26
+ step?: LiFiStep,
27
+ process?: Process
28
+ ) => {
29
+ if (e.cause?.name === 'UserRejectedRequestError') {
30
+ return new TransactionError(LiFiErrorCode.SignatureRejected, e.message, e)
31
+ }
32
+
33
+ if (
34
+ step &&
35
+ process?.txHash &&
36
+ e.code === LiFiErrorCode.TransactionFailed &&
37
+ e.message === ErrorMessage.TransactionReverted
38
+ ) {
39
+ const response = await fetchTxErrorDetails(
40
+ process.txHash,
41
+ step.action.fromChainId
42
+ )
43
+
44
+ const errorMessage = response?.error_message
45
+
46
+ if (errorMessage?.toLowerCase().includes('out of gas')) {
47
+ return new TransactionError(
48
+ LiFiErrorCode.GasLimitError,
49
+ ErrorMessage.GasLimitLow,
50
+ e
51
+ )
52
+ }
53
+ }
54
+
55
+ if (e instanceof BaseError) {
56
+ return e
57
+ }
58
+
59
+ return new UnknownError(e.message || ErrorMessage.UnknownError, e)
60
+ }
@@ -1,6 +1,6 @@
1
1
  import { ChainId } from '@lifi/types'
2
2
  import type { PublicClient } from 'viem'
3
- import { createPublicClient, fallback, http } from 'viem'
3
+ import { createPublicClient, fallback, http, webSocket } from 'viem'
4
4
  import { mainnet, type Chain } from 'viem/chains'
5
5
  import { config } from '../../config.js'
6
6
  import { getRpcUrls } from '../rpc.js'
@@ -19,9 +19,11 @@ export const getPublicClient = async (
19
19
  if (!publicClients[chainId]) {
20
20
  const urls = await getRpcUrls(chainId)
21
21
  const fallbackTransports = urls.map((url) =>
22
- http(url, {
23
- batch: true,
24
- })
22
+ url.startsWith('wss')
23
+ ? webSocket(url)
24
+ : http(url, {
25
+ batch: true,
26
+ })
25
27
  )
26
28
  const _chain = await config.getChainById(chainId)
27
29
  const chain: Chain = {
@@ -43,6 +45,9 @@ export const getPublicClient = async (
43
45
  publicClients[chainId] = createPublicClient({
44
46
  chain: chain,
45
47
  transport: fallback(fallbackTransports),
48
+ batch: {
49
+ multicall: true,
50
+ },
46
51
  })
47
52
  }
48
53
 
@@ -1,5 +1,6 @@
1
1
  import type { WalletClient } from 'viem'
2
- import { LiFiErrorCode, ProviderError } from '../../utils/errors.js'
2
+ import { LiFiErrorCode } from '../../errors/constants.js'
3
+ import { ProviderError } from '../../errors/errors.js'
3
4
  import type { StatusManager } from '../StatusManager.js'
4
5
  import type { LiFiStepExtended, SwitchChainHook } from '../types.js'
5
6
 
@@ -6,15 +6,13 @@ import {
6
6
  type SendOptions,
7
7
  type SignatureResult,
8
8
  } from '@solana/web3.js'
9
+ import bs58 from 'bs58'
9
10
  import { config } from '../../config.js'
11
+ import { LiFiErrorCode } from '../../errors/constants.js'
12
+ import { TransactionError } from '../../errors/errors.js'
10
13
  import { getStepTransaction } from '../../services/api.js'
11
14
  import { base64ToUint8Array } from '../../utils/base64ToUint8Array.js'
12
- import {
13
- LiFiErrorCode,
14
- TransactionError,
15
- getTransactionFailedMessage,
16
- parseError,
17
- } from '../../utils/index.js'
15
+ import { getTransactionFailedMessage } from '../../utils/index.js'
18
16
  import { BaseStepExecutor } from '../BaseStepExecutor.js'
19
17
  import { checkBalance } from '../checkBalance.js'
20
18
  import { getSubstatusMessage } from '../processMessages.js'
@@ -27,15 +25,12 @@ import type {
27
25
  import { sleep } from '../utils.js'
28
26
  import { waitForReceivingTransaction } from '../waitForReceivingTransaction.js'
29
27
  import { getSolanaConnection } from './connection.js'
28
+ import { parseSolanaErrors } from './parseSolanaErrors.js'
30
29
 
31
30
  export interface SolanaStepExecutorOptions extends StepExecutorOptions {
32
31
  walletAdapter: SignerWalletAdapter
33
32
  }
34
33
 
35
- const TX_RETRY_INTERVAL = 1000
36
- // https://solana.com/docs/advanced/confirmation
37
- const TIMEOUT_PERIOD = 60_000
38
-
39
34
  export class SolanaStepExecutor extends BaseStepExecutor {
40
35
  private walletAdapter: SignerWalletAdapter
41
36
 
@@ -149,8 +144,23 @@ export class SolanaStepExecutor extends BaseStepExecutor {
149
144
 
150
145
  this.checkWalletAdapter(step)
151
146
 
152
- const signedTx =
153
- await this.walletAdapter.signTransaction(versionedTransaction)
147
+ const signedTxPromise =
148
+ this.walletAdapter.signTransaction(versionedTransaction)
149
+
150
+ // We give users 2 minutes to sign the transaction or it should be considered expired
151
+ const signedTx = await Promise.race([
152
+ signedTxPromise,
153
+ // https://solana.com/docs/advanced/confirmation#transaction-expiration
154
+ // Use 2 minutes to account for fluctuations
155
+ sleep(120_000),
156
+ ])
157
+
158
+ if (!signedTx) {
159
+ throw new TransactionError(
160
+ LiFiErrorCode.TransactionExpired,
161
+ 'Transaction has expired: blockhash is no longer recent enough.'
162
+ )
163
+ }
154
164
 
155
165
  process = this.statusManager.updateProcess(
156
166
  step,
@@ -158,21 +168,31 @@ export class SolanaStepExecutor extends BaseStepExecutor {
158
168
  'PENDING'
159
169
  )
160
170
 
161
- const rawTransactionOptions: SendOptions = {
162
- // Skipping preflight i.e. tx simulation by RPC as we simulated the tx above
163
- skipPreflight: true,
164
- // Setting max retries to 0 as we are handling retries manually
165
- // Set this manually so that the default is skipped
166
- maxRetries: 0,
167
- // https://solana.com/docs/advanced/confirmation#use-an-appropriate-preflight-commitment-level
168
- preflightCommitment: 'confirmed',
169
- // minContextSlot: blockhashResult.context.slot,
171
+ const simulationResult = await connection.simulateTransaction(
172
+ signedTx,
173
+ {
174
+ commitment: 'processed',
175
+ replaceRecentBlockhash: true,
176
+ }
177
+ )
178
+
179
+ if (simulationResult.value.err) {
180
+ throw new TransactionError(
181
+ LiFiErrorCode.TransactionSimulationFailed,
182
+ 'Transaction simulation failed'
183
+ )
170
184
  }
171
185
 
172
- const txSignature = await connection.sendRawTransaction(
173
- signedTx.serialize(),
174
- rawTransactionOptions
175
- )
186
+ // Create transaction hash (signature)
187
+ const txSignature = bs58.encode(signedTx.signatures[0])
188
+
189
+ // A known weirdness - MAX_RECENT_BLOCKHASHES is 300
190
+ // https://github.com/solana-labs/solana/blob/master/sdk/program/src/clock.rs#L123
191
+ // but MAX_PROCESSING_AGE is 150
192
+ // https://github.com/solana-labs/solana/blob/master/sdk/program/src/clock.rs#L129
193
+ // the blockhash queue in the bank tells you 300 + current slot, but it won't be accepted 150 blocks later.
194
+ // https://solana.com/docs/advanced/confirmation#transaction-expiration
195
+ const lastValidBlockHeight = blockhashResult.lastValidBlockHeight - 150
176
196
 
177
197
  // In the following section, we wait and constantly check for the transaction to be confirmed
178
198
  // and resend the transaction if it is not confirmed within a certain time interval
@@ -183,7 +203,7 @@ export class SolanaStepExecutor extends BaseStepExecutor {
183
203
  {
184
204
  signature: txSignature,
185
205
  blockhash: blockhashResult.blockhash,
186
- lastValidBlockHeight: blockhashResult.lastValidBlockHeight,
206
+ lastValidBlockHeight: lastValidBlockHeight,
187
207
  abortSignal: abortController.signal,
188
208
  },
189
209
  'confirmed'
@@ -191,20 +211,34 @@ export class SolanaStepExecutor extends BaseStepExecutor {
191
211
  .then((result) => result.value)
192
212
 
193
213
  let confirmedTx: SignatureResult | null = null
194
- const startTime = Date.now()
214
+ let blockHeight = await connection.getBlockHeight()
215
+
216
+ const rawTransactionOptions: SendOptions = {
217
+ // We can skip preflight check after the first transaction has been sent
218
+ // https://solana.com/docs/advanced/retry#the-cost-of-skipping-preflight
219
+ skipPreflight: true,
220
+ // Setting max retries to 0 as we are handling retries manually
221
+ maxRetries: 0,
222
+ // https://solana.com/docs/advanced/confirmation#use-an-appropriate-preflight-commitment-level
223
+ preflightCommitment: 'confirmed',
224
+ }
225
+
226
+ const signedTxSerialized = signedTx.serialize()
195
227
 
196
- while (!confirmedTx && Date.now() - startTime <= TIMEOUT_PERIOD) {
228
+ // https://solana.com/docs/advanced/retry#customizing-rebroadcast-logic
229
+ while (!confirmedTx && blockHeight < lastValidBlockHeight) {
197
230
  await connection.sendRawTransaction(
198
- signedTx.serialize(),
231
+ signedTxSerialized,
199
232
  rawTransactionOptions
200
233
  )
201
234
  confirmedTx = await Promise.race([
202
235
  confirmTransactionPromise,
203
- sleep(TX_RETRY_INTERVAL),
236
+ sleep(1000),
204
237
  ])
205
238
  if (confirmedTx) {
206
239
  break
207
240
  }
241
+ blockHeight = await connection.getBlockHeight()
208
242
  }
209
243
 
210
244
  // Stop waiting for tx confirmation
@@ -233,7 +267,7 @@ export class SolanaStepExecutor extends BaseStepExecutor {
233
267
  if (!confirmedTx) {
234
268
  throw new TransactionError(
235
269
  LiFiErrorCode.TransactionExpired,
236
- 'Failed to land the transaction'
270
+ 'Transaction has expired: The block height has exceeded the maximum allowed limit.'
237
271
  )
238
272
  }
239
273
 
@@ -252,15 +286,14 @@ export class SolanaStepExecutor extends BaseStepExecutor {
252
286
  process = this.statusManager.updateProcess(step, process.type, 'DONE')
253
287
  }
254
288
  } catch (e: any) {
255
- const error = await parseError(e, step, process)
289
+ const error = await parseSolanaErrors(e, step, process)
256
290
  process = this.statusManager.updateProcess(
257
291
  step,
258
292
  process.type,
259
293
  'FAILED',
260
294
  {
261
295
  error: {
262
- message: error.message,
263
- htmlMessage: error.htmlMessage,
296
+ message: error.cause.message,
264
297
  code: error.code,
265
298
  },
266
299
  }
@@ -5,9 +5,8 @@ import { getRpcUrl } from '../rpc.js'
5
5
  let connection: Connection | undefined = undefined
6
6
 
7
7
  /**
8
- * getSolanaConnection is just a thin wrapper around getting the
9
- * connection (rpc provider) for Solana
10
- * @returns - Solana rpc connection
8
+ * getSolanaConnection is just a thin wrapper around getting the connection (RPC provider) for Solana
9
+ * @returns - Solana RPC connection
11
10
  */
12
11
  export const getSolanaConnection = async (): Promise<Connection> => {
13
12
  if (!connection) {
@@ -0,0 +1,45 @@
1
+ import type { LiFiStep, Process } from '@lifi/types'
2
+ import { BaseError } from '../../errors/baseError.js'
3
+ import { ErrorMessage, LiFiErrorCode } from '../../errors/constants.js'
4
+ import { TransactionError, UnknownError } from '../../errors/errors.js'
5
+ import { SDKError } from '../../errors/SDKError.js'
6
+
7
+ export const parseSolanaErrors = async (
8
+ e: Error,
9
+ step?: LiFiStep,
10
+ process?: Process
11
+ ): Promise<SDKError> => {
12
+ if (e instanceof SDKError) {
13
+ e.step = e.step ?? step
14
+ e.process = e.process ?? process
15
+ return e
16
+ }
17
+
18
+ const baseError = handleSpecificErrors(e)
19
+
20
+ return new SDKError(baseError, step, process)
21
+ }
22
+
23
+ const handleSpecificErrors = (e: any) => {
24
+ if (e.name === 'WalletSignTransactionError') {
25
+ return new TransactionError(LiFiErrorCode.SignatureRejected, e.message, e)
26
+ }
27
+
28
+ if (e.name === 'SendTransactionError') {
29
+ return new TransactionError(LiFiErrorCode.TransactionFailed, e.message, e)
30
+ }
31
+
32
+ if (e.message?.includes('simulate')) {
33
+ return new TransactionError(
34
+ LiFiErrorCode.TransactionSimulationFailed,
35
+ e.message,
36
+ e
37
+ )
38
+ }
39
+
40
+ if (e instanceof BaseError) {
41
+ return e
42
+ }
43
+
44
+ return new UnknownError(e.message || ErrorMessage.UnknownError, e)
45
+ }
@@ -1,7 +1,7 @@
1
1
  import type { LiFiStep } from '@lifi/types'
2
2
  import { formatUnits } from 'viem'
3
3
  import { getTokenBalance } from '../services/balance.js'
4
- import { BalanceError } from '../utils/errors.js'
4
+ import { BalanceError } from '../errors/errors.js'
5
5
  import { sleep } from './utils.js'
6
6
 
7
7
  export const checkBalance = async (
@@ -40,7 +40,7 @@ export const checkBalance = async (
40
40
  `start a new one with a maximum of ${current} ${token.symbol}.`
41
41
  }
42
42
 
43
- throw new BalanceError('The balance is too low.', errorMessage)
43
+ throw new BalanceError('The balance is too low.')
44
44
  }
45
45
  }
46
46
  }
@@ -1,5 +1,6 @@
1
1
  import type { LiFiStep } from '@lifi/types'
2
- import { LiFiErrorCode, TransactionError } from '../utils/errors.js'
2
+ import { LiFiErrorCode } from '../errors/constants.js'
3
+ import { TransactionError } from '../errors/errors.js'
3
4
  import type { StatusManager } from './StatusManager.js'
4
5
  import type { ExecutionOptions } from './types.js'
5
6
  import { checkStepSlippageThreshold } from './utils.js'
@@ -41,9 +42,7 @@ export const stepComparison = async (
41
42
  // The user declined the new exchange rate, so we are not going to proceed
42
43
  throw new TransactionError(
43
44
  LiFiErrorCode.ExchangeRateUpdateCanceled,
44
- 'Exchange rate has changed!',
45
- `Transaction was not sent, your funds are still in your wallet.
46
- The exchange rate has changed and the previous estimation can not be fulfilled due to value loss.`
45
+ `Exchange rate has changed!\nTransaction was not sent, your funds are still in your wallet.\nThe exchange rate has changed and the previous estimation can not be fulfilled due to value loss.`
47
46
  )
48
47
  }
49
48
 
@@ -5,7 +5,7 @@ import type {
5
5
  StatusResponse,
6
6
  } from '@lifi/types'
7
7
  import { getStatus } from '../services/api.js'
8
- import { ServerError } from '../utils/errors.js'
8
+ import { ServerError } from '../errors/errors.js'
9
9
  import { repeatUntilDone } from '../utils/utils.js'
10
10
  import type { StatusManager } from './StatusManager.js'
11
11
  import { getSubstatusMessage } from './processMessages.js'
@@ -0,0 +1,25 @@
1
+ import type { LiFiStep, Process } from '@lifi/types'
2
+ import { version } from '../version.js'
3
+ import type { BaseError } from './baseError.js'
4
+ import { type ErrorCode } from './constants.js'
5
+
6
+ // Note: SDKError is used to wrapper and present errors at the top level
7
+ // Where opportunity allows we also add the step and the process related to the error
8
+ export class SDKError extends Error {
9
+ step?: LiFiStep
10
+ process?: Process
11
+ code: ErrorCode
12
+ override name = 'SDKError'
13
+ override cause: BaseError
14
+
15
+ constructor(cause: BaseError, step?: LiFiStep, process?: Process) {
16
+ const errorMessage = `${cause.message ? `[${cause.name}] ${cause.message}` : 'Unknown error occurred'}\nLI.FI SDK version: ${version}`
17
+ super(errorMessage)
18
+ this.name = 'SDKError'
19
+ this.step = step
20
+ this.process = process
21
+ this.cause = cause
22
+ this.stack = this.cause.stack
23
+ this.code = cause.code
24
+ }
25
+ }
@@ -0,0 +1,22 @@
1
+ import type { ErrorCode, ErrorName } from './constants.js'
2
+ import { getRootCause } from './utils/rootCause.js'
3
+
4
+ // Note: we use the BaseErrors to capture errors at specific points in the code
5
+ // they can carry addition to help give more context
6
+ export class BaseError extends Error {
7
+ code: ErrorCode
8
+ override cause?: Error
9
+
10
+ constructor(name: ErrorName, code: number, message: string, cause?: Error) {
11
+ super(message)
12
+
13
+ this.name = name
14
+ this.code = code
15
+ this.cause = cause
16
+
17
+ const rootCause = getRootCause(this.cause)
18
+ if (rootCause && rootCause.stack) {
19
+ this.stack = rootCause.stack
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,45 @@
1
+ export enum ErrorName {
2
+ RPCError = 'RPCError',
3
+ ProviderError = 'ProviderError',
4
+ ServerError = 'ServerError',
5
+ TransactionError = 'TransactionError',
6
+ ValidationError = 'ValidationError',
7
+ BalanceError = 'BalanceError',
8
+ NotFoundError = 'NotFoundError',
9
+ UnknownError = 'UnknownError',
10
+ SlippageError = 'SlippageError',
11
+ HTTPError = 'HTTPError',
12
+ }
13
+
14
+ export type ErrorCode = LiFiErrorCode
15
+
16
+ export enum LiFiErrorCode {
17
+ InternalError = 1000,
18
+ ValidationError = 1001,
19
+ TransactionUnderpriced = 1002,
20
+ TransactionFailed = 1003,
21
+ Timeout = 1004,
22
+ ProviderUnavailable = 1005,
23
+ NotFound = 1006,
24
+ ChainSwitchError = 1007,
25
+ TransactionUnprepared = 1008,
26
+ GasLimitError = 1009,
27
+ TransactionCanceled = 1010,
28
+ SlippageError = 1011,
29
+ SignatureRejected = 1012,
30
+ BalanceError = 1013,
31
+ AllowanceRequired = 1014,
32
+ InsufficientFunds = 1015,
33
+ ExchangeRateUpdateCanceled = 1016,
34
+ WalletChangedDuringExecution = 1017,
35
+ TransactionExpired = 1018,
36
+ TransactionSimulationFailed = 1019,
37
+ }
38
+
39
+ export enum ErrorMessage {
40
+ UnknownError = 'Unknown error occurred.',
41
+ SlippageError = 'The slippage is larger than the defined threshold. Please request a new route to get a fresh quote.',
42
+ GasLimitLow = 'Gas limit is too low.',
43
+ TransactionUnderpriced = 'Transaction is underpriced.',
44
+ TransactionReverted = 'Transaction was reverted.',
45
+ }
@@ -0,0 +1,44 @@
1
+ import { ErrorName, LiFiErrorCode } from './constants.js'
2
+ import { BaseError } from './baseError.js'
3
+
4
+ export class RPCError extends BaseError {
5
+ constructor(code: LiFiErrorCode, message: string, cause?: Error) {
6
+ super(ErrorName.RPCError, code, message, cause)
7
+ }
8
+ }
9
+
10
+ export class ProviderError extends BaseError {
11
+ constructor(code: LiFiErrorCode, message: string, cause?: Error) {
12
+ super(ErrorName.ProviderError, code, message, cause)
13
+ }
14
+ }
15
+
16
+ export class TransactionError extends BaseError {
17
+ constructor(code: LiFiErrorCode, message: string, cause?: Error) {
18
+ super(ErrorName.TransactionError, code, message, cause)
19
+ }
20
+ }
21
+
22
+ export class UnknownError extends BaseError {
23
+ constructor(message: string, cause?: Error) {
24
+ super(ErrorName.UnknownError, LiFiErrorCode.InternalError, message, cause)
25
+ }
26
+ }
27
+
28
+ export class BalanceError extends BaseError {
29
+ constructor(message: string, cause?: Error) {
30
+ super(ErrorName.BalanceError, LiFiErrorCode.BalanceError, message, cause)
31
+ }
32
+ }
33
+
34
+ export class ServerError extends BaseError {
35
+ constructor(message: string) {
36
+ super(ErrorName.ServerError, LiFiErrorCode.InternalError, message)
37
+ }
38
+ }
39
+
40
+ export class ValidationError extends BaseError {
41
+ constructor(message: string) {
42
+ super(ErrorName.ValidationError, LiFiErrorCode.ValidationError, message)
43
+ }
44
+ }
@@ -0,0 +1,93 @@
1
+ import type { UnavailableRoutes } from '@lifi/types'
2
+ import { LiFiErrorCode } from './constants.js'
3
+ import { BaseError } from './baseError.js'
4
+ import type { ExtendedRequestInit } from '../types/request.js'
5
+ import { ErrorName, ErrorMessage } from './constants.js'
6
+
7
+ interface ServerErrorResponseBody {
8
+ code: number
9
+ message: string
10
+ errors?: UnavailableRoutes
11
+ }
12
+
13
+ const statusCodeToErrorClassificationMap = new Map([
14
+ [
15
+ 400,
16
+ { type: ErrorName.ValidationError, code: LiFiErrorCode.ValidationError },
17
+ ],
18
+ [404, { type: ErrorName.NotFoundError, code: LiFiErrorCode.NotFound }],
19
+ [
20
+ 409,
21
+ {
22
+ type: ErrorName.SlippageError,
23
+ code: LiFiErrorCode.SlippageError,
24
+ message: ErrorMessage.SlippageError,
25
+ },
26
+ ],
27
+ [500, { type: ErrorName.ServerError, code: LiFiErrorCode.InternalError }],
28
+ ])
29
+
30
+ const getErrorClassificationFromStatusCode = (code: number) =>
31
+ statusCodeToErrorClassificationMap.get(code) ?? {
32
+ type: ErrorName.ServerError,
33
+ code: LiFiErrorCode.InternalError,
34
+ }
35
+
36
+ const createInitialMessage = (response: Response) => {
37
+ const statusCode =
38
+ response.status || response.status === 0 ? response.status : ''
39
+ const title = response.statusText || ''
40
+ const status = `${statusCode} ${title}`.trim()
41
+ const reason = status ? `status code ${status}` : 'an unknown error'
42
+ return `Request failed with ${reason}`
43
+ }
44
+
45
+ export class HTTPError extends BaseError {
46
+ public response: Response
47
+ public status: number
48
+ public url: RequestInfo | URL
49
+ public fetchOptions: ExtendedRequestInit
50
+ public type?: ErrorName
51
+ public responseBody?: ServerErrorResponseBody
52
+
53
+ constructor(
54
+ response: Response,
55
+ url: RequestInfo | URL,
56
+ options: ExtendedRequestInit
57
+ ) {
58
+ const errorClassification = getErrorClassificationFromStatusCode(
59
+ response.status
60
+ )
61
+ const additionalMessage = errorClassification?.message
62
+ ? `\n${errorClassification.message}`
63
+ : ''
64
+ const message = createInitialMessage(response) + additionalMessage
65
+
66
+ super(ErrorName.HTTPError, errorClassification.code, message)
67
+
68
+ this.type = errorClassification.type
69
+ this.response = response
70
+ this.status = response.status
71
+ this.message = message
72
+ this.url = url
73
+ this.fetchOptions = options
74
+ }
75
+
76
+ async buildAdditionalDetails() {
77
+ if (this.type) {
78
+ this.message = `[${this.type}] ${this.message}`
79
+ }
80
+
81
+ try {
82
+ this.responseBody = await this.response.json()
83
+
84
+ const spacer = '\n '
85
+
86
+ if (this.responseBody) {
87
+ this.message += `${spacer}responseMessage: ${this.responseBody?.message.toString().replaceAll('\n', spacer)}`
88
+ }
89
+ } catch {}
90
+
91
+ return this
92
+ }
93
+ }
@@ -0,0 +1,7 @@
1
+ export * from './baseError.js'
2
+ export * from './constants.js'
3
+ export * from './errors.js'
4
+ export * from './httpError.js'
5
+ export * from './SDKError.js'
6
+ export * from './utils/rootCause.js'
7
+ export * from './utils/baseErrorRootCause.js'
@@ -0,0 +1,18 @@
1
+ import { BaseError } from '../baseError.js'
2
+ import { HTTPError } from '../httpError.js'
3
+
4
+ export const getRootCauseBaseError = (e: Error) => {
5
+ let rootCause = e
6
+ while (rootCause.cause && rootCause.cause instanceof BaseError) {
7
+ rootCause = rootCause.cause as BaseError
8
+ }
9
+ return rootCause as BaseError
10
+ }
11
+
12
+ export const getRootCauseBaseErrorMessage = (e: Error) => {
13
+ const rootCause = getRootCauseBaseError(e)
14
+
15
+ return rootCause instanceof HTTPError
16
+ ? (rootCause as HTTPError).responseBody?.message || rootCause.message
17
+ : rootCause.message
18
+ }
@@ -0,0 +1,7 @@
1
+ export const getRootCause = (e: Error | undefined) => {
2
+ let rootCause = e
3
+ while (rootCause?.cause) {
4
+ rootCause = rootCause.cause as Error
5
+ }
6
+ return rootCause
7
+ }