@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.
- package/LICENSE +201 -165
- package/package.json +7 -7
- package/src/_cjs/core/EVM/EVMStepExecutor.js +9 -4
- package/src/_cjs/core/EVM/EVMStepExecutor.js.map +1 -1
- package/src/_cjs/core/Solana/SolanaStepExecutor.js +13 -12
- package/src/_cjs/core/Solana/SolanaStepExecutor.js.map +1 -1
- package/src/_cjs/core/Solana/connection.js +4 -15
- package/src/_cjs/core/Solana/connection.js.map +1 -1
- package/src/_cjs/core/Solana/jito/JitoConnection.js +51 -21
- package/src/_cjs/core/Solana/jito/JitoConnection.js.map +1 -1
- package/src/_cjs/core/Solana/jito/constants.js +10 -23
- package/src/_cjs/core/Solana/jito/constants.js.map +1 -1
- package/src/_cjs/core/Solana/jito/sendAndConfirmBundle.js +36 -84
- package/src/_cjs/core/Solana/jito/sendAndConfirmBundle.js.map +1 -1
- package/src/_cjs/index.js.map +1 -1
- package/src/_cjs/request.js +7 -3
- package/src/_cjs/request.js.map +1 -1
- package/src/_cjs/services/api.js +1 -2
- package/src/_cjs/services/api.js.map +1 -1
- package/src/_cjs/version.js +1 -1
- package/src/_cjs/version.js.map +1 -1
- package/src/_esm/core/EVM/EVMStepExecutor.js +10 -4
- package/src/_esm/core/EVM/EVMStepExecutor.js.map +1 -1
- package/src/_esm/core/Solana/SolanaStepExecutor.js +13 -12
- package/src/_esm/core/Solana/SolanaStepExecutor.js.map +1 -1
- package/src/_esm/core/Solana/connection.js +7 -20
- package/src/_esm/core/Solana/connection.js.map +1 -1
- package/src/_esm/core/Solana/jito/JitoConnection.js +72 -23
- package/src/_esm/core/Solana/jito/JitoConnection.js.map +1 -1
- package/src/_esm/core/Solana/jito/constants.js +10 -23
- package/src/_esm/core/Solana/jito/constants.js.map +1 -1
- package/src/_esm/core/Solana/jito/sendAndConfirmBundle.js +45 -104
- package/src/_esm/core/Solana/jito/sendAndConfirmBundle.js.map +1 -1
- package/src/_esm/index.js.map +1 -1
- package/src/_esm/request.js +7 -3
- package/src/_esm/request.js.map +1 -1
- package/src/_esm/services/api.js +3 -4
- package/src/_esm/services/api.js.map +1 -1
- package/src/_esm/version.js +1 -1
- package/src/_esm/version.js.map +1 -1
- package/src/_types/core/EVM/EVMStepExecutor.d.ts.map +1 -1
- package/src/_types/core/Solana/SolanaStepExecutor.d.ts.map +1 -1
- package/src/_types/core/Solana/connection.d.ts +4 -3
- package/src/_types/core/Solana/connection.d.ts.map +1 -1
- package/src/_types/core/Solana/jito/JitoConnection.d.ts +35 -5
- package/src/_types/core/Solana/jito/JitoConnection.d.ts.map +1 -1
- package/src/_types/core/Solana/jito/constants.d.ts +1 -2
- package/src/_types/core/Solana/jito/constants.d.ts.map +1 -1
- package/src/_types/core/Solana/jito/sendAndConfirmBundle.d.ts +2 -2
- package/src/_types/core/Solana/jito/sendAndConfirmBundle.d.ts.map +1 -1
- package/src/_types/index.d.ts +2 -2
- package/src/_types/index.d.ts.map +1 -1
- package/src/_types/request.d.ts.map +1 -1
- package/src/_types/services/api.d.ts +1 -1
- package/src/_types/services/api.d.ts.map +1 -1
- package/src/_types/services/types.d.ts +0 -3
- package/src/_types/services/types.d.ts.map +1 -1
- package/src/_types/types/internal.d.ts +3 -0
- package/src/_types/types/internal.d.ts.map +1 -1
- package/src/_types/version.d.ts +1 -1
- package/src/_types/version.d.ts.map +1 -1
- package/src/core/EVM/EVMStepExecutor.ts +11 -3
- package/src/core/Solana/SolanaStepExecutor.ts +13 -12
- package/src/core/Solana/connection.ts +12 -25
- package/src/core/Solana/jito/JitoConnection.ts +97 -29
- package/src/core/Solana/jito/constants.ts +10 -25
- package/src/core/Solana/jito/sendAndConfirmBundle.ts +50 -135
- package/src/index.ts +6 -2
- package/src/request.ts +9 -3
- package/src/services/api.ts +4 -6
- package/src/services/types.ts +0 -4
- package/src/types/internal.ts +6 -0
- package/src/version.ts +1 -1
- package/src/_cjs/core/Solana/jito/isJitoRpc.js +0 -23
- package/src/_cjs/core/Solana/jito/isJitoRpc.js.map +0 -1
- package/src/_esm/core/Solana/jito/isJitoRpc.js +0 -21
- package/src/_esm/core/Solana/jito/isJitoRpc.js.map +0 -1
- package/src/_types/core/Solana/jito/isJitoRpc.d.ts +0 -2
- package/src/_types/core/Solana/jito/isJitoRpc.d.ts.map +0 -1
- 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;
|
|
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"}
|
package/src/_types/version.d.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
300
|
-
? await getMaxPriorityFeePerGas(
|
|
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
|
|
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
|
-
|
|
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
|
|
245
|
+
const signedTransaction = signedTransactions[0]
|
|
246
246
|
|
|
247
247
|
const simulationResult = await callSolanaWithRetry((connection) =>
|
|
248
|
-
connection.simulateTransaction(
|
|
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
|
-
|
|
261
|
+
confirmedTransaction =
|
|
262
|
+
await sendAndConfirmTransaction(signedTransaction)
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
if (!
|
|
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 (
|
|
272
|
+
if (confirmedTransaction.signatureResult.err) {
|
|
272
273
|
const reason =
|
|
273
|
-
typeof
|
|
274
|
-
? JSON.stringify(
|
|
275
|
-
:
|
|
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:
|
|
289
|
-
txLink: `${fromChain.metamask.blockExplorerUrls[0]}tx/${
|
|
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 {
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
method
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
128
|
+
const fallbackAccounts = JITO_TIP_ACCOUNTS
|
|
79
129
|
console.warn(
|
|
80
|
-
`Failed to fetch tip accounts from RPC, using
|
|
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
|
|
5
|
-
'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
63
|
-
for (const jitoConnection of jitoConnections) {
|
|
31
|
+
const confirmPromises = jitoConnections.map(async (jitoConnection) => {
|
|
64
32
|
try {
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
await jitoConnection.getBlockHeight('confirmed')
|
|
163
|
-
}
|
|
83
|
+
await sleep(400)
|
|
84
|
+
if (!abortController.signal.aborted) {
|
|
85
|
+
currentBlockHeight = await jitoConnection.getBlockHeight('confirmed')
|
|
164
86
|
}
|
|
165
|
-
|
|
166
|
-
})()
|
|
87
|
+
}
|
|
167
88
|
|
|
168
|
-
|
|
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
|
|
180
|
-
|
|
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
|
-
|
|
191
|
-
|
|
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
|
}
|