Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "4.0.0-alpha.9",
"version": "4.0.0-alpha.11",
"private": true,
"sideEffects": false,
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk-provider-bitcoin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lifi/sdk-provider-bitcoin",
"version": "4.0.0-alpha.9",
"version": "4.0.0-alpha.11",
"description": "LI.FI Bitcoin SDK Provider for Any-to-Any Cross-Chain-Swap",
"homepage": "https://github.com/lifinance/sdk",
"bugs": {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk-provider-bitcoin/src/version.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const name = '@lifi/sdk-provider-bitcoin'
export const version = '4.0.0-alpha.9'
export const version = '4.0.0-alpha.11'
2 changes: 1 addition & 1 deletion packages/sdk-provider-ethereum/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lifi/sdk-provider-ethereum",
"version": "4.0.0-alpha.9",
"version": "4.0.0-alpha.11",
"description": "LI.FI Ethereum SDK Provider for Any-to-Any Cross-Chain-Swap",
"homepage": "https://github.com/lifinance/sdk",
"bugs": {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk-provider-ethereum/src/version.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const name = '@lifi/sdk-provider-ethereum'
export const version = '4.0.0-alpha.9'
export const version = '4.0.0-alpha.11'
2 changes: 1 addition & 1 deletion packages/sdk-provider-solana/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lifi/sdk-provider-solana",
"version": "4.0.0-alpha.9",
"version": "4.0.0-alpha.11",
"description": "LI.FI Solana SDK Provider for Any-to-Any Cross-Chain-Swap",
"homepage": "https://github.com/lifinance/sdk",
"bugs": {
Expand Down
184 changes: 137 additions & 47 deletions packages/sdk-provider-solana/src/SolanaStepExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,50 @@ import {
import {
getBase64EncodedWireTransaction,
getTransactionCodec,
type Transaction,
} from '@solana/kit'
import { SolanaSignTransaction } from '@solana/wallet-standard-features'
import type { Wallet } from '@wallet-standard/base'
import { sendAndConfirmBundle } from './actions/sendAndConfirmBundle.js'
import { sendAndConfirmTransaction } from './actions/sendAndConfirmTransaction.js'
import { callSolanaWithRetry } from './client/connection.js'
import { parseSolanaErrors } from './errors/parseSolanaErrors.js'
import { callSolanaRpcsWithRetry } from './rpc/utils.js'
import type { SolanaStepExecutorOptions } from './types.js'
import { base64ToUint8Array } from './utils/base64ToUint8Array.js'
import { getWalletFeature } from './utils/getWalletFeature.js'
import { withTimeout } from './utils/withTimeout.js'

type ConfirmedTransactionResult = {
txSignature: string
bundleId?: string
}

export class SolanaStepExecutor extends BaseStepExecutor {
private wallet: Wallet
constructor(options: SolanaStepExecutorOptions) {
super(options)
this.wallet = options.wallet
}

private shouldUseJitoBundle(
client: SDKClient,
transactions: Transaction[]
): boolean {
const routeOptions = client.config.routeOptions as
| Record<string, unknown>
| undefined
const isJitoBundleEnabled = Boolean(routeOptions?.jitoBundle)

if (transactions.length > 1 && !isJitoBundleEnabled) {
throw new TransactionError(
LiFiErrorCode.TransactionUnprepared,
`Received ${transactions.length} transactions but Jito bundle is not enabled. Enable Jito bundle in routeOptions to submit multiple transactions.`
)
}

return transactions.length > 1 && isJitoBundleEnabled
}

getWalletAccount = async (step: LiFiStepExtended) => {
const account = this.wallet.accounts.find(
(account) => account.address === step.action.fromAddress
Expand Down Expand Up @@ -131,18 +157,28 @@ export class SolanaStepExecutor extends BaseStepExecutor {
)
}

const transactionBytes = base64ToUint8Array(transactionRequest.data)
// Handle both single transaction (string) and multiple transactions (array)
const transactionDataArray = Array.isArray(transactionRequest.data)
? transactionRequest.data
: [transactionRequest.data]

const transactionBytesArray = transactionDataArray.map((data) =>
base64ToUint8Array(data)
)

const signedTransactionOutputs = await withTimeout(
async () => {
const { signTransaction } = getWalletFeature(
this.wallet,
SolanaSignTransaction
)
return signTransaction({
account: walletAccount,
transaction: transactionBytes,
})
// Spread the inputs to sign all transactions at once
return signTransaction(
...transactionBytesArray.map((transaction) => ({
account: walletAccount,
transaction,
}))
)
},
{
// https://solana.com/docs/advanced/confirmation#transaction-expiration
Expand All @@ -166,58 +202,112 @@ export class SolanaStepExecutor extends BaseStepExecutor {

const transactionCodec = getTransactionCodec()

const signedTransaction = transactionCodec.decode(
signedTransactionOutputs[0].signedTransaction
// Decode all signed transactions
const signedTransactions: Transaction[] = signedTransactionOutputs.map(
(output) => transactionCodec.decode(output.signedTransaction)
)

const encodedTransaction =
getBase64EncodedWireTransaction(signedTransaction)

const simulationResult = await callSolanaWithRetry(
const useJitoBundle = this.shouldUseJitoBundle(
client,
(connection) =>
connection
.simulateTransaction(encodedTransaction, {
commitment: 'confirmed',
replaceRecentBlockhash: true,
encoding: 'base64',
})
.send()
signedTransactions
)

if (simulationResult.value.err) {
const errorMessage =
typeof simulationResult.value.err === 'object'
? JSON.stringify(simulationResult.value.err)
: simulationResult.value.err
throw new TransactionError(
LiFiErrorCode.TransactionSimulationFailed,
`Transaction simulation failed: ${errorMessage}`,
new Error(errorMessage)
let confirmedTransaction: ConfirmedTransactionResult

if (useJitoBundle) {
// Use Jito bundle for transaction submission
const bundleResult = await sendAndConfirmBundle(
client,
signedTransactions
)
}

const confirmedTransaction = await sendAndConfirmTransaction(
client,
signedTransaction
)
const allConfirmed = bundleResult.signatureResults.every(
(result) => result !== null
)

if (!confirmedTransaction.signatureResult) {
throw new TransactionError(
LiFiErrorCode.TransactionExpired,
'Transaction has expired: The block height has exceeded the maximum allowed limit.'
if (!allConfirmed) {
throw new TransactionError(
LiFiErrorCode.TransactionFailed,
'Bundle confirmation failed: Not all transactions were confirmed.'
)
}

// Check for errors in any of the transactions
const failedResult = bundleResult.signatureResults.find(
(result) => result?.err
)
}
if (failedResult?.err) {
const reason =
typeof failedResult.err === 'object'
? JSON.stringify(failedResult.err)
: String(failedResult.err)
throw new TransactionError(
LiFiErrorCode.TransactionFailed,
`Transaction failed: ${reason}`
)
}

if (confirmedTransaction.signatureResult.err) {
const reason =
typeof confirmedTransaction.signatureResult.err === 'object'
? JSON.stringify(confirmedTransaction.signatureResult.err)
: confirmedTransaction.signatureResult.err
throw new TransactionError(
LiFiErrorCode.TransactionFailed,
`Transaction failed: ${reason}`
confirmedTransaction = {
txSignature: bundleResult.txSignatures[0],
bundleId: bundleResult.bundleId,
}
} else {
// Use regular transaction submission
const signedTransaction = signedTransactions[0]

const encodedTransaction =
getBase64EncodedWireTransaction(signedTransaction)

const simulationResult = await callSolanaRpcsWithRetry(
client,
(connection) =>
connection
.simulateTransaction(encodedTransaction, {
commitment: 'confirmed',
replaceRecentBlockhash: true,
encoding: 'base64',
})
.send()
)

if (simulationResult.value.err) {
const errorMessage =
typeof simulationResult.value.err === 'object'
? JSON.stringify(simulationResult.value.err)
: simulationResult.value.err
throw new TransactionError(
LiFiErrorCode.TransactionSimulationFailed,
`Transaction simulation failed: ${errorMessage}`,
new Error(errorMessage)
)
}

const result = await sendAndConfirmTransaction(
client,
signedTransaction
)

if (!result.signatureResult) {
throw new TransactionError(
LiFiErrorCode.TransactionExpired,
'Transaction has expired: The block height has exceeded the maximum allowed limit.'
)
}

if (result.signatureResult.err) {
const reason =
typeof result.signatureResult.err === 'object'
? JSON.stringify(result.signatureResult.err)
: result.signatureResult.err
throw new TransactionError(
LiFiErrorCode.TransactionFailed,
`Transaction failed: ${reason}`
)
}

confirmedTransaction = {
txSignature: result.txSignature,
}
}

// Transaction has been confirmed and we can update the action
Expand Down
10 changes: 5 additions & 5 deletions packages/sdk-provider-solana/src/actions/getSolanaBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@lifi/sdk'
import { address, type JsonParsedTokenAccount } from '@solana/kit'

import { callSolanaWithRetry } from '../client/connection.js'
import { callSolanaRpcsWithRetry } from '../rpc/utils.js'

const SolSystemProgram = '11111111111111111111111111111111'
const TokenProgramId = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
Expand Down Expand Up @@ -47,21 +47,21 @@ const getSolanaBalanceDefault = async (
await Promise.allSettled([
withDedupe(
() =>
callSolanaWithRetry(client, (rpc) =>
callSolanaRpcsWithRetry(client, (rpc) =>
rpc.getSlot({ commitment: 'confirmed' }).send()
),
{ id: `${getSolanaBalanceDefault.name}.getSlot` }
),
withDedupe(
() =>
callSolanaWithRetry(client, (rpc) =>
callSolanaRpcsWithRetry(client, (rpc) =>
rpc.getBalance(accountAddress, { commitment: 'confirmed' }).send()
),
{ id: `${getSolanaBalanceDefault.name}.getBalance` }
),
withDedupe(
() =>
callSolanaWithRetry(client, (rpc) =>
callSolanaRpcsWithRetry(client, (rpc) =>
rpc
.getTokenAccountsByOwner(
accountAddress,
Expand All @@ -81,7 +81,7 @@ const getSolanaBalanceDefault = async (
),
withDedupe(
() =>
callSolanaWithRetry(client, (rpc) =>
callSolanaRpcsWithRetry(client, (rpc) =>
rpc
.getTokenAccountsByOwner(
accountAddress,
Expand Down
Loading