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 @@ -6,7 +6,10 @@ import { useLatest } from 'react-use'
import { useAccount, useNetwork, useSigner } from 'wagmi'
import { TransactionResponse } from '@ethersproject/providers'
import { twMerge } from 'tailwind-merge'
import { scaleFrom18DecimalsToNativeTokenDecimals } from '@arbitrum/sdk'
import {
getArbitrumNetwork,
scaleFrom18DecimalsToNativeTokenDecimals
} from '@arbitrum/sdk'

import { useAppState } from '../../state'
import { getNetworkName, isNetwork } from '../../util/networks'
Expand Down Expand Up @@ -84,6 +87,9 @@ import { useMainContentTabs } from '../MainContent/MainContent'
import { useIsOftV2Transfer } from './hooks/useIsOftV2Transfer'
import { OftV2TransferStarter } from '../../token-bridge-sdk/OftV2TransferStarter'
import { highlightOftTransactionHistoryDisclaimer } from '../TransactionHistory/OftTransactionHistoryDisclaimer'
import { useIsSelectedTokenEther } from '../../hooks/useIsSelectedTokenEther'
import { wrapEther } from './wrapEther'
import { isExperimentalFeatureEnabled } from '../../util'

const signerUndefinedError = 'Signer is undefined'
const transferNotAllowedError = 'Transfer not allowed'
Expand All @@ -108,6 +114,7 @@ export function TransferPanel() {
useState<ImportTokenModalStatus>(ImportTokenModalStatus.IDLE)
const [showSmartContractWalletTooltip, setShowSmartContractWalletTooltip] =
useState(false)
const isSelectedTokenEther = useIsSelectedTokenEther()

const {
app: {
Expand Down Expand Up @@ -706,6 +713,92 @@ export function TransferPanel() {
}
}

const wrapAndDepositEther = async () => {
if (!signer) {
throw new Error(signerUndefinedError)
}
if (!isTransferAllowed) {
throw new Error(transferNotAllowedError)
}
// Just in case, will be removed
if (!isExperimentalFeatureEnabled('eth-custom-orbit')) {
throw new Error('This type of transfer is experimental only.')
}

const sourceChainId = latestNetworks.current.sourceChain.id
const destinationChainId = latestNetworks.current.destinationChain.id

setTransferring(true)

try {
await wrapEther({ signer, sourceChainId, amount })

const wethAddress =
getArbitrumNetwork(sourceChainId).tokenBridge?.childWeth

const bridgeTransferStarter = BridgeTransferStarterFactory.create({
sourceChainErc20Address: wethAddress,
sourceChainId,
destinationChainId
})

const isNativeCurrencyApprovalRequired =
await bridgeTransferStarter.requiresNativeCurrencyApproval({
signer,
amount: amountBigNumber,
destinationAddress
})

if (isNativeCurrencyApprovalRequired) {
// show native currency approval dialog
const userConfirmation = await customFeeTokenApproval()
if (!userConfirmation) return false

const approvalTx = await bridgeTransferStarter.approveNativeCurrency({
signer,
amount: amountBigNumber,
destinationAddress
})

if (approvalTx) {
await approvalTx.wait()
}
}

const isTokenApprovalRequired =
await bridgeTransferStarter.requiresTokenApproval({
amount: amountBigNumber,
signer,
destinationAddress
})
if (isTokenApprovalRequired) {
const userConfirmation = await tokenAllowanceApproval()
if (!userConfirmation) return false

const approvalTx = await bridgeTransferStarter.approveToken({
signer,
amount: amountBigNumber
})

if (approvalTx) {
await approvalTx.wait()
}
}

await bridgeTransferStarter.transfer({
amount: amountBigNumber,
signer,
destinationAddress
})
} catch (error) {
if (isUserRejectedError(error)) {
return
}
} finally {
setTransferring(false)
}
}

const transfer = async () => {
const sourceChainId = latestNetworks.current.sourceChain.id

Expand Down Expand Up @@ -1144,6 +1237,10 @@ export function TransferPanel() {
if (isCctpTransfer) {
return transferCctp()
}
if (isDepositMode && nativeCurrency.isCustom && isSelectedTokenEther) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We can abstract this into it's own function which can be reused to identify the transfer case that is being implemented in these set of PRs.
isEthTransferToCustomNativeCurrencyChain(), and it's corresponding hook.

This flag can also be used in other places to show different UI case by case.

// We cannot transfer ETH to a custom native currency chain. We need to wrap it first instead.
return wrapAndDepositEther()
}
if (isDepositMode && selectedToken) {
return depositToken()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getArbitrumNetwork } from '@arbitrum/sdk'
import { ethers, Signer } from 'ethers'

const WETH_ABI = [
{
constant: false,
inputs: [],
name: 'deposit',
outputs: [],
payable: true,
stateMutability: 'payable',
type: 'function'
}
]

export async function wrapEther({
signer,
sourceChainId,
amount
}: {
signer: Signer
sourceChainId: number
amount: string | number
}) {
const wethAddress = getArbitrumNetwork(sourceChainId).tokenBridge?.childWeth

if (!wethAddress) {
throw new Error('Error wrapping ETH: No WETH address found.')
}

try {
const wethContract = new ethers.Contract(wethAddress, WETH_ABI, signer)
const amountInWei = ethers.utils.parseEther(amount.toString())

const tx = await wethContract.deposit({
value: amountInWei
})

return tx.wait()
} catch (error) {
console.error('Error wrapping ETH:', error)
throw error
}
}