Skip to content

Commit

Permalink
feat(bridge-ui-v2): account balance (#14159)
Browse files Browse the repository at this point in the history
Co-authored-by: Korbinian <KorbinianK@users.noreply.github.com>
  • Loading branch information
jscriptcoder and KorbinianK authored Jul 14, 2023
1 parent 9531a75 commit 081be64
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 17 deletions.
1 change: 1 addition & 0 deletions packages/bridge-ui-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"scripts": {
"dev": "vite dev",
"dev:a3": "vite dev --mode a3",
"build": "vite build",
"preview": "vite preview",
"test:pw": "playwright test",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,69 @@
<script lang="ts">
import type { FetchBalanceResult, GetAccountResult, PublicClient } from '@wagmi/core';
import { t } from 'svelte-i18n';
import { InputBox } from '$components/InputBox';
import LoadingText from '$components/LoadingText/LoadingText.svelte';
import { getBalance as getTokenBalance, type Token } from '$libs/token';
import { getLogger } from '$libs/util/logger';
import { truncateString } from '$libs/util/truncateString';
import { uid } from '$libs/util/uid';
import { account } from '$stores/account';
import { destChain, srcChain } from '$stores/network';
const log = getLogger('AmountInput');
export let token: Token;
let inputId = `input-${uid()}`;
let tokenBalance: Maybe<FetchBalanceResult>;
let computingTokenBalance = false;
async function updateTokenBalance(
token?: Token,
account?: GetAccountResult<PublicClient>,
srcChainId?: number,
destChainId?: number,
) {
if (!token || !account || !account.address) return;
computingTokenBalance = true;
try {
tokenBalance = await getTokenBalance(token, account.address, srcChainId, destChainId);
log('Token balance', tokenBalance);
} catch (error) {
console.error(error);
throw Error(`failed to get balance for ${token.symbol}`, { cause: error });
} finally {
computingTokenBalance = false;
}
}
export function renderTokenBalance(balance: Maybe<FetchBalanceResult>) {
if (!balance) return '0.00';
return `${truncateString(balance.formatted, 6)} ${balance.symbol}`;
}
$: updateTokenBalance(token, $account, $srcChain?.id, $destChain?.id);
</script>

<div class="f-col space-y-2">
<div class="f-between-center text-secondary-content">
<label class="body-regular" for={inputId}>{$t('amount_input.label')}</label>
<div class="body-small-regular">
<span>{$t('amount_input.balance')}:</span>
<span>399.92 ETH</span>
<span>
{#if computingTokenBalance}
<LoadingText mask="0.0000" class="text-white" />
<LoadingText mask="XXX" class="text-white" />
{:else}
{renderTokenBalance(tokenBalance)}
{/if}
</span>
</div>
</div>
<div class="relative f-items-center">
Expand Down
8 changes: 5 additions & 3 deletions packages/bridge-ui-v2/src/components/Bridge/Bridge.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,21 @@
import { ProcessingFee } from '$components/ProcessingFee';
import { RecipientInput } from '$components/RecipientInput';
import { TokenDropdown } from '$components/TokenDropdown';
import { tokens } from '$libs/token';
import { type Token, tokens } from '$libs/token';
import { destChain, srcChain } from '$stores/network';
let selectedToken: Token;
</script>

<Card class="md:w-[524px]" title={$t('bridge.title')} text={$t('bridge.subtitle')}>
<div class="space-y-[35px]">
<div class="space-y-4">
<div class="space-y-2">
<ChainSelector label={$t('chain.from')} value={$srcChain} />
<TokenDropdown {tokens} />
<TokenDropdown {tokens} bind:value={selectedToken} />
</div>

<AmountInput />
<AmountInput token={selectedToken} />

<div class="f-justify-center">
<button class="f-center rounded-full bg-secondary-icon w-[30px] h-[30px]">
Expand Down
11 changes: 5 additions & 6 deletions packages/bridge-ui-v2/src/components/Faucet/Faucet.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
let checkingMintable = false;
let switchingNetwork = false;
let selectedToken: Maybe<Token>;
let selectedToken: Token;
let mintButtonEnabled = false;
let reasonNotMintable = '';
Expand Down Expand Up @@ -90,15 +90,16 @@
// This function will check whether or not the button to mint should be
// enabled. If it shouldn't it'll also set the reason why so we can inform
// the user why they can't mint
async function shouldEnableMintButton(token: Maybe<Token>, network: Maybe<Chain>) {
async function updateMintButtonState(token?: Token, network?: Chain) {
if (!token || !network) return false;
checkingMintable = true;
mintButtonEnabled = false;
reasonNotMintable = '';
try {
await checkMintable(token, network);
return true;
mintButtonEnabled = true;
} catch (error) {
console.error(error);
Expand All @@ -121,8 +122,6 @@
} finally {
checkingMintable = false;
}
return false;
}
function getAlertMessage(connected: boolean, wrongChain: boolean, reasonNotMintable: string) {
Expand All @@ -135,7 +134,7 @@
$: wrongChain = isWrongChain($srcChain);
$: alertMessage = getAlertMessage(connected, wrongChain, reasonNotMintable);
$: shouldEnableMintButton(selectedToken, $srcChain).then((enable) => (mintButtonEnabled = enable));
$: updateMintButtonState(selectedToken, $srcChain);
</script>

<Card class="md:w-[524px]" title={$t('faucet.title')} text={$t('faucet.subtitle')}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script lang="ts">
import { classNames } from '$libs/util/classNames';
export let mask = 'Loading';
let classes = classNames('animate-pulse blur-sm font-bold', $$props.class);
</script>

<span class={classes}>{mask}</span>
Empty file.
27 changes: 20 additions & 7 deletions packages/bridge-ui-v2/src/libs/chain/chains.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Chain } from '@wagmi/core';
import type { Address } from 'abitype';

import {
PUBLIC_L1_BRIDGE_ADDRESS,
Expand All @@ -8,13 +9,15 @@ import {
PUBLIC_L1_EXPLORER_URL,
PUBLIC_L1_RPC_URL,
PUBLIC_L1_SIGNAL_SERVICE_ADDRESS,
PUBLIC_L1_TOKEN_VAULT_ADDRESS,
PUBLIC_L2_BRIDGE_ADDRESS,
PUBLIC_L2_CHAIN_ID,
PUBLIC_L2_CHAIN_NAME,
PUBLIC_L2_CROSS_CHAIN_SYNC_ADDRESS,
PUBLIC_L2_EXPLORER_URL,
PUBLIC_L2_RPC_URL,
PUBLIC_L2_SIGNAL_SERVICE_ADDRESS,
PUBLIC_L2_TOKEN_VAULT_ADDRESS,
} from '$env/static/public';

export const mainnetChain: Chain = {
Expand Down Expand Up @@ -61,15 +64,25 @@ export const taikoChain: Chain = {

export const chains = [mainnetChain, taikoChain];

export const chainContractsMap = {
export const chainContractsMap: Record<
string,
{
bridgeAddress: Address;
tokenVaultAddress: Address;
crossChainSyncAddress: Address;
signalServiceAddress: Address;
}
> = {
[PUBLIC_L1_CHAIN_ID]: {
bridgeAddress: PUBLIC_L1_BRIDGE_ADDRESS,
crossChainSyncAddress: PUBLIC_L1_CROSS_CHAIN_SYNC_ADDRESS,
signalServiceAddress: PUBLIC_L1_SIGNAL_SERVICE_ADDRESS,
bridgeAddress: PUBLIC_L1_BRIDGE_ADDRESS as Address,
tokenVaultAddress: PUBLIC_L1_TOKEN_VAULT_ADDRESS as Address,
crossChainSyncAddress: PUBLIC_L1_CROSS_CHAIN_SYNC_ADDRESS as Address,
signalServiceAddress: PUBLIC_L1_SIGNAL_SERVICE_ADDRESS as Address,
},
[PUBLIC_L2_CHAIN_ID]: {
bridgeAddress: PUBLIC_L2_BRIDGE_ADDRESS,
crossChainSyncAddress: PUBLIC_L2_CROSS_CHAIN_SYNC_ADDRESS,
signalServiceAddress: PUBLIC_L2_SIGNAL_SERVICE_ADDRESS,
bridgeAddress: PUBLIC_L2_BRIDGE_ADDRESS as Address,
tokenVaultAddress: PUBLIC_L2_TOKEN_VAULT_ADDRESS as Address,
crossChainSyncAddress: PUBLIC_L2_CROSS_CHAIN_SYNC_ADDRESS as Address,
signalServiceAddress: PUBLIC_L2_SIGNAL_SERVICE_ADDRESS as Address,
},
};
49 changes: 49 additions & 0 deletions packages/bridge-ui-v2/src/libs/token/getAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { getContract } from '@wagmi/core';
import { zeroAddress } from 'viem';

import { tokenVaultABI } from '$abi';
import { chainContractsMap } from '$libs/chain';
import { getLogger } from '$libs/util/logger';

import { isETH } from './tokens';
import type { Token } from './types';

const log = getLogger('token:getAddress');

export async function getAddress(token: Token, srcChainId?: number, destChainId?: number) {
if (!srcChainId) return;

// Get the address for the token on the source chain
let address = token.addresses[srcChainId];

// If the token isn't ETH or has no address...
if (!isETH(token) && (!address || address === zeroAddress)) {
if (!destChainId) return;

// Find the address on the destination chain instead. We are
// most likely on Taiko chain and the token hasn't yet been
// deployed on it.
const destChainTokenAddress = token.addresses[destChainId];

// Get the TokenVault contract on the source chain. The idea is to find
// the bridged address for the token on the destination chain if it's been
// deployed there. This is registered in the TokenVault contract,
// cacnonicalToBridged mapping.
const srcTokenVaultContract = getContract({
abi: tokenVaultABI,
address: chainContractsMap[srcChainId].tokenVaultAddress,
});

try {
address = await srcTokenVaultContract.read.canonicalToBridged([BigInt(destChainId), destChainTokenAddress]);

log(`Bridged address for ${token.symbol} is "${address}"`);
} catch (error) {
console.error(error);

throw Error(`Failed to get address for ${token.symbol} on chain ${srcChainId}`, { cause: error });
}
}

return address;
}
26 changes: 26 additions & 0 deletions packages/bridge-ui-v2/src/libs/token/getBalance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { fetchBalance } from '@wagmi/core';
import type { Address } from 'abitype';
import { zeroAddress } from 'viem';

import { getAddress } from './getAddress';
import { isETH } from './tokens';
import type { Token } from './types';

export async function getBalance(token: Token, userAddress: Address, srcChainId?: number, destChainId?: number) {
if (isETH(token)) {
return fetchBalance({ address: userAddress });
}

// We are dealing with an ERC20 token. We need to first find out its address
// on the current chain in order to fetch the balance.
const tokenAddress = await getAddress(token, srcChainId, destChainId);

if (!tokenAddress || tokenAddress === zeroAddress) return null;

// Wagmi is an excellent library 😊
return fetchBalance({
address: userAddress,
chainId: srcChainId,
token: tokenAddress,
});
}
2 changes: 2 additions & 0 deletions packages/bridge-ui-v2/src/libs/token/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export { checkMintable } from './checkMintable';
export { getAddress } from './getAddress';
export { getBalance } from './getBalance';
export { mint } from './mint';
export * from './tokens';
export * from './types';
9 changes: 9 additions & 0 deletions packages/bridge-ui-v2/src/libs/token/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,12 @@ export const testERC20Tokens: Token[] = jsonParseWithDefault<TokenEnv[]>(PUBLIC_
);

export const tokens = [ETHToken, ...testERC20Tokens];

export function isETH(token: Token) {
// Should be fine just by checking the symbol
return token.symbol.toLocaleLowerCase() === ETHToken.symbol.toLocaleLowerCase();
}

export function isERC20(token: Token): boolean {
return !isETH(token);
}
3 changes: 3 additions & 0 deletions packages/bridge-ui-v2/src/libs/util/truncateString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function truncateString(str: string, maxlength: number, strBoundary = '…') {
return str.length > maxlength ? `${str.substring(0, maxlength)}${strBoundary}` : str;
}

0 comments on commit 081be64

Please sign in to comment.