Skip to content

Commit

Permalink
refactor: improve sol broadcasting
Browse files Browse the repository at this point in the history
  • Loading branch information
chybisov committed Jul 17, 2024
1 parent b4fc1dc commit 808df13
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 113 deletions.
2 changes: 1 addition & 1 deletion src/core/EVM/EVMStepExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ import {
} from '../../utils/index.js'
import { BaseStepExecutor } from '../BaseStepExecutor.js'
import { checkBalance } from '../checkBalance.js'
import { getSubstatusMessage } from '../processMessages.js'
import { stepComparison } from '../stepComparison.js'
import type {
LiFiStepExtended,
StepExecutorOptions,
TransactionParameters,
} from '../types.js'
import { getSubstatusMessage } from '../utils.js'
import { waitForReceivingTransaction } from '../waitForReceivingTransaction.js'
import { checkAllowance } from './checkAllowance.js'
import { updateMultisigRouteProcess } from './multisig.js'
Expand Down
53 changes: 30 additions & 23 deletions src/core/Solana/SolanaStepExecutor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ExtendedTransactionInfo, FullStatusData } from '@lifi/types'
import { type SignerWalletAdapter } from '@solana/wallet-adapter-base'
import {
TransactionExpiredBlockheightExceededError,
VersionedTransaction,
type SendOptions,
type SignatureResult,
Expand All @@ -16,21 +17,22 @@ import {
} from '../../utils/index.js'
import { BaseStepExecutor } from '../BaseStepExecutor.js'
import { checkBalance } from '../checkBalance.js'
import { getSubstatusMessage } from '../processMessages.js'
import { stepComparison } from '../stepComparison.js'
import type {
LiFiStepExtended,
StepExecutorOptions,
TransactionParameters,
} from '../types.js'
import { getSubstatusMessage } from '../utils.js'
import { sleep } from '../utils.js'
import { waitForReceivingTransaction } from '../waitForReceivingTransaction.js'
import { getSolanaConnection } from './connection.js'

export interface SolanaStepExecutorOptions extends StepExecutorOptions {
walletAdapter: SignerWalletAdapter
}

const TX_RETRY_INTERVAL = 500
const TX_RETRY_INTERVAL = 1000
// https://solana.com/docs/advanced/confirmation
const TIMEOUT_PERIOD = 60_000

Expand Down Expand Up @@ -117,6 +119,10 @@ export class SolanaStepExecutor extends BaseStepExecutor {
data: step.transactionRequest.data,
}

const blockhashResult = await connection.getLatestBlockhash({
commitment: 'confirmed',
})

if (this.executionOptions?.updateTransactionRequestHook) {
const customizedTransactionRequest: TransactionParameters =
await this.executionOptions.updateTransactionRequestHook({
Expand All @@ -141,14 +147,6 @@ export class SolanaStepExecutor extends BaseStepExecutor {
base64ToUint8Array(transactionRequest.data)
)

const blockhashResult = await connection.getLatestBlockhashAndContext({
commitment: 'confirmed',
})

// Update transaction recent blockhash with the latest blockhash
versionedTransaction.message.recentBlockhash =
blockhashResult.value.blockhash

this.checkWalletAdapter(step)

const signedTx =
Expand Down Expand Up @@ -179,12 +177,14 @@ export class SolanaStepExecutor extends BaseStepExecutor {
// In the following section, we wait and constantly check for the transaction to be confirmed
// and resend the transaction if it is not confirmed within a certain time interval
// thus handling tx retries on the client side rather than relying on the RPC
const abortController = new AbortController()
const confirmTransactionPromise = connection
.confirmTransaction(
{
signature: txSignature,
blockhash: blockhashResult.value.blockhash,
lastValidBlockHeight: blockhashResult.value.lastValidBlockHeight,
blockhash: blockhashResult.blockhash,
lastValidBlockHeight: blockhashResult.lastValidBlockHeight,
abortSignal: abortController.signal,
},
'confirmed'
)
Expand All @@ -194,29 +194,36 @@ export class SolanaStepExecutor extends BaseStepExecutor {
const startTime = Date.now()

while (!confirmedTx && Date.now() - startTime <= TIMEOUT_PERIOD) {
await connection.sendRawTransaction(
signedTx.serialize(),
rawTransactionOptions
)
confirmedTx = await Promise.race([
confirmTransactionPromise,
new Promise<null>((resolve) =>
setTimeout(() => {
resolve(null)
}, TX_RETRY_INTERVAL)
),
sleep(TX_RETRY_INTERVAL),
])
if (confirmedTx) {
break
}

await connection.sendRawTransaction(
signedTx.serialize(),
rawTransactionOptions
)
}

// Stop waiting for tx confirmation
abortController.abort()

if (confirmedTx?.err) {
const reason =
typeof confirmedTx.err === 'object'
? JSON.stringify(confirmedTx.err)
: confirmedTx.err
if (
confirmedTx.err instanceof
TransactionExpiredBlockheightExceededError
) {
throw new TransactionError(
LiFiErrorCode.TransactionExpired,
`${reason}`
)
}
throw new TransactionError(
LiFiErrorCode.TransactionFailed,
`Transaction failed: ${reason}`
Expand All @@ -225,7 +232,7 @@ export class SolanaStepExecutor extends BaseStepExecutor {

if (!confirmedTx) {
throw new TransactionError(
LiFiErrorCode.TransactionFailed,
LiFiErrorCode.TransactionExpired,
'Failed to land the transaction'
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/StatusManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import type {
ProcessType,
} from '@lifi/types'
import { executionState } from './executionState.js'
import { getProcessMessage } from './processMessages.js'
import type { LiFiStepExtended } from './types.js'
import { getProcessMessage } from './utils.js'

type OptionalParameters = Partial<
Pick<
Expand Down
5 changes: 2 additions & 3 deletions src/core/checkBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { LiFiStep } from '@lifi/types'
import { formatUnits } from 'viem'
import { getTokenBalance } from '../services/balance.js'
import { BalanceError } from '../utils/errors.js'
import { sleep } from './utils.js'

export const checkBalance = async (
walletAddress: string,
Expand All @@ -15,9 +16,7 @@ export const checkBalance = async (

if (currentBalance < neededBalance) {
if (depth <= 3) {
await new Promise((resolve) => {
setTimeout(resolve, 200)
})
await sleep(200)
await checkBalance(walletAddress, step, depth + 1)
} else if (
(neededBalance * BigInt((1 - step.action.slippage) * 1_000_000_000)) /
Expand Down
81 changes: 81 additions & 0 deletions src/core/processMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
type ProcessStatus,
type ProcessType,
type StatusMessage,
type Substatus,
} from '@lifi/types'

const processMessages: Record<
ProcessType,
Partial<Record<ProcessStatus, string>>
> = {
TOKEN_ALLOWANCE: {
STARTED: 'Setting token allowance.',
PENDING: 'Waiting for token allowance.',
DONE: 'Token allowance set.',
},
SWITCH_CHAIN: {
PENDING: 'Chain switch required.',
DONE: 'Chain switched successfully.',
},
SWAP: {
STARTED: 'Preparing swap transaction.',
ACTION_REQUIRED: 'Please sign the transaction.',
PENDING: 'Waiting for swap transaction.',
DONE: 'Swap completed.',
},
CROSS_CHAIN: {
STARTED: 'Preparing bridge transaction.',
ACTION_REQUIRED: 'Please sign the transaction.',
PENDING: 'Waiting for bridge transaction.',
DONE: 'Bridge transaction confirmed.',
},
RECEIVING_CHAIN: {
PENDING: 'Waiting for destination chain.',
DONE: 'Bridge completed.',
},
TRANSACTION: {},
}
const substatusMessages: Record<
StatusMessage,
Partial<Record<Substatus, string>>
> = {
PENDING: {
BRIDGE_NOT_AVAILABLE: 'Bridge communication is temporarily unavailable.',
CHAIN_NOT_AVAILABLE: 'RPC communication is temporarily unavailable.',
UNKNOWN_ERROR:
'An unexpected error occurred. Please seek assistance in the LI.FI discord server.',
WAIT_SOURCE_CONFIRMATIONS:
'The bridge deposit has been received. The bridge is waiting for more confirmations to start the off-chain logic.',
WAIT_DESTINATION_TRANSACTION:
'The bridge off-chain logic is being executed. Wait for the transaction to appear on the destination chain.',
},
DONE: {
PARTIAL:
'Some of the received tokens are not the requested destination tokens.',
REFUNDED: 'The tokens were refunded to the sender address.',
COMPLETED: 'The transfer is complete.',
},
FAILED: {},
INVALID: {},
NOT_FOUND: {},
}

export function getProcessMessage(
type: ProcessType,
status: ProcessStatus
): string | undefined {
const processMessage = processMessages[type][status]
return processMessage
}

export function getSubstatusMessage(
status: StatusMessage,
substatus?: Substatus
): string | undefined {
if (!substatus) {
return
}
const message = substatusMessages[status][substatus]
return message
}
93 changes: 9 additions & 84 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,11 @@
import {
type LiFiStep,
type ProcessStatus,
type ProcessType,
type StatusMessage,
type Substatus,
} from '@lifi/types'

const processMessages: Record<
ProcessType,
Partial<Record<ProcessStatus, string>>
> = {
TOKEN_ALLOWANCE: {
STARTED: 'Setting token allowance.',
PENDING: 'Waiting for token allowance.',
DONE: 'Token allowance set.',
},
SWITCH_CHAIN: {
PENDING: 'Chain switch required.',
DONE: 'Chain switched successfully.',
},
SWAP: {
STARTED: 'Preparing swap transaction.',
ACTION_REQUIRED: 'Please sign the transaction.',
PENDING: 'Waiting for swap transaction.',
DONE: 'Swap completed.',
},
CROSS_CHAIN: {
STARTED: 'Preparing bridge transaction.',
ACTION_REQUIRED: 'Please sign the transaction.',
PENDING: 'Waiting for bridge transaction.',
DONE: 'Bridge transaction confirmed.',
},
RECEIVING_CHAIN: {
PENDING: 'Waiting for destination chain.',
DONE: 'Bridge completed.',
},
TRANSACTION: {},
}
const substatusMessages: Record<
StatusMessage,
Partial<Record<Substatus, string>>
> = {
PENDING: {
BRIDGE_NOT_AVAILABLE: 'Bridge communication is temporarily unavailable.',
CHAIN_NOT_AVAILABLE: 'RPC communication is temporarily unavailable.',
UNKNOWN_ERROR:
'An unexpected error occurred. Please seek assistance in the LI.FI discord server.',
WAIT_SOURCE_CONFIRMATIONS:
'The bridge deposit has been received. The bridge is waiting for more confirmations to start the off-chain logic.',
WAIT_DESTINATION_TRANSACTION:
'The bridge off-chain logic is being executed. Wait for the transaction to appear on the destination chain.',
},
DONE: {
PARTIAL:
'Some of the received tokens are not the requested destination tokens.',
REFUNDED: 'The tokens were refunded to the sender address.',
COMPLETED: 'The transfer is complete.',
},
FAILED: {},
INVALID: {},
NOT_FOUND: {},
}

export function getProcessMessage(
type: ProcessType,
status: ProcessStatus
): string | undefined {
const processMessage = processMessages[type][status]
return processMessage
}

export function getSubstatusMessage(
status: StatusMessage,
substatus?: Substatus
): string | undefined {
if (!substatus) {
return
}
const message = substatusMessages[status][substatus]
return message
}
import { type LiFiStep } from '@lifi/types'

/**
* Used to check if changed exchange rate is in the range of slippage threshold.
* We use a slippage value as a threshold to trigger the rate change hook.
* This can result in almost doubled slippage for the user and need to be revisited.
* @param oldStep
* @param newStep
* @param oldStep - old step
* @param newStep - new step
* @returns Boolean
*/
export function checkStepSlippageThreshold(
Expand All @@ -106,3 +25,9 @@ export function checkStepSlippageThreshold(
}
return actualSlippage <= setSlippage
}

export function sleep(ms: number): Promise<null> {
return new Promise((resolve) => {
setTimeout(() => resolve(null), ms)
})
}
2 changes: 1 addition & 1 deletion src/core/waitForReceivingTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getStatus } from '../services/api.js'
import { ServerError } from '../utils/errors.js'
import { repeatUntilDone } from '../utils/utils.js'
import type { StatusManager } from './StatusManager.js'
import { getSubstatusMessage } from './utils.js'
import { getSubstatusMessage } from './processMessages.js'

const TRANSACTION_HASH_OBSERVERS: Record<string, Promise<StatusResponse>> = {}

Expand Down
1 change: 1 addition & 0 deletions src/utils/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export enum LiFiErrorCode {
InsufficientFunds = 1015,
ExchangeRateUpdateCanceled = 1016,
WalletChangedDuringExecution = 1017,
TransactionExpired = 1018,
}

export enum EthersErrorType {
Expand Down

0 comments on commit 808df13

Please sign in to comment.