diff --git a/packages/bridge-ui/src/app.css b/packages/bridge-ui/src/app.css index f1e9de666cd..fba7e9c29ed 100644 --- a/packages/bridge-ui/src/app.css +++ b/packages/bridge-ui/src/app.css @@ -27,7 +27,6 @@ .btn.btn-accent { background-color: hsla(var(--af) / var(--tw-bg-opacity, 1)); border-color: hsla(var(--af) / var(--tw-bg-opacity, 1)); - height: 60px; } .btn.btn-accent:hover { diff --git a/packages/bridge-ui/src/bridge/ERC20Bridge.ts b/packages/bridge-ui/src/bridge/ERC20Bridge.ts index 2e79e42faca..e00a1e35813 100644 --- a/packages/bridge-ui/src/bridge/ERC20Bridge.ts +++ b/packages/bridge-ui/src/bridge/ERC20Bridge.ts @@ -173,15 +173,15 @@ export class ERC20Bridge implements Bridge { if (messageStatus === MessageStatus.New) { const proof = await this.prover.generateProof({ - srcChain: opts.message.srcChainId.toNumber(), + srcChain: opts.message.srcChainId, msgHash: opts.msgHash, sender: opts.srcBridgeAddress, srcBridgeAddress: opts.srcBridgeAddress, - destChain: opts.message.destChainId.toNumber(), + destChain: opts.message.destChainId, destHeaderSyncAddress: - chains[opts.message.destChainId.toNumber()].headerSyncAddress, + chains[opts.message.destChainId].headerSyncAddress, srcSignalServiceAddress: - chains[opts.message.srcChainId.toNumber()].signalServiceAddress, + chains[opts.message.srcChainId].signalServiceAddress, }); if (opts.message.gasLimit.gt(BigNumber.from(2500000))) { @@ -234,15 +234,14 @@ export class ERC20Bridge implements Bridge { if (messageStatus === MessageStatus.Failed) { const proofOpts = { - srcChain: opts.message.srcChainId.toNumber(), + srcChain: opts.message.srcChainId, msgHash: opts.msgHash, sender: opts.srcBridgeAddress, destBridgeAddress: opts.destBridgeAddress, - destChain: opts.message.destChainId.toNumber(), + destChain: opts.message.destChainId, destHeaderSyncAddress: - chains[opts.message.destChainId.toNumber()].headerSyncAddress, - srcHeaderSyncAddress: - chains[opts.message.srcChainId.toNumber()].headerSyncAddress, + chains[opts.message.destChainId].headerSyncAddress, + srcHeaderSyncAddress: chains[opts.message.srcChainId].headerSyncAddress, }; const proof = await this.prover.generateReleaseProof(proofOpts); diff --git a/packages/bridge-ui/src/bridge/ETHBridge.ts b/packages/bridge-ui/src/bridge/ETHBridge.ts index 8aceb3b8341..c82f6407ab5 100644 --- a/packages/bridge-ui/src/bridge/ETHBridge.ts +++ b/packages/bridge-ui/src/bridge/ETHBridge.ts @@ -7,11 +7,10 @@ import type { ClaimOpts, ReleaseOpts, } from '../domain/bridge'; -import TokenVault from '../constants/abi/TokenVault'; import type { Prover } from '../domain/proof'; -import { MessageStatus } from '../domain/message'; import BridgeABI from '../constants/abi/Bridge'; import { chains } from '../chain/chains'; +import { type Message, MessageStatus } from '../domain/message'; export class ETHBridge implements Bridge { private readonly prover: Prover; @@ -22,7 +21,7 @@ export class ETHBridge implements Bridge { static async prepareTransaction( opts: BridgeOpts, - ): Promise<{ contract: Contract; message: any; owner: string }> { + ): Promise<{ contract: Contract; message: Message; owner: string }> { const contract: Contract = new Contract( opts.bridgeAddress, BridgeABI, @@ -30,7 +29,7 @@ export class ETHBridge implements Bridge { ); const owner = await opts.signer.getAddress(); - const message = { + const message: Message = { sender: owner, srcChainId: opts.fromChainId, destChainId: opts.toChainId, @@ -113,15 +112,15 @@ export class ETHBridge implements Bridge { if (messageStatus === MessageStatus.New) { const proofOpts = { - srcChain: opts.message.srcChainId.toNumber(), + srcChain: opts.message.srcChainId, msgHash: opts.msgHash, sender: opts.srcBridgeAddress, srcBridgeAddress: opts.srcBridgeAddress, - destChain: opts.message.destChainId.toNumber(), + destChain: opts.message.destChainId, destHeaderSyncAddress: - chains[opts.message.destChainId.toNumber()].headerSyncAddress, + chains[opts.message.destChainId].headerSyncAddress, srcSignalServiceAddress: - chains[opts.message.srcChainId.toNumber()].signalServiceAddress, + chains[opts.message.srcChainId].signalServiceAddress, }; const proof = await this.prover.generateProof(proofOpts); @@ -169,15 +168,14 @@ export class ETHBridge implements Bridge { if (messageStatus === MessageStatus.Failed) { const proofOpts = { - srcChain: opts.message.srcChainId.toNumber(), + srcChain: opts.message.srcChainId, msgHash: opts.msgHash, sender: opts.srcBridgeAddress, destBridgeAddress: opts.destBridgeAddress, - destChain: opts.message.destChainId.toNumber(), + destChain: opts.message.destChainId, destHeaderSyncAddress: - chains[opts.message.destChainId.toNumber()].headerSyncAddress, - srcHeaderSyncAddress: - chains[opts.message.srcChainId.toNumber()].headerSyncAddress, + chains[opts.message.destChainId].headerSyncAddress, + srcHeaderSyncAddress: chains[opts.message.srcChainId].headerSyncAddress, }; const proof = await this.prover.generateReleaseProof(proofOpts); diff --git a/packages/bridge-ui/src/components/ChainDropdown.svelte b/packages/bridge-ui/src/components/ChainDropdown.svelte index db6d8e8c485..453e62a6421 100644 --- a/packages/bridge-ui/src/components/ChainDropdown.svelte +++ b/packages/bridge-ui/src/components/ChainDropdown.svelte @@ -1,5 +1,4 @@ <script lang="ts"> - import { _ } from 'svelte-i18n'; import { fromChain, toChain } from '../store/chain'; import type { Chain } from '../domain/chain'; import { ethers } from 'ethers'; diff --git a/packages/bridge-ui/src/components/TaikoBanner.svelte b/packages/bridge-ui/src/components/TaikoBanner.svelte index f04b21d13bc..0e38aa07021 100644 --- a/packages/bridge-ui/src/components/TaikoBanner.svelte +++ b/packages/bridge-ui/src/components/TaikoBanner.svelte @@ -1,5 +1,4 @@ <script> - import { _ } from 'svelte-i18n'; import SelectChain from './form/SelectChain.svelte'; </script> diff --git a/packages/bridge-ui/src/components/Transactions.svelte b/packages/bridge-ui/src/components/Transactions.svelte deleted file mode 100644 index 67bd281025a..00000000000 --- a/packages/bridge-ui/src/components/Transactions.svelte +++ /dev/null @@ -1,58 +0,0 @@ -<script lang="ts"> - import { transactions } from '../store/transactions'; - import Transaction from './Transaction.svelte'; - import TransactionDetail from './TransactionDetail.svelte'; - import MessageStatusTooltip from './MessageStatusTooltip.svelte'; - import InsufficientBalanceTooltip from './InsufficientBalanceTooltip.svelte'; - import type { BridgeTransaction } from '../domain/transactions'; - import { chains } from '../chain/chains'; - - let selectedTransaction: BridgeTransaction; - let showMessageStatusTooltip: boolean; - let showInsufficientBalance: boolean; -</script> - -<div class="my-4 md:px-4"> - {#if $transactions.length} - <table class="table-auto"> - <thead> - <tr class="text-transaction-table"> - <th>From</th> - <th>To</th> - <th>Amount</th> - <th>Status</th> - <th>Details</th> - </tr> - </thead> - <tbody class="text-sm md:text-base"> - {#each $transactions as transaction} - <Transaction - onTooltipClick={(showInsufficientBalanceMessage = false) => { - if (showInsufficientBalanceMessage) { - showInsufficientBalance = true; - } else { - showMessageStatusTooltip = true; - } - }} - onShowTransactionDetailsClick={() => { - selectedTransaction = transaction; - }} - toChain={chains[transaction.toChainId]} - fromChain={chains[transaction.fromChainId]} - {transaction} /> - {/each} - </tbody> - </table> - {:else} - No transactions - {/if} - - {#if selectedTransaction} - <TransactionDetail - transaction={selectedTransaction} - onClose={() => (selectedTransaction = null)} /> - {/if} - - <MessageStatusTooltip bind:show={showMessageStatusTooltip} /> - <InsufficientBalanceTooltip bind:show={showInsufficientBalance} /> -</div> diff --git a/packages/bridge-ui/src/components/InsufficientBalanceTooltip.svelte b/packages/bridge-ui/src/components/Transactions/InsufficientBalanceTooltip.svelte similarity index 87% rename from packages/bridge-ui/src/components/InsufficientBalanceTooltip.svelte rename to packages/bridge-ui/src/components/Transactions/InsufficientBalanceTooltip.svelte index f5a5dc6064b..68415e8460e 100644 --- a/packages/bridge-ui/src/components/InsufficientBalanceTooltip.svelte +++ b/packages/bridge-ui/src/components/Transactions/InsufficientBalanceTooltip.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import TooltipModal from './modals/TooltipModal.svelte'; + import TooltipModal from '../modals/TooltipModal.svelte'; export let show: boolean; </script> diff --git a/packages/bridge-ui/src/components/MessageStatusTooltip.svelte b/packages/bridge-ui/src/components/Transactions/MessageStatusTooltip.svelte similarity index 72% rename from packages/bridge-ui/src/components/MessageStatusTooltip.svelte rename to packages/bridge-ui/src/components/Transactions/MessageStatusTooltip.svelte index e98c856b45c..e6a9df8181d 100644 --- a/packages/bridge-ui/src/components/MessageStatusTooltip.svelte +++ b/packages/bridge-ui/src/components/Transactions/MessageStatusTooltip.svelte @@ -1,5 +1,6 @@ <script lang="ts"> - import TooltipModal from './modals/TooltipModal.svelte'; + import { L1_CHAIN_NAME, L2_CHAIN_NAME } from '../../constants/envVars'; + import TooltipModal from '../modals/TooltipModal.svelte'; export let show: boolean; </script> @@ -12,14 +13,9 @@ <ul class="list-disc ml-4"> <li class="mb-2"> <strong>Pending</strong>: Your asset is not ready to be bridged. Taiko - A2 => {import.meta.env - ? import.meta.env.VITE_MAINNET_CHAIN_NAME - : 'Ethereum A2'} bridging can take several hours before being ready. - {import.meta.env - ? import.meta.env.VITE_MAINNET_CHAIN_NAME - : 'Ethereum A2'} => {import.meta.env - ? import.meta.env.VITE_TAIKO_CHAIN_NAME - : 'Taiko A2'} should be available to claim within minutes. + A2 => {L2_CHAIN_NAME} bridging can take several hours before being ready. + {L1_CHAIN_NAME} => {L2_CHAIN_NAME} should be available to claim within + minutes. </li> <li class="mb-2"> <strong>Claim</strong>: Your asset is ready to be claimed on the diff --git a/packages/bridge-ui/src/components/Transaction.svelte b/packages/bridge-ui/src/components/Transactions/Transaction.svelte similarity index 68% rename from packages/bridge-ui/src/components/Transaction.svelte rename to packages/bridge-ui/src/components/Transactions/Transaction.svelte index 749cfc1ab5c..a83ec3d1b06 100644 --- a/packages/bridge-ui/src/components/Transaction.svelte +++ b/packages/bridge-ui/src/components/Transactions/Transaction.svelte @@ -1,43 +1,45 @@ <script lang="ts"> - import type { BridgeTransaction } from '../domain/transactions'; - import type { Chain } from '../domain/chain'; + import { createEventDispatcher } from 'svelte'; + import type { BridgeTransaction } from '../../domain/transactions'; import { ArrowTopRightOnSquare } from 'svelte-heros-v2'; - import { MessageStatus } from '../domain/message'; + import { MessageStatus } from '../../domain/message'; import { Contract, ethers } from 'ethers'; - import { signer } from '../store/signer'; - import { pendingTransactions } from '../store/transactions'; + import { signer } from '../../store/signer'; + import { pendingTransactions } from '../../store/transactions'; import { _ } from 'svelte-i18n'; - import { - fromChain as fromChainStore, - toChain as toChainStore, - } from '../store/chain'; - import { BridgeType } from '../domain/bridge'; + import { fromChain } from '../../store/chain'; + import { BridgeType } from '../../domain/bridge'; import { onDestroy, onMount } from 'svelte'; import { LottiePlayer } from '@lottiefiles/svelte-lottie-player'; - import { errorToast, successToast } from './Toast.svelte'; - import HeaderSyncABI from '../constants/abi/HeaderSync'; - import { fetchSigner, switchNetwork } from '@wagmi/core'; - import BridgeABI from '../constants/abi/Bridge'; - import ButtonWithTooltip from './ButtonWithTooltip.svelte'; - import TokenVaultABI from '../constants/abi/TokenVault'; - import { chains, mainnetChain, taikoChain } from '../chain/chains'; - import { providers } from '../provider/providers'; - import { bridges } from '../bridge/bridges'; - import { tokenVaults } from '../vault/tokenVaults'; - import { isOnCorrectChain } from '../utils/isOnCorrectChain'; + import { errorToast, successToast } from '../Toast.svelte'; + import HeaderSyncABI from '../../constants/abi/HeaderSync'; + import BridgeABI from '../../constants/abi/Bridge'; + import ButtonWithTooltip from '../ButtonWithTooltip.svelte'; + import TokenVaultABI from '../../constants/abi/TokenVault'; + import { chains } from '../../chain/chains'; + import { providers } from '../../provider/providers'; + import { bridges } from '../../bridge/bridges'; + import { tokenVaults } from '../../vault/tokenVaults'; + import { isOnCorrectChain } from '../../utils/isOnCorrectChain'; + import Button from '../buttons/Button.svelte'; + import { switchChainAndSetSigner } from '../../utils/switchChainAndSetSigner'; export let transaction: BridgeTransaction; - export let fromChain: Chain; - export let toChain: Chain; - export let onTooltipClick: (showInsufficientBalanceMessage: boolean) => void; - export let onShowTransactionDetailsClick: () => void; + const dispatch = createEventDispatcher<{ + tooltipClick: void; + insufficientBalance: void; + transactionDetailsClick: BridgeTransaction; + relayerAutoClaim: (informed: boolean) => Promise<void>; + }>(); let loading: boolean; - let processable: boolean = false; let interval: ReturnType<typeof setInterval>; + let txToChain = chains[transaction.toChainId]; + let txFromChain = chains[transaction.fromChainId]; + let alreadyInformedAboutClaim = false; onMount(async () => { processable = await isProcessable(); @@ -50,30 +52,35 @@ } }); - async function switchChainAndSetSigner(chain: Chain) { - await switchNetwork({ - chainId: chain.id, - }); - const provider = new ethers.providers.Web3Provider(window.ethereum); - await provider.send('eth_requestAccounts', []); - - fromChainStore.set(chain); - if (chain === mainnetChain) { - toChainStore.set(taikoChain); + async function onClaimClick() { + // Has the user sent processing fees?. We also check if the user + // has already been informed about the relayer auto-claim. + const processingFee = transaction.message?.processingFee.toString(); + if (processingFee && processingFee !== '0' && !alreadyInformedAboutClaim) { + dispatch( + 'relayerAutoClaim', + // TODO: this is a hack. The idea is to move all these + // functions outside of the component, where they + // make more sense. We don't need to repeat the same + // logic per transaction. + async (informed) => { + alreadyInformedAboutClaim = informed; + await claim(transaction); + }, + ); } else { - toChainStore.set(mainnetChain); + await claim(transaction); } - const wagmiSigner = await fetchSigner(); - signer.set(wagmiSigner); } + // TODO: move outside of component async function claim(bridgeTx: BridgeTransaction) { try { loading = true; // if the current "from chain", ie, the chain youre connected to, is not the destination // of the bridge transaction, we need to change chains so your wallet is pointed // to the right network. - if ($fromChainStore.id !== bridgeTx.toChainId) { + if ($fromChain.id !== bridgeTx.toChainId) { const chain = chains[bridgeTx.toChainId]; await switchChainAndSetSigner(chain); } @@ -88,7 +95,7 @@ // TODO: estimate Claim transaction const userBalance = await $signer.getBalance('latest'); if (!userBalance.gt(ethers.utils.parseEther('0.0001'))) { - onTooltipClick(true); + dispatch('insufficientBalance'); return; } @@ -110,6 +117,7 @@ }); successToast($_('toast.transactionSent')); + // TODO: keep the MessageStatus as contract and use another way. transaction.status = MessageStatus.ClaimInProgress; } catch (e) { console.error(e); @@ -119,10 +127,11 @@ } } + // TODO: move outside of component async function releaseTokens(bridgeTx: BridgeTransaction) { try { loading = true; - if (fromChain.id !== bridgeTx.fromChainId) { + if (txFromChain.id !== bridgeTx.fromChainId) { const chain = chains[bridgeTx.fromChainId]; await switchChainAndSetSigner(chain); } @@ -161,6 +170,7 @@ } } + // TODO: this could also live in an utility: isTransactionProcessable? async function isProcessable() { if (!transaction.receipt) return false; if (!transaction.message) return false; @@ -180,6 +190,7 @@ return transaction.receipt.blockNumber <= srcBlock.number; } + // TODO: web worker? function startInterval() { return setInterval(async () => { processable = await isProcessable(); @@ -234,14 +245,15 @@ <tr class="text-transaction-table"> <td> - <svelte:component this={fromChain.icon} height={18} width={18} /> - <span class="ml-2 hidden md:inline-block">{fromChain.name}</span> + <svelte:component this={txFromChain.icon} height={18} width={18} /> + <span class="ml-2 hidden md:inline-block">{txFromChain.name}</span> </td> <td> - <svelte:component this={toChain.icon} height={18} width={18} /> - <span class="ml-2 hidden md:inline-block">{toChain.name}</span> + <svelte:component this={txToChain.icon} height={18} width={18} /> + <span class="ml-2 hidden md:inline-block">{txToChain.name}</span> </td> <td> + <!-- TODO: function to check is we're dealing with ETH or ERC20? --> {transaction.message && (transaction.message?.data === '0x' || !transaction.message?.data) ? ethers.utils.formatEther( @@ -254,11 +266,9 @@ </td> <td> - <ButtonWithTooltip onClick={() => onTooltipClick(false)}> + <ButtonWithTooltip onClick={() => dispatch('tooltipClick')}> <span slot="buttonText"> - {#if transaction.receipt && transaction.receipt.status !== 1} - <span class="border border-transparent p-0">Failed</span> - {:else if !processable} + {#if !processable} Pending {:else if (!transaction.receipt && transaction.status === MessageStatus.New) || loading} <div class="inline-block"> @@ -274,29 +284,35 @@ controlsLayout={[]} /> </div> {:else if transaction.receipt && [MessageStatus.New, MessageStatus.ClaimInProgress].includes(transaction.status)} - <button - class="cursor-pointer border rounded p-1 btn btn-sm border-white disabled:border-gray-800" - on:click={async () => await claim(transaction)} + <!-- + TODO: we need some destructuring here. + We keep on accessing transaction props + over and over again. + --> + <Button + type="accent" + size="sm" + on:click={onClaimClick} disabled={transaction.status === MessageStatus.ClaimInProgress}> Claim - </button> + </Button> {:else if transaction.status === MessageStatus.Retriable} - <button - class="cursor-pointer border rounded p-1 btn btn-sm border-white" - on:click={async () => await claim(transaction)}> - Retry - </button> + <Button type="accent" size="sm" on:click={onClaimClick}>Retry</Button> {:else if transaction.status === MessageStatus.Failed} <!-- todo: releaseTokens() on src bridge with proof from destBridge--> - <button - class="cursor-pointer border rounded p-1 btn btn-sm border-white" + <Button + type="accent" + size="sm" on:click={async () => await releaseTokens(transaction)}> Release - </button> + </Button> {:else if transaction.status === MessageStatus.Done} <span class="border border-transparent p-0">Claimed</span> {:else if transaction.status === MessageStatus.FailedReleased} <span class="border border-transparent p-0">Released</span> + {:else if transaction.receipt && transaction.receipt.status !== 1} + <!-- TODO: make sure this is now respecting the correct flow --> + <span class="border border-transparent p-0">Failed</span> {/if} </span> </ButtonWithTooltip> @@ -305,7 +321,7 @@ <td> <button class="cursor-pointer inline-block" - on:click={onShowTransactionDetailsClick}> + on:click={() => dispatch('transactionDetailsClick', transaction)}> <ArrowTopRightOnSquare /> </button> </td> diff --git a/packages/bridge-ui/src/components/TransactionDetail.svelte b/packages/bridge-ui/src/components/Transactions/TransactionDetail.svelte similarity index 89% rename from packages/bridge-ui/src/components/TransactionDetail.svelte rename to packages/bridge-ui/src/components/Transactions/TransactionDetail.svelte index e5f3868acc5..d3a9f88246f 100644 --- a/packages/bridge-ui/src/components/TransactionDetail.svelte +++ b/packages/bridge-ui/src/components/Transactions/TransactionDetail.svelte @@ -1,11 +1,10 @@ <script lang="ts"> import { ethers } from 'ethers'; import { ArrowTopRightOnSquare } from 'svelte-heros-v2'; - import { truncateString } from '../utils/truncateString'; - import Modal from './modals/Modal.svelte'; - import type { BridgeTransaction } from '../domain/transactions'; - import { addressSubsection } from '../utils/addressSubsection'; - import { chains } from '../chain/chains'; + import Modal from '../modals/Modal.svelte'; + import type { BridgeTransaction } from '../../domain/transactions'; + import { addressSubsection } from '../../utils/addressSubsection'; + import { chains } from '../../chain/chains'; // TODO: can we always guarantee that this object is defined? // in which case we need to guard => transaction?.prop diff --git a/packages/bridge-ui/src/components/Transactions/Transactions.svelte b/packages/bridge-ui/src/components/Transactions/Transactions.svelte new file mode 100644 index 00000000000..13b34ff3e46 --- /dev/null +++ b/packages/bridge-ui/src/components/Transactions/Transactions.svelte @@ -0,0 +1,75 @@ +<script lang="ts"> + import { transactions } from '../../store/transactions'; + import Transaction from './Transaction.svelte'; + import TransactionDetail from './TransactionDetail.svelte'; + import MessageStatusTooltip from './MessageStatusTooltip.svelte'; + import InsufficientBalanceTooltip from './InsufficientBalanceTooltip.svelte'; + import type { BridgeTransaction } from '../../domain/transactions'; + import NoticeModal from '../modals/NoticeModal.svelte'; + + let selectedTransaction: BridgeTransaction; + let showMessageStatusTooltip: boolean; + let showInsufficientBalance: boolean; + let showRelayerAutoclaimTooltip: boolean; + + // TODO: temporary hack until we move the claim and release functions + // outside of the Transaction component. + let confirmNotice: (informed: boolean) => Promise<void>; +</script> + +<div class="my-4 md:px-4"> + {#if $transactions.length} + <table class="table-auto"> + <thead> + <tr class="text-transaction-table"> + <th>From</th> + <th>To</th> + <th>Amount</th> + <th>Status</th> + <th>Details</th> + </tr> + </thead> + <tbody class="text-sm md:text-base"> + {#each $transactions as transaction} + <Transaction + on:tooltipClick={() => (showMessageStatusTooltip = true)} + on:insufficientBalance={() => (showInsufficientBalance = true)} + on:relayerAutoClaim={({ detail }) => { + // We're passing the claiming of the transaction here, which + // will be called after confirming the notice. + confirmNotice = detail; + showRelayerAutoclaimTooltip = true; + }} + on:transactionDetailsClick={() => { + selectedTransaction = transaction; + }} + {transaction} /> + {/each} + </tbody> + </table> + {:else} + No transactions + {/if} + + {#if selectedTransaction} + <TransactionDetail + transaction={selectedTransaction} + onClose={() => (selectedTransaction = null)} /> + {/if} + + <MessageStatusTooltip bind:show={showMessageStatusTooltip} /> + + <InsufficientBalanceTooltip bind:show={showInsufficientBalance} /> + + <NoticeModal + bind:show={showRelayerAutoclaimTooltip} + onConfirm={confirmNotice} + name="RelayerAutoclaim"> + <!-- TODO: translations? --> + <div class="text-center"> + When bridging, you selected the <strong>Recommended</strong> or + <strong>Custom</strong> amount for the Processing Fee. You can wait for the + relayer to auto-claim the bridged token or manually claim it now. + </div> + </NoticeModal> +</div> diff --git a/packages/bridge-ui/src/components/Transactions/index.ts b/packages/bridge-ui/src/components/Transactions/index.ts new file mode 100644 index 00000000000..5c914036b27 --- /dev/null +++ b/packages/bridge-ui/src/components/Transactions/index.ts @@ -0,0 +1 @@ +export { default } from './Transactions.svelte'; diff --git a/packages/bridge-ui/src/components/buttons/Button.svelte b/packages/bridge-ui/src/components/buttons/Button.svelte new file mode 100644 index 00000000000..5c994c9d590 --- /dev/null +++ b/packages/bridge-ui/src/components/buttons/Button.svelte @@ -0,0 +1,16 @@ +<!-- + TODO: this is a PoC. The idea would be to have a button component that can be + styled based on props following daisyUI classes. +--> +<script lang="ts"> + import type { SizeButton, TypeButton } from '../../domain/button'; + + export let type: TypeButton = ''; + export let size: SizeButton = 'md'; + + const classes = `btn ${`btn-${type}`} ${`btn-${size}`} ${$$props.class}`; +</script> + +<button {...$$restProps} on:click class={classes}> + <slot /> +</button> diff --git a/packages/bridge-ui/src/components/form/BridgeForm.svelte b/packages/bridge-ui/src/components/form/BridgeForm.svelte index 256ebd02baf..a485da08382 100644 --- a/packages/bridge-ui/src/components/form/BridgeForm.svelte +++ b/packages/bridge-ui/src/components/form/BridgeForm.svelte @@ -3,12 +3,11 @@ import { LottiePlayer } from '@lottiefiles/svelte-lottie-player'; import { token } from '../../store/token'; - import { processingFee } from '../../store/fee'; import { fromChain, toChain } from '../../store/chain'; import { activeBridge, bridgeType } from '../../store/bridge'; import { signer } from '../../store/signer'; import { BigNumber, Contract, ethers, Signer } from 'ethers'; - import ProcessingFee from './ProcessingFee.svelte'; + import ProcessingFee from './ProcessingFee'; import SelectToken from '../buttons/SelectToken.svelte'; import type { Token } from '../../domain/token'; @@ -21,7 +20,6 @@ transactioner, transactions as transactionsStore, } from '../../store/transactions'; - import { ProcessingFeeMethod } from '../../domain/fee'; import Memo from './Memo.svelte'; import ERC20_ABI from '../../constants/abi/ERC20'; import TokenVaultABI from '../../constants/abi/TokenVault'; @@ -39,20 +37,22 @@ import { providers } from '../../provider/providers'; import { tokenVaults } from '../../vault/tokenVaults'; import { isOnCorrectChain } from '../../utils/isOnCorrectChain'; + import { ProcessingFeeMethod } from '../../domain/fee'; + import Button from '../buttons/Button.svelte'; let amount: string; let amountInput: HTMLInputElement; let requiresAllowance: boolean = false; let btnDisabled: boolean = true; let tokenBalance: string; - let customFee: string = '0'; - let recommendedFee: string = '0'; let memo: string = ''; let loading: boolean = false; let isFaucetModalOpen: boolean = false; let memoError: string; let to: string = ''; let showTo: boolean = false; + let feeMethod: ProcessingFeeMethod = ProcessingFeeMethod.RECOMMENDED; + let feeAmount: string = '0'; // TODO: too much going on here. We need to extract // logic and unit test the hell out of all this. @@ -333,10 +333,12 @@ memo: memo, to: showTo && to ? to : await $signer.getAddress(), }); + const requiredGas = gasEstimate.mul(feeData.gasPrice); const userBalance = await $signer.getBalance('latest'); const processingFee = getProcessingFee(); let balanceAvailableForTx = userBalance.sub(requiredGas); + if (processingFee) { balanceAvailableForTx = balanceAvailableForTx.sub(processingFee); } @@ -362,17 +364,11 @@ } function getProcessingFee() { - if ($processingFee === ProcessingFeeMethod.NONE) { + if (feeMethod === ProcessingFeeMethod.NONE) { return undefined; } - if ($processingFee === ProcessingFeeMethod.CUSTOM) { - return BigNumber.from(ethers.utils.parseEther(customFee)); - } - - if ($processingFee === ProcessingFeeMethod.RECOMMENDED) { - return BigNumber.from(ethers.utils.parseEther(recommendedFee)); - } + return BigNumber.from(ethers.utils.parseEther(feeAmount)); } $: getUserBalance($signer, $token, $fromChain); @@ -458,12 +454,12 @@ <To bind:showTo bind:to /> -<ProcessingFee bind:customFee bind:recommendedFee /> +<ProcessingFee bind:method={feeMethod} bind:amount={feeAmount} /> <Memo bind:memo bind:memoError /> {#if loading} - <button class="btn btn-accent w-full" disabled={true}> + <Button type="accent" size="lg" class="w-full" disabled={true}> <LottiePlayer src="/lottie/loader.json" autoplay={true} @@ -474,21 +470,24 @@ height={26} width={26} controlsLayout={[]} /> - </button> + </Button> {:else if !requiresAllowance} - <button - class="btn btn-accent w-full mt-4" + <Button + type="accent" + size="lg" + class="w-full" on:click={bridge} disabled={btnDisabled}> {$_('home.bridge')} - </button> + </Button> {:else} - <button - class="btn btn-accent approve-btn w-full mt-4" + <Button + type="accent" + class="w-full" on:click={approve} disabled={btnDisabled}> {$_('home.approve')} - </button> + </Button> {/if} <style> diff --git a/packages/bridge-ui/src/components/form/Memo.svelte b/packages/bridge-ui/src/components/form/Memo.svelte index 0e21be6519f..5c0347a7060 100644 --- a/packages/bridge-ui/src/components/form/Memo.svelte +++ b/packages/bridge-ui/src/components/form/Memo.svelte @@ -1,6 +1,5 @@ <script lang="ts"> import TooltipModal from '../modals/TooltipModal.svelte'; - import Tooltip from '../Tooltip.svelte'; import ButtonWithTooltip from '../ButtonWithTooltip.svelte'; export let memo: string = ''; diff --git a/packages/bridge-ui/src/components/form/ProcessingFee.svelte b/packages/bridge-ui/src/components/form/ProcessingFee.svelte deleted file mode 100644 index 37de24a0a90..00000000000 --- a/packages/bridge-ui/src/components/form/ProcessingFee.svelte +++ /dev/null @@ -1,108 +0,0 @@ -<script lang="ts"> - import { _ } from 'svelte-i18n'; - import { processingFee } from '../../store/fee'; - import { ProcessingFeeMethod } from '../../domain/fee'; - import { toChain, fromChain } from '../../store/chain'; - import { token } from '../../store/token'; - import { signer } from '../../store/signer'; - import { recommendProcessingFee } from '../../utils/recommendProcessingFee'; - import TooltipModal from '../modals/TooltipModal.svelte'; - import ButtonWithTooltip from '../ButtonWithTooltip.svelte'; - import { processingFees } from '../../fee/processingFees'; - - export let customFee: string; - export let recommendedFee: string = '0'; - - let tooltipOpen: boolean = false; - - $: recommendProcessingFee( - $toChain, - $fromChain, - $processingFee, - $token, - $signer, - ) - .then((fee) => (recommendedFee = fee)) - .catch((e) => console.error(e)); - - function selectProcessingFee(fee) { - $processingFee = fee; - } - - function updateAmount(e: any) { - customFee = (e.target.value as number).toString(); - } -</script> - -<div class="my-10"> - <div class="flex flex-row justify-between"> - <ButtonWithTooltip onClick={() => (tooltipOpen = true)}> - <span slot="buttonText">{$_('bridgeForm.processingFeeLabel')}</span> - </ButtonWithTooltip> - </div> - - {#if $processingFee === ProcessingFeeMethod.CUSTOM} - <label class="mt-2 input-group relative"> - <input - type="number" - step="0.01" - placeholder="0.01" - min="0" - on:input={updateAmount} - class="input input-primary bg-dark-2 border-dark-2 input-md md:input-lg w-full focus:ring-0 !rounded-r-none" - name="amount" /> - <span class="!rounded-r-lg bg-dark-2">ETH</span> - </label> - {:else if $processingFee === ProcessingFeeMethod.RECOMMENDED} - <div class="flex flex-row"> - <span class="mt-2 text-sm">{recommendedFee} ETH</span> - </div> - {/if} - - <div class="flex mt-2 space-x-2"> - {#each Array.from(processingFees) as fee} - <button - class="{$processingFee === fee[0] - ? 'border-accent hover:border-accent' - : ''} btn btn-md text-xs font-semibold md:w-32 dark:bg-dark-5" - on:click={() => selectProcessingFee(fee[0])} - >{fee[1].displayText}</button> - {/each} - </div> -</div> - -<TooltipModal title="Processing Fees" bind:isOpen={tooltipOpen}> - <span slot="body"> - <div class="text-left"> - The amount you pay the relayer to process your bridge message on the - destination chain. - <br /><br /> - <ul class="list-disc ml-4"> - <li> - <strong>Recommended</strong>: The recommended fee is the lowest fee - that will get your transaction processed in a reasonable amount of - time. - </li> - <li> - <strong>Custom</strong>: You can set a custom fee for the relayer to - incentivize them to prioritize your request. A lower fee may result in - longer processing time. - </li> - <li> - <strong>None</strong>: You can select no fee if you want to come back - here and claim the bridged asset yourself. - </li> - </ul> - </div> - </span> -</TooltipModal> - -<style> - /* hide number input arrows */ - input[type='number']::-webkit-outer-spin-button, - input[type='number']::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - -moz-appearance: textfield !important; - } -</style> diff --git a/packages/bridge-ui/src/components/form/ProcessingFee/ProcessingFee.svelte b/packages/bridge-ui/src/components/form/ProcessingFee/ProcessingFee.svelte new file mode 100644 index 00000000000..3d3dded0985 --- /dev/null +++ b/packages/bridge-ui/src/components/form/ProcessingFee/ProcessingFee.svelte @@ -0,0 +1,107 @@ +<script lang="ts"> + import { _ } from 'svelte-i18n'; + import { ProcessingFeeMethod } from '../../../domain/fee'; + import { toChain, fromChain } from '../../../store/chain'; + import { token } from '../../../store/token'; + import { signer } from '../../../store/signer'; + import { recommendProcessingFee } from '../../../utils/recommendProcessingFee'; + import ButtonWithTooltip from '../../ButtonWithTooltip.svelte'; + import { processingFees } from '../../../fee/processingFees'; + import GeneralTooltip from './ProcessingFeeTooltip.svelte'; + import NoticeModal from '../../modals/NoticeModal.svelte'; + + export let method: ProcessingFeeMethod = ProcessingFeeMethod.RECOMMENDED; + export let amount: string = '0'; + + let showProcessingFeeTooltip: boolean = false; + let showNoneFeeTooltip: boolean = false; + + $: recommendProcessingFee($toChain, $fromChain, method, $token, $signer) + .then((recommendedFee) => (amount = recommendedFee)) + .catch((e) => console.error(e)); + + function updateAmount(event: Event) { + const target = event.target as HTMLInputElement; + amount = target.value.toString(); + } + + function focus(input: HTMLInputElement) { + input.select(); + } + + function selectFee(selectedMethod: ProcessingFeeMethod) { + return () => { + method = selectedMethod; + if (selectedMethod === ProcessingFeeMethod.NONE) { + showNoneFeeTooltip = true; + } + }; + } +</script> + +<div class="my-10"> + <div class="flex flex-row justify-between"> + <ButtonWithTooltip onClick={() => (showProcessingFeeTooltip = true)}> + <span slot="buttonText">{$_('bridgeForm.processingFeeLabel')}</span> + </ButtonWithTooltip> + </div> + + <!-- + TODO: how about showing recommended also in a readonly input + and when clicking on Custom it becomes editable? + + TODO: transition between options + --> + {#if method === ProcessingFeeMethod.CUSTOM} + <label class="mt-2 input-group relative"> + <input + use:focus + type="number" + step="0.01" + placeholder="0.01" + min="0" + on:input={updateAmount} + class="input input-primary bg-dark-2 border-dark-2 input-md md:input-lg w-full focus:ring-0 !rounded-r-none" + name="amount" /> + <span class="!rounded-r-lg bg-dark-2">ETH</span> + </label> + {:else if method === ProcessingFeeMethod.RECOMMENDED} + <div class="flex flex-row"> + <span class="mt-2 text-sm">{amount} ETH</span> + </div> + {/if} + + <div class="flex mt-2 space-x-2"> + {#each Array.from(processingFees) as fee} + {@const [feeMethod, { displayText }] = fee} + {@const selected = method === feeMethod} + + <button + class="{selected + ? 'border-accent hover:border-accent' + : ''} btn btn-md text-xs font-semibold md:w-32 dark:bg-dark-5" + on:click={selectFee(feeMethod)}>{displayText}</button> + {/each} + </div> +</div> + +<GeneralTooltip bind:show={showProcessingFeeTooltip} /> + +<NoticeModal bind:show={showNoneFeeTooltip} name="NoneFeeTooltip"> + <!-- TODO: translations? --> + <div class="text-center"> + Selecting <strong>None</strong> means that you'll require ETH on the receiving + chain in otder to claim the bridged token. Pleas, come back later to manually + claim. + </div> +</NoticeModal> + +<style> + /* hide number input arrows */ + input[type='number']::-webkit-outer-spin-button, + input[type='number']::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + -moz-appearance: textfield !important; + } +</style> diff --git a/packages/bridge-ui/src/components/form/ProcessingFee/ProcessingFeeTooltip.svelte b/packages/bridge-ui/src/components/form/ProcessingFee/ProcessingFeeTooltip.svelte new file mode 100644 index 00000000000..4bac37572cd --- /dev/null +++ b/packages/bridge-ui/src/components/form/ProcessingFee/ProcessingFeeTooltip.svelte @@ -0,0 +1,31 @@ +<script> + import TooltipModal from '../../modals/TooltipModal.svelte'; + + export let show = false; +</script> + +<TooltipModal title="Processing Fees" bind:isOpen={show}> + <span slot="body"> + <div class="text-left"> + The amount you pay the relayer to process your bridge message on the + destination chain. + <br /><br /> + <ul class="list-disc ml-4"> + <li> + <strong>Recommended</strong>: The recommended fee is the lowest fee + that will get your transaction processed in a reasonable amount of + time. + </li> + <li> + <strong>Custom</strong>: You can set a custom fee for the relayer to + incentivize them to prioritize your request. A lower fee may result in + longer processing time. + </li> + <li> + <strong>None</strong>: You can select no fee if you want to come back + here and claim the bridged asset yourself. + </li> + </ul> + </div> + </span> +</TooltipModal> diff --git a/packages/bridge-ui/src/components/form/ProcessingFee/index.ts b/packages/bridge-ui/src/components/form/ProcessingFee/index.ts new file mode 100644 index 00000000000..645d7e05066 --- /dev/null +++ b/packages/bridge-ui/src/components/form/ProcessingFee/index.ts @@ -0,0 +1 @@ +export { default } from './ProcessingFee.svelte'; diff --git a/packages/bridge-ui/src/components/modals/Modal.svelte b/packages/bridge-ui/src/components/modals/Modal.svelte index cf82cfaf6b7..3e618fc827b 100644 --- a/packages/bridge-ui/src/components/modals/Modal.svelte +++ b/packages/bridge-ui/src/components/modals/Modal.svelte @@ -1,23 +1,22 @@ <script lang="ts"> export let title: string = null; export let isOpen: boolean = false; - export let onClose: () => void = null; export let showXButton: boolean = true; + export let onClose: () => void = null; const onCloseClicked = () => { isOpen = false; - if (onClose) { - onClose(); - } + onClose?.(); }; -</script> -<svelte:window - on:keydown={function (e) { + const onWindowKeydownPressed = (e) => { if (e.key === 'Escape') { onCloseClicked(); } - }} /> + }; +</script> + +<svelte:window on:keydown={onWindowKeydownPressed} /> <div class="modal bg-black/60" class:modal-open={isOpen}> <div class="modal-box bg-dark-2"> diff --git a/packages/bridge-ui/src/components/modals/NoticeModal.svelte b/packages/bridge-ui/src/components/modals/NoticeModal.svelte new file mode 100644 index 00000000000..65b15e44826 --- /dev/null +++ b/packages/bridge-ui/src/components/modals/NoticeModal.svelte @@ -0,0 +1,68 @@ +<script lang="ts"> + import { onMount } from 'svelte'; + import { localStoragePrefix } from '../../config'; + import Button from '../buttons/Button.svelte'; + import Modal from './Modal.svelte'; + + export let show = false; + export let name = 'NoticeModal'; + export let title = 'Notice'; + export let onConfirm: (noShowAgain: boolean) => void = null; + + let noShowAgainLocalStorageKey = `${localStoragePrefix}_${name}_noShowAgain`; + let noShowAgainStorage = false; + let noShowAgainCheckbox = false; + + onMount(() => { + // Has the user opted out of seeing this message? + noShowAgainStorage = Boolean( + localStorage.getItem(noShowAgainLocalStorageKey), + ); + noShowAgainCheckbox = noShowAgainStorage; + }); + + function onConfirmNotice() { + if (noShowAgainCheckbox) { + // If checkbox is checked, store it in localStorage so + // the user doesn't see the message again. + localStorage.setItem(noShowAgainLocalStorageKey, 'true'); + noShowAgainStorage = true; + } + + show = false; + + onConfirm?.(noShowAgainCheckbox); + } +</script> + +<!-- + TODO: we might want noShowAgainStorage to be dynamic, otherwise + the user will have to refresh the page to see the message again + if they delete the localStorage entry. +--> +<Modal {title} isOpen={show && !noShowAgainStorage} showXButton={false}> + <div + class=" + flex + w-full + flex-col + justify-between + space-y-6 + "> + <slot /> + + <div class="text-left flex items-center"> + <input + style:border-radius="0.5rem" + type="checkbox" + id="noShowAgain_{name}" + bind:checked={noShowAgainCheckbox} + class="checkbox checkbox-secundary mr-2" /> + <label for="noShowAgain_{name}">Do not show this message again</label> + </div> + + <div class="flex justify-center"> + <Button type="accent" on:click={onConfirmNotice}>Confirm</Button> + </div> + </div> +</Modal> diff --git a/packages/bridge-ui/src/config.ts b/packages/bridge-ui/src/config.ts new file mode 100644 index 00000000000..f0b417feb5d --- /dev/null +++ b/packages/bridge-ui/src/config.ts @@ -0,0 +1,3 @@ +export const localStoragePrefix = 'bridge-ui'; + +// Add more configuration items here diff --git a/packages/bridge-ui/src/domain/bridge.ts b/packages/bridge-ui/src/domain/bridge.ts index 9f467c22c27..2a69bd1b129 100644 --- a/packages/bridge-ui/src/domain/bridge.ts +++ b/packages/bridge-ui/src/domain/bridge.ts @@ -1,4 +1,5 @@ import type { BigNumber, ethers, Transaction } from 'ethers'; +import type { ChainID } from './chain'; import type { Message } from './message'; export enum BridgeType { @@ -19,8 +20,8 @@ export type BridgeOpts = { amountInWei: BigNumber; signer: ethers.Signer; tokenAddress: string; - fromChainId: number; - toChainId: number; + fromChainId: ChainID; + toChainId: ChainID; tokenVaultAddress?: string; bridgeAddress?: string; processingFeeInWei?: BigNumber; diff --git a/packages/bridge-ui/src/domain/button.ts b/packages/bridge-ui/src/domain/button.ts new file mode 100644 index 00000000000..3d9456c3726 --- /dev/null +++ b/packages/bridge-ui/src/domain/button.ts @@ -0,0 +1,5 @@ +// TODO: still to figure out after design + +export type TypeButton = 'primary' | 'secondary' | 'accent' | ''; // TODO: daisyUI? + +export type SizeButton = 'sm' | 'md' | 'lg'; diff --git a/packages/bridge-ui/src/domain/fee.ts b/packages/bridge-ui/src/domain/fee.ts index d27615c0b17..c68f0d0b0b1 100644 --- a/packages/bridge-ui/src/domain/fee.ts +++ b/packages/bridge-ui/src/domain/fee.ts @@ -5,6 +5,7 @@ export enum ProcessingFeeMethod { } export interface ProcessingFeeDetails { + method: ProcessingFeeMethod; displayText: string; timeToConfirm: number; } diff --git a/packages/bridge-ui/src/domain/message.ts b/packages/bridge-ui/src/domain/message.ts index 2d01befb0c4..fb50ca74a17 100644 --- a/packages/bridge-ui/src/domain/message.ts +++ b/packages/bridge-ui/src/domain/message.ts @@ -1,4 +1,5 @@ import type { BigNumber } from 'ethers'; +import type { ChainID } from './chain'; export enum MessageStatus { New, @@ -12,8 +13,8 @@ export enum MessageStatus { export type Message = { id: number; sender: string; - srcChainId: BigNumber; - destChainId: BigNumber; + srcChainId: ChainID; + destChainId: ChainID; owner: string; to: string; refundAddress: string; diff --git a/packages/bridge-ui/src/domain/relayerApi.ts b/packages/bridge-ui/src/domain/relayerApi.ts index e323d670695..a7e64c35f00 100644 --- a/packages/bridge-ui/src/domain/relayerApi.ts +++ b/packages/bridge-ui/src/domain/relayerApi.ts @@ -1,3 +1,4 @@ +import type { Address, ChainID } from './chain'; import type { BridgeTransaction } from './transactions'; export interface RelayerAPI { @@ -9,12 +10,31 @@ export interface RelayerAPI { getBlockInfo(): Promise<Map<number, RelayerBlockInfo>>; } +export type TransactionData = { + Message: { + Id: number; + SrcChainId: ChainID; + DestChainId: ChainID; + To: string; + Memo: string; + Owner: Address; + Sender: Address; + GasLimit: number; + CallValue: number; + DepositValue: number; + ProcessingFee: number; + RefundAddress: Address; + Data: string; + }; + Raw: { + transactionHash: string; + }; +}; + export type APIResponseTransaction = { id: number; name: string; - data: { - [key: string]: any; - }; + data: TransactionData; status: number; eventType: number; chainID: number; diff --git a/packages/bridge-ui/src/domain/transactions.ts b/packages/bridge-ui/src/domain/transactions.ts index 6ea4aa3bc75..45ed00ebce9 100644 --- a/packages/bridge-ui/src/domain/transactions.ts +++ b/packages/bridge-ui/src/domain/transactions.ts @@ -1,4 +1,5 @@ import type { BigNumber, ethers } from 'ethers'; +import type { ChainID } from './chain'; import type { Message, MessageStatus } from './message'; export type BridgeTransaction = { @@ -11,8 +12,8 @@ export type BridgeTransaction = { interval?: NodeJS.Timer; amountInWei?: BigNumber; symbol?: string; - fromChainId: number; - toChainId: number; + fromChainId: ChainID; + toChainId: ChainID; }; export interface Transactioner { diff --git a/packages/bridge-ui/src/fee/processingFees.ts b/packages/bridge-ui/src/fee/processingFees.ts index 1991718a298..d61b17dd3f2 100644 --- a/packages/bridge-ui/src/fee/processingFees.ts +++ b/packages/bridge-ui/src/fee/processingFees.ts @@ -6,6 +6,7 @@ export const processingFees: Map<ProcessingFeeMethod, ProcessingFeeDetails> = [ ProcessingFeeMethod.RECOMMENDED, { + method: ProcessingFeeMethod.RECOMMENDED, displayText: 'Recommended', timeToConfirm: 15 * 60 * 1000, }, @@ -13,6 +14,7 @@ export const processingFees: Map<ProcessingFeeMethod, ProcessingFeeDetails> = [ ProcessingFeeMethod.CUSTOM, { + method: ProcessingFeeMethod.CUSTOM, displayText: 'Custom', timeToConfirm: 15 * 60 * 1000, }, @@ -20,6 +22,7 @@ export const processingFees: Map<ProcessingFeeMethod, ProcessingFeeDetails> = [ ProcessingFeeMethod.NONE, { + method: ProcessingFeeMethod.NONE, displayText: 'None', timeToConfirm: 15 * 60 * 1000, }, diff --git a/packages/bridge-ui/src/pages/home/Home.svelte b/packages/bridge-ui/src/pages/home/Home.svelte index b4d1903f9d3..744b102884e 100644 --- a/packages/bridge-ui/src/pages/home/Home.svelte +++ b/packages/bridge-ui/src/pages/home/Home.svelte @@ -1,10 +1,9 @@ <script lang="ts"> - import { _ } from 'svelte-i18n'; import { location } from 'svelte-spa-router'; import { transactions } from '../../store/transactions'; import BridgeForm from '../../components/form/BridgeForm.svelte'; import TaikoBanner from '../../components/TaikoBanner.svelte'; - import Transactions from '../../components/Transactions.svelte'; + import Transactions from '../../components/Transactions'; import { Tabs, TabList, Tab, TabPanel } from '../../components/Tabs'; let bridgeWidth: number; diff --git a/packages/bridge-ui/src/relayer-api/RelayerAPIService.ts b/packages/bridge-ui/src/relayer-api/RelayerAPIService.ts index 3430bd62cf0..0acbe50135c 100644 --- a/packages/bridge-ui/src/relayer-api/RelayerAPIService.ts +++ b/packages/bridge-ui/src/relayer-api/RelayerAPIService.ts @@ -94,9 +94,9 @@ export class RelayerAPIService implements RelayerAPI { owner: tx.data.Message.Owner, sender: tx.data.Message.Sender, gasLimit: BigNumber.from(tx.data.Message.GasLimit), - callValue: tx.data.Message.CallValue, - srcChainId: BigNumber.from(tx.data.Message.SrcChainId), - destChainId: BigNumber.from(tx.data.Message.DestChainId), + callValue: BigNumber.from(tx.data.Message.CallValue), + srcChainId: tx.data.Message.SrcChainId, + destChainId: tx.data.Message.DestChainId, depositValue: BigNumber.from(`${tx.data.Message.DepositValue}`), processingFee: BigNumber.from(`${tx.data.Message.ProcessingFee}`), refundAddress: tx.data.Message.RefundAddress, diff --git a/packages/bridge-ui/src/storage/StorageService.spec.ts b/packages/bridge-ui/src/storage/StorageService.spec.ts index d6252abd0df..d9e5e799ee7 100644 --- a/packages/bridge-ui/src/storage/StorageService.spec.ts +++ b/packages/bridge-ui/src/storage/StorageService.spec.ts @@ -96,7 +96,7 @@ describe('storage tests', () => { const svc = new StorageService(mockStorage as any, providers); - const addresses = await svc.getAllByAddress('0x123', L2_CHAIN_ID); + const addresses = await svc.getAllByAddress('0x123'); expect(addresses).toEqual([]); }); @@ -120,7 +120,7 @@ describe('storage tests', () => { const svc = new StorageService(mockStorage as any, providers); - const addresses = await svc.getAllByAddress('0x123', L1_CHAIN_ID); + const addresses = await svc.getAllByAddress('0x123'); expect(addresses).toEqual([ { @@ -156,7 +156,7 @@ describe('storage tests', () => { const svc = new StorageService(mockStorage as any, providers); - const addresses = await svc.getAllByAddress('0x123', L1_CHAIN_ID); + const addresses = await svc.getAllByAddress('0x123'); expect(addresses).toEqual([ { @@ -201,7 +201,7 @@ describe('storage tests', () => { const svc = new StorageService(mockStorage as any, providers); - const addresses = await svc.getAllByAddress('0x123', L1_CHAIN_ID); + const addresses = await svc.getAllByAddress('0x123'); expect(addresses).toEqual([ { @@ -253,7 +253,7 @@ describe('storage tests', () => { const svc = new StorageService(mockStorage as any, providers); - const addresses = await svc.getAllByAddress('0x123', L2_CHAIN_ID); + const addresses = await svc.getAllByAddress('0x123'); expect(addresses).toEqual([ { diff --git a/packages/bridge-ui/src/storage/StorageService.ts b/packages/bridge-ui/src/storage/StorageService.ts index f632b61a7a9..855699b5a66 100644 --- a/packages/bridge-ui/src/storage/StorageService.ts +++ b/packages/bridge-ui/src/storage/StorageService.ts @@ -23,10 +23,7 @@ export class StorageService implements Transactioner { this.providers = providers; } - async getAllByAddress( - address: string, - chainID?: number, - ): Promise<BridgeTransaction[]> { + async getAllByAddress(address: string): Promise<BridgeTransaction[]> { const txs: BridgeTransaction[] = JSON.parse( this.storage.getItem(`transactions-${address.toLowerCase()}`), ); diff --git a/packages/bridge-ui/src/store/fee.ts b/packages/bridge-ui/src/store/fee.ts deleted file mode 100644 index 21d5a41bad0..00000000000 --- a/packages/bridge-ui/src/store/fee.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { writable } from 'svelte/store'; -import { ProcessingFeeMethod } from '../domain/fee'; - -export const processingFee = writable<ProcessingFeeMethod>( - ProcessingFeeMethod.RECOMMENDED, -); diff --git a/packages/bridge-ui/src/store/userToken.ts b/packages/bridge-ui/src/store/userToken.ts index c415a14d019..e4556e7d7a7 100644 --- a/packages/bridge-ui/src/store/userToken.ts +++ b/packages/bridge-ui/src/store/userToken.ts @@ -1,4 +1,4 @@ -import type { Token, TokenService } from 'src/domain/token'; +import type { Token, TokenService } from '../domain/token'; import { writable } from 'svelte/store'; const tokenService = writable<TokenService>(); diff --git a/packages/bridge-ui/src/utils/switchChainAndSetSigner.ts b/packages/bridge-ui/src/utils/switchChainAndSetSigner.ts new file mode 100644 index 00000000000..11ec1490706 --- /dev/null +++ b/packages/bridge-ui/src/utils/switchChainAndSetSigner.ts @@ -0,0 +1,26 @@ +import { fetchSigner, switchNetwork } from '@wagmi/core'; +import { ethers } from 'ethers'; +import { fromChain, toChain } from '../store/chain'; +import type { Chain } from '../domain/chain'; +import { mainnetChain, taikoChain } from '../chain/chains'; +import { signer } from '../store/signer'; + +export async function switchChainAndSetSigner(chain: Chain) { + const chainId = chain.id; + + await switchNetwork({ chainId }); + + const provider = new ethers.providers.Web3Provider(globalThis.ethereum); + await provider.send('eth_requestAccounts', []); + + fromChain.set(chain); + if (chain.id === mainnetChain.id) { + toChain.set(taikoChain); + } else { + toChain.set(mainnetChain); + } + + const wagmiSigner = await fetchSigner({ chainId }); + + signer.set(wagmiSigner); +} diff --git a/packages/bridge-ui/tailwind.config.cjs b/packages/bridge-ui/tailwind.config.cjs index 548e97c5efc..b365f4b8297 100644 --- a/packages/bridge-ui/tailwind.config.cjs +++ b/packages/bridge-ui/tailwind.config.cjs @@ -28,7 +28,6 @@ module.exports = { }, daisyui: { styled: true, - themes: true, base: true, utils: true, logs: true,