Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85e8e7da89 | ||
|
|
c2d4b2bee7 | ||
|
|
058b996052 | ||
|
|
9e947e87b0 | ||
|
|
a79ef0eb57 | ||
|
|
5e54597795 | ||
|
|
c13f7bdf38 | ||
|
|
957773d998 | ||
|
|
9c9dbdeb22 | ||
|
|
834999db37 | ||
|
|
52ac168c2a | ||
|
|
b207650edf | ||
|
|
7492bcd83d | ||
|
|
1723e25706 | ||
|
|
4afb3d5104 | ||
|
|
c5d3e1a19c | ||
|
|
7d450a911e | ||
|
|
7c9e399015 | ||
|
|
75976be02f | ||
|
|
3d71b20921 | ||
|
|
a1269bd76b | ||
|
|
7278d1233a | ||
|
|
8ebfb5b9e5 | ||
|
|
4082d4b8a9 | ||
|
|
ab10ebf197 | ||
|
|
c2c8379a9a | ||
|
|
71824d45d4 | ||
|
|
4be4d089b4 | ||
|
|
846a3d259d | ||
|
|
0d25fc62c5 | ||
|
|
9039b0304c | ||
|
|
10d435bdd1 |
@@ -2,6 +2,9 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { Links } from '../../lib/links';
|
||||
import classNames from 'classnames';
|
||||
import { NavLink, Outlet } from 'react-router-dom';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import { useWeb3ConnectStore, useWeb3Disconnect } from '@vegaprotocol/web3';
|
||||
|
||||
export const Assets = () => {
|
||||
const linkClasses = ({ isActive }: { isActive: boolean }) => {
|
||||
@@ -11,8 +14,8 @@ export const Assets = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-[500px] px-4 mx-auto my-8">
|
||||
<nav className="flex mb-6 text-lg gap-4">
|
||||
<div className="container px-4 mx-auto my-4 max-w-[1200px]">
|
||||
<nav className="flex items-end my-6 text-lg gap-4">
|
||||
<NavLink to={Links.DEPOSIT()} className={linkClasses}>
|
||||
{t('Deposit')}
|
||||
</NavLink>
|
||||
@@ -22,10 +25,34 @@ export const Assets = () => {
|
||||
<NavLink to={Links.TRANSFER()} className={linkClasses}>
|
||||
{t('Transfer')}
|
||||
</NavLink>
|
||||
<div className="ml-auto">
|
||||
<EthereumConnect />
|
||||
</div>
|
||||
</nav>
|
||||
<div className="pt-4 border-t md:p-6 md:border md:rounded-xl border-default">
|
||||
<Outlet />
|
||||
</div>
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EthereumConnect = () => {
|
||||
const { account, connector } = useWeb3React();
|
||||
const disconnect = useWeb3Disconnect(connector);
|
||||
const openDialog = useWeb3ConnectStore((store) => store.open);
|
||||
|
||||
return (
|
||||
<div className="text-sm">
|
||||
{account ? (
|
||||
<div className="flex flex-col items-end">
|
||||
<p>{truncateByChars(account)}</p>
|
||||
<button onClick={disconnect} className="underline underline-offset-4">
|
||||
{t('Disconnect')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button onClick={openDialog} className="underline underline-offset-4">
|
||||
{t('Connect Ethereum wallet')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
export const Allowance = ({ allowance }: { allowance: BigNumber }) => {
|
||||
let value = '';
|
||||
|
||||
const format = (divisor: string) => {
|
||||
const result = allowance.dividedBy(divisor);
|
||||
return result.isInteger() ? result.toString() : result.toFixed(1);
|
||||
};
|
||||
|
||||
if (allowance.isGreaterThan(new BigNumber('1e14'))) {
|
||||
value = t('>100t');
|
||||
} else if (allowance.isGreaterThanOrEqualTo(new BigNumber('1e12'))) {
|
||||
// Trillion
|
||||
value = `${format('1e12')}t`;
|
||||
} else if (allowance.isGreaterThanOrEqualTo(new BigNumber('1e9'))) {
|
||||
// Billion
|
||||
value = `${format('1e9')}b`;
|
||||
} else if (allowance.isGreaterThanOrEqualTo(new BigNumber('1e6'))) {
|
||||
// Million
|
||||
value = `${format('1e6')}m`;
|
||||
} else {
|
||||
value = allowance.toString();
|
||||
}
|
||||
|
||||
return <span>{value}</span>;
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
import * as Select from '@radix-ui/react-select';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TokenIcon, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { isAssetTypeERC20 } from '@vegaprotocol/utils';
|
||||
import { useAssetBalance } from '@vegaprotocol/deposits';
|
||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { useState } from 'react';
|
||||
import type { MarketMaybeWithCandles } from '@vegaprotocol/markets';
|
||||
import { Markets } from './markets';
|
||||
import { getMarketsForAsset } from './deposit';
|
||||
|
||||
export const AssetSelect = ({
|
||||
asset,
|
||||
assets,
|
||||
onSelect,
|
||||
markets,
|
||||
}: {
|
||||
asset?: AssetFieldsFragment;
|
||||
assets: AssetFieldsFragment[];
|
||||
onSelect: (assetId: string) => void;
|
||||
markets: MarketMaybeWithCandles[];
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const filteredAssets = assets
|
||||
.filter((a) => a.source.__typename === 'ERC20')
|
||||
.filter((a) => a.symbol.toLowerCase().includes(search.toLowerCase()));
|
||||
filteredAssets.sort((a, b) => {
|
||||
if (a.name < b.name) return -1;
|
||||
if (a.name > b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Select.Root onValueChange={onSelect} open={open} onOpenChange={setOpen}>
|
||||
<Select.Trigger className="flex items-center w-full px-2 py-1 text-left border rounded gap-2 border-vega-clight-400">
|
||||
<Trigger
|
||||
open={open}
|
||||
asset={asset}
|
||||
search={search}
|
||||
setSearch={(text) => setSearch(text)}
|
||||
/>
|
||||
</Select.Trigger>
|
||||
<Select.Content className="min-w-[320px] w-full p-2 mt-2 bg-white border rounded dark:bg-black border-vega-clight-400 dark:border-vega-cdark-400">
|
||||
<Select.Group className="flex flex-col gap-2">
|
||||
{filteredAssets.length ? (
|
||||
filteredAssets.map((a) => (
|
||||
<AssetSelectItem key={a.id} asset={a} />
|
||||
))
|
||||
) : (
|
||||
<p className="tet-sm">{t('No items')}</p>
|
||||
)}
|
||||
</Select.Group>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{asset && <Markets markets={getMarketsForAsset(markets, asset)} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Trigger = ({
|
||||
open,
|
||||
asset,
|
||||
search,
|
||||
setSearch,
|
||||
}: {
|
||||
open: boolean;
|
||||
asset?: AssetFieldsFragment;
|
||||
search: string;
|
||||
setSearch: (text: string) => void;
|
||||
}) => {
|
||||
if (open) {
|
||||
return (
|
||||
<input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder={t('Search…')}
|
||||
// eslint-disable-next-line
|
||||
autoFocus={true}
|
||||
className="w-full py-0.5 px-1 text-sm outline-none"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (asset) {
|
||||
return (
|
||||
<>
|
||||
{isAssetTypeERC20(asset) && (
|
||||
<TokenIcon address={asset.source.contractAddress} size={20} />
|
||||
)}
|
||||
<span>{asset.symbol}</span>
|
||||
<span className="text-xs text-muted">{asset.name}</span>
|
||||
<span className="text-sm text-muted">
|
||||
<AssetBalance asset={asset} />
|
||||
</span>
|
||||
<span className="ml-auto">
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<span>{t('Please select')}</span>
|
||||
<span className="ml-auto">
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AssetSelectItem = ({ asset }: { asset: AssetFieldsFragment }) => {
|
||||
return (
|
||||
<Select.Item
|
||||
value={asset.id}
|
||||
className="flex items-center px-2 py-1 cursor-pointer gap-2 hover:bg-vega-clight-600 dark:hover:vega-cdark-600 focus:bg-vega-clight-700 dark:focus:bg-vega-cdark-700 rounded-xs"
|
||||
>
|
||||
{isAssetTypeERC20(asset) && (
|
||||
<TokenIcon address={asset.source.contractAddress} />
|
||||
)}
|
||||
<span>{asset.symbol}</span>
|
||||
<span className="text-xs text-muted">{asset.name}</span>
|
||||
<span className="ml-auto">
|
||||
<AssetBalance asset={asset} />
|
||||
</span>
|
||||
</Select.Item>
|
||||
);
|
||||
};
|
||||
|
||||
const AssetBalance = ({ asset }: { asset: AssetFieldsFragment }) => {
|
||||
const balance = useAssetBalance(asset);
|
||||
return <>{balance}</>;
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { Token } from '@vegaprotocol/smart-contracts';
|
||||
import { addDecimal, isAssetTypeERC20 } from '@vegaprotocol/utils';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const Balance = ({
|
||||
asset,
|
||||
balance,
|
||||
}: {
|
||||
asset: AssetFieldsFragment;
|
||||
balance?: BigNumber;
|
||||
}) => {
|
||||
const { provider, account } = useWeb3React();
|
||||
const [fallback, setBalance] = useState<BigNumber>(new BigNumber(0));
|
||||
|
||||
useEffect(() => {
|
||||
const run = async () => {
|
||||
if (!isAssetTypeERC20(asset)) return;
|
||||
|
||||
if (provider && account) {
|
||||
const signer = provider.getSigner();
|
||||
const tokenContract = new Token(
|
||||
asset.source.contractAddress,
|
||||
signer || provider
|
||||
);
|
||||
|
||||
const balanceRes = await tokenContract.balanceOf(account);
|
||||
setBalance(
|
||||
new BigNumber(addDecimal(balanceRes.toString(), asset.decimals))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
}, [asset, account, provider]);
|
||||
|
||||
return (
|
||||
<p className="font-mono text-lg">
|
||||
{balance ? balance.toString() : fallback.toString()}
|
||||
<small className="ml-1 text-muted font-alpha">{asset.symbol}</small>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
@@ -25,20 +25,18 @@ export const DepositGetStarted = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pt-6 border-t border-default">
|
||||
<div className={wrapperClasses}>
|
||||
<h3 className="text-lg">{t('Get started')}</h3>
|
||||
<GetStartedCheckList />
|
||||
{step > OnboardingStep.ONBOARDING_DEPOSIT_STEP && (
|
||||
<TradingAnchorButton
|
||||
href={Links.HOME()}
|
||||
onClick={() => dismiss()}
|
||||
intent={Intent.Info}
|
||||
>
|
||||
{t('Start trading')}
|
||||
</TradingAnchorButton>
|
||||
)}
|
||||
</div>
|
||||
<div className={wrapperClasses}>
|
||||
<h3 className="text-lg">{t('Get started')}</h3>
|
||||
<GetStartedCheckList />
|
||||
{step > OnboardingStep.ONBOARDING_DEPOSIT_STEP && (
|
||||
<TradingAnchorButton
|
||||
href={Links.HOME()}
|
||||
onClick={() => dismiss()}
|
||||
intent={Intent.Info}
|
||||
>
|
||||
{t('Start trading')}
|
||||
</TradingAnchorButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,843 @@
|
||||
import { DepositContainer } from '@vegaprotocol/deposits';
|
||||
import type { Dispatch, FormEvent, ReactNode, SetStateAction } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { DepositGetStarted } from './deposit-get-started';
|
||||
import {
|
||||
useAssetDetailsDialogStore,
|
||||
useEnabledAssets,
|
||||
} from '@vegaprotocol/assets';
|
||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import {
|
||||
addDecimal,
|
||||
isAssetTypeERC20,
|
||||
removeDecimal,
|
||||
truncateByChars,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Intent,
|
||||
TokenIcon,
|
||||
TradingButton,
|
||||
TradingInput,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import {
|
||||
EthTxStatus,
|
||||
useEthereumConfig,
|
||||
useEthTransactionStore,
|
||||
useWeb3ConnectStore,
|
||||
} from '@vegaprotocol/web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {
|
||||
CollateralBridge,
|
||||
prepend0x,
|
||||
Token,
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import { MaxUint256 } from '@ethersproject/constants';
|
||||
import { useTopTradedMarkets } from '../../lib/hooks/use-top-traded-markets';
|
||||
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
|
||||
import { getAsset } from '@vegaprotocol/markets';
|
||||
import classNames from 'classnames';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Markets } from './markets';
|
||||
import { Balance } from './balance';
|
||||
import { Allowance } from './allowance';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { Faucet } from './faucet';
|
||||
import { AssetSelect } from './asset-select';
|
||||
|
||||
export const Deposit = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const [searchParams] = useSearchParams();
|
||||
const assetId = searchParams.get('assetId') || undefined;
|
||||
const { data: assets } = useEnabledAssets();
|
||||
const { data: markets } = useTopTradedMarkets();
|
||||
const { config } = useEthereumConfig();
|
||||
|
||||
if (!assets) return null;
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<DepositContainer assetId={assetId} />
|
||||
<DepositGetStarted />
|
||||
<div className="flex">
|
||||
<div className="flex flex-col w-2/3 gap-4">
|
||||
<DepositFlow
|
||||
assets={assets}
|
||||
assetId={assetId}
|
||||
bridgeAddress={config.collateral_bridge_contract.address}
|
||||
confirmations={config.confirmations}
|
||||
markets={markets || []}
|
||||
faucetEnabled={VEGA_ENV !== Networks.MAINNET}
|
||||
/>
|
||||
<DepositGetStarted />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const getMarketsForAsset = (
|
||||
markets: MarketMaybeWithDataAndCandles[],
|
||||
asset: AssetFieldsFragment
|
||||
) => {
|
||||
return markets
|
||||
.filter((m) => {
|
||||
const marketAsset = getAsset(m);
|
||||
return marketAsset.id === asset.id;
|
||||
})
|
||||
.slice(0, 4);
|
||||
};
|
||||
|
||||
interface DepositState {
|
||||
asset: AssetFieldsFragment | undefined;
|
||||
amount: string;
|
||||
allowance: BigNumber | undefined;
|
||||
balance: BigNumber | undefined;
|
||||
step: number;
|
||||
}
|
||||
|
||||
type SetDepositState = Dispatch<SetStateAction<DepositState>>;
|
||||
|
||||
const DepositFlow = ({
|
||||
assets,
|
||||
assetId,
|
||||
bridgeAddress,
|
||||
confirmations,
|
||||
markets,
|
||||
faucetEnabled,
|
||||
}: {
|
||||
assets: AssetFieldsFragment[];
|
||||
assetId?: string;
|
||||
bridgeAddress: string;
|
||||
confirmations: number;
|
||||
markets: MarketMaybeWithDataAndCandles[];
|
||||
faucetEnabled: boolean;
|
||||
}) => {
|
||||
const { provider, account } = useWeb3React();
|
||||
const [state, setState] = useState<DepositState>(() => {
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
return {
|
||||
asset,
|
||||
amount: '',
|
||||
allowance: undefined,
|
||||
balance: undefined,
|
||||
step: asset ? 1 : 0,
|
||||
};
|
||||
});
|
||||
|
||||
const handleAssetChanged = useCallback(
|
||||
async (assetId?: string) => {
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
|
||||
if (!asset) {
|
||||
setState((curr) => ({
|
||||
...curr,
|
||||
asset: undefined,
|
||||
allowance: undefined,
|
||||
balance: undefined,
|
||||
step: 0,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAssetTypeERC20(asset)) throw new Error('invalid asset');
|
||||
|
||||
let allowance = new BigNumber(0);
|
||||
let balance = new BigNumber(0);
|
||||
|
||||
// if you have connected and therefore have a provider get
|
||||
// balance and allowance
|
||||
if (provider && account) {
|
||||
const signer = provider.getSigner();
|
||||
const tokenContract = new Token(
|
||||
asset.source.contractAddress,
|
||||
signer || provider
|
||||
);
|
||||
const allowanceRes = await tokenContract.allowance(
|
||||
account,
|
||||
bridgeAddress
|
||||
);
|
||||
allowance = new BigNumber(
|
||||
addDecimal(allowanceRes.toString(), asset.decimals)
|
||||
);
|
||||
|
||||
const balanceRes = await tokenContract.balanceOf(account);
|
||||
balance = new BigNumber(
|
||||
addDecimal(balanceRes.toString(), asset.decimals)
|
||||
);
|
||||
}
|
||||
|
||||
setState((curr) => ({
|
||||
...curr,
|
||||
asset,
|
||||
allowance,
|
||||
balance,
|
||||
step: isApproved(allowance) ? 2 : 1,
|
||||
}));
|
||||
},
|
||||
[account, assets, bridgeAddress, provider]
|
||||
);
|
||||
|
||||
const refetchBalances = useCallback(() => {
|
||||
// refetch balance and allowance
|
||||
if (state.asset) {
|
||||
handleAssetChanged(state.asset.id);
|
||||
}
|
||||
}, [state.asset, handleAssetChanged]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<StepWrapper step={0} currentStep={state.step} title={'Select asset'}>
|
||||
<AssetSelect
|
||||
asset={state.asset}
|
||||
assets={assets}
|
||||
markets={markets}
|
||||
onSelect={handleAssetChanged}
|
||||
/>
|
||||
</StepWrapper>
|
||||
<StepWrapper step={1} currentStep={state.step} title={'Approve'}>
|
||||
<Approval
|
||||
state={state}
|
||||
bridgeAddress={bridgeAddress}
|
||||
refetchBalances={refetchBalances}
|
||||
/>
|
||||
</StepWrapper>
|
||||
<StepWrapper step={2} currentStep={state.step} title={'Deposit'}>
|
||||
<SendDeposit
|
||||
state={state}
|
||||
setState={setState}
|
||||
bridgeAddress={bridgeAddress}
|
||||
faucetEnabled={faucetEnabled}
|
||||
refetchBalances={refetchBalances}
|
||||
confirmations={confirmations}
|
||||
/>
|
||||
</StepWrapper>
|
||||
{state.step === 3 && (
|
||||
<div className="flex gap-2">
|
||||
<TradingButton onClick={() => handleAssetChanged()}>
|
||||
{t('Deposit another asset')}
|
||||
</TradingButton>
|
||||
<TradingButton>{t('Start trading')}</TradingButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StepWrapper = ({
|
||||
children,
|
||||
step,
|
||||
currentStep,
|
||||
title,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
step: number;
|
||||
currentStep: number;
|
||||
title: string;
|
||||
}) => {
|
||||
const complete = currentStep > step;
|
||||
const hidden = currentStep < step;
|
||||
return (
|
||||
<section className="flex flex-col gap-3">
|
||||
<h3
|
||||
className={classNames('flex gap-3', {
|
||||
'text-muted': hidden,
|
||||
})}
|
||||
>
|
||||
{complete ? (
|
||||
<span className="inline-block w-4 text-vega-green-600 dar:text-vega-green">
|
||||
<VegaIcon name={VegaIconNames.TICK} size={18} />
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-block w-6 h-6 text-center rounded-full bg-vega-clight-600">
|
||||
{step + 1}
|
||||
</span>
|
||||
)}{' '}
|
||||
{title}
|
||||
</h3>
|
||||
{!hidden && <div className="pl-7">{children}</div>}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const DepositFlow2 = ({
|
||||
assets,
|
||||
assetId,
|
||||
bridgeAddress,
|
||||
confirmations,
|
||||
markets,
|
||||
faucetEnabled,
|
||||
}: {
|
||||
assets: AssetFieldsFragment[];
|
||||
assetId?: string;
|
||||
bridgeAddress: string;
|
||||
confirmations: number;
|
||||
markets: MarketMaybeWithDataAndCandles[];
|
||||
faucetEnabled: boolean;
|
||||
}) => {
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const { provider, account } = useWeb3React();
|
||||
|
||||
const [state, setState] = useState<DepositState>(() => {
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
return {
|
||||
asset,
|
||||
amount: '',
|
||||
allowance: undefined,
|
||||
balance: undefined,
|
||||
complete: false,
|
||||
};
|
||||
});
|
||||
|
||||
const handleAssetChanged = useCallback(
|
||||
async (assetId?: string) => {
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
|
||||
if (!asset) {
|
||||
setState((curr) => ({
|
||||
...curr,
|
||||
asset: undefined,
|
||||
allowance: undefined,
|
||||
balance: undefined,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAssetTypeERC20(asset)) throw new Error('invalid asset');
|
||||
|
||||
let allowance = new BigNumber(0);
|
||||
let balance = new BigNumber(0);
|
||||
|
||||
// if you have connected and therefore have a provider get
|
||||
// balance and allowance
|
||||
if (provider && account) {
|
||||
const signer = provider.getSigner();
|
||||
const tokenContract = new Token(
|
||||
asset.source.contractAddress,
|
||||
signer || provider
|
||||
);
|
||||
const allowanceRes = await tokenContract.allowance(
|
||||
account,
|
||||
bridgeAddress
|
||||
);
|
||||
allowance = new BigNumber(
|
||||
addDecimal(allowanceRes.toString(), asset.decimals)
|
||||
);
|
||||
|
||||
const balanceRes = await tokenContract.balanceOf(account);
|
||||
balance = new BigNumber(
|
||||
addDecimal(balanceRes.toString(), asset.decimals)
|
||||
);
|
||||
}
|
||||
|
||||
setState((curr) => ({
|
||||
...curr,
|
||||
asset,
|
||||
allowance,
|
||||
balance,
|
||||
}));
|
||||
},
|
||||
[account, assets, bridgeAddress, provider]
|
||||
);
|
||||
|
||||
const refetchBalances = useCallback(() => {
|
||||
// refetch balance and allowance
|
||||
if (state.asset) {
|
||||
handleAssetChanged(state.asset.id);
|
||||
}
|
||||
}, [state.asset, handleAssetChanged]);
|
||||
|
||||
const filteredAssets = assets
|
||||
.filter((a) => a.source.__typename === 'ERC20')
|
||||
.filter((a) => a.symbol.toLowerCase().includes(search.toLowerCase()));
|
||||
filteredAssets.sort((a, b) => {
|
||||
if (a.name < b.name) return -1;
|
||||
if (a.name > b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
const isAssetSelected = state.asset && isAssetTypeERC20(state.asset);
|
||||
|
||||
let step = 0;
|
||||
if (isAssetSelected) {
|
||||
if (!state.allowance || state.allowance.isLessThanOrEqualTo(0)) {
|
||||
step = 1;
|
||||
} else {
|
||||
step = 2;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-end justify-between">
|
||||
<StepBreadcrumb step={step} />
|
||||
<div>
|
||||
{!isAssetSelected && (
|
||||
<TradingInput
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="Search..."
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isAssetSelected ? (
|
||||
<AssetSelected
|
||||
state={state}
|
||||
setState={setState}
|
||||
markets={markets}
|
||||
confirmations={confirmations}
|
||||
bridgeAddress={bridgeAddress}
|
||||
faucetEnabled={faucetEnabled}
|
||||
onCancel={() => handleAssetChanged()}
|
||||
refetchBalances={refetchBalances}
|
||||
/>
|
||||
) : (
|
||||
<AssetList
|
||||
assets={filteredAssets}
|
||||
markets={markets}
|
||||
onSelect={handleAssetChanged}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AssetList = ({
|
||||
assets,
|
||||
markets,
|
||||
onSelect,
|
||||
}: {
|
||||
assets: AssetFieldsFragment[];
|
||||
markets: MarketMaybeWithDataAndCandles[];
|
||||
onSelect: (assetId: string) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{assets.map((a) => {
|
||||
if (!isAssetTypeERC20(a)) return null;
|
||||
|
||||
const marketsForAsset = getMarketsForAsset(markets, a);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={a.id}
|
||||
onClick={() => {
|
||||
onSelect(a.id);
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
onSelect(a.id);
|
||||
}
|
||||
}}
|
||||
className={classNames(
|
||||
'p-5 rounded text-left',
|
||||
'bg-vega-clight-800 dark:bg-vega-cdark-800 hover:bg-vega-clight-700 dark:hover:bg-vega-cdark-700 cursor-pointer'
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col flex-1 gap-3">
|
||||
<div className="flex justify-between">
|
||||
<TokenHeader asset={a} />
|
||||
<div className="text-right">
|
||||
<Balance asset={a} />
|
||||
</div>
|
||||
</div>
|
||||
<Markets markets={marketsForAsset} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AssetSelected = ({
|
||||
state,
|
||||
setState,
|
||||
markets,
|
||||
bridgeAddress,
|
||||
confirmations,
|
||||
faucetEnabled,
|
||||
refetchBalances,
|
||||
onCancel,
|
||||
}: {
|
||||
state: DepositState;
|
||||
setState: SetDepositState;
|
||||
markets: MarketMaybeWithDataAndCandles[];
|
||||
bridgeAddress: string;
|
||||
confirmations: number;
|
||||
faucetEnabled: boolean;
|
||||
refetchBalances: () => void;
|
||||
onCancel: () => void;
|
||||
}) => {
|
||||
if (!state.asset) {
|
||||
throw new Error('no asset selected');
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'p-5 rounded flex flex-col gap-3',
|
||||
'bg-vega-clight-700 dark:bg-vega-cdark-700'
|
||||
)}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<TokenHeader asset={state.asset} />
|
||||
<div className="text-right">
|
||||
<Balance asset={state.asset} balance={state.balance} />
|
||||
</div>
|
||||
</div>
|
||||
<Markets markets={getMarketsForAsset(markets, state.asset)} />
|
||||
<div className="relative">
|
||||
<TransactionContainer
|
||||
state={state}
|
||||
setState={setState}
|
||||
bridgeAddress={bridgeAddress}
|
||||
confirmations={confirmations}
|
||||
refetchBalances={refetchBalances}
|
||||
faucetEnabled={faucetEnabled}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
<div className="absolute bottom-0 right-0">
|
||||
<TradingButton
|
||||
onClick={() => onCancel()}
|
||||
size="small"
|
||||
intent={Intent.Danger}
|
||||
>
|
||||
{t('Change asset')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const TransactionContainer = ({
|
||||
state,
|
||||
setState,
|
||||
bridgeAddress,
|
||||
confirmations,
|
||||
refetchBalances,
|
||||
faucetEnabled,
|
||||
onCancel,
|
||||
}: {
|
||||
state: DepositState;
|
||||
setState: SetDepositState;
|
||||
bridgeAddress: string;
|
||||
confirmations: number;
|
||||
refetchBalances: () => void;
|
||||
faucetEnabled: boolean;
|
||||
onCancel: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Approval
|
||||
state={state}
|
||||
bridgeAddress={bridgeAddress}
|
||||
refetchBalances={refetchBalances}
|
||||
/>
|
||||
<SendDeposit
|
||||
state={state}
|
||||
bridgeAddress={bridgeAddress}
|
||||
confirmations={confirmations}
|
||||
setAmount={(amount) => setState((curr) => ({ ...curr, amount }))}
|
||||
faucetEnabled={faucetEnabled}
|
||||
refetchBalances={refetchBalances}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Approval = ({
|
||||
state,
|
||||
bridgeAddress,
|
||||
refetchBalances,
|
||||
}: {
|
||||
state: DepositState;
|
||||
bridgeAddress: string;
|
||||
refetchBalances: () => void;
|
||||
}) => {
|
||||
const openDialog = useWeb3ConnectStore((store) => store.open);
|
||||
const { account, provider } = useWeb3React();
|
||||
const [id, setId] = useState<number | null>(null);
|
||||
|
||||
const tx = useEthTransactionStore((store) => {
|
||||
return store.transactions.find((t) => t?.id == id);
|
||||
});
|
||||
const send = useEthTransactionStore((store) => store.create);
|
||||
|
||||
const submitApproval = () => {
|
||||
if (!provider) throw new Error('no provider');
|
||||
if (!isAssetTypeERC20(state.asset)) {
|
||||
throw new Error('no provider');
|
||||
}
|
||||
const signer = provider.getSigner();
|
||||
const contract = new Token(
|
||||
state.asset.source.contractAddress,
|
||||
signer || provider
|
||||
);
|
||||
const id = send(contract, 'approve', [
|
||||
bridgeAddress,
|
||||
MaxUint256.toString(),
|
||||
]);
|
||||
setId(id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.asset) return; // this can get triggered when you clear the selected asset
|
||||
if (tx?.status === EthTxStatus.Confirmed) {
|
||||
refetchBalances();
|
||||
}
|
||||
}, [tx?.status, state.asset, refetchBalances]);
|
||||
|
||||
if (!state.asset) return null;
|
||||
|
||||
// No asset selected, show generic approval title
|
||||
if (!account) {
|
||||
return (
|
||||
<TradingButton onClick={openDialog} size="small">
|
||||
{t('Connect Ethereum wallet')}
|
||||
</TradingButton>
|
||||
);
|
||||
}
|
||||
|
||||
return isApproved(state.allowance) ? (
|
||||
<div className="flex justify-between gap-2">
|
||||
<div>
|
||||
{tx && tx.status === EthTxStatus.Pending ? (
|
||||
<TradingButton disabled={true} size="small">
|
||||
{t('Approving...')}
|
||||
</TradingButton>
|
||||
) : (
|
||||
<TradingButton onClick={submitApproval} size="small">
|
||||
{t('Re-approve')}
|
||||
</TradingButton>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{state.allowance && (
|
||||
<div className="font-mono text-lg">
|
||||
<Allowance allowance={state.allowance} />
|
||||
<small className="ml-1 text-xs text-muted font-alpha">
|
||||
{t('Approved')}
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<div>
|
||||
<p className="text-sm text-muted">
|
||||
{t(
|
||||
'Before you can make a deposit of %s you need to approve its use with Vega in your Ethereum wallet.',
|
||||
state.asset.symbol
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{tx && tx.status === EthTxStatus.Pending ? (
|
||||
<TradingButton intent={Intent.Success} disabled={true} size="small">
|
||||
{t('Approving...')}
|
||||
</TradingButton>
|
||||
) : (
|
||||
<TradingButton
|
||||
onClick={submitApproval}
|
||||
size="small"
|
||||
intent={Intent.Success}
|
||||
>
|
||||
{t('Approve %s deposits', state.asset.symbol)}
|
||||
</TradingButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SendDeposit = ({
|
||||
state,
|
||||
setState,
|
||||
bridgeAddress,
|
||||
confirmations,
|
||||
faucetEnabled,
|
||||
refetchBalances,
|
||||
}: {
|
||||
state: DepositState;
|
||||
setState: SetDepositState;
|
||||
bridgeAddress: string;
|
||||
confirmations: number;
|
||||
faucetEnabled: boolean;
|
||||
refetchBalances: () => void;
|
||||
}) => {
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { provider, account } = useWeb3React();
|
||||
|
||||
const [id, setId] = useState<number | null>(null);
|
||||
const send = useEthTransactionStore((store) => store.create);
|
||||
const tx = useEthTransactionStore((store) => {
|
||||
return store.transactions.find((t) => t?.id == id);
|
||||
});
|
||||
|
||||
const submitDeposit = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!provider) {
|
||||
throw new Error('no provider');
|
||||
}
|
||||
if (!pubKey) {
|
||||
throw new Error('no vega pubkey');
|
||||
}
|
||||
if (!isAssetTypeERC20(state.asset)) {
|
||||
throw new Error('no asset selected');
|
||||
}
|
||||
const signer = provider.getSigner();
|
||||
const contract = new CollateralBridge(bridgeAddress, signer || provider);
|
||||
const id = send(
|
||||
contract,
|
||||
'deposit_asset',
|
||||
[
|
||||
state.asset.source.contractAddress,
|
||||
removeDecimal(state.amount, state.asset.decimals),
|
||||
prepend0x(pubKey),
|
||||
],
|
||||
state.asset.id,
|
||||
confirmations ?? 1,
|
||||
true
|
||||
);
|
||||
setId(id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.asset) return; // this can get triggered when you clear the selected asset
|
||||
if (tx?.status === EthTxStatus.Confirmed) {
|
||||
refetchBalances();
|
||||
setId(null);
|
||||
setState((curr) => ({ ...curr, step: 3 }));
|
||||
}
|
||||
}, [tx?.status, state.asset, setState, refetchBalances]);
|
||||
|
||||
if (!state.asset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Dont show deposit ui unless approved
|
||||
if (!isApproved(state.allowance)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<TradingButton onClick={openVegaWalletDialog} size="small">
|
||||
{t('Connect Vega wallet')}
|
||||
</TradingButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="flex flex-col items-start gap-2" onSubmit={submitDeposit}>
|
||||
<div className="flex self-stretch gap-1 ">
|
||||
<div className="flex-1">
|
||||
<TradingInput
|
||||
name="amount"
|
||||
value={state.amount}
|
||||
onChange={(e) =>
|
||||
setState((curr) => ({ ...curr, amount: e.target.value }))
|
||||
}
|
||||
placeholder="Enter amount"
|
||||
/>
|
||||
</div>
|
||||
{state.balance && (
|
||||
<TradingButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setState((curr) => ({
|
||||
...curr,
|
||||
amount: state.balance ? state.balance.toString() : '',
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<span className="whitespace-nowrap">{t('Use max')}</span>
|
||||
</TradingButton>
|
||||
)}
|
||||
{faucetEnabled && (
|
||||
<Faucet asset={state.asset} refetchBalances={refetchBalances} />
|
||||
)}
|
||||
</div>
|
||||
<TradingButton type="submit" size="small" intent={Intent.Success}>
|
||||
<span className="whitespace-nowrap">
|
||||
{t('Deposit %s', state.asset.symbol)}
|
||||
</span>
|
||||
</TradingButton>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const TokenHeader = ({ asset }: { asset: AssetFieldsFragment }) => {
|
||||
const isErc20 = isAssetTypeERC20(asset);
|
||||
const openDialog = useAssetDetailsDialogStore((store) => store.open);
|
||||
|
||||
return (
|
||||
<div className="flex items-center text-lg gap-2">
|
||||
{isErc20 && <TokenIcon address={asset.source.contractAddress} />}
|
||||
<h3 className="text-lg">
|
||||
{asset.symbol}
|
||||
{isErc20 && (
|
||||
<button
|
||||
className="ml-1 text-sm underline underline-offset-2 text-muted"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openDialog(asset.id);
|
||||
}}
|
||||
>
|
||||
{truncateByChars(asset.source.contractAddress)}
|
||||
</button>
|
||||
)}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StepBreadcrumb = ({ step }: { step: number }) => {
|
||||
return (
|
||||
<ol className="text-sm">
|
||||
{[
|
||||
'Choose an asset to deposit to the Vega network',
|
||||
'Approve deposits',
|
||||
'Deposit',
|
||||
].map((text, i) => {
|
||||
const stepNum = i + 1;
|
||||
return (
|
||||
<li key={i} className="flex items-center gap-2">
|
||||
{step >= stepNum ? (
|
||||
<span className="block inline w-4 text-vega-green-600 dark:text-vega-green">
|
||||
<VegaIcon name={VegaIconNames.TICK} />
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
// align number with center of tick
|
||||
className="inline-block w-4 text-center text-muted"
|
||||
>
|
||||
{stepNum}
|
||||
</span>
|
||||
)}{' '}
|
||||
<span>{text}</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
);
|
||||
};
|
||||
|
||||
const isApproved = (allowance: BigNumber | undefined) => {
|
||||
if (!allowance) return false;
|
||||
return allowance.isGreaterThan(0);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Token } from '@vegaprotocol/smart-contracts';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { isAssetTypeERC20 } from '@vegaprotocol/utils';
|
||||
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const Faucet = ({
|
||||
asset,
|
||||
refetchBalances,
|
||||
}: {
|
||||
asset: AssetFieldsFragment;
|
||||
refetchBalances: () => void;
|
||||
}) => {
|
||||
const { provider } = useWeb3React();
|
||||
|
||||
const [id, setId] = useState<number | null>(null);
|
||||
const send = useEthTransactionStore((store) => store.create);
|
||||
const tx = useEthTransactionStore((store) => {
|
||||
return store.transactions.find((t) => t?.id == id);
|
||||
});
|
||||
|
||||
const submitFaucet = async () => {
|
||||
if (!provider) throw new Error('no provider');
|
||||
if (!isAssetTypeERC20(asset)) {
|
||||
throw new Error('no asset selected');
|
||||
}
|
||||
const signer = provider.getSigner();
|
||||
const contract = new Token(
|
||||
asset.source.contractAddress,
|
||||
signer || provider
|
||||
);
|
||||
const id = send(contract, 'faucet', []);
|
||||
setId(id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (tx?.status === EthTxStatus.Confirmed) {
|
||||
refetchBalances();
|
||||
}
|
||||
}, [tx?.status, refetchBalances]);
|
||||
|
||||
if (tx && tx.status === EthTxStatus.Pending) {
|
||||
return (
|
||||
<TradingButton size="small" disabled={true}>
|
||||
{t('Fauceting...')}
|
||||
</TradingButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TradingButton size="small" onClick={submitFaucet}>
|
||||
<span className="whitespace-nowrap">{t('Faucet')}</span>
|
||||
</TradingButton>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import { calcCandleVolume, useCandles } from '@vegaprotocol/markets';
|
||||
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const Markets = ({
|
||||
markets,
|
||||
}: {
|
||||
markets: MarketMaybeWithDataAndCandles[];
|
||||
}) => {
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-1 md:gap-4">
|
||||
{markets.length ? (
|
||||
markets.map((m) => {
|
||||
return <MarketCard key={m.id} market={m} />;
|
||||
})
|
||||
) : (
|
||||
<p className="text-xs">{t('Traded in 0 markets')}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MarketCard = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
const { oneDayCandles } = useCandles({ marketId: market.id });
|
||||
const vol = oneDayCandles ? calcCandleVolume(oneDayCandles) : '0';
|
||||
const volume =
|
||||
vol && vol !== '0'
|
||||
? addDecimalsFormatNumber(vol, market.positionDecimalPlaces)
|
||||
: '0.00';
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-between p-2 text-xs rounded-lg col-span-2 lg:col-span-1 bg-vega-clight-600 dark:bg-vega-cdark-600">
|
||||
<h3>{market.tradableInstrument.instrument.code}</h3>
|
||||
<p className="text-muted">Vol {volume}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export * from './lib';
|
||||
export { TESTNET_ASSET_ALLOWLIST } from './testnet-allowed-assets';
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Lots of assets on testnet are no longer used this is an allow
|
||||
// list, only the assets listed below should be used
|
||||
export const TESTNET_ASSET_ALLOWLIST = [
|
||||
'deadbeef00000000000000000000000000000000000000000000000000000001', // XYZalpha
|
||||
'deadbeef00000000000000000000000000000000000000000000000000000002', // XYZbeta
|
||||
'deadbeef00000000000000000000000000000000000000000000000000000003', // XYZdelta
|
||||
'deadbeef00000000000000000000000000000000000000000000000000000004', // XYZepsilon
|
||||
'deadbeef00000000000000000000000000000000000000000000000000000005', // XYZgamma
|
||||
'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55', // VEGA
|
||||
'e1cc8e2598d11c4c3ccc4521f0fc06f4b6d940a8607ca38b72bec138600f0525', // tBTC (fairground)
|
||||
'cee709223217281d7893b650850ae8ee8a18b7539b5658f9b4cc24de95dd18ad', // tBTC (test)
|
||||
'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d', // tUSDC (test)
|
||||
'c398a73a07a9b51dfa0f20302be3003d2d3fc09f2523d71547ac2b8ccb074665', // DAI
|
||||
'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663', // tDAI (test)
|
||||
'a7cb2a2855b992b08fa612e3189c317ad54c3934b9e9ba3be4ba33457c1152a2', // USDT
|
||||
'XYZalpha',
|
||||
'XYZbeta',
|
||||
'XYZDelta',
|
||||
'XYZepsilon',
|
||||
'XYZgamma',
|
||||
];
|
||||
@@ -9,6 +9,12 @@ import { useTokenContract } from '@vegaprotocol/web3';
|
||||
const REFETCH_DELAY = 5000;
|
||||
|
||||
export const AssetBalance = ({ asset }: { asset: AssetFieldsFragment }) => {
|
||||
const balance = useAssetBalance(asset);
|
||||
|
||||
return <Balance balance={balance} symbol={asset.symbol} />;
|
||||
};
|
||||
|
||||
export const useAssetBalance = (asset: AssetFieldsFragment) => {
|
||||
const [setBalance, getBalance] = useBalancesStore((state) => [
|
||||
state.setBalance,
|
||||
state.getBalance,
|
||||
@@ -33,10 +39,5 @@ export const AssetBalance = ({ asset }: { asset: AssetFieldsFragment }) => {
|
||||
}
|
||||
}, [asset.id, fetchFromEth, getBalance]);
|
||||
|
||||
return (
|
||||
<Balance
|
||||
balance={getBalance(asset.id)?.balanceOnEth?.toString()}
|
||||
symbol={asset.symbol}
|
||||
/>
|
||||
);
|
||||
return getBalance(asset.id)?.balanceOnEth?.toString();
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@ export * from './theme-switcher';
|
||||
export * from './thumbs';
|
||||
export * from './tiny-scroll';
|
||||
export * from './toast';
|
||||
export * from './token-icons';
|
||||
export * from './toggle';
|
||||
export * from './tooltip';
|
||||
export * from './traffic-light';
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
export const Ethereum = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
shape-rendering="geometricPrecision"
|
||||
text-rendering="geometricPrecision"
|
||||
image-rendering="optimizeQuality"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
viewBox="0 0 784.37 1277.39"
|
||||
>
|
||||
<polygon
|
||||
fill="#343434"
|
||||
fill-rule="nonzero"
|
||||
points="392.07,0 383.5,29.11 383.5,873.74 392.07,882.29 784.13,650.54 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#8C8C8C"
|
||||
fill-rule="nonzero"
|
||||
points="392.07,0 -0,650.54 392.07,882.29 392.07,472.33 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#3C3C3B"
|
||||
fill-rule="nonzero"
|
||||
points="392.07,956.52 387.24,962.41 387.24,1263.28 392.07,1277.38 784.37,724.89 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#8C8C8C"
|
||||
fill-rule="nonzero"
|
||||
points="392.07,1277.38 392.07,956.52 -0,724.89 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#141414"
|
||||
fill-rule="nonzero"
|
||||
points="392.07,882.29 784.13,650.54 392.07,472.33 "
|
||||
/>
|
||||
<polygon
|
||||
fill="#393939"
|
||||
fill-rule="nonzero"
|
||||
points="0,650.54 392.07,882.29 392.07,472.33 "
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './token-icon';
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Vega } from './vega';
|
||||
import { USDc } from './usdc';
|
||||
import { USDt } from './usdt';
|
||||
|
||||
export const TokenIconMap: Record<
|
||||
string,
|
||||
({ size }: { size: number }) => JSX.Element
|
||||
> = {
|
||||
// Vega
|
||||
'0xcB84d72e61e383767C4DFEb2d8ff7f4FB89abc6e': Vega, // mainnet
|
||||
'0xdf1B0F223cb8c7aB3Ef8469e529fe81E73089BD9': Vega, // testnet
|
||||
'0x62720E92FB7d77699A5D3754c429a3116900a84b': Vega, // stagnet
|
||||
|
||||
// USDc
|
||||
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': USDc, // mainnet
|
||||
'0xdBa6373d0DAAAA44bfAd663Ff93B1bF34cE054E9': USDc, // testnet
|
||||
|
||||
// USDt
|
||||
'0xdAC17F958D2ee523a2206206994597C13D831ec7': USDt, // mainnet
|
||||
'0x607c609C21171578847d7c31f29C235850f47Bb7': USDt, // tesnet
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import classNames from 'classnames';
|
||||
import { TokenIconMap } from './token-icon-record';
|
||||
|
||||
interface TokenIconProps {
|
||||
address: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const TokenIcon = ({ address, size = 28 }: TokenIconProps) => {
|
||||
const Element = TokenIconMap[address];
|
||||
return (
|
||||
<span className={classNames('inline-block', 'align-text-bottom')}>
|
||||
{Element ? (
|
||||
<Element size={size} />
|
||||
) : (
|
||||
<span
|
||||
style={{ width: size, height: size }}
|
||||
className="flex items-center justify-center rounded-full bg-vega-clight-500 dark:bg-vega-cdark-500"
|
||||
>
|
||||
<span className="block text-[9px] font-mono leading-[0.9]">
|
||||
ERC
|
||||
<br />
|
||||
20
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
export const USDc = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 2000 2000">
|
||||
<path
|
||||
d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z"
|
||||
fill="#2775ca"
|
||||
/>
|
||||
<path
|
||||
d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
export const USDt = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 339.43 295.27">
|
||||
<path
|
||||
fill="#50af95"
|
||||
d="M62.15,1.45l-61.89,130a2.52,2.52,0,0,0,.54,2.94L167.95,294.56a2.55,2.55,0,0,0,3.53,0L338.63,134.4a2.52,2.52,0,0,0,.54-2.94l-61.89-130A2.5,2.5,0,0,0,275,0H64.45a2.5,2.5,0,0,0-2.3,1.45h0Z"
|
||||
/>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M191.19,144.8v0c-1.2.09-7.4,0.46-21.23,0.46-11,0-18.81-.33-21.55-0.46v0c-42.51-1.87-74.24-9.27-74.24-18.13s31.73-16.25,74.24-18.15v28.91c2.78,0.2,10.74.67,21.74,0.67,13.2,0,19.81-.55,21-0.66v-28.9c42.42,1.89,74.08,9.29,74.08,18.13s-31.65,16.24-74.08,18.12h0Zm0-39.25V79.68h59.2V40.23H89.21V79.68H148.4v25.86c-48.11,2.21-84.29,11.74-84.29,23.16s36.18,20.94,84.29,23.16v82.9h42.78V151.83c48-2.21,84.12-11.73,84.12-23.14s-36.09-20.93-84.12-23.15h0Zm0,0h0Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
export const Vega = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 42 42">
|
||||
<rect width="42" height="42" rx="21" fill="black" />
|
||||
<path d="M13 27.2726H16.4545V10H13V27.2726Z" fill="white" />
|
||||
<path d="M25.667 23.8181H29.1215V10H25.667V23.8181Z" fill="white" />
|
||||
<path d="M19.333 33.6059H22.7875V30.1514H19.333V33.6059Z" fill="white" />
|
||||
<path
|
||||
d="M22.7871 30.7271H26.2416V27.2726H22.7871V30.7271Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M29.1211 27.2726H31.9999V23.8181H29.1211V27.2726Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M16.4551 30.7271H19.3339V27.2726H16.4551V30.7271Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user