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 @@ -392,13 +392,43 @@ export function TransferPanel() {
case 'dialog': {
return confirmDialog(step.payload)
}

case 'scw_tooltip': {
showDelayedSmartContractTxRequest()
return
}

case 'tx': {
try {
const tx = await signer!.sendTransaction(step.payload.txRequest)
const txReceipt = await tx.wait()

return { data: txReceipt }
} catch (error) {
// capture error and show toast for anything that's not user rejecting error
if (!isUserRejectedError(error)) {
handleError({
error,
label: step.payload.txRequestLabel,
category: 'transaction_signing'
})

errorToast(`${(error as Error)?.message ?? error}`)
}

return { error: error as unknown as Error }
}
}
}
}

const transferCctp = async () => {
if (!selectedToken) {
return
}
if (!walletAddress) {
throw new Error(`walletAddress is undefined`)
}
if (!signer) {
throw new Error(signerUndefinedError)
}
Expand All @@ -412,11 +442,18 @@ export function TransferPanel() {
const { sourceChainProvider, destinationChainProvider, sourceChain } =
latestNetworks.current

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

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

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

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

const isTokenApprovalRequired =
await cctpTransferStarter.requiresTokenApproval({
amount: amountBigNumber,
owner: await signer.getAddress()
})

if (isTokenApprovalRequired) {
const userConfirmation = await confirmDialog('approve_token')
if (!userConfirmation) return false

if (isSmartContractWallet) {
showDelayedSmartContractTxRequest()
}
try {
const tx = await cctpTransferStarter.approveToken({
signer,
amount: amountBigNumber
})

await tx.wait()
} catch (error) {
if (isUserRejectedError(error)) {
return
}
handleError({
error,
label: 'cctp_approve_token',
category: 'token_approval'
})
errorToast(
`USDC approval transaction failed: ${
(error as Error)?.message ?? error
}`
)
return
}
}

let depositForBurnTx

try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Provider } from '@ethersproject/providers'
import { Provider, TransactionRequest } from '@ethersproject/providers'
import { BigNumber, ContractTransaction, Signer } from 'ethers'
import { Config } from 'wagmi'

Expand Down Expand Up @@ -99,6 +99,10 @@ export type RequiresTokenApprovalProps = {
destinationAddress?: string
}

export type ApproveTokenPrepareTxRequestProps = {
amount?: BigNumber
}

export type ApproveTokenProps = {
signer: Signer
amount?: BigNumber
Expand Down Expand Up @@ -147,6 +151,16 @@ export abstract class BridgeTransferStarter {
props: RequiresTokenApprovalProps
): Promise<boolean>

// not marking this as abstract for now, as we need a dummy implementation for every class
// only cctp is going to override it for now, and we'll do the same for others one by one
// finally, once we have all implementations we'll mark it as abstract
public async approveTokenPrepareTxRequest(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
props?: ApproveTokenPrepareTxRequestProps
): Promise<TransactionRequest> {
return {} as TransactionRequest
}

public abstract approveTokenEstimateGas(
props: ApproveTokenProps
): Promise<BigNumber | void>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TransactionRequest } from '@ethersproject/providers'
import { ERC20__factory } from '@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory'

import {
ApproveTokenPrepareTxRequestProps,
ApproveTokenProps,
BridgeTransferStarter,
RequiresTokenApprovalProps,
Expand Down Expand Up @@ -50,9 +51,9 @@ export class CctpTransferStarter extends BridgeTransferStarter {
return allowance.lt(amount)
}

public async approveTokenPrepareTxRequest(params?: {
amount: BigNumber | undefined
}): Promise<TransactionRequest> {
public async approveTokenPrepareTxRequest(
props?: ApproveTokenPrepareTxRequestProps
): Promise<TransactionRequest> {
const {
//
usdcContractAddress,
Expand All @@ -63,7 +64,7 @@ export class CctpTransferStarter extends BridgeTransferStarter {
to: usdcContractAddress,
data: ERC20__factory.createInterface().encodeFunctionData('approve', [
tokenMessengerContractAddress,
params?.amount ?? constants.MaxUint256
props?.amount ?? constants.MaxUint256
]),
value: BigNumber.from(0)
}
Expand Down
54 changes: 41 additions & 13 deletions packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,80 @@
import { BigNumber, providers } from 'ethers'
import { BridgeTransferStarter } from '@/token-bridge-sdk/BridgeTransferStarter'

import { DialogType } from '../components/common/Dialog2'

export type Dialog = Extract<
DialogType,
| 'confirm_cctp_deposit'
| 'confirm_cctp_withdrawal'
| 'scw_custom_destination_address'
| 'approve_token'
>

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

export type UiDriverStep =
| { type: 'start' } //
| { type: 'return' }
| { type: 'dialog'; payload: Dialog }
| { type: 'scw_tooltip' }
| {
type: 'tx'
payload: {
txRequest: providers.TransactionRequest
txRequestLabel: string
}
}

export type UiDriverStepResultFor<TStep extends UiDriverStep> = //
TStep extends { type: 'start' }
export type UiDriverStepType = UiDriverStep['type']

export type UiDriverStepPayloadFor<TStepType extends UiDriverStepType> =
Extract<UiDriverStep, { type: TStepType }> extends {
payload: infer TPayload
}
? TPayload
: never

type Result<T> =
| { data: T; error?: undefined }
| { data?: undefined; error: Error }

export type UiDriverStepResultFor<TStepType extends UiDriverStepType> =
TStepType extends 'start'
? void
: //
TStep extends { type: 'return' }
: TStepType extends 'return'
? void
: //
TStep extends { type: 'dialog' }
: TStepType extends 'dialog'
? boolean
: //
never
: TStepType extends 'scw_tooltip'
? void
: TStepType extends 'tx'
? Result<providers.TransactionReceipt>
: never

export type UiDriverStepGenerator<TStep extends UiDriverStep = UiDriverStep> = (
context: UiDriverContext
) => AsyncGenerator<TStep, void, UiDriverStepResultFor<TStep>>
) => AsyncGenerator<TStep, void, UiDriverStepResultFor<TStep['type']>>

export type UiDriverStepExecutor<TStep extends UiDriverStep = UiDriverStep> = (
step: TStep
) => Promise<UiDriverStepResultFor<TStep>>
) => Promise<UiDriverStepResultFor<TStep['type']>>

// TypeScript doesn't to the greatest job with generators
// This 2nd generator helps with types both for params and result when yielding a step
export async function* step<TStep extends UiDriverStep>(
step: TStep
): AsyncGenerator<
TStep,
UiDriverStepResultFor<TStep>,
UiDriverStepResultFor<TStep>
UiDriverStepResultFor<TStep['type']>,
UiDriverStepResultFor<TStep['type']>
> {
return yield step
}
Expand Down
Loading
Loading