@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.
- package/package.json +1 -1
- package/src/_cjs/core/Solana/SolanaStepExecutor.js +57 -12
- package/src/_cjs/core/Solana/SolanaStepExecutor.js.map +1 -1
- package/src/_cjs/core/Solana/connection.js +21 -2
- package/src/_cjs/core/Solana/connection.js.map +1 -1
- package/src/_cjs/core/Solana/jito/JitoConnection.js +67 -0
- package/src/_cjs/core/Solana/jito/JitoConnection.js.map +1 -0
- package/src/_cjs/core/Solana/jito/constants.js +27 -0
- package/src/_cjs/core/Solana/jito/constants.js.map +1 -0
- package/src/_cjs/core/Solana/jito/isJitoRpc.js +23 -0
- package/src/_cjs/core/Solana/jito/isJitoRpc.js.map +1 -0
- package/src/_cjs/core/Solana/jito/sendAndConfirmBundle.js +118 -0
- package/src/_cjs/core/Solana/jito/sendAndConfirmBundle.js.map +1 -0
- package/src/_cjs/index.js.map +1 -1
- package/src/_cjs/services/api.js +9 -1
- package/src/_cjs/services/api.js.map +1 -1
- package/src/_cjs/utils/uint8ArrayToBase64.js +11 -0
- package/src/_cjs/utils/uint8ArrayToBase64.js.map +1 -0
- package/src/_cjs/version.js +1 -1
- package/src/_cjs/version.js.map +1 -1
- package/src/_esm/core/Solana/SolanaStepExecutor.js +83 -12
- package/src/_esm/core/Solana/SolanaStepExecutor.js.map +1 -1
- package/src/_esm/core/Solana/connection.js +26 -1
- package/src/_esm/core/Solana/connection.js.map +1 -1
- package/src/_esm/core/Solana/jito/JitoConnection.js +90 -0
- package/src/_esm/core/Solana/jito/JitoConnection.js.map +1 -0
- package/src/_esm/core/Solana/jito/constants.js +25 -0
- package/src/_esm/core/Solana/jito/constants.js.map +1 -0
- package/src/_esm/core/Solana/jito/isJitoRpc.js +21 -0
- package/src/_esm/core/Solana/jito/isJitoRpc.js.map +1 -0
- package/src/_esm/core/Solana/jito/sendAndConfirmBundle.js +140 -0
- package/src/_esm/core/Solana/jito/sendAndConfirmBundle.js.map +1 -0
- package/src/_esm/index.js.map +1 -1
- package/src/_esm/services/api.js +11 -2
- package/src/_esm/services/api.js.map +1 -1
- package/src/_esm/utils/uint8ArrayToBase64.js +10 -0
- package/src/_esm/utils/uint8ArrayToBase64.js.map +1 -0
- package/src/_esm/version.js +1 -1
- package/src/_esm/version.js.map +1 -1
- package/src/_types/core/Solana/SolanaStepExecutor.d.ts +18 -0
- package/src/_types/core/Solana/SolanaStepExecutor.d.ts.map +1 -1
- package/src/_types/core/Solana/connection.d.ts +7 -1
- package/src/_types/core/Solana/connection.d.ts.map +1 -1
- package/src/_types/core/Solana/jito/JitoConnection.d.ts +55 -0
- package/src/_types/core/Solana/jito/JitoConnection.d.ts.map +1 -0
- package/src/_types/core/Solana/jito/constants.d.ts +3 -0
- package/src/_types/core/Solana/jito/constants.d.ts.map +1 -0
- package/src/_types/core/Solana/jito/isJitoRpc.d.ts +2 -0
- package/src/_types/core/Solana/jito/isJitoRpc.d.ts.map +1 -0
- package/src/_types/core/Solana/jito/sendAndConfirmBundle.d.ts +15 -0
- package/src/_types/core/Solana/jito/sendAndConfirmBundle.d.ts.map +1 -0
- package/src/_types/index.d.ts +1 -1
- package/src/_types/index.d.ts.map +1 -1
- package/src/_types/services/api.d.ts.map +1 -1
- package/src/_types/services/types.d.ts +3 -0
- package/src/_types/services/types.d.ts.map +1 -1
- package/src/_types/utils/uint8ArrayToBase64.d.ts +2 -0
- package/src/_types/utils/uint8ArrayToBase64.d.ts.map +1 -0
- package/src/_types/version.d.ts +1 -1
- package/src/_types/version.d.ts.map +1 -1
- package/src/core/Solana/SolanaStepExecutor.ts +122 -22
- package/src/core/Solana/connection.ts +35 -4
- package/src/core/Solana/jito/JitoConnection.ts +122 -0
- package/src/core/Solana/jito/constants.ts +26 -0
- package/src/core/Solana/jito/isJitoRpc.ts +21 -0
- package/src/core/Solana/jito/sendAndConfirmBundle.ts +195 -0
- package/src/index.ts +1 -0
- package/src/services/api.ts +20 -11
- package/src/services/types.ts +4 -0
- package/src/utils/uint8ArrayToBase64.ts +12 -0
- 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
package/src/services/api.ts
CHANGED
|
@@ -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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
/**
|
package/src/services/types.ts
CHANGED
|
@@ -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.
|
|
2
|
+
export const version = '3.14.0-alpha.0'
|