@lifi/sdk 3.3.0 → 3.4.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 +4 -4
- package/src/_cjs/config.js +6 -5
- package/src/_cjs/config.js.map +1 -1
- package/src/_cjs/core/Solana/SolanaStepExecutor.js +12 -57
- package/src/_cjs/core/Solana/SolanaStepExecutor.js.map +1 -1
- package/src/_cjs/core/Solana/connection.js +30 -9
- package/src/_cjs/core/Solana/connection.js.map +1 -1
- package/src/_cjs/core/Solana/getSolanaBalance.js +5 -5
- package/src/_cjs/core/Solana/getSolanaBalance.js.map +1 -1
- package/src/_cjs/core/Solana/sendAndConfirmTransaction.js +68 -0
- package/src/_cjs/core/Solana/sendAndConfirmTransaction.js.map +1 -0
- package/src/_cjs/utils/withDedupe.js.map +1 -1
- package/src/_cjs/version.js +1 -1
- package/src/_esm/config.js +6 -5
- package/src/_esm/config.js.map +1 -1
- package/src/_esm/core/Solana/SolanaStepExecutor.js +14 -75
- package/src/_esm/core/Solana/SolanaStepExecutor.js.map +1 -1
- package/src/_esm/core/Solana/connection.js +40 -10
- package/src/_esm/core/Solana/connection.js.map +1 -1
- package/src/_esm/core/Solana/getSolanaBalance.js +6 -6
- package/src/_esm/core/Solana/getSolanaBalance.js.map +1 -1
- package/src/_esm/core/Solana/sendAndConfirmTransaction.js +76 -0
- package/src/_esm/core/Solana/sendAndConfirmTransaction.js.map +1 -0
- package/src/_esm/utils/withDedupe.js.map +1 -1
- package/src/_esm/version.js +1 -1
- package/src/_types/config.d.ts +2 -2
- package/src/_types/config.d.ts.map +1 -1
- package/src/_types/core/Solana/SolanaStepExecutor.d.ts.map +1 -1
- package/src/_types/core/Solana/connection.d.ts +14 -3
- package/src/_types/core/Solana/connection.d.ts.map +1 -1
- package/src/_types/core/Solana/getSolanaBalance.d.ts.map +1 -1
- package/src/_types/core/Solana/sendAndConfirmTransaction.d.ts +13 -0
- package/src/_types/core/Solana/sendAndConfirmTransaction.d.ts.map +1 -0
- package/src/_types/utils/withDedupe.d.ts +1 -1
- package/src/_types/utils/withDedupe.d.ts.map +1 -1
- package/src/_types/version.d.ts +1 -1
- package/src/config.ts +6 -6
- package/src/core/Solana/SolanaStepExecutor.ts +15 -98
- package/src/core/Solana/connection.ts +43 -10
- package/src/core/Solana/getSolanaBalance.ts +27 -7
- package/src/core/Solana/sendAndConfirmTransaction.ts +111 -0
- package/src/utils/withDedupe.ts +3 -3
- package/src/version.ts +1 -1
|
@@ -1,18 +1,51 @@
|
|
|
1
1
|
import { ChainId } from '@lifi/types'
|
|
2
2
|
import { Connection } from '@solana/web3.js'
|
|
3
|
-
import {
|
|
3
|
+
import { getRpcUrls } from '../rpc.js'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const connections = new Map<string, Connection>()
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* @returns -
|
|
8
|
+
* Initializes the Solana connections if they haven't been initialized yet.
|
|
9
|
+
* @returns - Promise that resolves when connections are initialized.
|
|
10
10
|
*/
|
|
11
|
-
export const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
export const ensureConnections = async (): Promise<void> => {
|
|
12
|
+
const rpcUrls = await getRpcUrls(ChainId.SOL)
|
|
13
|
+
for (const rpcUrl of rpcUrls) {
|
|
14
|
+
if (!connections.get(rpcUrl)) {
|
|
15
|
+
const connection = new Connection(rpcUrl)
|
|
16
|
+
connections.set(rpcUrl, connection)
|
|
17
|
+
}
|
|
16
18
|
}
|
|
17
|
-
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Wrapper around getting the connection (RPC provider) for Solana
|
|
23
|
+
* @returns - Solana RPC connections
|
|
24
|
+
*/
|
|
25
|
+
export const getSolanaConnections = async (): Promise<Connection[]> => {
|
|
26
|
+
await ensureConnections()
|
|
27
|
+
return Array.from(connections.values())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Calls a function on the Connection instances with retry logic.
|
|
32
|
+
* @param fn - The function to call, which receives a Connection instance.
|
|
33
|
+
* @returns - The result of the function call.
|
|
34
|
+
*/
|
|
35
|
+
export async function callSolanaWithRetry<R>(
|
|
36
|
+
fn: (connection: Connection) => Promise<R>
|
|
37
|
+
): Promise<R> {
|
|
38
|
+
// Ensure connections are initialized
|
|
39
|
+
await ensureConnections()
|
|
40
|
+
let lastError: any = null
|
|
41
|
+
for (const connection of connections.values()) {
|
|
42
|
+
try {
|
|
43
|
+
const result = await fn(connection)
|
|
44
|
+
return result
|
|
45
|
+
} catch (error) {
|
|
46
|
+
lastError = error
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Throw the last encountered error
|
|
50
|
+
throw lastError
|
|
18
51
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ChainId, Token, TokenAmount } from '@lifi/types'
|
|
2
2
|
import { PublicKey } from '@solana/web3.js'
|
|
3
3
|
import { SolSystemProgram } from '../../constants.js'
|
|
4
|
-
import {
|
|
4
|
+
import { withDedupe } from '../../utils/withDedupe.js'
|
|
5
|
+
import { callSolanaWithRetry } from './connection.js'
|
|
5
6
|
import { TokenProgramAddress } from './types.js'
|
|
6
7
|
|
|
7
8
|
export const getSolanaBalance = async (
|
|
@@ -26,15 +27,34 @@ const getSolanaBalanceDefault = async (
|
|
|
26
27
|
tokens: Token[],
|
|
27
28
|
walletAddress: string
|
|
28
29
|
): Promise<TokenAmount[]> => {
|
|
29
|
-
const connection = await getSolanaConnection()
|
|
30
30
|
const accountPublicKey = new PublicKey(walletAddress)
|
|
31
31
|
const tokenProgramPublicKey = new PublicKey(TokenProgramAddress)
|
|
32
32
|
const [slot, balance, tokenAccountsByOwner] = await Promise.allSettled([
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
withDedupe(
|
|
34
|
+
() =>
|
|
35
|
+
callSolanaWithRetry((connection) => connection.getSlot('confirmed')),
|
|
36
|
+
{ id: `${getSolanaBalanceDefault.name}.getSlot` }
|
|
37
|
+
),
|
|
38
|
+
withDedupe(
|
|
39
|
+
() =>
|
|
40
|
+
callSolanaWithRetry((connection) =>
|
|
41
|
+
connection.getBalance(accountPublicKey, 'confirmed')
|
|
42
|
+
),
|
|
43
|
+
{ id: `${getSolanaBalanceDefault.name}.getBalance` }
|
|
44
|
+
),
|
|
45
|
+
withDedupe(
|
|
46
|
+
() =>
|
|
47
|
+
callSolanaWithRetry((connection) =>
|
|
48
|
+
connection.getParsedTokenAccountsByOwner(
|
|
49
|
+
accountPublicKey,
|
|
50
|
+
{
|
|
51
|
+
programId: tokenProgramPublicKey,
|
|
52
|
+
},
|
|
53
|
+
'confirmed'
|
|
54
|
+
)
|
|
55
|
+
),
|
|
56
|
+
{ id: `${getSolanaBalanceDefault.name}.getParsedTokenAccountsByOwner` }
|
|
57
|
+
),
|
|
38
58
|
])
|
|
39
59
|
const blockNumber = slot.status === 'fulfilled' ? BigInt(slot.value) : 0n
|
|
40
60
|
const solBalance = balance.status === 'fulfilled' ? BigInt(balance.value) : 0n
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SendOptions,
|
|
3
|
+
SignatureResult,
|
|
4
|
+
VersionedTransaction,
|
|
5
|
+
} from '@solana/web3.js'
|
|
6
|
+
import bs58 from 'bs58'
|
|
7
|
+
import { sleep } from '../../utils/sleep.js'
|
|
8
|
+
import { getSolanaConnections } from './connection.js'
|
|
9
|
+
|
|
10
|
+
export type ConfirmedTransactionResult = {
|
|
11
|
+
signatureResult: SignatureResult | null
|
|
12
|
+
txSignature: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Sends a Solana transaction to multiple RPC endpoints and returns the confirmation
|
|
17
|
+
* as soon as any of them confirm the transaction.
|
|
18
|
+
* @param signedTx - The signed transaction to send.
|
|
19
|
+
* @returns - The confirmation result of the transaction.
|
|
20
|
+
*/
|
|
21
|
+
export async function sendAndConfirmTransaction(
|
|
22
|
+
signedTx: VersionedTransaction
|
|
23
|
+
): Promise<ConfirmedTransactionResult> {
|
|
24
|
+
const connections = await getSolanaConnections()
|
|
25
|
+
|
|
26
|
+
const signedTxSerialized = signedTx.serialize()
|
|
27
|
+
// Create transaction hash (signature)
|
|
28
|
+
const txSignature = bs58.encode(signedTx.signatures[0])
|
|
29
|
+
|
|
30
|
+
if (!txSignature) {
|
|
31
|
+
throw new Error('Transaction signature is missing.')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const rawTransactionOptions: SendOptions = {
|
|
35
|
+
// We can skip preflight check after the first transaction has been sent
|
|
36
|
+
// https://solana.com/docs/advanced/retry#the-cost-of-skipping-preflight
|
|
37
|
+
skipPreflight: true,
|
|
38
|
+
// Setting max retries to 0 as we are handling retries manually
|
|
39
|
+
maxRetries: 0,
|
|
40
|
+
// https://solana.com/docs/advanced/confirmation#use-an-appropriate-preflight-commitment-level
|
|
41
|
+
preflightCommitment: 'confirmed',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const connection of connections) {
|
|
45
|
+
connection
|
|
46
|
+
.sendRawTransaction(signedTxSerialized, rawTransactionOptions)
|
|
47
|
+
.catch()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const abortControllers: AbortController[] = []
|
|
51
|
+
|
|
52
|
+
const confirmPromises = connections.map(async (connection) => {
|
|
53
|
+
const abortController = new AbortController()
|
|
54
|
+
abortControllers.push(abortController)
|
|
55
|
+
try {
|
|
56
|
+
const blockhashResult = await connection.getLatestBlockhash('confirmed')
|
|
57
|
+
|
|
58
|
+
const confirmTransactionPromise = connection
|
|
59
|
+
.confirmTransaction(
|
|
60
|
+
{
|
|
61
|
+
signature: txSignature,
|
|
62
|
+
blockhash: blockhashResult.blockhash,
|
|
63
|
+
lastValidBlockHeight: blockhashResult.lastValidBlockHeight,
|
|
64
|
+
abortSignal: abortController.signal,
|
|
65
|
+
},
|
|
66
|
+
'confirmed'
|
|
67
|
+
)
|
|
68
|
+
.then((result) => result.value)
|
|
69
|
+
|
|
70
|
+
let signatureResult: SignatureResult | null = null
|
|
71
|
+
let blockHeight = await connection.getBlockHeight('confirmed')
|
|
72
|
+
|
|
73
|
+
while (
|
|
74
|
+
!signatureResult &&
|
|
75
|
+
blockHeight < blockhashResult.lastValidBlockHeight
|
|
76
|
+
) {
|
|
77
|
+
await connection.sendRawTransaction(
|
|
78
|
+
signedTxSerialized,
|
|
79
|
+
rawTransactionOptions
|
|
80
|
+
)
|
|
81
|
+
signatureResult = await Promise.race([
|
|
82
|
+
confirmTransactionPromise,
|
|
83
|
+
sleep(1000),
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
if (signatureResult || abortController.signal.aborted) {
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
blockHeight = await connection.getBlockHeight('confirmed')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
abortController.abort()
|
|
94
|
+
|
|
95
|
+
return signatureResult
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (abortController.signal.aborted) {
|
|
98
|
+
return Promise.reject(new Error('Confirmation aborted.'))
|
|
99
|
+
}
|
|
100
|
+
throw error
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const signatureResult = await Promise.any(confirmPromises).catch(() => null)
|
|
105
|
+
|
|
106
|
+
for (const abortController of abortControllers) {
|
|
107
|
+
abortController.abort()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { signatureResult, txSignature }
|
|
111
|
+
}
|
package/src/utils/withDedupe.ts
CHANGED
|
@@ -29,10 +29,10 @@ type WithDedupeOptions = {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Deduplicates in-flight promises. */
|
|
32
|
-
export function withDedupe<
|
|
33
|
-
fn: () => Promise<
|
|
32
|
+
export function withDedupe<T>(
|
|
33
|
+
fn: () => Promise<T>,
|
|
34
34
|
{ enabled = true, id }: WithDedupeOptions
|
|
35
|
-
): Promise<
|
|
35
|
+
): Promise<T> {
|
|
36
36
|
if (!enabled || !id) {
|
|
37
37
|
return fn()
|
|
38
38
|
}
|
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.4.0'
|