Skip to content

Commit

Permalink
feat(bridge-ui-v2): destination token bridged (#14448)
Browse files Browse the repository at this point in the history
Co-authored-by: Francisco <[email protected]>
  • Loading branch information
KorbinianK and jscriptcoder authored Aug 16, 2023
1 parent 35ef27d commit 072afbf
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import { Spinner } from '$components/Spinner';
import { activitiesConfig } from '$config';
import { type BridgeTransaction, fetchTransactions } from '$libs/bridge';
import { web3modal } from '$libs/connect';
import { bridgeTxService } from '$libs/storage';
import { account, network } from '$stores';
import type { Account } from '$stores/account';
Expand Down Expand Up @@ -51,8 +50,6 @@
return bridgeTx.slice(start, end);
};
const onWalletConnect = () => web3modal.openModal();
const onAccountChange = async (newAccount: Account, oldAccount?: Account) => {
// We want to make sure that we are connected and only
// fetch if the account has changed
Expand Down
17 changes: 2 additions & 15 deletions packages/bridge-ui-v2/src/components/Activities/Transaction.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import { t } from 'svelte-i18n';
import { formatEther } from 'viem';
import type { BridgeTransaction, MessageStatus } from '$libs/bridge';
import type { BridgeTransaction } from '$libs/bridge';
import { chainUrlMap } from '$libs/chain';
export let item: BridgeTransaction;
import { createEventDispatcher, onDestroy } from 'svelte';
import { createEventDispatcher } from 'svelte';
import { DesktopOrLarger } from '$components/DesktopOrLarger';
import { Icon } from '$components/Icon';
Expand All @@ -23,19 +23,6 @@
const handlePress = () => dispatch('press');
const mapStatusToText = (status: MessageStatus) => {
switch (status) {
case 1:
return 'Pending';
case 2:
return 'Claimed';
case 3:
return 'Failed';
default:
return 'Unknown';
}
};
let attrs = isDesktopOrLarger ? {} : { role: 'button' };
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import { InputBox } from '$components/InputBox';
import { LoadingText } from '$components/LoadingText';
import { Tooltip } from '$components/Tooltip';
import { processingFeeComponent } from '$config';
import { ProcessingFeeMethod } from '$libs/fee';
import { parseToWei } from '$libs/util/parseToWei';
import { uid } from '$libs/util/uid';
Expand Down Expand Up @@ -63,12 +62,6 @@
closeModal();
}
function closeModalWithDelay() {
// By adding delay there is enough time to see the selected option
// before closing the modal. Better experience for the user.
setTimeout(closeModal, processingFeeComponent.closingDelayOptionClick);
}
function focusInputBox() {
inputBox.focus();
}
Expand Down
2 changes: 0 additions & 2 deletions packages/bridge-ui-v2/src/components/InputBox/InputBox.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script lang="ts">
import { onMount } from 'svelte';
import { classNames } from '$libs/util/classNames';
export let error = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { erc20ABI, getNetwork,readContract } from '@wagmi/core';
import { erc20ABI, getNetwork, readContract } from '@wagmi/core';
import { fetchToken } from '@wagmi/core';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import type { Address } from 'viem';
import { formatUnits } from 'viem';
Expand All @@ -12,11 +13,16 @@
import Erc20 from '$components/Icon/ERC20.svelte';
import { Spinner } from '$components/Spinner';
import { tokenService } from '$libs/storage/services';
import { type Token, type TokenEnv,TokenType } from '$libs/token';
import { type GetCrossChainAddressArgs, type Token, type TokenEnv, TokenType } from '$libs/token';
import { getCrossChainAddress } from '$libs/token/getCrossChainAddress';
import { getLogger } from '$libs/util/logger';
import { uid } from '$libs/util/uid';
import { account } from '$stores/account';
import { destNetwork } from '../Bridge/state';
const dispatch = createEventDispatcher();
const log = getLogger('component:AddCustomERC20');
const dialogId = `dialog-${uid()}`;
Expand All @@ -32,10 +38,29 @@
let disabled = true;
let isValidEthereumAddress = false;
const addCustomErc20Token = () => {
const addCustomErc20Token = async () => {
if (customToken) {
tokenService.storeToken(customToken, $account?.address as Address);
customTokens = tokenService.getTokens($account?.address as Address);
const { chain: srcChain } = getNetwork();
const destChain = $destNetwork;
if (!srcChain || !destChain) return;
// let's check if this token has already been bridged
const bridgedAddress = await getCrossChainAddress({
token: customToken,
srcChainId: srcChain.id,
destChainId: destChain.id,
} as GetCrossChainAddressArgs);
// only update the token if we actually have a bridged address
if (bridgedAddress && bridgedAddress !== customToken.addresses[destChain.id]) {
customToken.addresses[destChain.id] = bridgedAddress as Address;
tokenService.updateToken(customToken, $account?.address as Address);
}
}
tokenAddress = '';
tokenDetails = null;
Expand All @@ -58,6 +83,7 @@
const address = $account.address;
tokenService.removeToken(token, address as Address);
customTokens = tokenService.getTokens(address as Address);
dispatch('tokenRemoved', { token });
};
const onAddressValidation = async (event: { detail: { isValidEthereumAddress: boolean } }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
</div>
</dialog>

<AddCustomErc20 bind:modalOpen />
<AddCustomErc20 bind:modalOpen on:tokenRemoved />

<!-- <OnNetwork change={onNetworkChange} /> -->
<OnAccount change={onAccountChange} />
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,6 @@
</li>
</ul>

<AddCustomErc20 bind:modalOpen={addArc20ModalOpen} />
<AddCustomErc20 bind:modalOpen={addArc20ModalOpen} on:tokenRemoved />

<OnAccount change={onAccountChange} />
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Erc20 from '$components/Icon/ERC20.svelte';
import { warningToast } from '$components/NotificationToast';
import { tokenService } from '$libs/storage/services';
import type { Token } from '$libs/token';
import { ETHToken, type Token } from '$libs/token';
import { getCrossChainAddress } from '$libs/token/getCrossChainAddress';
import { uid } from '$libs/util/uid';
import { account } from '$stores/account';
Expand Down Expand Up @@ -69,15 +69,26 @@
srcChainId: chain.id,
destChainId: destChain.id,
});
token.addresses[destChain.id] = bridgedAddress as Address;
tokenService.updateToken(token, $account?.address as Address);
// only update the token if we actually have a new bridged address
if (bridgedAddress && bridgedAddress !== token.addresses[destChain.id]) {
token.addresses[destChain.id] = bridgedAddress as Address;
tokenService.updateToken(token, $account?.address as Address);
}
}
value = token;
closeMenu();
};
const handleTokenRemoved = (event: { detail: { token: Token } }) => {
// if the selected token is the one that was removed by the user, remove it
if (event.detail.token === value) {
value = ETHToken;
}
};
onDestroy(() => closeMenu());
</script>

Expand Down Expand Up @@ -114,7 +125,7 @@
</button>

{#if isDesktopOrLarger}
<DropdownView {id} {menuOpen} {tokens} {value} {selectToken} />
<DropdownView {id} {menuOpen} {tokens} {value} {selectToken} on:tokenRemoved={handleTokenRemoved} />
{:else}
<DialogView {id} {menuOpen} {tokens} {value} {selectToken} {closeMenu} />
{/if}
Expand Down
4 changes: 2 additions & 2 deletions packages/bridge-ui-v2/src/libs/bridge/checkBalanceToBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ export async function checkBalanceToBridge({

const isTokenAlreadyDeployed = await isDeployedCrossChain({
token,
srcChainId: destChainId,
destChainId: srcChainId,
srcChainId,
destChainId,
});

try {
Expand Down
24 changes: 16 additions & 8 deletions packages/bridge-ui-v2/src/libs/fee/recommendProcessingFee.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { getPublicClient } from '@wagmi/core';
import { zeroAddress } from 'viem';

import { recommentProcessingFee } from '$config';
import { getAddress, type Token, TokenType } from '$libs/token';
import { isDeployedCrossChain, type Token, TokenType } from '$libs/token';
import { getLogger } from '$libs/util/logger';

const log = getLogger('libs:recommendedProcessingFee');

type RecommendProcessingFeeArgs = {
token: Token;
Expand All @@ -25,16 +27,22 @@ export async function recommendProcessingFee({ token, destChainId, srcChainId }:
throw Error('missing required source chain for ERC20 token');
}

const tokenAddress = await getAddress({ token, srcChainId, destChainId });
const isTokenAlreadyDeployed = await isDeployedCrossChain({
token,
srcChainId,
destChainId,
});

if (!tokenAddress || tokenAddress === zeroAddress) {
// Gas limit for erc20 if not deployed on the destination chain
// already is about ~2.9m, so we add some to make it enticing
gasLimit = erc20NotDeployedGasLimit;
} else {
if (isTokenAlreadyDeployed) {
// Gas limit for erc20 if already deployed on the destination chain is
// about ~1m, so again, add some to ensure processing
gasLimit = erc20DeployedGasLimit;
log(`token ${token.symbol} is already deployed on chain ${destChainId}`);
} else {
// Gas limit for erc20 if not deployed on the destination chain
// already is about ~2.9m, so we add some to make it enticing
gasLimit = erc20NotDeployedGasLimit;
log(`token ${token.symbol} is not deployed on chain ${destChainId}`);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class CustomTokenService implements TokenService {

// Filter out zero addresses from the new token's addresses
const filteredAddresses = Object.fromEntries(
Object.entries(token.addresses).filter(([key, value]) => value !== zeroAddress),
Object.entries(token.addresses).filter(([, value]) => value !== zeroAddress),
);

// Find the stored token to update
Expand Down
51 changes: 18 additions & 33 deletions packages/bridge-ui-v2/src/libs/token/getAddress.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { getContract, type GetContractResult } from '@wagmi/core';
import { type Address, getContract, type GetContractResult } from '@wagmi/core';

import { tokenVaultABI } from '$abi';
import { PUBLIC_L1_CHAIN_ID, PUBLIC_L2_CHAIN_ID } from '$env/static/public';
import { chainContractsMap } from '$libs/chain';

import { getAddress } from './getAddress';
import { ETHToken, testERC20Tokens } from './tokens';
Expand All @@ -16,46 +14,33 @@ const HORSEToken = testERC20Tokens[1];
const mockTokenContract = {
read: {
canonicalToBridged: vi.fn(),
isBridgedToken: vi.fn(),
bridgedToCanonical: vi.fn(),
},
} as unknown as GetContractResult<readonly unknown[], unknown>;

describe('getAddress', () => {
beforeAll(() => {
vi.mocked(getContract).mockReturnValue(mockTokenContract);
});
beforeEach(() => {
vi.resetAllMocks();

it('should return undefined if ETH', async () => {
expect(await getAddress({ token: ETHToken, srcChainId: Number(PUBLIC_L1_CHAIN_ID) })).toBeUndefined();
vi.mocked(getContract).mockReturnValue(mockTokenContract);
});

it('should return the address if ERC20 and has address on the source chain', async () => {
expect(await getAddress({ token: HORSEToken, srcChainId: Number(PUBLIC_L1_CHAIN_ID) })).toEqual(
HORSEToken.addresses[PUBLIC_L1_CHAIN_ID],
);
describe('ETH Tests', () => {
it('should return undefined if ETH', async () => {
expect(await getAddress({ token: ETHToken, srcChainId: Number(PUBLIC_L1_CHAIN_ID) })).toBeUndefined();
});
});

it('should return undefined if ERC20 and has no address on the source chain and no destination chain is is passed in', async () => {
expect(await getAddress({ token: HORSEToken, srcChainId: Number(PUBLIC_L2_CHAIN_ID) })).toBeUndefined();
});
describe('ERC20 Tests', () => {
it('should return the address if ERC20 and has address on the source chain', async () => {
expect(await getAddress({ token: HORSEToken, srcChainId: Number(PUBLIC_L1_CHAIN_ID) })).toEqual(
HORSEToken.addresses[PUBLIC_L1_CHAIN_ID],
);
});

it('should return the address of deployed ERC20 token', async () => {
vi.mocked(mockTokenContract.read.canonicalToBridged).mockResolvedValue('0x456789');

expect(
await getAddress({
token: HORSEToken,
srcChainId: Number(PUBLIC_L2_CHAIN_ID),
destChainId: Number(PUBLIC_L1_CHAIN_ID),
}),
).toEqual('0x456789');
expect(mockTokenContract.read.canonicalToBridged).toHaveBeenCalledWith([
BigInt(1),
HORSEToken.addresses[PUBLIC_L1_CHAIN_ID],
]);
expect(getContract).toHaveBeenCalledWith({
abi: tokenVaultABI,
address: chainContractsMap[PUBLIC_L2_CHAIN_ID].tokenVaultAddress,
chainId: Number(PUBLIC_L2_CHAIN_ID),
it('should return undefined if ERC20 and has no address on the source chain and no destination chain is is passed in', async () => {
expect(await getAddress({ token: HORSEToken, srcChainId: Number(PUBLIC_L2_CHAIN_ID) })).toBeUndefined();
});
});
});
Loading

1 comment on commit 072afbf

@vercel
Copy link

@vercel vercel bot commented on 072afbf Aug 16, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

bridge-ui-v2 – ./packages/bridge-ui-v2

bridge-ui-v2-git-main-taikoxyz.vercel.app
taiko-mono-bridge-ui-v2.vercel.app
bridge-ui-v2-taikoxyz.vercel.app

Please sign in to comment.