From 246b1e0378f558387e137ff63337a31ad64fed08 Mon Sep 17 00:00:00 2001 From: Lukas Rosario Date: Wed, 20 Sep 2023 15:08:04 -0400 Subject: [PATCH 1/5] add depositTo and withdrawTo functionality for smart contract wallets --- .../public/icons/question-mark-circled.svg | 1 + .../BridgeToInput/BridgeToInput.tsx | 35 ++++++++++++ .../DepositContainer/DepositContainer.tsx | 56 ++++++++++++++++--- .../WithdrawContainer/WithdrawContainer.tsx | 45 +++++++++++++-- apps/bridge/src/utils/hooks/useGetCode.ts | 19 +++++++ .../utils/hooks/usePrepareERC20DepositTo.ts | 49 ++++++++++++++++ .../hooks/usePrepareERC20WithdrawalTo.ts | 38 +++++++++++++ 7 files changed, 228 insertions(+), 15 deletions(-) create mode 100644 apps/bridge/public/icons/question-mark-circled.svg create mode 100644 apps/bridge/src/components/BridgeToInput/BridgeToInput.tsx create mode 100644 apps/bridge/src/utils/hooks/useGetCode.ts create mode 100644 apps/bridge/src/utils/hooks/usePrepareERC20DepositTo.ts create mode 100644 apps/bridge/src/utils/hooks/usePrepareERC20WithdrawalTo.ts diff --git a/apps/bridge/public/icons/question-mark-circled.svg b/apps/bridge/public/icons/question-mark-circled.svg new file mode 100644 index 0000000000..497edd4e33 --- /dev/null +++ b/apps/bridge/public/icons/question-mark-circled.svg @@ -0,0 +1 @@ + diff --git a/apps/bridge/src/components/BridgeToInput/BridgeToInput.tsx b/apps/bridge/src/components/BridgeToInput/BridgeToInput.tsx new file mode 100644 index 0000000000..b811a0cd84 --- /dev/null +++ b/apps/bridge/src/components/BridgeToInput/BridgeToInput.tsx @@ -0,0 +1,35 @@ +import { Dispatch, SetStateAction } from 'react'; +import Image from 'next/image'; + +type BridgeToInputProps = { + bridgeTo: string; + setBridgeTo: Dispatch>; + action: 'deposit' | 'withdraw'; +}; + +export function BridgeToInput({ bridgeTo, setBridgeTo, action }: BridgeToInputProps) { + function handleChangeBridgeTo(e: { target: { value: SetStateAction } }) { + setBridgeTo(e.target.value); + } + + return ( +
+
+ {action === 'deposit' ? 'Deposit to' : 'Withdraw to'} +
+ + Only send funds on networks supported by your wallet provider if it is a smart contract + wallet or there may be permanent loss of funds. + + tooltip +
+
+ +
+ ); +} \ No newline at end of file diff --git a/apps/bridge/src/components/DepositContainer/DepositContainer.tsx b/apps/bridge/src/components/DepositContainer/DepositContainer.tsx index c66bff977d..2206aba231 100644 --- a/apps/bridge/src/components/DepositContainer/DepositContainer.tsx +++ b/apps/bridge/src/components/DepositContainer/DepositContainer.tsx @@ -1,6 +1,7 @@ import { useCallback, useMemo, useState } from 'react'; import { BridgeButton } from 'apps/bridge/src/components/BridgeButton/BridgeButton'; import { BridgeInput } from 'apps/bridge/src/components/BridgeInput/BridgeInput'; +import { BridgeToInput } from 'apps/bridge/src/components/BridgeToInput/BridgeToInput'; import { ConnectWalletButton } from 'apps/bridge/src/components/ConnectWalletButton/ConnectWalletButton'; import { DepositModal } from 'apps/bridge/src/components/DepositModal/DepositModal'; import { FaqSidebar } from 'apps/bridge/src/components/Faq/FaqSidebar'; @@ -11,11 +12,14 @@ import { getAssetListForChainEnv } from 'apps/bridge/src/utils/assets/getAssetLi import { useApproveContract } from 'apps/bridge/src/utils/hooks/useApproveContract'; import { useChainEnv } from 'apps/bridge/src/utils/hooks/useChainEnv'; import { useDisclosure } from 'apps/bridge/src/utils/hooks/useDisclosure'; +import { useGetCode } from 'apps/bridge/src/utils/hooks/useGetCode'; import { useIsContractApproved } from 'apps/bridge/src/utils/hooks/useIsContractApproved'; import { useIsPermittedToBridge } from 'apps/bridge/src/utils/hooks/useIsPermittedToBridge'; import { useIsWalletConnected } from 'apps/bridge/src/utils/hooks/useIsWalletConnected'; import { usePrepareERC20Deposit } from 'apps/bridge/src/utils/hooks/usePrepareERC20Deposit'; +import { usePrepareERC20DepositTo } from 'apps/bridge/src/utils/hooks/usePrepareERC20DepositTo'; import { usePrepareETHDeposit } from 'apps/bridge/src/utils/hooks/usePrepareETHDeposit'; +import { utils } from 'ethers'; import { parseUnits } from 'ethers/lib/utils.js'; import getConfig from 'next/config'; import { useAccount, useBalance, useContractWrite } from 'wagmi'; @@ -29,6 +33,7 @@ export function DepositContainer() { const [depositAmount, setDepositAmount] = useState('0'); const [L1ApproveTxHash, setL1ApproveTxHash] = useState<`0x${string}` | undefined>(undefined); const [L1DepositTxHash, setL1DepositTxHash] = useState<`0x${string}` | undefined>(undefined); + const [depositTo, setDepositTo] = useState(''); const [isApprovalTx, setIsApprovalTx] = useState(false); const isWalletConnected = useIsWalletConnected(); const [selectedAsset, setSelectedAsset] = useState(assetList[0]); @@ -36,6 +41,8 @@ export function DepositContainer() { publicRuntimeConfig.assets.split(',').includes(asset.L1symbol.toLowerCase()), ); const { address } = useAccount(); + const codeAtAddress = useGetCode(address); + const isSmartContractWallet = !!codeAtAddress && codeAtAddress !== '0x'; const { data: L1Balance } = useBalance({ address, @@ -83,7 +90,7 @@ export function DepositContainer() { // deposit eth const depositETHConfig = usePrepareETHDeposit({ - userAddress: address, + userAddress: isSmartContractWallet ? (depositTo as `0x${string}`) : address, depositAmount, isPermittedToBridge, includeTosVersionByte, @@ -99,8 +106,17 @@ export function DepositContainer() { isPermittedToBridge, includeTosVersionByte, }); + const depositERC20ToConfig = usePrepareERC20DepositTo({ + asset: selectedAsset, + to: depositTo as `0x${string}`, + depositAmount, + readApprovalResult, + isPermittedToBridge, + includeTosVersionByte, + }); const { writeAsync: depositERC20Write } = useContractWrite(depositERC20Config); + const { writeAsync: depositERC20ToWrite } = useContractWrite(depositERC20ToConfig); const initiateApproval = useCallback(() => { void (async () => { @@ -118,7 +134,9 @@ export function DepositContainer() { // next, call the transfer function setIsApprovalTx(false); - const depositResult = await depositERC20Write?.(); + const depositResult = await (isSmartContractWallet + ? depositERC20ToWrite?.() + : depositERC20Write?.()); if (depositResult?.hash) { const depositTxHash = depositResult.hash; setL1DepositTxHash(depositTxHash); @@ -128,7 +146,14 @@ export function DepositContainer() { onCloseDepositModal(); } })(); - }, [approveWrite, depositERC20Write, onCloseDepositModal, onOpenDepositModal, setIsApprovalTx]); + }, [ + approveWrite, + depositERC20ToWrite, + depositERC20Write, + isSmartContractWallet, + onCloseDepositModal, + onOpenDepositModal, + ]); const initiateDeposit = useCallback(() => { void (async () => { @@ -136,9 +161,17 @@ export function DepositContainer() { try { // Only bridge on mainnet if user has accepted ToS. Always allow bridging on testnet. if (isPermittedToBridge) { - const depositResult = await (selectedAsset.L1contract - ? depositERC20Write?.() - : depositETHWrite?.()); + let depositMethod; + if (selectedAsset.L1contract) { + if (isSmartContractWallet) { + depositMethod = depositERC20ToWrite; + } else { + depositMethod = depositERC20Write; + } + } else { + depositMethod = depositETHWrite; + } + const depositResult = await depositMethod?.(); if (depositResult?.hash) { const depositTxHash = depositResult.hash; setL1DepositTxHash(depositTxHash); @@ -155,6 +188,8 @@ export function DepositContainer() { onOpenDepositModal, isPermittedToBridge, selectedAsset.L1contract, + isSmartContractWallet, + depositERC20ToWrite, depositERC20Write, depositETHWrite, onCloseDepositModal, @@ -172,7 +207,8 @@ export function DepositContainer() { disabled={ parseFloat(depositAmount) <= 0 || parseFloat(depositAmount) >= parseFloat(L1Balance?.formatted ?? '0') || - depositAmount === '' + depositAmount === '' || + (isSmartContractWallet && !utils.isAddress(depositTo ?? '')) } toChainId={chainId} className="text-md flex w-full items-center justify-center rounded-md p-4 font-sans font-bold uppercase sm:w-auto" @@ -184,7 +220,7 @@ export function DepositContainer() { button = ( Approval @@ -216,6 +252,8 @@ export function DepositContainer() { {button} + {isSmartContractWallet && } +
); -} +} \ No newline at end of file diff --git a/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx b/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx index fd0ad3ced7..166b90926d 100644 --- a/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx +++ b/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx @@ -1,5 +1,6 @@ import { useCallback, useState } from 'react'; import { BridgeInput } from 'apps/bridge/src/components/BridgeInput/BridgeInput'; +import { BridgeToInput } from 'apps/bridge/src/components/BridgeToInput/BridgeToInput'; import { ConnectWalletButton } from 'apps/bridge/src/components/ConnectWalletButton/ConnectWalletButton'; import { FaqSidebar } from 'apps/bridge/src/components/Faq/FaqSidebar'; import { BaseButton } from 'apps/bridge/src/components/SwitchNetworkButton/SwitchNetworkButton'; @@ -9,10 +10,13 @@ import { Asset } from 'apps/bridge/src/types/Asset'; import { getAssetListForChainEnv } from 'apps/bridge/src/utils/assets/getAssetListForChainEnv'; import { useChainEnv } from 'apps/bridge/src/utils/hooks/useChainEnv'; import { useDisclosure } from 'apps/bridge/src/utils/hooks/useDisclosure'; +import { useGetCode } from 'apps/bridge/src/utils/hooks/useGetCode'; import { useIsPermittedToBridge } from 'apps/bridge/src/utils/hooks/useIsPermittedToBridge'; import { useIsWalletConnected } from 'apps/bridge/src/utils/hooks/useIsWalletConnected'; import { usePrepareERC20Withdrawal } from 'apps/bridge/src/utils/hooks/usePrepareERC20Withdrawal'; +import { usePrepareERC20WithdrawalTo } from 'apps/bridge/src/utils/hooks/usePrepareERC20WithdrawalTo'; import { usePrepareETHWithdrawal } from 'apps/bridge/src/utils/hooks/usePrepareETHWithdrawal'; +import { utils } from 'ethers'; import getConfig from 'next/config'; import { useAccount, useBalance, useContractWrite } from 'wagmi'; @@ -24,6 +28,7 @@ const chainId = parseInt(publicRuntimeConfig.l2ChainID); export function WithdrawContainer() { const [withdrawAmount, setWithdrawAmount] = useState(''); const [L2TxHash, setL2TxHash] = useState(''); + const [withdrawTo, setWithdrawTo] = useState(''); const isWalletConnected = useIsWalletConnected(); const activeAssets = assetList.filter((asset) => publicRuntimeConfig.assets.split(',').includes(asset.L1symbol.toLowerCase()), @@ -31,6 +36,9 @@ export function WithdrawContainer() { const [selectedAsset, setSelectedAsset] = useState(assetList[0]); const { address } = useAccount(); + const codeAtAddress = useGetCode(address); + const isSmartContractWallet = !!codeAtAddress && codeAtAddress !== '0x'; + const { data: L2Balance } = useBalance({ address, token: selectedAsset.L2contract, @@ -48,10 +56,19 @@ export function WithdrawContainer() { isPermittedToBridge, includeTosVersionByte, }); + const erc20WithdrawalToConfig = usePrepareERC20WithdrawalTo({ + asset: selectedAsset, + to: withdrawTo as `0x${string}`, + withdrawAmount, + isPermittedToBridge, + includeTosVersionByte, + }); + const { writeAsync: withdrawERC20 } = useContractWrite(erc20WithdrawalConfig); + const { writeAsync: withdrawERC20To } = useContractWrite(erc20WithdrawalToConfig); const withdrawConfig = usePrepareETHWithdrawal({ - userAddress: address, + userAddress: isSmartContractWallet ? (withdrawTo as `0x${string}`) : address, withdrawAmount, isPermittedToBridge, includeTosVersionByte, @@ -75,9 +92,17 @@ export function WithdrawContainer() { try { // Only bridge on mainnet if user has accepted ToS. Always allow bridging on testnet. if (isPermittedToBridge) { - const withdrawalResult = await (selectedAsset.L1contract - ? withdrawERC20?.() - : withdraw?.()); + let withdrawMethod; + if (selectedAsset.L1contract) { + if (isSmartContractWallet) { + withdrawMethod = withdrawERC20To; + } else { + withdrawMethod = withdrawERC20; + } + } else { + withdrawMethod = withdraw; + } + const withdrawalResult = await withdrawMethod?.(); if (withdrawalResult?.hash) { const withdrawalTxHsh = withdrawalResult.hash; setL2TxHash(withdrawalTxHsh); @@ -94,6 +119,8 @@ export function WithdrawContainer() { onOpenWithdrawModal, isPermittedToBridge, selectedAsset.L1contract, + isSmartContractWallet, + withdrawERC20To, withdrawERC20, withdraw, onCloseWithdrawModal, @@ -111,7 +138,8 @@ export function WithdrawContainer() { disabled={ parseFloat(withdrawAmount) <= 0 || parseFloat(withdrawAmount) >= parseFloat(L2Balance?.formatted ?? '0') || - withdrawAmount === '' + withdrawAmount === '' || + (isSmartContractWallet && !utils.isAddress(withdrawTo ?? '')) } toChainId={chainId} className="text-md flex w-full items-center justify-center rounded-md p-4 font-sans font-bold uppercase sm:w-auto" @@ -142,6 +170,11 @@ export function WithdrawContainer() { > {button} + + {isSmartContractWallet && ( + + )} +
); -} +} \ No newline at end of file diff --git a/apps/bridge/src/utils/hooks/useGetCode.ts b/apps/bridge/src/utils/hooks/useGetCode.ts new file mode 100644 index 0000000000..8157db24b8 --- /dev/null +++ b/apps/bridge/src/utils/hooks/useGetCode.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react'; +import { providers } from 'ethers'; +import { useProvider } from 'wagmi'; + +export function useGetCode(address?: `0x${string}`) { + const provider = useProvider(); + const [code, setCode] = useState(undefined); + + useEffect(() => { + if (address) { + void (async () => { + const codeAtAddress = await provider.getCode(address); + setCode(codeAtAddress); + })(); + } + }, [address, provider]); + + return code; +} \ No newline at end of file diff --git a/apps/bridge/src/utils/hooks/usePrepareERC20DepositTo.ts b/apps/bridge/src/utils/hooks/usePrepareERC20DepositTo.ts new file mode 100644 index 0000000000..5e3adc2626 --- /dev/null +++ b/apps/bridge/src/utils/hooks/usePrepareERC20DepositTo.ts @@ -0,0 +1,49 @@ +import L1StandartBridge from 'apps/bridge/src/contract-abis/L1StandardBridge'; +import { Asset } from 'apps/bridge/src/types/Asset'; +import { BigNumber } from 'ethers'; +import { parseUnits } from 'ethers/lib/utils.js'; +import getConfig from 'next/config'; +import { Address, usePrepareContractWrite } from 'wagmi'; + +const { publicRuntimeConfig } = getConfig(); + +type UsePrepareERC20DepositToProps = { + asset: Asset; + to: `0x${string}`; + depositAmount: string; + readApprovalResult?: boolean; + isPermittedToBridge: boolean; + includeTosVersionByte: boolean; +}; + +export function usePrepareERC20DepositTo({ + asset, + to, + depositAmount, + isPermittedToBridge, + includeTosVersionByte, +}: UsePrepareERC20DepositToProps) { + const { config: depositConfig } = usePrepareContractWrite({ + address: + isPermittedToBridge && depositAmount !== '' + ? publicRuntimeConfig.l1BridgeProxyAddress + : undefined, + abi: L1StandartBridge, + functionName: 'depositERC20To', + chainId: parseInt(publicRuntimeConfig.l1ChainID), + args: [ + asset.L1contract as Address, + asset.L2contract as Address, + to, + depositAmount !== '' + ? parseUnits(depositAmount, asset.decimals) + : parseUnits('0', asset.decimals), + 100000, + includeTosVersionByte ? publicRuntimeConfig.tosVersion : '0x', + ], + cacheTime: 0, + staleTime: 1, + overrides: { gasLimit: BigNumber.from(300000) }, + }); + return depositConfig; +} \ No newline at end of file diff --git a/apps/bridge/src/utils/hooks/usePrepareERC20WithdrawalTo.ts b/apps/bridge/src/utils/hooks/usePrepareERC20WithdrawalTo.ts new file mode 100644 index 0000000000..63a2a136d5 --- /dev/null +++ b/apps/bridge/src/utils/hooks/usePrepareERC20WithdrawalTo.ts @@ -0,0 +1,38 @@ +import L2StandardBridge from '@eth-optimism/contracts-bedrock/artifacts/contracts/L2/L2StandardBridge.sol/L2StandardBridge.json'; +import { Asset } from 'apps/bridge/src/types/Asset'; +import { parseUnits } from 'ethers/lib/utils.js'; +import getConfig from 'next/config'; +import { Address, usePrepareContractWrite } from 'wagmi'; + +const { publicRuntimeConfig } = getConfig(); + +type UsePrepareERC20WithdrawalTo = { + asset: Asset; + to: `0x${string}`; + withdrawAmount: string; + isPermittedToBridge: boolean; + includeTosVersionByte: boolean; +}; + +export function usePrepareERC20WithdrawalTo({ + asset, + to, + withdrawAmount, + isPermittedToBridge, + includeTosVersionByte, +}: UsePrepareERC20WithdrawalTo) { + const { config } = usePrepareContractWrite({ + address: isPermittedToBridge ? publicRuntimeConfig.L2StandardBridge : undefined, + abi: L2StandardBridge.abi, + functionName: 'withdrawTo', + chainId: parseInt(publicRuntimeConfig.l2ChainID), + args: [ + asset.L2contract as Address, + to, + parseUnits(withdrawAmount === '' ? '0' : withdrawAmount, asset.decimals), + 100000, + includeTosVersionByte ? publicRuntimeConfig.tosVersion : '0x', + ], + }); + return config; +} \ No newline at end of file From ac0f6fba626ce62c2c1b90f3a5af8202871178e3 Mon Sep 17 00:00:00 2001 From: Lukas Rosario Date: Wed, 20 Sep 2023 15:11:55 -0400 Subject: [PATCH 2/5] tx list updates --- .../array/mergeAndSortTransactionLists.ts | 34 +++++++------------ .../transactions/explorerTxToBridgeDeposit.ts | 11 +++--- .../explorerTxToBridgeWithdrawal.ts | 7 ++-- .../utils/transactions/isETHOrERC20Deposit.ts | 2 +- .../transactions/isETHOrERCWithdrawal.ts | 2 +- 5 files changed, 26 insertions(+), 30 deletions(-) diff --git a/apps/bridge/src/utils/array/mergeAndSortTransactionLists.ts b/apps/bridge/src/utils/array/mergeAndSortTransactionLists.ts index 50cbc39c3d..f6909b5792 100644 --- a/apps/bridge/src/utils/array/mergeAndSortTransactionLists.ts +++ b/apps/bridge/src/utils/array/mergeAndSortTransactionLists.ts @@ -4,29 +4,19 @@ export function mergeAndSortTransactionsLists( txList1: BridgeTransaction[], txList2: BridgeTransaction[], ) { - const merged = []; - let index1 = 0; - let index2 = 0; - let current = 0; - - while (current < txList1.length + txList2.length) { - const isTxList1Depleted = index1 >= txList1.length; - const isTxList2Depleted = index2 >= txList2.length; + return [...txList1, ...txList2].sort((a, b) => { + if (a.blockTimestamp && b.blockTimestamp) { + return parseInt(b.blockTimestamp) - parseInt(a.blockTimestamp); + } - if ( - !isTxList1Depleted && - (isTxList2Depleted || - parseInt(txList1[index1].blockTimestamp) < parseInt(txList2[index2].blockTimestamp)) - ) { - merged[current] = txList1[index1]; - index1 += 1; - } else { - merged[current] = txList2[index2]; - index2 += 1; + if (a.blockTimestamp) { + return -1; } - current += 1; - } + if (b.blockTimestamp) { + return 1; + } - return merged.reverse(); -} + return 0; + }); +} \ No newline at end of file diff --git a/apps/bridge/src/utils/transactions/explorerTxToBridgeDeposit.ts b/apps/bridge/src/utils/transactions/explorerTxToBridgeDeposit.ts index 85f250f4b3..21050f27ce 100644 --- a/apps/bridge/src/utils/transactions/explorerTxToBridgeDeposit.ts +++ b/apps/bridge/src/utils/transactions/explorerTxToBridgeDeposit.ts @@ -39,24 +39,27 @@ export function explorerTxToBridgeDeposit(tx: BlockExplorerTransaction): BridgeT }; } - const decodedWithdrawData = l1StandardBridgeInterface.decodeFunctionData( + const functionName = l1StandardBridgeInterface.getFunction(tx.input.slice(0, 10)).name; + const decodedDepositData = l1StandardBridgeInterface.decodeFunctionData( tx.input.slice(0, 10), tx.input, ); const token = assetList.find( (asset) => asset.L1chainId === parseInt(publicRuntimeConfig.l1ChainID) && - asset.L1contract?.toLowerCase() === (decodedWithdrawData[0] as string).toLowerCase(), + asset.L1contract?.toLowerCase() === (decodedDepositData[0] as string).toLowerCase(), ) as Asset; return { type: 'Deposit', from: tx.from, to: tx.to, assetSymbol: token.L1symbol ?? '', - amount: (decodedWithdrawData[2] as BigNumber).toString(), + amount: ( + (functionName === 'depositERC20' ? decodedDepositData[2] : decodedDepositData[3]) as BigNumber + ).toString(), blockTimestamp: tx.timeStamp, hash: tx.hash as `0x${string}`, status: 'Complete', priceApiId: token.apiId, }; -} +} \ No newline at end of file diff --git a/apps/bridge/src/utils/transactions/explorerTxToBridgeWithdrawal.ts b/apps/bridge/src/utils/transactions/explorerTxToBridgeWithdrawal.ts index af4726ad63..53cea33c14 100644 --- a/apps/bridge/src/utils/transactions/explorerTxToBridgeWithdrawal.ts +++ b/apps/bridge/src/utils/transactions/explorerTxToBridgeWithdrawal.ts @@ -38,6 +38,7 @@ export function explorerTxToBridgeWithdrawal(tx: BlockExplorerTransaction): Brid }; } + const functionName = l2StandardBridgeInterface.getFunction(tx.input.slice(0, 10)).name; const decodedWithdrawData = l2StandardBridgeInterface.decodeFunctionData( tx.input.slice(0, 10), tx.input, @@ -52,9 +53,11 @@ export function explorerTxToBridgeWithdrawal(tx: BlockExplorerTransaction): Brid from: tx.from, to: tx.to, assetSymbol: token.L2symbol ?? '', - amount: (decodedWithdrawData[1] as BigNumber).toString(), + amount: ( + (functionName === 'withdraw' ? decodedWithdrawData[1] : decodedWithdrawData[2]) as BigNumber + ).toString(), blockTimestamp: tx.timeStamp, hash: tx.hash as `0x${string}`, priceApiId: token.apiId, }; -} +} \ No newline at end of file diff --git a/apps/bridge/src/utils/transactions/isETHOrERC20Deposit.ts b/apps/bridge/src/utils/transactions/isETHOrERC20Deposit.ts index 502fe83a43..e2a7912e5a 100644 --- a/apps/bridge/src/utils/transactions/isETHOrERC20Deposit.ts +++ b/apps/bridge/src/utils/transactions/isETHOrERC20Deposit.ts @@ -32,7 +32,7 @@ export function isETHOrERC20Deposit(tx: BlockExplorerTransaction) { // ERC-20 desposit if (tx.to === ERC20_DEPOSIT_ADDRESS) { const functionName = l1StandardBridgeInterface.getFunction(tx.input.slice(0, 10)).name; - if (functionName === 'depositERC20') { + if (functionName === 'depositERC20' || functionName === 'depositERC20To') { return true; } } diff --git a/apps/bridge/src/utils/transactions/isETHOrERCWithdrawal.ts b/apps/bridge/src/utils/transactions/isETHOrERCWithdrawal.ts index 83fe9554b2..a39517b1e8 100644 --- a/apps/bridge/src/utils/transactions/isETHOrERCWithdrawal.ts +++ b/apps/bridge/src/utils/transactions/isETHOrERCWithdrawal.ts @@ -32,7 +32,7 @@ export function isETHOrERC20Withdrawal(tx: BlockExplorerTransaction) { // ERC-20 Withdrawal if (tx.to === ERC20_WITHDRAWAL_ADDRESS) { const functionName = l2StandardBridgeInterface.getFunction(tx.input.slice(0, 10)).name; - if (functionName === 'withdraw') { + if (functionName === 'withdraw' || functionName === 'withdrawTo') { return true; } } From 979dbf5e043d38fdd83d7ad3cab47f5859fd575c Mon Sep 17 00:00:00 2001 From: Lukas Rosario Date: Thu, 21 Sep 2023 18:18:58 -0400 Subject: [PATCH 3/5] compliance check --- .../DepositContainer/DepositContainer.tsx | 11 +++++++---- .../WithdrawContainer/WithdrawContainer.tsx | 9 ++++++--- apps/bridge/src/contexts/OFACContext.tsx | 2 +- .../src/utils/hooks/useIsPermittedToBridgeTo.ts | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 apps/bridge/src/utils/hooks/useIsPermittedToBridgeTo.ts diff --git a/apps/bridge/src/components/DepositContainer/DepositContainer.tsx b/apps/bridge/src/components/DepositContainer/DepositContainer.tsx index 2206aba231..bdded8e46b 100644 --- a/apps/bridge/src/components/DepositContainer/DepositContainer.tsx +++ b/apps/bridge/src/components/DepositContainer/DepositContainer.tsx @@ -23,6 +23,7 @@ import { utils } from 'ethers'; import { parseUnits } from 'ethers/lib/utils.js'; import getConfig from 'next/config'; import { useAccount, useBalance, useContractWrite } from 'wagmi'; +import { useIsPermittedToBridgeTo } from 'apps/bridge/src/utils/hooks/useIsPermittedToBridgeTo'; const assetList = getAssetListForChainEnv(); @@ -86,7 +87,9 @@ export function DepositContainer() { const chainEnv = useChainEnv(); const isMainnet = chainEnv === 'mainnet'; const includeTosVersionByte = isMainnet; - const isPermittedToBridge = useIsPermittedToBridge(); + const isUserPermittedToBridge = useIsPermittedToBridge(); + const isPermittedToBridgeTo = useIsPermittedToBridgeTo(depositTo as `0x${string}`); + const isPermittedToBridge = isSmartContractWallet ? isUserPermittedToBridge && isPermittedToBridgeTo : isUserPermittedToBridge; // deposit eth const depositETHConfig = usePrepareETHDeposit({ @@ -205,10 +208,10 @@ export function DepositContainer() { = parseFloat(L1Balance?.formatted ?? '0') || depositAmount === '' || - (isSmartContractWallet && !utils.isAddress(depositTo ?? '')) + (isSmartContractWallet && !utils.isAddress(depositTo ?? ''))) || !isPermittedToBridge } toChainId={chainId} className="text-md flex w-full items-center justify-center rounded-md p-4 font-sans font-bold uppercase sm:w-auto" @@ -220,7 +223,7 @@ export function DepositContainer() { button = ( Approval diff --git a/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx b/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx index 166b90926d..dae2421de8 100644 --- a/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx +++ b/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx @@ -19,6 +19,7 @@ import { usePrepareETHWithdrawal } from 'apps/bridge/src/utils/hooks/usePrepareE import { utils } from 'ethers'; import getConfig from 'next/config'; import { useAccount, useBalance, useContractWrite } from 'wagmi'; +import { useIsPermittedToBridgeTo } from 'apps/bridge/src/utils/hooks/useIsPermittedToBridgeTo'; const assetList = getAssetListForChainEnv(); @@ -48,7 +49,9 @@ export function WithdrawContainer() { const chainEnv = useChainEnv(); const isMainnet = chainEnv === 'mainnet'; const includeTosVersionByte = isMainnet; - const isPermittedToBridge = useIsPermittedToBridge(); + const isUserPermittedToBridge = useIsPermittedToBridge(); + const isPermittedToBridgeTo = useIsPermittedToBridgeTo(withdrawTo as `0x${string}`); + const isPermittedToBridge = isSmartContractWallet ? isUserPermittedToBridge && isPermittedToBridgeTo : isUserPermittedToBridge; const erc20WithdrawalConfig = usePrepareERC20Withdrawal({ asset: selectedAsset, @@ -136,10 +139,10 @@ export function WithdrawContainer() { = parseFloat(L2Balance?.formatted ?? '0') || withdrawAmount === '' || - (isSmartContractWallet && !utils.isAddress(withdrawTo ?? '')) + (isSmartContractWallet && !utils.isAddress(withdrawTo ?? ''))) || !isPermittedToBridge } toChainId={chainId} className="text-md flex w-full items-center justify-center rounded-md p-4 font-sans font-bold uppercase sm:w-auto" diff --git a/apps/bridge/src/contexts/OFACContext.tsx b/apps/bridge/src/contexts/OFACContext.tsx index a74ed3c938..b633a0d66f 100644 --- a/apps/bridge/src/contexts/OFACContext.tsx +++ b/apps/bridge/src/contexts/OFACContext.tsx @@ -19,7 +19,7 @@ export const OFACContext = createContext({ isOFACAllowedLoading: false, }); -async function fetchIsAllowed(address?: `0x${string}`): Promise<{ result: boolean }> { +export async function fetchIsAllowed(address?: `0x${string}`): Promise<{ result: boolean }> { if (!address) { return { result: false }; } diff --git a/apps/bridge/src/utils/hooks/useIsPermittedToBridgeTo.ts b/apps/bridge/src/utils/hooks/useIsPermittedToBridgeTo.ts new file mode 100644 index 0000000000..80d25b93c8 --- /dev/null +++ b/apps/bridge/src/utils/hooks/useIsPermittedToBridgeTo.ts @@ -0,0 +1,14 @@ +import { fetchIsAllowed } from 'apps/bridge/src/contexts/OFACContext'; +import { useQuery } from 'react-query'; + +export function useIsPermittedToBridgeTo(address?: `0x${string}`) { + const { data: isBridgeToAllowed, isLoading: isBridgeToAllowedLoading } = useQuery( + ['isBridgeToAllowed', address], + async () => fetchIsAllowed(address), + { + select: (r) => r.result, + }, + ); + + return !!isBridgeToAllowed && !isBridgeToAllowedLoading; +} \ No newline at end of file From 55be441da24e1eded04d5f2af998e0a40dd9ee51 Mon Sep 17 00:00:00 2001 From: Lukas Rosario Date: Mon, 25 Sep 2023 15:15:33 -0400 Subject: [PATCH 4/5] fix prettier --- prettier.config.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/prettier.config.js b/prettier.config.js index 71f2360b16..9c8b8f963c 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,19 +1,19 @@ -{ - "arrowParens": "always", - "bracketSameLine": false, - "jsxSingleQuote": false, - "printWidth": 100, - "semi": true, - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "all", - "useTabs": false, - "overrides": [ +module.exports = { + arrowParens: 'always', + bracketSameLine: false, + jsxSingleQuote: false, + printWidth: 100, + semi: true, + singleQuote: true, + tabWidth: 2, + trailingComma: 'all', + useTabs: false, + overrides: [ { - "files": "*.json", - "options": { - "parser": "json-stringify" - } - } - ] -} \ No newline at end of file + files: '*.json', + options: { + parser: 'json-stringify', + }, + }, + ], +}; From 1ee304a736fe248ad64e3bbdd142d6c5d439ef95 Mon Sep 17 00:00:00 2001 From: Lukas Rosario Date: Mon, 25 Sep 2023 15:17:44 -0400 Subject: [PATCH 5/5] fix prettier & address nits --- .../DepositContainer/DepositContainer.tsx | 38 +++++++++++-------- .../WithdrawContainer/WithdrawContainer.tsx | 30 ++++++++------- .../array/mergeAndSortTransactionLists.ts | 2 +- apps/bridge/src/utils/hooks/useGetCode.ts | 2 +- .../utils/hooks/useIsPermittedToBridgeTo.ts | 2 +- 5 files changed, 41 insertions(+), 33 deletions(-) diff --git a/apps/bridge/src/components/DepositContainer/DepositContainer.tsx b/apps/bridge/src/components/DepositContainer/DepositContainer.tsx index bdded8e46b..be27baba98 100644 --- a/apps/bridge/src/components/DepositContainer/DepositContainer.tsx +++ b/apps/bridge/src/components/DepositContainer/DepositContainer.tsx @@ -89,7 +89,9 @@ export function DepositContainer() { const includeTosVersionByte = isMainnet; const isUserPermittedToBridge = useIsPermittedToBridge(); const isPermittedToBridgeTo = useIsPermittedToBridgeTo(depositTo as `0x${string}`); - const isPermittedToBridge = isSmartContractWallet ? isUserPermittedToBridge && isPermittedToBridgeTo : isUserPermittedToBridge; + const isPermittedToBridge = isSmartContractWallet + ? isUserPermittedToBridge && isPermittedToBridgeTo + : isUserPermittedToBridge; // deposit eth const depositETHConfig = usePrepareETHDeposit({ @@ -166,11 +168,7 @@ export function DepositContainer() { if (isPermittedToBridge) { let depositMethod; if (selectedAsset.L1contract) { - if (isSmartContractWallet) { - depositMethod = depositERC20ToWrite; - } else { - depositMethod = depositERC20Write; - } + depositMethod = isSmartContractWallet ? depositERC20ToWrite : depositERC20Write; } else { depositMethod = depositETHWrite; } @@ -199,20 +197,23 @@ export function DepositContainer() { ]); let button; + let depositDisabled; if (!isWalletConnected) { button = ( ); } else if (readApprovalResult || selectedAsset.L1symbol === 'ETH') { + depositDisabled = + parseFloat(depositAmount) <= 0 || + parseFloat(depositAmount) >= parseFloat(L1Balance?.formatted ?? '0') || + depositAmount === '' || + (isSmartContractWallet && !utils.isAddress(depositTo ?? '')) || + !isPermittedToBridge; + button = ( = parseFloat(L1Balance?.formatted ?? '0') || - depositAmount === '' || - (isSmartContractWallet && !utils.isAddress(depositTo ?? ''))) || !isPermittedToBridge - } + disabled={depositDisabled} toChainId={chainId} className="text-md flex w-full items-center justify-center rounded-md p-4 font-sans font-bold uppercase sm:w-auto" > @@ -220,10 +221,13 @@ export function DepositContainer() { ); } else { + depositDisabled = + (isSmartContractWallet && !utils.isAddress(depositTo ?? '')) || !isPermittedToBridge; + button = ( Approval @@ -255,7 +259,9 @@ export function DepositContainer() { {button} - {isSmartContractWallet && } + {isSmartContractWallet && ( + + )}
-
{button}
+
{button}
); -} \ No newline at end of file +} diff --git a/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx b/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx index dae2421de8..f2bad5c490 100644 --- a/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx +++ b/apps/bridge/src/components/WithdrawContainer/WithdrawContainer.tsx @@ -51,7 +51,9 @@ export function WithdrawContainer() { const includeTosVersionByte = isMainnet; const isUserPermittedToBridge = useIsPermittedToBridge(); const isPermittedToBridgeTo = useIsPermittedToBridgeTo(withdrawTo as `0x${string}`); - const isPermittedToBridge = isSmartContractWallet ? isUserPermittedToBridge && isPermittedToBridgeTo : isUserPermittedToBridge; + const isPermittedToBridge = isSmartContractWallet + ? isUserPermittedToBridge && isPermittedToBridgeTo + : isUserPermittedToBridge; const erc20WithdrawalConfig = usePrepareERC20Withdrawal({ asset: selectedAsset, @@ -97,11 +99,7 @@ export function WithdrawContainer() { if (isPermittedToBridge) { let withdrawMethod; if (selectedAsset.L1contract) { - if (isSmartContractWallet) { - withdrawMethod = withdrawERC20To; - } else { - withdrawMethod = withdrawERC20; - } + withdrawMethod = isSmartContractWallet ? withdrawERC20To : withdrawERC20; } else { withdrawMethod = withdraw; } @@ -130,20 +128,24 @@ export function WithdrawContainer() { ]); let button; + let withdrawDisabled; + if (!isWalletConnected) { button = ( ); } else { + withdrawDisabled = + parseFloat(withdrawAmount) <= 0 || + parseFloat(withdrawAmount) >= parseFloat(L2Balance?.formatted ?? '0') || + withdrawAmount === '' || + (isSmartContractWallet && !utils.isAddress(withdrawTo ?? '')) || + !isPermittedToBridge; + button = ( = parseFloat(L2Balance?.formatted ?? '0') || - withdrawAmount === '' || - (isSmartContractWallet && !utils.isAddress(withdrawTo ?? ''))) || !isPermittedToBridge - } + disabled={withdrawDisabled} toChainId={chainId} className="text-md flex w-full items-center justify-center rounded-md p-4 font-sans font-bold uppercase sm:w-auto" > @@ -187,10 +189,10 @@ export function WithdrawContainer() { chainId={publicRuntimeConfig.l2ChainID} isDeposit={false} /> -
{button}
+
{button}
); -} \ No newline at end of file +} diff --git a/apps/bridge/src/utils/array/mergeAndSortTransactionLists.ts b/apps/bridge/src/utils/array/mergeAndSortTransactionLists.ts index f6909b5792..41891f1a2a 100644 --- a/apps/bridge/src/utils/array/mergeAndSortTransactionLists.ts +++ b/apps/bridge/src/utils/array/mergeAndSortTransactionLists.ts @@ -19,4 +19,4 @@ export function mergeAndSortTransactionsLists( return 0; }); -} \ No newline at end of file +} diff --git a/apps/bridge/src/utils/hooks/useGetCode.ts b/apps/bridge/src/utils/hooks/useGetCode.ts index 8157db24b8..05f86ca826 100644 --- a/apps/bridge/src/utils/hooks/useGetCode.ts +++ b/apps/bridge/src/utils/hooks/useGetCode.ts @@ -16,4 +16,4 @@ export function useGetCode(address?: `0x${string}`) { }, [address, provider]); return code; -} \ No newline at end of file +} diff --git a/apps/bridge/src/utils/hooks/useIsPermittedToBridgeTo.ts b/apps/bridge/src/utils/hooks/useIsPermittedToBridgeTo.ts index 80d25b93c8..3ccf3275a7 100644 --- a/apps/bridge/src/utils/hooks/useIsPermittedToBridgeTo.ts +++ b/apps/bridge/src/utils/hooks/useIsPermittedToBridgeTo.ts @@ -11,4 +11,4 @@ export function useIsPermittedToBridgeTo(address?: `0x${string}`) { ); return !!isBridgeToAllowed && !isBridgeToAllowedLoading; -} \ No newline at end of file +}