@lifi/sdk 3.14.0-alpha.0 → 3.14.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 (80) hide show
  1. package/LICENSE +201 -165
  2. package/package.json +7 -7
  3. package/src/_cjs/core/EVM/EVMStepExecutor.js +9 -4
  4. package/src/_cjs/core/EVM/EVMStepExecutor.js.map +1 -1
  5. package/src/_cjs/core/Solana/SolanaStepExecutor.js +13 -12
  6. package/src/_cjs/core/Solana/SolanaStepExecutor.js.map +1 -1
  7. package/src/_cjs/core/Solana/connection.js +4 -15
  8. package/src/_cjs/core/Solana/connection.js.map +1 -1
  9. package/src/_cjs/core/Solana/jito/JitoConnection.js +51 -21
  10. package/src/_cjs/core/Solana/jito/JitoConnection.js.map +1 -1
  11. package/src/_cjs/core/Solana/jito/constants.js +10 -23
  12. package/src/_cjs/core/Solana/jito/constants.js.map +1 -1
  13. package/src/_cjs/core/Solana/jito/sendAndConfirmBundle.js +36 -84
  14. package/src/_cjs/core/Solana/jito/sendAndConfirmBundle.js.map +1 -1
  15. package/src/_cjs/index.js.map +1 -1
  16. package/src/_cjs/request.js +7 -3
  17. package/src/_cjs/request.js.map +1 -1
  18. package/src/_cjs/services/api.js +1 -2
  19. package/src/_cjs/services/api.js.map +1 -1
  20. package/src/_cjs/version.js +1 -1
  21. package/src/_cjs/version.js.map +1 -1
  22. package/src/_esm/core/EVM/EVMStepExecutor.js +10 -4
  23. package/src/_esm/core/EVM/EVMStepExecutor.js.map +1 -1
  24. package/src/_esm/core/Solana/SolanaStepExecutor.js +13 -12
  25. package/src/_esm/core/Solana/SolanaStepExecutor.js.map +1 -1
  26. package/src/_esm/core/Solana/connection.js +7 -20
  27. package/src/_esm/core/Solana/connection.js.map +1 -1
  28. package/src/_esm/core/Solana/jito/JitoConnection.js +72 -23
  29. package/src/_esm/core/Solana/jito/JitoConnection.js.map +1 -1
  30. package/src/_esm/core/Solana/jito/constants.js +10 -23
  31. package/src/_esm/core/Solana/jito/constants.js.map +1 -1
  32. package/src/_esm/core/Solana/jito/sendAndConfirmBundle.js +45 -104
  33. package/src/_esm/core/Solana/jito/sendAndConfirmBundle.js.map +1 -1
  34. package/src/_esm/index.js.map +1 -1
  35. package/src/_esm/request.js +7 -3
  36. package/src/_esm/request.js.map +1 -1
  37. package/src/_esm/services/api.js +3 -4
  38. package/src/_esm/services/api.js.map +1 -1
  39. package/src/_esm/version.js +1 -1
  40. package/src/_esm/version.js.map +1 -1
  41. package/src/_types/core/EVM/EVMStepExecutor.d.ts.map +1 -1
  42. package/src/_types/core/Solana/SolanaStepExecutor.d.ts.map +1 -1
  43. package/src/_types/core/Solana/connection.d.ts +4 -3
  44. package/src/_types/core/Solana/connection.d.ts.map +1 -1
  45. package/src/_types/core/Solana/jito/JitoConnection.d.ts +35 -5
  46. package/src/_types/core/Solana/jito/JitoConnection.d.ts.map +1 -1
  47. package/src/_types/core/Solana/jito/constants.d.ts +1 -2
  48. package/src/_types/core/Solana/jito/constants.d.ts.map +1 -1
  49. package/src/_types/core/Solana/jito/sendAndConfirmBundle.d.ts +2 -2
  50. package/src/_types/core/Solana/jito/sendAndConfirmBundle.d.ts.map +1 -1
  51. package/src/_types/index.d.ts +2 -2
  52. package/src/_types/index.d.ts.map +1 -1
  53. package/src/_types/request.d.ts.map +1 -1
  54. package/src/_types/services/api.d.ts +1 -1
  55. package/src/_types/services/api.d.ts.map +1 -1
  56. package/src/_types/services/types.d.ts +0 -3
  57. package/src/_types/services/types.d.ts.map +1 -1
  58. package/src/_types/types/internal.d.ts +3 -0
  59. package/src/_types/types/internal.d.ts.map +1 -1
  60. package/src/_types/version.d.ts +1 -1
  61. package/src/_types/version.d.ts.map +1 -1
  62. package/src/core/EVM/EVMStepExecutor.ts +11 -3
  63. package/src/core/Solana/SolanaStepExecutor.ts +13 -12
  64. package/src/core/Solana/connection.ts +12 -25
  65. package/src/core/Solana/jito/JitoConnection.ts +97 -29
  66. package/src/core/Solana/jito/constants.ts +10 -25
  67. package/src/core/Solana/jito/sendAndConfirmBundle.ts +50 -135
  68. package/src/index.ts +6 -2
  69. package/src/request.ts +9 -3
  70. package/src/services/api.ts +4 -6
  71. package/src/services/types.ts +0 -4
  72. package/src/types/internal.ts +6 -0
  73. package/src/version.ts +1 -1
  74. package/src/_cjs/core/Solana/jito/isJitoRpc.js +0 -23
  75. package/src/_cjs/core/Solana/jito/isJitoRpc.js.map +0 -1
  76. package/src/_esm/core/Solana/jito/isJitoRpc.js +0 -21
  77. package/src/_esm/core/Solana/jito/isJitoRpc.js.map +0 -1
  78. package/src/_types/core/Solana/jito/isJitoRpc.d.ts +0 -2
  79. package/src/_types/core/Solana/jito/isJitoRpc.d.ts.map +0 -1
  80. package/src/core/Solana/jito/isJitoRpc.ts +0 -21
@@ -1,5 +1,7 @@
1
1
  import type { ChainId, ExtendedChain, RouteOptions } from '@lifi/types';
2
2
  import type { SDKProvider } from '../core/types.js';
3
+ import type { ExtendedRequestInit } from './request.js';
4
+ export type RequestInterceptor = (request: ExtendedRequestInit) => ExtendedRequestInit | Promise<ExtendedRequestInit>;
3
5
  export interface SDKBaseConfig {
4
6
  apiKey?: string;
5
7
  apiUrl: string;
@@ -13,6 +15,7 @@ export interface SDKBaseConfig {
13
15
  widgetVersion?: string;
14
16
  preloadChains: boolean;
15
17
  debug: boolean;
18
+ requestInterceptor?: RequestInterceptor;
16
19
  }
17
20
  export interface SDKConfig extends Partial<Omit<SDKBaseConfig, 'integrator'>> {
18
21
  integrator: string;
@@ -1 +1 @@
1
- {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../types/internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,WAAW,EAAE,CAAA;IACxB,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,OAAO,CAAA;IACtB,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,SAAU,SAAQ,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IAC3E,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../types/internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAEvD,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,mBAAmB,KACzB,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;AAEvD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,WAAW,EAAE,CAAA;IACxB,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,OAAO,CAAA;IACtB,KAAK,EAAE,OAAO,CAAA;IACd,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;CACxC;AAED,MAAM,WAAW,SAAU,SAAQ,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IAC3E,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA"}
@@ -1,3 +1,3 @@
1
1
  export declare const name = "@lifi/sdk";
2
- export declare const version = "3.14.0-alpha.0";
2
+ export declare const version = "3.14.0";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,cAAc,CAAA;AAC/B,eAAO,MAAM,OAAO,mBAAmB,CAAA"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,cAAc,CAAA;AAC/B,eAAO,MAAM,OAAO,WAAW,CAAA"}
@@ -222,6 +222,7 @@ export class EVMStepExecutor extends BaseStepExecutor {
222
222
  }
223
223
 
224
224
  private prepareUpdatedStep = async (
225
+ client: Client,
225
226
  step: LiFiStepExtended,
226
227
  signedTypedData?: SignedTypedData[]
227
228
  ) => {
@@ -280,6 +281,7 @@ export class EVMStepExecutor extends BaseStepExecutor {
280
281
  let transactionRequest: TransactionParameters | undefined
281
282
  if (step.transactionRequest) {
282
283
  transactionRequest = {
284
+ chainId: step.transactionRequest.chainId,
283
285
  to: step.transactionRequest.to,
284
286
  from: step.transactionRequest.from,
285
287
  data: step.transactionRequest.data,
@@ -296,8 +298,8 @@ export class EVMStepExecutor extends BaseStepExecutor {
296
298
  // ? BigInt(step.transactionRequest.maxFeePerGas as string)
297
299
  // : undefined,
298
300
  maxPriorityFeePerGas:
299
- this.client.account?.type === 'local'
300
- ? await getMaxPriorityFeePerGas(this.client)
301
+ client.account?.type === 'local'
302
+ ? await getMaxPriorityFeePerGas(client)
301
303
  : step.transactionRequest.maxPriorityFeePerGas
302
304
  ? BigInt(step.transactionRequest.maxPriorityFeePerGas)
303
305
  : undefined,
@@ -525,9 +527,15 @@ export class EVMStepExecutor extends BaseStepExecutor {
525
527
 
526
528
  await checkBalance(this.client.account!.address, step)
527
529
 
530
+ // Make sure that the client and chain is still correct
531
+ const updatedClient = await this.checkClient(step, process)
532
+ if (!updatedClient) {
533
+ return step
534
+ }
535
+
528
536
  // Try to prepare a new transaction request and update the step with typed data
529
537
  let { transactionRequest, isRelayerTransaction } =
530
- await this.prepareUpdatedStep(step, signedTypedData)
538
+ await this.prepareUpdatedStep(updatedClient, step, signedTypedData)
531
539
 
532
540
  process = this.statusManager.updateProcess(
533
541
  step,
@@ -198,7 +198,7 @@ export class SolanaStepExecutor extends BaseStepExecutor {
198
198
  )
199
199
  }
200
200
 
201
- let confirmedTx: any
201
+ let confirmedTransaction: any
202
202
 
203
203
  if (shouldUseJitoBundle) {
204
204
  // Use Jito bundle for multiple transactions
@@ -235,17 +235,17 @@ export class SolanaStepExecutor extends BaseStepExecutor {
235
235
 
236
236
  // Use the first transaction's signature result for reporting
237
237
  // (all transactions succeeded if we reach here)
238
- confirmedTx = {
238
+ confirmedTransaction = {
239
239
  signatureResult: bundleResult.signatureResults[0],
240
240
  txSignature: bundleResult.txSignatures[0],
241
241
  bundleId: bundleResult.bundleId,
242
242
  }
243
243
  } else {
244
244
  // Use regular transaction for single transaction
245
- const signedTx = signedTransactions[0]
245
+ const signedTransaction = signedTransactions[0]
246
246
 
247
247
  const simulationResult = await callSolanaWithRetry((connection) =>
248
- connection.simulateTransaction(signedTx, {
248
+ connection.simulateTransaction(signedTransaction, {
249
249
  commitment: 'confirmed',
250
250
  replaceRecentBlockhash: true,
251
251
  })
@@ -258,21 +258,22 @@ export class SolanaStepExecutor extends BaseStepExecutor {
258
258
  )
259
259
  }
260
260
 
261
- confirmedTx = await sendAndConfirmTransaction(signedTx)
261
+ confirmedTransaction =
262
+ await sendAndConfirmTransaction(signedTransaction)
262
263
  }
263
264
 
264
- if (!confirmedTx.signatureResult) {
265
+ if (!confirmedTransaction.signatureResult) {
265
266
  throw new TransactionError(
266
267
  LiFiErrorCode.TransactionExpired,
267
268
  'Transaction has expired: The block height has exceeded the maximum allowed limit.'
268
269
  )
269
270
  }
270
271
 
271
- if (confirmedTx.signatureResult.err) {
272
+ if (confirmedTransaction.signatureResult.err) {
272
273
  const reason =
273
- typeof confirmedTx.signatureResult.err === 'object'
274
- ? JSON.stringify(confirmedTx.signatureResult.err)
275
- : confirmedTx.signatureResult.err
274
+ typeof confirmedTransaction.signatureResult.err === 'object'
275
+ ? JSON.stringify(confirmedTransaction.signatureResult.err)
276
+ : confirmedTransaction.signatureResult.err
276
277
  throw new TransactionError(
277
278
  LiFiErrorCode.TransactionFailed,
278
279
  `Transaction failed: ${reason}`
@@ -285,8 +286,8 @@ export class SolanaStepExecutor extends BaseStepExecutor {
285
286
  process.type,
286
287
  'PENDING',
287
288
  {
288
- txHash: confirmedTx.txSignature,
289
- txLink: `${fromChain.metamask.blockExplorerUrls[0]}tx/${confirmedTx.txSignature}`,
289
+ txHash: confirmedTransaction.txSignature,
290
+ txLink: `${fromChain.metamask.blockExplorerUrls[0]}tx/${confirmedTransaction.txSignature}`,
290
291
  }
291
292
  )
292
293
 
@@ -1,23 +1,8 @@
1
1
  import { ChainId } from '@lifi/types'
2
- import { type Cluster, Connection } from '@solana/web3.js'
2
+ import { Connection } from '@solana/web3.js'
3
3
  import { getRpcUrls } from '../rpc.js'
4
- import { isJitoRpc } from './jito/isJitoRpc.js'
5
4
  import { JitoConnection } from './jito/JitoConnection.js'
6
5
 
7
- /**
8
- * Detect the cluster (network) from an RPC URL
9
- */
10
- const detectCluster = (rpcUrl: string): Cluster => {
11
- const url = rpcUrl.toLowerCase()
12
- if (url.includes('devnet')) {
13
- return 'devnet'
14
- }
15
- if (url.includes('testnet')) {
16
- return 'testnet'
17
- }
18
- return 'mainnet-beta'
19
- }
20
-
21
6
  const connections = new Map<string, Connection | JitoConnection>()
22
7
 
23
8
  /**
@@ -28,8 +13,8 @@ const ensureConnections = async (): Promise<void> => {
28
13
  const rpcUrls = await getRpcUrls(ChainId.SOL)
29
14
  for (const rpcUrl of rpcUrls) {
30
15
  if (!connections.get(rpcUrl)) {
31
- const connection = (await isJitoRpc(rpcUrl))
32
- ? new JitoConnection(rpcUrl, detectCluster(rpcUrl))
16
+ const connection = (await JitoConnection.isJitoRpc(rpcUrl))
17
+ ? new JitoConnection(rpcUrl)
33
18
  : new Connection(rpcUrl)
34
19
  connections.set(rpcUrl, connection)
35
20
  }
@@ -38,17 +23,19 @@ const ensureConnections = async (): Promise<void> => {
38
23
 
39
24
  /**
40
25
  * Wrapper around getting the connection (RPC provider) for Solana
41
- * @returns - Solana RPC connections
26
+ * Returns only non-Jito RPC connections (excludes JitoConnection instances)
27
+ * @returns - Solana RPC connections (excluding Jito connections)
42
28
  */
43
- export const getSolanaConnections = async (): Promise<
44
- (Connection | JitoConnection)[]
45
- > => {
29
+ export const getSolanaConnections = async (): Promise<Connection[]> => {
46
30
  await ensureConnections()
47
- return Array.from(connections.values())
31
+ return Array.from(connections.values()).filter(
32
+ (conn): conn is Connection =>
33
+ conn instanceof Connection && !(conn instanceof JitoConnection)
34
+ )
48
35
  }
49
36
 
50
37
  /**
51
- * Get Jito-enabled connections only
38
+ * Get Jito-enabled connections only.
52
39
  * @returns - Array of JitoConnection instances
53
40
  */
54
41
  export const getJitoConnections = async (): Promise<JitoConnection[]> => {
@@ -78,5 +65,5 @@ export async function callSolanaWithRetry<R>(
78
65
  }
79
66
  }
80
67
  // Throw the last encountered error
81
- throw lastError
68
+ throw lastError || new Error('No Solana RPC connections available')
82
69
  }
@@ -1,8 +1,4 @@
1
- import {
2
- type Cluster,
3
- Connection,
4
- type VersionedTransaction,
5
- } from '@solana/web3.js'
1
+ import { Connection, type VersionedTransaction } from '@solana/web3.js'
6
2
  import { uint8ArrayToBase64 } from '../../../utils/uint8ArrayToBase64.js'
7
3
  import { JITO_TIP_ACCOUNTS } from './constants.js'
8
4
 
@@ -17,41 +13,92 @@ export type SimulateBundleResult = {
17
13
  }
18
14
  }
19
15
 
16
+ export type BundleStatus = {
17
+ bundle_id: string
18
+ transactions: string[]
19
+ slot: number
20
+ confirmation_status: 'processed' | 'confirmed' | 'finalized'
21
+ err:
22
+ | {
23
+ Ok: null
24
+ }
25
+ | any
26
+ }
27
+
28
+ export type BundleStatusResult = {
29
+ context: {
30
+ slot: number
31
+ }
32
+ value: BundleStatus[]
33
+ }
34
+
35
+ /**
36
+ * Makes a direct RPC request to an endpoint
37
+ *
38
+ */
39
+ async function rpcRequest<T>(
40
+ endpoint: string,
41
+ method: string,
42
+ params: any[]
43
+ ): Promise<T> {
44
+ const response = await fetch(endpoint, {
45
+ method: 'POST',
46
+ headers: {
47
+ 'Content-Type': 'application/json',
48
+ },
49
+ body: JSON.stringify({
50
+ jsonrpc: '2.0',
51
+ id: 1,
52
+ method,
53
+ params,
54
+ }),
55
+ })
56
+ if (!response.ok) {
57
+ throw new Error(`Jito RPC Error: ${response.status} ${response.statusText}`)
58
+ }
59
+ const data = await response.json()
60
+ if (data.error) {
61
+ throw new Error(`Jito RPC Error: ${data.error.message}`)
62
+ }
63
+ return data.result
64
+ }
65
+
20
66
  /**
21
67
  * Extended Connection class with Jito bundle support
22
68
  * Adds simulateBundle, sendBundle, and getTipAccounts methods
23
69
  */
24
70
  export class JitoConnection extends Connection {
25
71
  private tipAccountsCache: string[] | null = null
26
- private cluster: Cluster
27
72
 
28
- constructor(endpoint: string, cluster: Cluster = 'mainnet-beta') {
29
- super(endpoint)
30
- this.cluster = cluster
73
+ /**
74
+ * Check if an RPC endpoint supports Jito bundles
75
+ * @param rpcUrl - The RPC endpoint URL to check
76
+ * @returns true if the endpoint supports Jito bundle methods
77
+ */
78
+ static async isJitoRpc(rpcUrl: string): Promise<boolean> {
79
+ try {
80
+ // method exists if the request is successfull and doesn't throw an error
81
+ await rpcRequest(rpcUrl, 'getTipAccounts', [])
82
+ return true
83
+ } catch {
84
+ return false
85
+ }
31
86
  }
32
87
 
33
88
  /**
34
89
  * Makes a direct RPC request to the Jito-enabled endpoint
35
90
  */
36
- private async rpcRequest<T>(method: string, params: any[]): Promise<T> {
37
- const response = await fetch(this.rpcEndpoint, {
38
- method: 'POST',
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- },
42
- body: JSON.stringify({
43
- jsonrpc: '2.0',
44
- id: 1,
45
- method,
91
+ protected async rpcRequest<T>(method: string, params: any[]): Promise<T> {
92
+ try {
93
+ return await rpcRequest(this.rpcEndpoint, method, params)
94
+ } catch (error) {
95
+ console.error(`Jito RPC request failed: ${method}`, {
96
+ endpoint: this.rpcEndpoint,
46
97
  params,
47
- }),
48
- })
49
-
50
- const data = await response.json()
51
- if (data.error) {
52
- throw new Error(`Jito RPC Error: ${data.error.message}`)
98
+ error,
99
+ })
100
+ throw error
53
101
  }
54
- return data.result
55
102
  }
56
103
 
57
104
  /**
@@ -62,7 +109,7 @@ export class JitoConnection extends Connection {
62
109
  }
63
110
 
64
111
  /**
65
- * Get the tip accounts from the Jito endpoint
112
+ * Get the tip accounts from the Jito endpoint, using fallbacks if results are empty
66
113
  * Results are cached to avoid repeated RPC calls
67
114
  */
68
115
  async getTipAccounts(): Promise<string[]> {
@@ -72,12 +119,15 @@ export class JitoConnection extends Connection {
72
119
 
73
120
  try {
74
121
  const accounts = await this.rpcRequest<string[]>('getTipAccounts', [])
122
+ if (!accounts.length) {
123
+ throw new Error('RPC has no tip accounts')
124
+ }
75
125
  this.tipAccountsCache = accounts
76
126
  return accounts
77
127
  } catch (error) {
78
- const fallbackAccounts = JITO_TIP_ACCOUNTS[this.cluster]
128
+ const fallbackAccounts = JITO_TIP_ACCOUNTS
79
129
  console.warn(
80
- `Failed to fetch tip accounts from RPC, using ${this.cluster} fallback:`,
130
+ `Failed to fetch tip accounts from RPC, using fallback`,
81
131
  error
82
132
  )
83
133
  return fallbackAccounts
@@ -92,6 +142,15 @@ export class JitoConnection extends Connection {
92
142
  return accounts[Math.floor(Math.random() * accounts.length)]
93
143
  }
94
144
 
145
+ /**
146
+ * Manually refresh the tip accounts cache
147
+ * Useful for long-running processes that may need updated tip accounts
148
+ */
149
+ async refreshTipAccounts(): Promise<string[]> {
150
+ this.tipAccountsCache = null
151
+ return this.getTipAccounts()
152
+ }
153
+
95
154
  /**
96
155
  * Simulate a bundle before sending it
97
156
  * @param bundle - Array of signed transactions
@@ -119,4 +178,13 @@ export class JitoConnection extends Connection {
119
178
  )
120
179
  return this.rpcRequest<string>('sendBundle', [encodedTransactions])
121
180
  }
181
+
182
+ /**
183
+ * Get the status of submitted bundles
184
+ * @param bundleIds - Array of bundle UUIDs to check
185
+ * @returns Bundle status information
186
+ */
187
+ async getBundleStatuses(bundleIds: string[]): Promise<BundleStatusResult> {
188
+ return this.rpcRequest<BundleStatusResult>('getBundleStatuses', [bundleIds])
189
+ }
122
190
  }
@@ -1,26 +1,11 @@
1
- import type { Cluster } from '@solana/web3.js'
2
-
3
1
  // Jito Tip accounts gotten from https://jito-foundation.gitbook.io/mev/mev-payment-and-distribution/on-chain-addresses
4
- export const JITO_TIP_ACCOUNTS: Record<Cluster, string[]> = {
5
- 'mainnet-beta': [
6
- 'Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY',
7
- 'DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL',
8
- '96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5',
9
- '3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT',
10
- 'HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe',
11
- 'ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49',
12
- 'ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt',
13
- 'DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh',
14
- ],
15
- devnet: [],
16
- testnet: [
17
- 'BkMx5bRzQeP6tUZgzEs3xeDWJfQiLYvNDqSgmGZKYJDq',
18
- 'CwWZzvRgmxj9WLLhdoWUVrHZ1J8db3w2iptKuAitHqoC',
19
- '4uRnem4BfVpZBv7kShVxUYtcipscgZMSHi3B9CSL6gAA',
20
- 'AzfhMPcx3qjbvCK3UUy868qmc5L451W341cpFqdL3EBe',
21
- '84DrGKhycCUGfLzw8hXsUYX9SnWdh2wW3ozsTPrC5xyg',
22
- '7aewvu8fMf1DK4fKoMXKfs3h3wpAQ7r7D8T1C71LmMF',
23
- 'G2d63CEgKBdgtpYT2BuheYQ9HFuFCenuHLNyKVpqAuSD',
24
- 'F7ThiQUBYiEcyaxpmMuUeACdoiSLKg4SZZ8JSfpFNwAf',
25
- ],
26
- }
2
+ export const JITO_TIP_ACCOUNTS = [
3
+ 'Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY',
4
+ 'DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL',
5
+ '96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5',
6
+ '3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT',
7
+ 'HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe',
8
+ 'ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49',
9
+ 'ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt',
10
+ 'DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh',
11
+ ]
@@ -1,5 +1,4 @@
1
1
  import type { SignatureResult, VersionedTransaction } from '@solana/web3.js'
2
- import bs58 from 'bs58'
3
2
  import { sleep } from '../../../utils/sleep.js'
4
3
  import { getJitoConnections } from '../connection.js'
5
4
 
@@ -13,8 +12,8 @@ export type BundleResult = {
13
12
  * Send and confirm a bundle of transactions using Jito
14
13
  * Automatically selects a Jito-enabled RPC connection and polls for confirmation
15
14
  * across multiple Jito RPCs in parallel
16
- * @param signedTransactions - Array of signed transactions
17
- * @returns Bundle ID, transaction signatures, and confirmation results
15
+ * @param signedTransactions - an Array of signed transactions
16
+ * @returns - {@link BundleResult} object containing Bundle ID, transaction signatures, and confirmation results
18
17
  */
19
18
  export async function sendAndConfirmBundle(
20
19
  signedTransactions: VersionedTransaction[]
@@ -27,147 +26,67 @@ export async function sendAndConfirmBundle(
27
26
  )
28
27
  }
29
28
 
30
- // Validate bundle requirements
31
- if (signedTransactions.length === 0) {
32
- throw new Error('Bundle must contain at least one transaction')
33
- }
34
-
35
- // Check that all transactions have the same blockhash (required for bundles)
36
- const firstBlockhash = signedTransactions[0].message.recentBlockhash
37
- const allSameBlockhash = signedTransactions.every(
38
- (tx) => tx.message.recentBlockhash === firstBlockhash
39
- )
40
-
41
- if (!allSameBlockhash) {
42
- console.warn(
43
- 'Bundle transactions have different blockhashes:',
44
- signedTransactions.map((tx) => tx.message.recentBlockhash)
45
- )
46
- }
47
-
48
- // Extract transaction signatures
49
- const txSignatures = signedTransactions.map((tx, index) => {
50
- if (!tx.signatures[0]) {
51
- throw new Error(
52
- `Transaction signature is missing for transaction at index ${index}.`
53
- )
54
- }
55
- const signature = bs58.encode(tx.signatures[0])
56
- return signature
57
- })
58
-
59
29
  const abortController = new AbortController()
60
- let bundleId: string | null = null
61
30
 
62
- // Try to simulate and send bundle with the first connection
63
- for (const jitoConnection of jitoConnections) {
31
+ const confirmPromises = jitoConnections.map(async (jitoConnection) => {
64
32
  try {
65
- // Simulate bundle first
66
- const simulationResult =
67
- await jitoConnection.simulateBundle(signedTransactions)
68
-
69
- if (simulationResult.value.summary !== 'succeeded') {
70
- const summary = simulationResult.value.summary as {
71
- failed: { error: any; tx_signature: string }
72
- }
73
-
74
- // Extract readable error message from TransactionFailure
75
- const errorMsg =
76
- summary.failed?.error?.TransactionFailure?.[1] ||
77
- JSON.stringify(summary.failed?.error) ||
78
- 'Unknown simulation error'
79
-
80
- throw new Error(`Bundle simulation failed: ${errorMsg}`)
81
- }
82
-
83
- // Send bundle using JitoConnection method
84
- bundleId = await jitoConnection.sendBundle(signedTransactions)
85
- break
86
- } catch (error) {
87
- // Try next connection if this one fails
88
- if (jitoConnection === jitoConnections[jitoConnections.length - 1]) {
89
- throw error
33
+ // Send initial bundle for this connection
34
+ let bundleId: string
35
+ try {
36
+ bundleId = await jitoConnection.sendBundle(signedTransactions)
37
+ } catch (_) {
38
+ return null
90
39
  }
91
- }
92
- }
93
40
 
94
- if (!bundleId) {
95
- throw new Error('Failed to send bundle to any Jito connection')
96
- }
97
-
98
- // Now confirm the bundle across all Jito connections in parallel
99
- const confirmPromises = jitoConnections.map(async (jitoConnection) => {
100
- try {
101
- // Get initial blockhash and block height
102
41
  const [blockhashResult, initialBlockHeight] = await Promise.all([
103
42
  jitoConnection.getLatestBlockhash('confirmed'),
104
43
  jitoConnection.getBlockHeight('confirmed'),
105
44
  ])
106
- let signatureResults: (SignatureResult | null)[] = txSignatures.map(
107
- () => null
108
- )
109
-
110
- const pollingPromise = (async () => {
111
- let pollingBlockHeight = initialBlockHeight
112
- while (
113
- pollingBlockHeight < blockhashResult.lastValidBlockHeight &&
114
- !abortController.signal.aborted
45
+ let currentBlockHeight = initialBlockHeight
46
+
47
+ while (
48
+ currentBlockHeight < blockhashResult.lastValidBlockHeight &&
49
+ !abortController.signal.aborted
50
+ ) {
51
+ const statusResponse = await jitoConnection.getBundleStatuses([
52
+ bundleId,
53
+ ])
54
+
55
+ const bundleStatus = statusResponse.value[0]
56
+
57
+ // Check if bundle is confirmed or finalized
58
+ if (
59
+ bundleStatus &&
60
+ (bundleStatus.confirmation_status === 'confirmed' ||
61
+ bundleStatus.confirmation_status === 'finalized')
115
62
  ) {
116
- const statusResponse =
63
+ // Bundle confirmed! Extract transaction signatures from bundle status
64
+ const txSignatures = bundleStatus.transactions
65
+ // Fetch individual signature results
66
+ const sigResponse =
117
67
  await jitoConnection.getSignatureStatuses(txSignatures)
118
68
 
119
- const allConfirmed = statusResponse.value.every(
120
- (status) =>
121
- status &&
122
- (status.confirmationStatus === 'confirmed' ||
123
- status.confirmationStatus === 'finalized')
124
- )
125
-
126
- if (allConfirmed) {
127
- signatureResults = statusResponse.value
128
- // Immediately abort all other connections when we find results
129
- abortController.abort()
130
- return signatureResults
69
+ if (!sigResponse?.value || !Array.isArray(sigResponse.value)) {
70
+ // Keep polling, if can't find signature results.
71
+ continue
131
72
  }
132
73
 
133
- await sleep(400)
134
- // Update block height independently to avoid stale reads
135
- if (!abortController.signal.aborted) {
136
- pollingBlockHeight =
137
- await jitoConnection.getBlockHeight('confirmed')
74
+ // Immediately abort all other connections when we find a result
75
+ abortController.abort()
76
+ return {
77
+ bundleId,
78
+ txSignatures,
79
+ signatureResults: sigResponse.value,
138
80
  }
139
81
  }
140
- return null
141
- })()
142
-
143
- const sendingPromise = (async (): Promise<
144
- (SignatureResult | null)[] | null
145
- > => {
146
- let sendingBlockHeight = initialBlockHeight
147
- while (
148
- sendingBlockHeight < blockhashResult.lastValidBlockHeight &&
149
- !abortController.signal.aborted &&
150
- signatureResults.every((r) => r === null)
151
- ) {
152
- try {
153
- // Re-send bundle periodically
154
- await jitoConnection.sendBundle(signedTransactions)
155
- } catch (_) {
156
- // Silently retry on send failures - polling will detect success
157
- }
158
82
 
159
- await sleep(1000)
160
- if (!abortController.signal.aborted) {
161
- sendingBlockHeight =
162
- await jitoConnection.getBlockHeight('confirmed')
163
- }
83
+ await sleep(400)
84
+ if (!abortController.signal.aborted) {
85
+ currentBlockHeight = await jitoConnection.getBlockHeight('confirmed')
164
86
  }
165
- return null
166
- })()
87
+ }
167
88
 
168
- // Wait for polling to find the results
169
- const result = await Promise.race([pollingPromise, sendingPromise])
170
- return result
89
+ return null
171
90
  } catch (error) {
172
91
  if (abortController.signal.aborted) {
173
92
  return null // Don't treat abortion as an error
@@ -176,20 +95,16 @@ export async function sendAndConfirmBundle(
176
95
  }
177
96
  })
178
97
 
179
- // Wait for the first connection to return (either success or timeout)
180
- // If a connection finds confirmation, it aborts all others via abortController
181
- // If all connections reject (throw errors), catch and return null array
182
- const signatureResults =
183
- (await Promise.any(confirmPromises).catch(() => null)) ??
184
- txSignatures.map(() => null)
98
+ // Wait for first successful confirmation
99
+ const result = await Promise.any(confirmPromises).catch(() => null)
185
100
 
186
101
  if (!abortController.signal.aborted) {
187
102
  abortController.abort()
188
103
  }
189
104
 
190
- return {
191
- bundleId,
192
- txSignatures,
193
- signatureResults,
105
+ if (!result) {
106
+ throw new Error('Failed to send and confirm bundle')
194
107
  }
108
+
109
+ return result
195
110
  }