Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,13 @@ export function TransferPanel() {
return handleTxSigningError(error, step.payload.txRequestLabel)
}
}

case 'tx_history_add': {
addPendingTransaction(step.payload)
switchToTransactionHistoryTab()

return
}
}
}

Expand All @@ -472,21 +479,26 @@ export function TransferPanel() {
const destinationAddress = latestDestinationAddress.current

try {
const { sourceChainProvider, destinationChainProvider, sourceChain } =
latestNetworks.current
const {
//
sourceChainProvider,
destinationChainProvider
} = latestNetworks.current

const cctpTransferStarter = new CctpTransferStarter({
sourceChainProvider,
destinationChainProvider
})

const returnEarly = await drive(stepGeneratorForCctp, stepExecutor, {
amount,
amountBigNumber,
isDepositMode,
isSmartContractWallet,
walletAddress,
destinationAddress,
transferStarter: cctpTransferStarter
transferStarter: cctpTransferStarter,
wagmiConfig
})

// this is only necessary while we are migrating to the ui driver
Expand All @@ -497,94 +509,6 @@ export function TransferPanel() {
return
}

let depositForBurnTx

try {
if (isSmartContractWallet) {
showDelayedSmartContractTxRequest()
}
const transfer = await cctpTransferStarter.transfer({
amount: amountBigNumber,
signer,
destinationAddress,
wagmiConfig
})
depositForBurnTx = transfer.sourceChainTransaction
} catch (error) {
if (isUserRejectedError(error)) {
return
}
handleError({
error,
label: 'cctp_transfer',
category: 'transaction_signing'
})
errorToast(
`USDC ${
isDepositMode ? 'Deposit' : 'Withdrawal'
} transaction failed: ${(error as Error)?.message ?? error}`
)
}

const childChainName = getNetworkName(childChain.id)

if (isSmartContractWallet) {
// For SCW, we assume that the transaction went through
trackEvent(isDepositMode ? 'CCTP Deposit' : 'CCTP Withdrawal', {
accountType: 'Smart Contract',
network: childChainName,
amount: Number(amount),
complete: false,
version: 2
})

return
}

if (!depositForBurnTx) {
return
}

trackEvent(isDepositMode ? 'CCTP Deposit' : 'CCTP Withdrawal', {
accountType: 'EOA',
network: childChainName,
amount: Number(amount),
complete: false,
version: 2
})

const newTransfer: MergedTransaction = {
txId: depositForBurnTx.hash,
asset: 'USDC',
assetType: AssetType.ERC20,
blockNum: null,
createdAt: dayjs().valueOf(),
direction: isDepositMode ? 'deposit' : 'withdraw',
isWithdrawal: !isDepositMode,
resolvedAt: null,
status: 'pending',
uniqueId: null,
value: amount,
depositStatus: DepositStatus.CCTP_DEFAULT_STATE,
destination: destinationAddress ?? walletAddress,
sender: walletAddress,
isCctp: true,
tokenAddress: getUsdcTokenAddressFromSourceChainId(sourceChain.id),
cctpData: {
sourceChainId: sourceChain.id,
attestationHash: null,
messageBytes: null,
receiveMessageTransactionHash: null,
receiveMessageTimestamp: null
},
parentChainId: parentChain.id,
childChainId: childChain.id,
sourceChainId: networks.sourceChain.id,
destinationChainId: networks.destinationChain.id
}

addPendingTransaction(newTransfer)
switchToTransactionHistoryTab()
setTransferring(false)
clearAmountInput()
clearRoute()
Expand Down
13 changes: 12 additions & 1 deletion packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { BigNumber, Signer } from 'ethers'
import { Provider, StaticJsonRpcProvider } from '@ethersproject/providers'
import {
Provider,
StaticJsonRpcProvider,
TransactionRequest
} from '@ethersproject/providers'
import { SimulateContractReturnType } from 'viem'

import { isNetwork, rpcURLs } from '../util/networks'
import { ChainId } from '../types/ChainId'
Expand Down Expand Up @@ -123,3 +128,9 @@ export function getProviderForChainId(chainId: ChainId): StaticJsonRpcProvider {

return createProviderWithCache(chainId)
}

export function isSimulateContractReturnType(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To narrow down the type since it's either ethers or wagmi (TransactionRequest | SimulateContractReturnType)

value: TransactionRequest | SimulateContractReturnType
): value is SimulateContractReturnType {
return 'request' in value && 'result' in value
}
22 changes: 21 additions & 1 deletion packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { BigNumber, providers } from 'ethers'
import { BridgeTransferStarter } from '@/token-bridge-sdk/BridgeTransferStarter'
import { SimulateContractReturnType } from '@wagmi/core'
import { Config, SimulateContractReturnType } from '@wagmi/core'
import { Chain } from 'wagmi/chains'

import { DialogType } from '../components/common/Dialog2'
import { trackEvent } from '../util/AnalyticsUtils'
import { MergedTransaction } from '../state/app/state'

export type Dialog = Extract<
DialogType,
Expand All @@ -13,12 +16,14 @@ export type Dialog = Extract<
>

export type UiDriverContext = {
amount: string
amountBigNumber: BigNumber
isDepositMode: boolean
isSmartContractWallet: boolean
walletAddress: string
destinationAddress?: string
transferStarter: BridgeTransferStarter
wagmiConfig: Config
}

export type UiDriverStep =
Expand All @@ -40,6 +45,17 @@ export type UiDriverStep =
txRequestLabel: string
}
}
| {
type: 'analytics'
payload: {
event: Parameters<typeof trackEvent>[0]
properties?: Parameters<typeof trackEvent>[1]
}
}
| {
type: 'tx_history_add'
payload: MergedTransaction
}

export type UiDriverStepType = UiDriverStep['type']

Expand All @@ -65,6 +81,10 @@ export type UiDriverStepResultFor<TStepType extends UiDriverStepType> =
? void
: TStepType extends 'tx_ethers' | 'tx_wagmi'
? Result<providers.TransactionReceipt>
: TStepType extends 'analytics'
? void
: TStepType extends 'tx_history_add'
? void
: never

export type UiDriverStepGenerator<TStep extends UiDriverStep = UiDriverStep> = (
Expand Down
113 changes: 111 additions & 2 deletions packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
import { step, UiDriverStepGenerator } from './UiDriver'
import dayjs from 'dayjs'
import {
getChainIdFromProvider,
isSimulateContractReturnType
} from '@/token-bridge-sdk/utils'

import { step, UiDriverStepGenerator, UiDriverContext } from './UiDriver'
import {
stepGeneratorForDialog,
stepGeneratorForSmartContractWalletDestinationDialog,
stepGeneratorForTransactionEthers
stepGeneratorForTransactionEthers,
stepGeneratorForTransactionWagmi
} from './UiDriverCommon'

import { getNetworkName } from '../util/networks'
import { DepositStatus, MergedTransaction } from '../state/app/state'
import { AssetType } from '../hooks/arbTokenBridge.types'
import { getUsdcTokenAddressFromSourceChainId } from '../state/cctpState'

export const stepGeneratorForCctp: UiDriverStepGenerator = async function* (
context
) {
const [sourceChainId, destinationChainId] = await Promise.all([
getChainIdFromProvider(context.transferStarter.sourceChainProvider),
getChainIdFromProvider(context.transferStarter.destinationChainProvider)
])

const deposit = context.isDepositMode
const dialog = `confirm_cctp_${deposit ? 'deposit' : 'withdrawal'}` as const

Expand All @@ -32,4 +49,96 @@ export const stepGeneratorForCctp: UiDriverStepGenerator = async function* (
txRequestLabel: 'stepGeneratorForCctp.approveToken'
})
}

const request = await context.transferStarter.transferPrepareTxRequest({
amount: context.amountBigNumber,
from: context.walletAddress,
destinationAddress: context.destinationAddress,
wagmiConfig: context.wagmiConfig
})

// narrow the union type
if (!isSimulateContractReturnType(request)) {
throw new Error(
`Expected "SimulateContractReturnType" for wagmi transaction`
)
}

const receipt = yield* stepGeneratorForTransactionWagmi(context, {
txRequest: request,
txRequestLabel: 'stepGeneratorForCctp.transfer'
})

if (typeof receipt === 'undefined') {
return
}

yield* step({
type: 'analytics',
payload: {
event: context.isDepositMode ? 'CCTP Deposit' : 'CCTP Withdrawal',
properties: {
accountType: context.isSmartContractWallet ? 'Smart Contract' : 'EOA',
network: getNetworkName(
context.isDepositMode ? destinationChainId : sourceChainId
),
amount: Number(context.amount),
complete: false,
version: 2
}
}
})

yield* step({
type: 'tx_history_add',
payload: await createMergedTransaction(
{ ...context, sourceChainId, destinationChainId },
receipt.transactionHash
)
})
}

async function createMergedTransaction(
{
isDepositMode,
walletAddress,
destinationAddress,
amount,
sourceChainId,
destinationChainId
}: UiDriverContext & { sourceChainId: number; destinationChainId: number },
depositForBurnTxHash: string
): Promise<MergedTransaction> {
const childChainId = isDepositMode ? destinationChainId : sourceChainId
const parentChainId = isDepositMode ? sourceChainId : destinationChainId

return {
txId: depositForBurnTxHash,
asset: 'USDC',
assetType: AssetType.ERC20,
blockNum: null,
createdAt: dayjs().valueOf(),
direction: isDepositMode ? 'deposit' : 'withdraw',
isWithdrawal: !isDepositMode,
resolvedAt: null,
status: 'pending',
uniqueId: null,
value: amount,
depositStatus: DepositStatus.CCTP_DEFAULT_STATE,
destination: destinationAddress ?? walletAddress,
sender: walletAddress,
isCctp: true,
tokenAddress: getUsdcTokenAddressFromSourceChainId(sourceChainId),
cctpData: {
sourceChainId,
attestationHash: null,
messageBytes: null,
receiveMessageTransactionHash: null,
receiveMessageTimestamp: null
},
parentChainId,
childChainId,
sourceChainId,
destinationChainId
}
}