@lifi/sdk 3.13.5 → 3.14.0-alpha.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 (71) hide show
  1. package/package.json +1 -1
  2. package/src/_cjs/core/Solana/SolanaStepExecutor.js +57 -12
  3. package/src/_cjs/core/Solana/SolanaStepExecutor.js.map +1 -1
  4. package/src/_cjs/core/Solana/connection.js +21 -2
  5. package/src/_cjs/core/Solana/connection.js.map +1 -1
  6. package/src/_cjs/core/Solana/jito/JitoConnection.js +67 -0
  7. package/src/_cjs/core/Solana/jito/JitoConnection.js.map +1 -0
  8. package/src/_cjs/core/Solana/jito/constants.js +27 -0
  9. package/src/_cjs/core/Solana/jito/constants.js.map +1 -0
  10. package/src/_cjs/core/Solana/jito/isJitoRpc.js +23 -0
  11. package/src/_cjs/core/Solana/jito/isJitoRpc.js.map +1 -0
  12. package/src/_cjs/core/Solana/jito/sendAndConfirmBundle.js +118 -0
  13. package/src/_cjs/core/Solana/jito/sendAndConfirmBundle.js.map +1 -0
  14. package/src/_cjs/index.js.map +1 -1
  15. package/src/_cjs/services/api.js +9 -1
  16. package/src/_cjs/services/api.js.map +1 -1
  17. package/src/_cjs/utils/uint8ArrayToBase64.js +11 -0
  18. package/src/_cjs/utils/uint8ArrayToBase64.js.map +1 -0
  19. package/src/_cjs/version.js +1 -1
  20. package/src/_cjs/version.js.map +1 -1
  21. package/src/_esm/core/Solana/SolanaStepExecutor.js +83 -12
  22. package/src/_esm/core/Solana/SolanaStepExecutor.js.map +1 -1
  23. package/src/_esm/core/Solana/connection.js +26 -1
  24. package/src/_esm/core/Solana/connection.js.map +1 -1
  25. package/src/_esm/core/Solana/jito/JitoConnection.js +90 -0
  26. package/src/_esm/core/Solana/jito/JitoConnection.js.map +1 -0
  27. package/src/_esm/core/Solana/jito/constants.js +25 -0
  28. package/src/_esm/core/Solana/jito/constants.js.map +1 -0
  29. package/src/_esm/core/Solana/jito/isJitoRpc.js +21 -0
  30. package/src/_esm/core/Solana/jito/isJitoRpc.js.map +1 -0
  31. package/src/_esm/core/Solana/jito/sendAndConfirmBundle.js +140 -0
  32. package/src/_esm/core/Solana/jito/sendAndConfirmBundle.js.map +1 -0
  33. package/src/_esm/index.js.map +1 -1
  34. package/src/_esm/services/api.js +11 -2
  35. package/src/_esm/services/api.js.map +1 -1
  36. package/src/_esm/utils/uint8ArrayToBase64.js +10 -0
  37. package/src/_esm/utils/uint8ArrayToBase64.js.map +1 -0
  38. package/src/_esm/version.js +1 -1
  39. package/src/_esm/version.js.map +1 -1
  40. package/src/_types/core/Solana/SolanaStepExecutor.d.ts +18 -0
  41. package/src/_types/core/Solana/SolanaStepExecutor.d.ts.map +1 -1
  42. package/src/_types/core/Solana/connection.d.ts +7 -1
  43. package/src/_types/core/Solana/connection.d.ts.map +1 -1
  44. package/src/_types/core/Solana/jito/JitoConnection.d.ts +55 -0
  45. package/src/_types/core/Solana/jito/JitoConnection.d.ts.map +1 -0
  46. package/src/_types/core/Solana/jito/constants.d.ts +3 -0
  47. package/src/_types/core/Solana/jito/constants.d.ts.map +1 -0
  48. package/src/_types/core/Solana/jito/isJitoRpc.d.ts +2 -0
  49. package/src/_types/core/Solana/jito/isJitoRpc.d.ts.map +1 -0
  50. package/src/_types/core/Solana/jito/sendAndConfirmBundle.d.ts +15 -0
  51. package/src/_types/core/Solana/jito/sendAndConfirmBundle.d.ts.map +1 -0
  52. package/src/_types/index.d.ts +1 -1
  53. package/src/_types/index.d.ts.map +1 -1
  54. package/src/_types/services/api.d.ts.map +1 -1
  55. package/src/_types/services/types.d.ts +3 -0
  56. package/src/_types/services/types.d.ts.map +1 -1
  57. package/src/_types/utils/uint8ArrayToBase64.d.ts +2 -0
  58. package/src/_types/utils/uint8ArrayToBase64.d.ts.map +1 -0
  59. package/src/_types/version.d.ts +1 -1
  60. package/src/_types/version.d.ts.map +1 -1
  61. package/src/core/Solana/SolanaStepExecutor.ts +122 -22
  62. package/src/core/Solana/connection.ts +35 -4
  63. package/src/core/Solana/jito/JitoConnection.ts +122 -0
  64. package/src/core/Solana/jito/constants.ts +26 -0
  65. package/src/core/Solana/jito/isJitoRpc.ts +21 -0
  66. package/src/core/Solana/jito/sendAndConfirmBundle.ts +195 -0
  67. package/src/index.ts +1 -0
  68. package/src/services/api.ts +20 -11
  69. package/src/services/types.ts +4 -0
  70. package/src/utils/uint8ArrayToBase64.ts +12 -0
  71. package/src/version.ts +1 -1
@@ -0,0 +1,21 @@
1
+ export async function isJitoRpc(rpcUrl: string): Promise<boolean> {
2
+ try {
3
+ const response = await fetch(rpcUrl, {
4
+ method: 'POST',
5
+ headers: { 'Content-Type': 'application/json' },
6
+ body: JSON.stringify({
7
+ jsonrpc: '2.0',
8
+ id: 1,
9
+ method: 'getTipAccounts',
10
+ params: [],
11
+ }),
12
+ })
13
+
14
+ const data = await response.json()
15
+
16
+ // If we get a result (not a "method not found" error), it supports Jito
17
+ return !!data.result && !data.error
18
+ } catch {
19
+ return false
20
+ }
21
+ }
@@ -0,0 +1,195 @@
1
+ import type { SignatureResult, VersionedTransaction } from '@solana/web3.js'
2
+ import bs58 from 'bs58'
3
+ import { sleep } from '../../../utils/sleep.js'
4
+ import { getJitoConnections } from '../connection.js'
5
+
6
+ export type BundleResult = {
7
+ bundleId: string
8
+ txSignatures: string[]
9
+ signatureResults: (SignatureResult | null)[]
10
+ }
11
+
12
+ /**
13
+ * Send and confirm a bundle of transactions using Jito
14
+ * Automatically selects a Jito-enabled RPC connection and polls for confirmation
15
+ * across multiple Jito RPCs in parallel
16
+ * @param signedTransactions - Array of signed transactions
17
+ * @returns Bundle ID, transaction signatures, and confirmation results
18
+ */
19
+ export async function sendAndConfirmBundle(
20
+ signedTransactions: VersionedTransaction[]
21
+ ): Promise<BundleResult> {
22
+ const jitoConnections = await getJitoConnections()
23
+
24
+ if (jitoConnections.length === 0) {
25
+ throw new Error(
26
+ 'No Jito-enabled RPC connection available for bundle submission'
27
+ )
28
+ }
29
+
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
+ const abortController = new AbortController()
60
+ let bundleId: string | null = null
61
+
62
+ // Try to simulate and send bundle with the first connection
63
+ for (const jitoConnection of jitoConnections) {
64
+ 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
90
+ }
91
+ }
92
+ }
93
+
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
+ const [blockhashResult, initialBlockHeight] = await Promise.all([
103
+ jitoConnection.getLatestBlockhash('confirmed'),
104
+ jitoConnection.getBlockHeight('confirmed'),
105
+ ])
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
115
+ ) {
116
+ const statusResponse =
117
+ await jitoConnection.getSignatureStatuses(txSignatures)
118
+
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
131
+ }
132
+
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')
138
+ }
139
+ }
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
+
159
+ await sleep(1000)
160
+ if (!abortController.signal.aborted) {
161
+ sendingBlockHeight =
162
+ await jitoConnection.getBlockHeight('confirmed')
163
+ }
164
+ }
165
+ return null
166
+ })()
167
+
168
+ // Wait for polling to find the results
169
+ const result = await Promise.race([pollingPromise, sendingPromise])
170
+ return result
171
+ } catch (error) {
172
+ if (abortController.signal.aborted) {
173
+ return null // Don't treat abortion as an error
174
+ }
175
+ throw error
176
+ }
177
+ })
178
+
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)
185
+
186
+ if (!abortController.signal.aborted) {
187
+ abortController.abort()
188
+ }
189
+
190
+ return {
191
+ bundleId,
192
+ txSignatures,
193
+ signatureResults,
194
+ }
195
+ }
package/src/index.ts CHANGED
@@ -125,6 +125,7 @@ export {
125
125
  export { getNameServiceAddress } from './services/getNameServiceAddress.js'
126
126
  export type {
127
127
  GetStatusRequestExtended,
128
+ GetStepTransactionRequest,
128
129
  QuoteRequest,
129
130
  QuoteRequestFromAmount,
130
131
  QuoteRequestToAmount,
@@ -3,6 +3,7 @@ import {
3
3
  type ChainKey,
4
4
  type ChainsRequest,
5
5
  type ChainsResponse,
6
+ ChainType,
6
7
  type ConnectionsRequest,
7
8
  type ConnectionsResponse,
8
9
  type ContractCallsQuoteRequest,
@@ -247,17 +248,25 @@ export const getStepTransaction = async (
247
248
  console.warn('SDK Validation: Invalid Step', step)
248
249
  }
249
250
 
250
- return await request<LiFiStep>(
251
- `${config.get().apiUrl}/advanced/stepTransaction`,
252
- {
253
- method: 'POST',
254
- headers: {
255
- 'Content-Type': 'application/json',
256
- },
257
- body: JSON.stringify(step),
258
- signal: options?.signal,
259
- }
260
- )
251
+ const _config = config.get()
252
+ let requestUrl = `${_config.apiUrl}/advanced/stepTransaction`
253
+ const fromChain = await config.getChainById(step.action.fromChainId)
254
+
255
+ const isJitoBundleEnabled = Boolean(_config.routeOptions?.jitoBundle)
256
+ // add jitoBundle param to url if from chain is SVM and jitoBundle is enabled in config
257
+ if (fromChain.chainType === ChainType.SVM && isJitoBundleEnabled) {
258
+ const queryParams = new URLSearchParams({ jitoBundle: 'true' })
259
+ requestUrl = `${requestUrl}?${queryParams}`
260
+ }
261
+
262
+ return await request<LiFiStep>(requestUrl, {
263
+ method: 'POST',
264
+ headers: {
265
+ 'Content-Type': 'application/json',
266
+ },
267
+ body: JSON.stringify(step),
268
+ signal: options?.signal,
269
+ })
261
270
  }
262
271
 
263
272
  /**
@@ -7,6 +7,10 @@ export type GetStatusRequestExtended = GetStatusRequest & {
7
7
  fromAddress?: string
8
8
  }
9
9
 
10
+ export type GetStepTransactionRequest = {
11
+ jitoBundle?: boolean
12
+ }
13
+
10
14
  export type QuoteRequestFromAmount = QuoteRequestBase
11
15
 
12
16
  export type QuoteRequestToAmount = Omit<QuoteRequestBase, 'fromAmount'> & {
@@ -0,0 +1,12 @@
1
+ export function uint8ArrayToBase64(bytes: Uint8Array): string {
2
+ // Node.js environment
3
+ if (typeof Buffer !== 'undefined') {
4
+ return Buffer.from(bytes).toString('base64')
5
+ }
6
+
7
+ // Browser environment
8
+ const binaryString = Array.from(bytes, (byte) =>
9
+ String.fromCharCode(byte)
10
+ ).join('')
11
+ return btoa(binaryString)
12
+ }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export const name = '@lifi/sdk'
2
- export const version = '3.13.5'
2
+ export const version = '3.14.0-alpha.0'