Skip to content

Commit

Permalink
implements walletclient method to wait for tx
Browse files Browse the repository at this point in the history
  • Loading branch information
douglance committed Nov 21, 2024
1 parent b6d72e9 commit d9f2e92
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 134 deletions.
124 changes: 90 additions & 34 deletions src/experimental/actions.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
import { BigNumber } from 'ethers'
import { Account, Address, PublicClient, TransactionRequest } from 'viem'
import {
Account,
Address,
Hash,
PublicClient,
TransactionRequest,
WalletClient,
} from 'viem'
import { EthBridger } from '../lib/assetBridger/ethBridger'
import { transformPublicClientToProvider } from './transformViemToEthers'
import {
transformPublicClientToProvider,
viemTransactionReceiptToEthersTransactionReceipt,
} from './transformViemToEthers'
import { ParentTransactionReceipt } from '../lib/message/ParentTransaction'
import { ParentToChildMessageStatus } from '../lib/message/ParentToChildMessage'

export type PrepareDepositEthParameters = {
amount: bigint
account: Account | Address
}

export type PrepareDepositEthToParameters = {
amount: bigint
account: Address
destinationAddress: Address
parentPublicClient: PublicClient
export type WaitForCrossChainTxParameters = {
hash: Hash
timeout?: number
confirmations?: number
}

export type CrossChainTransactionStatus = {
status: 'success' | 'failed'
complete: boolean
message?: unknown
childTxReceipt?: unknown
}

export type ArbitrumDepositActions = {
prepareDepositEthTransaction: (
params: PrepareDepositEthParameters
) => Promise<TransactionRequest>
prepareDepositEthToTransaction: (
params: PrepareDepositEthToParameters
) => Promise<TransactionRequest>
}

export type ArbitrumParentWalletActions = {
waitForCrossChainTransaction: (
params: WaitForCrossChainTxParameters
) => Promise<CrossChainTransactionStatus>
}

async function prepareDepositEthTransaction(
Expand All @@ -42,39 +63,74 @@ async function prepareDepositEthTransaction(
}
}

async function prepareDepositEthToTransaction(
client: PublicClient,
{
amount,
account,
destinationAddress,
parentPublicClient,
}: PrepareDepositEthToParameters
): Promise<TransactionRequest> {
const childProvider = transformPublicClientToProvider(client)
const parentProvider = transformPublicClientToProvider(parentPublicClient)
const ethBridger = await EthBridger.fromProvider(childProvider)
async function waitForCrossChainTransaction(
parentClient: PublicClient,
childClient: PublicClient,
{ hash, timeout, confirmations }: WaitForCrossChainTxParameters
): Promise<CrossChainTransactionStatus> {
const childProvider = transformPublicClientToProvider(childClient)

const request = await ethBridger.getDepositToRequest({
amount: BigNumber.from(amount),
destinationAddress,
from: account,
parentProvider,
childProvider,
// Wait for the transaction to be mined and get the receipt
const viemReceipt = await parentClient.waitForTransactionReceipt({
hash,
confirmations,
})

return {
to: request.txRequest.to as `0x${string}`,
value: BigNumber.from(request.txRequest.value).toBigInt(),
data: request.txRequest.data as `0x${string}`,
const ethersReceipt =
viemTransactionReceiptToEthersTransactionReceipt(viemReceipt)
const parentReceipt = new ParentTransactionReceipt(ethersReceipt)

// Try to get eth deposits first
try {
const ethDeposits = await parentReceipt.getEthDeposits(childProvider)
if (ethDeposits.length > 0) {
const result = await ethDeposits[0].wait(confirmations, timeout)
return {
status: result ? 'success' : 'failed',
complete: Boolean(result),
message: ethDeposits[0],
childTxReceipt: result,
}
}
} catch (e) {
// Not an eth deposit, continue to check for other message types
}

// Check for other cross chain messages
try {
const messages = await parentReceipt.getParentToChildMessages(childProvider)
if (messages.length > 0) {
const result = await messages[0].waitForStatus(confirmations, timeout)
return {
status:
result.status === ParentToChildMessageStatus.REDEEMED
? 'success'
: 'failed',
complete: result.status === ParentToChildMessageStatus.REDEEMED,
message: messages[0],
childTxReceipt: result,
}
}
} catch (e) {
// Not a cross chain message
}

throw new Error('No cross chain message found in transaction')
}

export function arbitrumDepositActions() {
return (client: PublicClient): ArbitrumDepositActions => ({
prepareDepositEthTransaction: params =>
prepareDepositEthTransaction(client, params),
prepareDepositEthToTransaction: params =>
prepareDepositEthToTransaction(client, params),
})
}

export function arbitrumParentWalletActions(
parentClient: PublicClient,
childClient: PublicClient
) {
return (walletClient: WalletClient): ArbitrumParentWalletActions => ({
waitForCrossChainTransaction: (params: WaitForCrossChainTxParameters) =>
waitForCrossChainTransaction(parentClient, childClient, params),
})
}
27 changes: 25 additions & 2 deletions src/experimental/createArbitrumClient.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
import { Chain, PublicClient, createPublicClient, http } from 'viem'
import { arbitrumDepositActions, ArbitrumDepositActions } from './actions'
import {
Chain,
PublicClient,
WalletClient,
createPublicClient,
http,
} from 'viem'
import {
ArbitrumDepositActions,
ArbitrumParentWalletActions,
arbitrumDepositActions,
arbitrumParentWalletActions,
} from './actions'

export type ArbitrumClients = {
parentPublicClient: PublicClient
childPublicClient: PublicClient & ArbitrumDepositActions
parentWalletClient: WalletClient & ArbitrumParentWalletActions
childWalletClient: WalletClient
}

export type CreateArbitrumClientParams = {
parentChain: Chain
childChain: Chain
parentRpcUrl?: string
childRpcUrl?: string
parentWalletClient: WalletClient
childWalletClient: WalletClient
}

export function createArbitrumClient({
parentChain,
childChain,
parentRpcUrl,
childRpcUrl,
parentWalletClient,
childWalletClient,
}: CreateArbitrumClientParams): ArbitrumClients {
const parentPublicClient = createPublicClient({
chain: parentChain,
Expand All @@ -29,8 +46,14 @@ export function createArbitrumClient({
transport: http(childRpcUrl || childChain.rpcUrls.default.http[0]),
}).extend(arbitrumDepositActions())

const extendedParentWalletClient = parentWalletClient.extend(
arbitrumParentWalletActions(parentPublicClient, childPublicClient)
)

return {
parentPublicClient,
childPublicClient,
parentWalletClient: extendedParentWalletClient,
childWalletClient,
}
}
53 changes: 52 additions & 1 deletion src/experimental/transformViemToEthers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import {
Log as EthersLog,
TransactionReceipt as EthersTransactionReceipt,
} from '@ethersproject/abstract-provider'
import { StaticJsonRpcProvider } from '@ethersproject/providers'
import { Chain, Client, PublicClient, Transport } from 'viem'
import { BigNumber } from 'ethers'
import {
Chain,
Client,
PublicClient,
Transport,
Log as ViemLog,
TransactionReceipt as ViemTransactionReceipt,
} from 'viem'

// based on https://wagmi.sh/react/ethers-adapters#reference-implementation
export function publicClientToProvider<TChain extends Chain | undefined>(
Expand Down Expand Up @@ -42,3 +54,42 @@ export const transformPublicClientToProvider = (
}
throw new Error('Invalid provider')
}

function viemLogToEthersLog(log: ViemLog): EthersLog {
return {
blockNumber: Number(log.blockNumber),
blockHash: log.blockHash!,
transactionIndex: log.transactionIndex!,
removed: log.removed,
address: log.address,
data: log.data,
topics: log.topics,
transactionHash: log.transactionHash!,
logIndex: log.logIndex!,
}
}

export function viemTransactionReceiptToEthersTransactionReceipt(
receipt: ViemTransactionReceipt
): EthersTransactionReceipt {
return {
to: receipt.to!,
from: receipt.from!,
contractAddress: receipt.contractAddress!,
transactionIndex: receipt.transactionIndex,
gasUsed: BigNumber.from(receipt.gasUsed),
logsBloom: receipt.logsBloom,
blockHash: receipt.blockHash,
transactionHash: receipt.transactionHash,
logs: receipt.logs.map(log => viemLogToEthersLog(log)),
blockNumber: Number(receipt.blockNumber),
// todo: if we need this we can add it later
confirmations: -1,
cumulativeGasUsed: BigNumber.from(receipt.cumulativeGasUsed),
effectiveGasPrice: BigNumber.from(receipt.effectiveGasPrice),
// all transactions that we care about are well past byzantium
byzantium: true,
type: Number(receipt.type),
status: receipt.status === 'success' ? 1 : 0,
}
}
Loading

0 comments on commit d9f2e92

Please sign in to comment.