Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial setup for viem api #551

Closed
wants to merge 20 commits into from
Closed
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
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"@ethersproject/bignumber": "^5.1.1",
"@ethersproject/bytes": "^5.0.8",
"async-mutex": "^0.4.0",
"ethers": "^5.1.0"
"ethers": "^5.1.0",
"viem": "^2.21.48"
},
"devDependencies": {
"@arbitrum/nitro-contracts": "^1.1.1",
Expand Down Expand Up @@ -84,7 +85,7 @@
"ts-node": "^10.2.1",
"tslint": "^6.1.3",
"typechain": "7.0.0",
"typescript": "^4.9.5",
"typescript": "^5.6.3",
"yargs": "^17.3.1"
},
"files": [
Expand Down
44 changes: 44 additions & 0 deletions scripts/testSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
isArbitrumNetworkWithCustomFeeToken,
} from '../tests/integration/custom-fee-token/customFeeTokenTestHelpers'
import { fundParentSigner } from '../tests/integration/testHelpers'
import { Chain } from 'viem'

dotenv.config()

Expand Down Expand Up @@ -85,6 +86,8 @@ export const testSetup = async (): Promise<{
inboxTools: InboxTools
parentDeployer: Signer
childDeployer: Signer
localEthChain: Chain
localArbChain: Chain
}> => {
const ethProvider = new JsonRpcProvider(config.ethUrl)
const arbProvider = new JsonRpcProvider(config.arbUrl)
Expand Down Expand Up @@ -113,6 +116,23 @@ export const testSetup = async (): Promise<{

assertArbitrumNetworkHasTokenBridge(setChildChain)

// Generate Viem chains using the network data we already have
const localEthChain = generateViemChain(
{
chainId: setChildChain.parentChainId,
name: 'EthLocal',
},
config.ethUrl
)

const localArbChain = generateViemChain(
{
chainId: setChildChain.chainId,
name: setChildChain.name,
},
config.arbUrl
)

const erc20Bridger = new Erc20Bridger(setChildChain)
const adminErc20Bridger = new AdminErc20Bridger(setChildChain)
const ethBridger = new EthBridger(setChildChain)
Expand All @@ -136,6 +156,8 @@ export const testSetup = async (): Promise<{
inboxTools,
parentDeployer,
childDeployer,
localEthChain,
localArbChain,
}
}

Expand All @@ -153,3 +175,25 @@ export function getLocalNetworksFromFile(): {

return { l2Network: localL2, l3Network: localL3 }
}

function generateViemChain(
networkData: {
chainId: number
name: string
},
rpcUrl: string
): Chain {
return {
id: networkData.chainId,
name: networkData.name,
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
},
rpcUrls: {
default: { http: [rpcUrl] },
public: { http: [rpcUrl] },
},
} as const
}
221 changes: 221 additions & 0 deletions src/experimental/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import { BigNumber } from 'ethers'
import {
Account,
Address,
Hash,
PublicClient,
TransactionRequest,
WalletClient,
} from 'viem'
import { EthBridger } from '../lib/assetBridger/ethBridger'
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
}

const DEFAULT_CONFIRMATIONS = 1
const DEFAULT_TIMEOUT = 1000 * 60 * 5 // 5 minutes

export type WaitForCrossChainTxParameters = {
hash: Hash
timeout?: number
confirmations?: number
}

export type SendCrossChainTransactionParameters = {
request: TransactionRequest
timeout?: number
confirmations?: number
}

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

export type DepositEthParameters = {
amount: bigint
account: Account | Address
confirmations?: number
timeout?: number
}

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

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

sendCrossChainTransaction: (
params: SendCrossChainTransactionParameters
) => Promise<CrossChainTransactionStatus>

depositEth: (
params: DepositEthParameters
) => Promise<CrossChainTransactionStatus>
}

async function prepareDepositEthTransaction(
client: PublicClient,
{ amount, account }: PrepareDepositEthParameters
): Promise<TransactionRequest> {
const provider = transformPublicClientToProvider(client)
const ethBridger = await EthBridger.fromProvider(provider)
const request = await ethBridger.getDepositRequest({
amount: BigNumber.from(amount),
from: typeof account === 'string' ? account : account.address,
})

return {
to: request.txRequest.to as `0x${string}`,
value: BigNumber.from(request.txRequest.value).toBigInt(),
data: request.txRequest.data as `0x${string}`,
}
}

async function waitForCrossChainTransaction(
parentClient: PublicClient,
childClient: PublicClient,
{
hash,
confirmations = DEFAULT_CONFIRMATIONS,
timeout = DEFAULT_TIMEOUT,
}: WaitForCrossChainTxParameters
): Promise<CrossChainTransactionStatus> {
const childProvider = transformPublicClientToProvider(childClient)

const viemReceipt = await parentClient.waitForTransactionReceipt({
hash,
confirmations,
})

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,
hash,
}
}
} 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,
hash,
}
}
} catch (e) {
// Not a cross chain message
}

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

async function sendCrossChainTransaction(
parentClient: PublicClient,
childClient: PublicClient,
walletClient: WalletClient,
{
request,
confirmations = DEFAULT_CONFIRMATIONS,
timeout = DEFAULT_TIMEOUT,
}: SendCrossChainTransactionParameters
): Promise<CrossChainTransactionStatus> {
const hash = await walletClient.sendTransaction({
...request,
chain: walletClient.chain,
account: walletClient.account as Account,
})

return waitForCrossChainTransaction(parentClient, childClient, {
hash,
confirmations,
timeout,
})
}

async function depositEth(
parentClient: PublicClient,
childClient: PublicClient,
walletClient: WalletClient,
{
amount,
account,
confirmations = DEFAULT_CONFIRMATIONS,
timeout = DEFAULT_TIMEOUT,
}: DepositEthParameters
): Promise<CrossChainTransactionStatus> {
const request = await prepareDepositEthTransaction(childClient, {
amount,
account,
})

return sendCrossChainTransaction(parentClient, childClient, walletClient, {
request,
confirmations,
timeout,
})
}

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

export function arbitrumParentWalletActions(
parentClient: PublicClient,
childClient: PublicClient
) {
return (walletClient: WalletClient): ArbitrumParentWalletActions => ({
waitForCrossChainTransaction: (params: WaitForCrossChainTxParameters) =>
waitForCrossChainTransaction(parentClient, childClient, params),
sendCrossChainTransaction: (params: SendCrossChainTransactionParameters) =>
sendCrossChainTransaction(
parentClient,
childClient,
walletClient,
params
),
depositEth: (params: DepositEthParameters) =>
depositEth(parentClient, childClient, walletClient, params),
})
}
59 changes: 59 additions & 0 deletions src/experimental/createArbitrumClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
Chain,
PublicClient,
WalletClient,
createPublicClient,
http,
} from 'viem'
import {
ArbitrumDepositActions,
ArbitrumParentWalletActions,
arbitrumParentClientActions,
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,
transport: http(parentRpcUrl || parentChain.rpcUrls.default.http[0]),
})

const childPublicClient = createPublicClient({
chain: childChain,
transport: http(childRpcUrl || childChain.rpcUrls.default.http[0]),
}).extend(arbitrumParentClientActions())

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

return {
parentPublicClient,
childPublicClient,
parentWalletClient: extendedParentWalletClient,
childWalletClient,
}
}
Loading