Skip to content

Commit

Permalink
fix: improve transaction receipt polling
Browse files Browse the repository at this point in the history
  • Loading branch information
chybisov committed Jul 12, 2024
1 parent 25e7edc commit 349c5b0
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 60 deletions.
47 changes: 15 additions & 32 deletions src/core/EVM/EVMStepExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type {
import type {
Hash,
PublicClient,
ReplacementReason,
SendTransactionParameters,
WalletClient,
} from 'viem'
Expand Down Expand Up @@ -35,7 +34,8 @@ import { checkAllowance } from './checkAllowance.js'
import { updateMultisigRouteProcess } from './multisig.js'
import { switchChain } from './switchChain.js'
import type { MultisigConfig, MultisigTransaction } from './types.js'
import { getMaxPriorityFeePerGas, retryCount, retryDelay } from './utils.js'
import { getMaxPriorityFeePerGas } from './utils.js'
import { waitForTransactionReceipt } from './waitForTransactionReceipt.js'

export interface EVMStepExecutorOptions extends StepExecutorOptions {
walletClient: WalletClient
Expand Down Expand Up @@ -350,40 +350,23 @@ export class EVMStepExecutor extends BaseStepExecutor {
}
}

let replacementReason: ReplacementReason | undefined
const transactionReceipt = await this.walletClient
.extend(publicActions)
.waitForTransactionReceipt({
hash: txHash,
onReplaced: (response) => {
replacementReason = response.reason
this.statusManager.updateProcess(step, process.type, 'PENDING', {
txHash: response.transaction.hash,
txLink: `${fromChain.metamask.blockExplorerUrls[0]}tx/${response.transaction.hash}`,
})
},
retryCount,
retryDelay,
})

if (transactionReceipt.status === 'reverted') {
throw new TransactionError(
LiFiErrorCode.TransactionFailed,
'Transaction was reverted.'
)
}
if (replacementReason === 'cancelled') {
throw new TransactionError(
LiFiErrorCode.TransactionCanceled,
'User canceled transaction.'
)
}
const transactionReceipt = await waitForTransactionReceipt({
walletClient: this.walletClient,
chainId: fromChain.id,
txHash,
onReplaced: (response) => {
this.statusManager.updateProcess(step, process.type, 'PENDING', {
txHash: response.transaction.hash,
txLink: `${fromChain.metamask.blockExplorerUrls[0]}tx/${response.transaction.hash}`,
})
},
})

// if it's multisig wallet client and the process is in ACTION_REQUIRED
// then signatures are still needed
if (isMultisigWalletClient && process.status === 'ACTION_REQUIRED') {
await updateMultisigRouteProcess(
transactionReceipt.transactionHash,
transactionReceipt?.transactionHash || txHash,
step,
process.type,
fromChain,
Expand All @@ -392,7 +375,7 @@ export class EVMStepExecutor extends BaseStepExecutor {
)
}

if (!isMultisigWalletClient) {
if (!isMultisigWalletClient && transactionReceipt?.transactionHash) {
process = this.statusManager.updateProcess(
step,
process.type,
Expand Down
37 changes: 10 additions & 27 deletions src/core/EVM/checkAllowance.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { Chain, LiFiStep, Process, ProcessType } from '@lifi/types'
import type { Address, Hash, ReplacementReason, WalletClient } from 'viem'
import { maxUint256, publicActions } from 'viem'
import { LiFiErrorCode, TransactionError } from '../../utils/index.js'
import type { Address, Hash, WalletClient } from 'viem'
import { maxUint256 } from 'viem'
import { parseError } from '../../utils/parseError.js'
import type { StatusManager } from '../StatusManager.js'
import type { ExecutionOptions } from '../types.js'
import { getAllowance } from './getAllowance.js'
import { setAllowance } from './setAllowance.js'
import { retryCount, retryDelay } from './utils.js'
import { waitForTransactionReceipt } from './waitForTransactionReceipt.js'

export const checkAllowance = async (
chain: Chain,
Expand Down Expand Up @@ -127,42 +126,26 @@ const waitForApprovalTransaction = async (
chain: Chain,
statusManager: StatusManager
) => {
const client = walletClient.extend(publicActions)
statusManager.updateProcess(step, processType, 'PENDING', {
txHash,
txLink: `${chain.metamask.blockExplorerUrls[0]}tx/${txHash}`,
})

let replacementReason: ReplacementReason | undefined
const transactionReceipt = await client.waitForTransactionReceipt({
hash: txHash,
const transactionReceipt = await waitForTransactionReceipt({
walletClient,
chainId: chain.id,
txHash: txHash,
onReplaced(response) {
replacementReason = response.reason
statusManager.updateProcess(step, processType, 'PENDING', {
txHash: response.transaction.hash,
txLink: `${chain.metamask.blockExplorerUrls[0]}tx/${response.transaction.hash}`,
})
},
retryCount,
retryDelay,
})

if (transactionReceipt.status === 'reverted') {
throw new TransactionError(
LiFiErrorCode.TransactionFailed,
'Transaction was reverted.'
)
}

if (replacementReason === 'cancelled') {
throw new TransactionError(
LiFiErrorCode.TransactionCanceled,
'User canceled transaction.'
)
}

const transactionHash = transactionReceipt?.transactionHash || txHash
statusManager.updateProcess(step, processType, 'DONE', {
txHash: transactionReceipt.transactionHash,
txLink: `${chain.metamask.blockExplorerUrls[0]}tx/${transactionReceipt.transactionHash}`,
txHash: transactionHash,
txLink: `${chain.metamask.blockExplorerUrls[0]}tx/${transactionHash}`,
})
}
2 changes: 1 addition & 1 deletion src/core/EVM/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ export const getMulticallAddress = async (
export const retryDelay = ({ count }: { count: number; error: Error }) =>
Math.min(~~(1 << count) * 200, 3000)

export const retryCount = 20
export const retryCount = 30
86 changes: 86 additions & 0 deletions src/core/EVM/waitForTransactionReceipt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { ChainId } from '@lifi/types'
import type {
Chain,
Hash,
PublicClient,
ReplacementReason,
ReplacementReturnType,
TransactionReceipt,
WalletClient,
} from 'viem'
import { publicActions } from 'viem'
import { LiFiErrorCode, TransactionError } from '../../utils/index.js'
import { getPublicClient } from './publicClient.js'
import { retryCount, retryDelay } from './utils.js'

interface WaitForTransactionReceiptProps {
walletClient: WalletClient
chainId: ChainId
txHash: Hash
onReplaced?: (response: ReplacementReturnType<Chain | undefined>) => void
}

export async function waitForTransactionReceipt({
walletClient,
chainId,
txHash,
onReplaced,
}: WaitForTransactionReceiptProps): Promise<TransactionReceipt | undefined> {
let { transactionReceipt, replacementReason } = await waitForReceipt(
walletClient.extend(publicActions),
txHash,
onReplaced
)

if (!transactionReceipt?.status) {
const publicClient = await getPublicClient(chainId)
const result = await waitForReceipt(publicClient, txHash, onReplaced)
transactionReceipt = result.transactionReceipt
replacementReason = result.replacementReason
}

if (transactionReceipt?.status === 'reverted') {
throw new TransactionError(
LiFiErrorCode.TransactionFailed,
'Transaction was reverted.'
)
}
if (replacementReason === 'cancelled') {
throw new TransactionError(
LiFiErrorCode.TransactionCanceled,
'User canceled transaction.'
)
}

return transactionReceipt
}

async function waitForReceipt(
client: PublicClient | WalletClient,
txHash: Hash,
onReplaced?: (response: ReplacementReturnType<Chain | undefined>) => void
): Promise<{
transactionReceipt?: TransactionReceipt
replacementReason?: ReplacementReason
}> {
let replacementReason: ReplacementReason | undefined
let transactionReceipt: TransactionReceipt | undefined

try {
transactionReceipt = await (
client as PublicClient
).waitForTransactionReceipt({
hash: txHash,
onReplaced: (response) => {
replacementReason = response.reason
onReplaced?.(response)
},
retryCount,
retryDelay,
})
} catch (error) {
// We can ignore errors from waitForTransactionReceipt as we have a status check fallback
}

return { transactionReceipt, replacementReason }
}

0 comments on commit 349c5b0

Please sign in to comment.