chore(#315): Convert token to use wallet lib

* delete token version of vega wallet serivce

* update use-user-vote to use new wallet service

* remove typo

* add further types for transaction submissions, add assets to withdraw page query

* update api client package to get generated types, adjust render logic of withdrawals page

* fix withdrawals list rendering

* update determine id function to not use nodejs buffer

* update service api client so it accepts new tx types

* remove stray logs and formatting

* make filtering erc20 assets the responsibility of the withdraw/deposit lib and not the app

* remove sha3 dep and use js-sha3 and ethers to determine ids

* use hook for fetching withdrawals form lib, add type policy to ensure withdrawal state is updated correctly

* fix: markets page feature
This commit is contained in:
Matthew Russell 2022-05-17 09:04:41 -04:00 committed by GitHub
parent b10432fdf3
commit d8bf887245
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 265 additions and 1106 deletions

View File

@ -147,6 +147,7 @@ const IconLine = ({ inverted }: { inverted: boolean }) => (
const NavDrawer = ({ inverted }: { inverted: boolean }) => {
const { appState, appDispatch } = useAppState();
return (
<>
<button

View File

@ -25,7 +25,6 @@ export const TransactionComplete = ({
<p>
<EtherscanLink tx={hash} />
</p>
LINK
{footer && <p data-testid="transaction-complete-footer">{footer}</p>}
</Callout>
);

View File

@ -1,143 +0,0 @@
import React from 'react';
import * as Sentry from '@sentry/react';
import { gql, useApolloClient } from '@apollo/client';
import { sigToId } from '../lib/sig-to-id';
import { vegaWalletService } from '../lib/vega-wallet/vega-wallet-service';
import type { WithdrawSubmissionInput } from '../lib/vega-wallet/vega-wallet-service';
import type {
WithdrawalPoll,
WithdrawalPollVariables,
} from './__generated__/WithdrawalPoll';
export enum Status {
Idle,
Submitted,
Pending,
Success,
Failure,
}
type Submit = (
amount: string,
asset: string,
receiverAddress: string
) => Promise<void>;
const WITHDRAWAL_QUERY = gql`
query WithdrawalPoll($partyId: ID!) {
party(id: $partyId) {
id
withdrawals {
id
amount
status
asset {
id
symbol
decimals
}
createdTimestamp
withdrawnTimestamp
txHash
details {
... on Erc20WithdrawalDetails {
receiverAddress
}
}
}
}
}
`;
export function useCreateWithdrawal(pubKey: string): [Status, Submit] {
const mountedRef = React.useRef(true);
const client = useApolloClient();
const [status, setStatus] = React.useState(Status.Idle);
const [id, setId] = React.useState('');
const safeSetStatus = (status: Status) => {
if (mountedRef.current) {
setStatus(status);
}
};
const submit = React.useCallback(
async (amount: string, asset: string, receiverAddress: string) => {
const command: WithdrawSubmissionInput = {
pubKey,
withdrawSubmission: {
amount,
asset,
ext: {
erc20: {
receiverAddress,
},
},
},
};
safeSetStatus(Status.Submitted);
try {
const [err, res] = await vegaWalletService.commandSync(command);
if (err || !res) {
safeSetStatus(Status.Failure);
} else {
const id = sigToId(res.signature.value);
setId(id);
// Now await subscription
}
safeSetStatus(Status.Pending);
} catch (err) {
safeSetStatus(Status.Failure);
Sentry.captureException(err);
}
},
[pubKey]
);
React.useEffect(() => {
let interval: ReturnType<typeof setInterval>;
if (status === Status.Pending) {
interval = setInterval(async () => {
try {
const { data } = await client.query<
WithdrawalPoll,
WithdrawalPollVariables
>({
fetchPolicy: 'network-only',
query: WITHDRAWAL_QUERY,
variables: { partyId: pubKey },
});
// find matching withdrawals
const withdrawal = data?.party?.withdrawals?.find((e) => {
return e.id === id;
});
if (withdrawal) {
safeSetStatus(Status.Success);
clearInterval(interval);
}
} catch (err) {
clearInterval(interval);
}
}, 1000);
}
return () => {
clearInterval(interval);
};
}, [client, id, pubKey, status]);
React.useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
return [status, submit];
}

View File

@ -1,66 +0,0 @@
import React from 'react';
import { gql, useApolloClient } from '@apollo/client';
import type {
Erc20ApprovalPoll,
Erc20ApprovalPoll_erc20WithdrawalApproval,
Erc20ApprovalPollVariables,
} from './__generated__/Erc20ApprovalPoll';
const ERC20_APPROVAL_QUERY = gql`
query Erc20ApprovalPoll($withdrawalId: ID!) {
erc20WithdrawalApproval(withdrawalId: $withdrawalId) {
assetSource
amount
nonce
signatures
targetAddress
expiry
}
}
`;
export const usePollERC20Approval = (withdrawalId: string) => {
const mountedRef = React.useRef(true);
const client = useApolloClient();
const [erc20Approval, setErc20Approval] =
React.useState<Erc20ApprovalPoll_erc20WithdrawalApproval | null>(null);
const safeSetErc20Approval = (
approval: Erc20ApprovalPoll_erc20WithdrawalApproval
) => {
if (mountedRef.current) {
setErc20Approval(approval);
}
};
React.useEffect(() => {
const interval = setInterval(async () => {
try {
const res = await client.query<
Erc20ApprovalPoll,
Erc20ApprovalPollVariables
>({
query: ERC20_APPROVAL_QUERY,
variables: { withdrawalId },
});
if (res.data.erc20WithdrawalApproval) {
safeSetErc20Approval(res.data.erc20WithdrawalApproval);
clearInterval(interval);
}
} catch (err) {
// No op. If the erc20 withdrawal is not created yet it will error
// but we will just want to poll until it is. There is no bus event for
// erc20 approvals yet..
}
}, 1000);
return () => {
clearInterval(interval);
mountedRef.current = false;
};
}, [withdrawalId, client]);
return erc20Approval;
};

View File

@ -76,6 +76,7 @@
"Loading": "Loading...",
"Something went wrong": "Something went wrong",
"Try again": "Try again",
"Incomplete": "Incomplete",
"Complete": "Complete",
"View on Etherscan (opens in a new tab)": "View on Etherscan (opens in a new tab)",
"Transaction in progress": "Transaction in progress",
@ -437,7 +438,7 @@
"withdrawalsText": "These withdrawals need to be completed with an Ethereum transaction.",
"withdrawalsNone": "You don't have any pending withdrawals.",
"withdrawalsCompleteButton": "Finish withdrawal",
"withdrawalsPreparingButton": "Preparing withdrawal",
"withdrawalTransaction": "Transaction ({{foreignChain}})",
"signature": "Signature",
"created": "Created",
"toEthereum": "To (Ethereum)",

View File

@ -136,6 +136,13 @@ export function createClient() {
},
},
},
Withdrawal: {
fields: {
pendingOnForeignChain: {
read: (isPending = false) => isPending,
},
},
},
},
});

View File

@ -1,9 +0,0 @@
import { sigToId } from './sig-to-id';
test('creating an id from a signature', () => {
const sig =
'3594b7f4e1c078aaa65b3efac57e1436da64d60c2961bf432dc62daa36f8b0b2602ed9c990ee46dd8ab5181e19c19b06587f080d22fc4543c1869970e678f20f';
const expected =
'9ebd2c424a9426bf5f38112878eb2f99bfdf5ec17b5782b944e6be8177084327';
expect(sigToId(sig)).toEqual(expected);
});

View File

@ -1,18 +0,0 @@
import { ethers } from 'ethers';
import { SHA3 } from 'sha3';
export const sigToId = (sig: string) => {
// Prepend 0x
if (sig.slice(0, 2) !== '0x') {
sig = '0x' + sig;
}
// Create the ID
const hash = new SHA3(256);
const bytes = ethers.utils.arrayify(sig);
hash.update(Buffer.from(bytes));
const id = ethers.utils.hexlify(hash.digest());
// Remove 0x as core doesn't keep them in the API
return id.substring(2);
};

View File

@ -1,303 +0,0 @@
import type { VoteValue } from '../../__generated__/globalTypes';
import type { VegaKey } from '../../contexts/app-state/app-state-context';
import type { VOTE_VALUE_MAP } from '../../routes/governance/components/vote-details';
import { LocalStorage } from '@vegaprotocol/react-helpers';
import type { GenericErrorResponse } from './vega-wallet-types';
export const MINIMUM_WALLET_VERSION =
process.env['NX_SUPPORTED_WALLET_VERSION'];
export const DEFAULT_WALLET_URL = 'http://localhost:1789';
export const HOSTED_WALLET_URL = 'https://wallet.testnet.vega.xyz';
const TOKEN_STORAGE_KEY = 'vega_wallet_token';
const WALLET_URL_KEY = 'vega_wallet_url';
const KEY_STORAGE_KEY = 'vega_wallet_key';
const Endpoints = {
STATUS: 'status',
TOKEN: 'auth/token',
KEYS: 'keys',
COMMAND: 'command/sync',
VERSION: 'version',
};
export const Errors = {
NO_TOKEN: 'No token',
SERVICE_UNAVAILABLE: 'Wallet service unavailable',
SESSION_EXPIRED: 'Session expired',
INVALID_CREDENTIALS: 'Invalid credentials',
COMMAND_FAILED: 'Command failed',
INVALID_URL: 'Invalid wallet URL',
};
export interface DelegateSubmissionInput {
pubKey: string;
delegateSubmission: {
nodeId: string;
amount: string;
};
}
export interface UndelegateSubmissionInput {
pubKey: string;
undelegateSubmission: {
nodeId: string;
amount: string;
method: 'METHOD_NOW' | 'METHOD_AT_END_OF_EPOCH';
};
}
export interface VoteSubmissionInput {
pubKey: string;
voteSubmission: {
value: typeof VOTE_VALUE_MAP[VoteValue];
proposalId: string;
};
}
export interface WithdrawSubmissionInput {
pubKey: string;
withdrawSubmission: {
amount: string;
asset: string;
ext: {
erc20: {
receiverAddress: string;
};
};
};
}
export type CommandSyncInput =
| DelegateSubmissionInput
| UndelegateSubmissionInput
| VoteSubmissionInput
| WithdrawSubmissionInput;
export interface CommandSyncResponse {
inputData: string;
pubKey: string;
signature: {
algo: string;
value: string;
version: number;
};
version: number;
}
export interface IVegaWalletService {
url: string;
token: string;
getToken(params: {
wallet: string;
passphrase: string;
}): Promise<[string | undefined, string | undefined]>;
revokeToken(): Promise<[string | undefined, boolean]>;
getKeys(): Promise<[string | undefined, VegaKey[] | undefined]>;
}
export class VegaWalletService implements IVegaWalletService {
version: number;
url: string;
token: string;
key: string;
constructor() {
this.version = 1;
this.url = LocalStorage.getItem(WALLET_URL_KEY) || DEFAULT_WALLET_URL;
this.token = LocalStorage.getItem(TOKEN_STORAGE_KEY) || '';
this.key = LocalStorage.getItem(KEY_STORAGE_KEY) || '';
}
async getToken(params: {
wallet: string;
passphrase: string;
url: string;
}): Promise<[string | undefined, string | undefined]> {
const urlValid = this.validateUrl(params.url);
if (urlValid) {
this.setWalletUrl(params.url);
} else {
return [Errors.INVALID_URL, undefined];
}
try {
const res = await fetch(`${this.getUrl()}/${Endpoints.TOKEN}`, {
method: 'post',
body: JSON.stringify(params),
});
const json = await res.json();
if ('token' in json) {
this.setToken(json.token);
return [undefined, json.token];
} else {
return [Errors.INVALID_CREDENTIALS, undefined];
}
} catch (err) {
return this.handleServiceUnavailable();
}
}
async revokeToken(): Promise<[string | undefined, boolean]> {
if (!this.token) {
return [Errors.NO_TOKEN, false];
}
try {
const res = await fetch(`${this.getUrl()}/${Endpoints.TOKEN}`, {
method: 'delete',
headers: { authorization: `Bearer ${this.token}` },
});
const json = await res.json();
if (json.success) {
this.clearKey();
this.clearToken();
this.clearWalletUrl();
return [undefined, true];
} else {
return [undefined, false];
}
} catch (err) {
return this.handleServiceUnavailable(false);
}
}
async getKeys(): Promise<[string | undefined, VegaKey[] | undefined]> {
if (!this.token) {
return [Errors.NO_TOKEN, undefined];
}
try {
const res = await fetch(`${this.getUrl()}/${Endpoints.KEYS}`, {
headers: { authorization: `Bearer ${this.token}` },
});
const err = this.verifyResponse(res);
if (err) {
return [err, undefined];
}
const json = await res.json();
return [undefined, json.keys];
} catch (err) {
return this.handleServiceUnavailable();
}
}
async commandSync(
body: CommandSyncInput
): Promise<[string | undefined, CommandSyncResponse | undefined]> {
if (!this.token) {
return [Errors.NO_TOKEN, undefined];
}
try {
const res = await fetch(`${this.getUrl()}/${Endpoints.COMMAND}`, {
method: 'post',
body: JSON.stringify({
...body,
propagate: true,
}),
headers: { authorization: `Bearer ${this.token}` },
});
const err = this.verifyResponse(res);
if (err) {
return [err, undefined];
}
const json = await res.json();
if ('errors' in json) {
return [Errors.COMMAND_FAILED, undefined];
} else {
return [undefined, json];
}
} catch (err) {
return this.handleServiceUnavailable();
}
}
setKey(key: string) {
this.key = key;
LocalStorage.setItem(KEY_STORAGE_KEY, key);
}
private clearKey() {
this.key = '';
LocalStorage.removeItem(KEY_STORAGE_KEY);
}
private setToken(token: string) {
this.token = token;
LocalStorage.setItem(TOKEN_STORAGE_KEY, token);
}
private clearToken() {
this.token = '';
LocalStorage.removeItem(TOKEN_STORAGE_KEY);
}
private setWalletUrl(url: string) {
this.url = url;
LocalStorage.setItem(WALLET_URL_KEY, url);
}
private clearWalletUrl() {
this.url = DEFAULT_WALLET_URL;
LocalStorage.removeItem(WALLET_URL_KEY);
}
private getUrl() {
return `${this.url}/api/v${this.version}`;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private handleServiceUnavailable(returnVal?: boolean): [string, any] {
this.clearWalletUrl();
return [Errors.SERVICE_UNAVAILABLE, returnVal];
}
private validateUrl(url: string) {
try {
new URL(url);
} catch (err) {
return false;
}
return true;
}
/**
* Parses the response object to either return an error string or null if
* everything looks good. Clears token 403 response returned
*/
private verifyResponse(res: Response): string | null {
if (res.status === 403) {
this.clearToken();
return Errors.SESSION_EXPIRED;
} else if (!res.ok) {
return Errors.COMMAND_FAILED;
}
return null;
}
}
export function hasErrorProperty(obj: unknown): obj is GenericErrorResponse {
if (
(obj as GenericErrorResponse).error !== undefined &&
typeof (obj as GenericErrorResponse).error === 'string'
) {
return true;
}
return false;
}
export const vegaWalletService = new VegaWalletService();

View File

@ -1,3 +0,0 @@
export interface GenericErrorResponse {
error: string;
}

View File

@ -2,8 +2,6 @@ import { captureException, captureMessage } from '@sentry/minimal';
import * as React from 'react';
import { VoteValue } from '../../../../__generated__/globalTypes';
import type { VoteSubmissionInput } from '../../../../lib/vega-wallet/vega-wallet-service';
import { vegaWalletService } from '../../../../lib/vega-wallet/vega-wallet-service';
import { VOTE_VALUE_MAP } from './vote-types';
import { useVegaWallet } from '@vegaprotocol/wallet';
@ -44,7 +42,7 @@ export function useUserVote(
yesVotes: Votes | null,
noVotes: Votes | null
) {
const { keypair } = useVegaWallet();
const { keypair, sendTx } = useVegaWallet();
const yes = React.useMemo(() => yesVotes || [], [yesVotes]);
const no = React.useMemo(() => noVotes || [], [noVotes]);
@ -98,19 +96,15 @@ export function useUserVote(
setVoteState(VoteState.Pending);
try {
const variables: VoteSubmissionInput = {
const variables = {
pubKey: keypair.pub,
propagate: true,
voteSubmission: {
value: VOTE_VALUE_MAP[value],
proposalId,
},
};
const [err] = await vegaWalletService.commandSync(variables);
if (err) {
setVoteState(VoteState.Failed);
captureException(err);
}
await sendTx(variables);
// Now await vote via poll in parent component
} catch (err) {

View File

@ -58,10 +58,12 @@ export const AssociatePage = ({
() => totalVestedBalance.plus(totalLockedBalance).isEqualTo(0),
[totalLockedBalance, totalVestedBalance]
);
const zeroVega = React.useMemo(
() => walletBalance.isEqualTo(0),
[walletBalance]
);
React.useEffect(() => {
if (zeroVega && !zeroVesting) {
setSelectedStakingMethod(StakingMethod.Contract);

View File

@ -5,8 +5,8 @@ import { useTranslation } from 'react-i18next';
import { useAppState } from '../../contexts/app-state/app-state-context';
import { BigNumber } from '../../lib/bignumber';
import { removeDecimal } from '../../lib/decimals';
import { vegaWalletService } from '../../lib/vega-wallet/vega-wallet-service';
import type { UndelegateSubmissionInput } from '../../lib/vega-wallet/vega-wallet-service';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { UndelegateSubmissionBody } from '@vegaprotocol/vegawallet-service-api-client';
interface PendingStakeProps {
pendingAmount: BigNumber;
@ -27,28 +27,26 @@ export const PendingStake = ({
pubkey,
}: PendingStakeProps) => {
const { t } = useTranslation();
const { sendTx } = useVegaWallet();
const { appState } = useAppState();
const [formState, setFormState] = React.useState(FormState.Default);
const removeStakeNow = async () => {
setFormState(FormState.Pending);
const undelegateInput: UndelegateSubmissionInput = {
pubKey: pubkey,
undelegateSubmission: {
nodeId,
amount: removeDecimal(new BigNumber(pendingAmount), appState.decimals),
method: 'METHOD_NOW',
},
};
try {
const command = undelegateInput;
const [err] = await vegaWalletService.commandSync(command);
if (err) {
setFormState(FormState.Failure);
Sentry.captureException(err);
}
const command: UndelegateSubmissionBody = {
pubKey: pubkey,
propagate: true,
undelegateSubmission: {
nodeId,
amount: removeDecimal(
new BigNumber(pendingAmount),
appState.decimals
),
method: 'METHOD_NOW',
},
};
await sendTx(command);
} catch (err) {
setFormState(FormState.Failure);
Sentry.captureException(err);

View File

@ -12,11 +12,6 @@ import { useNetworkParam } from '../../hooks/use-network-param';
import { useSearchParams } from '../../hooks/use-search-params';
import { BigNumber } from '../../lib/bignumber';
import { addDecimal, removeDecimal } from '../../lib/decimals';
import type {
DelegateSubmissionInput,
UndelegateSubmissionInput,
} from '../../lib/vega-wallet/vega-wallet-service';
import { vegaWalletService } from '../../lib/vega-wallet/vega-wallet-service';
import type {
PartyDelegations,
PartyDelegationsVariables,
@ -25,6 +20,11 @@ import { StakeFailure } from './stake-failure';
import { StakePending } from './stake-pending';
import { StakeSuccess } from './stake-success';
import { Button, FormGroup } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type {
DelegateSubmissionBody,
UndelegateSubmissionBody,
} from '@vegaprotocol/vegawallet-service-api-client';
export const PARTY_DELEGATIONS_QUERY = gql`
query PartyDelegations($partyId: ID!) {
@ -81,6 +81,7 @@ export const StakingForm = ({
const navigate = useNavigate();
const client = useApolloClient();
const { appState } = useAppState();
const { sendTx } = useVegaWallet();
const [formState, setFormState] = React.useState(FormState.Default);
const { t } = useTranslation();
const [action, setAction] = React.useState<StakeAction>(params.action);
@ -114,15 +115,17 @@ export const StakingForm = ({
async function onSubmit() {
setFormState(FormState.Pending);
const delegateInput: DelegateSubmissionInput = {
const delegateInput: DelegateSubmissionBody = {
pubKey: pubkey,
propagate: true,
delegateSubmission: {
nodeId,
amount: removeDecimal(new BigNumber(amount), appState.decimals),
},
};
const undelegateInput: UndelegateSubmissionInput = {
const undelegateInput: UndelegateSubmissionBody = {
pubKey: pubkey,
propagate: true,
undelegateSubmission: {
nodeId,
amount: removeDecimal(new BigNumber(amount), appState.decimals),
@ -134,12 +137,7 @@ export const StakingForm = ({
};
try {
const command = action === Actions.Add ? delegateInput : undelegateInput;
const [err] = await vegaWalletService.commandSync(command);
if (err) {
setFormState(FormState.Failure);
Sentry.captureException(err);
}
await sendTx(command);
// await success via poll
} catch (err) {

View File

@ -140,11 +140,53 @@ export interface WithdrawPage_party {
withdrawals: WithdrawPage_party_withdrawals[] | null;
}
export interface WithdrawPage_assets_source_BuiltinAsset {
__typename: "BuiltinAsset";
}
export interface WithdrawPage_assets_source_ERC20 {
__typename: "ERC20";
/**
* The address of the erc20 contract
*/
contractAddress: string;
}
export type WithdrawPage_assets_source = WithdrawPage_assets_source_BuiltinAsset | WithdrawPage_assets_source_ERC20;
export interface WithdrawPage_assets {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The full name of the asset (e.g: Great British Pound)
*/
name: string;
/**
* The precision of the asset
*/
decimals: number;
/**
* The origin source of the asset (e.g: an erc20 asset)
*/
source: WithdrawPage_assets_source;
}
export interface WithdrawPage {
/**
* An entity that is trading on the VEGA network
*/
party: WithdrawPage_party | null;
/**
* The list of all assets in use in the vega network
*/
assets: WithdrawPage_assets[] | null;
}
export interface WithdrawPageVariables {

View File

@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { AccountType } from '../../__generated__/globalTypes';
import { EthWalletContainer } from '../../components/eth-wallet-container';
import { Heading } from '../../components/heading';
import { SplashLoader } from '../../components/splash-loader';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
@ -15,7 +14,7 @@ import type {
WithdrawPage,
WithdrawPageVariables,
} from './__generated__/WithdrawPage';
import { WithdrawForm } from './withdraw-form';
import { WithdrawManager } from '@vegaprotocol/withdraws';
const Withdraw = () => {
const { t } = useTranslation();
@ -24,9 +23,11 @@ const Withdraw = () => {
<>
<Heading title={t('withdrawPageHeading')} />
<p>{t('withdrawPageText')}</p>
<VegaWalletContainer>
{(currVegaKey) => <WithdrawContainer currVegaKey={currVegaKey} />}
</VegaWalletContainer>
<div className="mb-24">
<VegaWalletContainer>
{(currVegaKey) => <WithdrawContainer currVegaKey={currVegaKey} />}
</VegaWalletContainer>
</div>
<Callout title={t('withdrawPageInfoCalloutTitle')}>
<p className="mb-0">{t('withdrawPageInfoCalloutText')}</p>
</Callout>
@ -74,6 +75,17 @@ const WITHDRAW_PAGE_QUERY = gql`
}
}
}
assets {
id
symbol
name
decimals
source {
... on ERC20 {
contractAddress
}
}
}
}
`;
@ -123,27 +135,21 @@ export const WithdrawContainer = ({ currVegaKey }: WithdrawContainerProps) => {
return (
<>
{hasPendingWithdrawals && (
<Callout
title={t('pendingWithdrawalsCalloutTitle')}
intent={Intent.Prompt}
>
<p>{t('pendingWithdrawalsCalloutText')}</p>
<p className="mb-0">
<Link to={Routes.WITHDRAWALS}>
{t('pendingWithdrawalsCalloutButton')}
</Link>
</p>
</Callout>
<div className="mb-24">
<Callout
title={t('pendingWithdrawalsCalloutTitle')}
intent={Intent.Prompt}
>
<p>{t('pendingWithdrawalsCalloutText')}</p>
<p className="mb-0">
<Link to={Routes.WITHDRAWALS}>
{t('pendingWithdrawalsCalloutButton')}
</Link>
</p>
</Callout>
</div>
)}
<EthWalletContainer>
{(connectedAddress) => (
<WithdrawForm
accounts={accounts}
currVegaKey={currVegaKey}
connectedAddress={connectedAddress}
/>
)}
</EthWalletContainer>
<WithdrawManager assets={data.assets || []} accounts={accounts} />
</>
);
};

View File

@ -1,153 +0,0 @@
import { Callout, FormGroup, Intent, Select } from '@vegaprotocol/ui-toolkit';
import { ethers } from 'ethers';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router';
import { Loader } from '../../components/loader';
import { StatefulButton } from '../../components/stateful-button';
import { AmountInput } from '../../components/token-input';
import type { VegaKeyExtended } from '@vegaprotocol/wallet';
import {
Status as WithdrawStatus,
useCreateWithdrawal,
} from '../../hooks/use-create-withdrawal';
import { BigNumber } from '../../lib/bignumber';
import { removeDecimal } from '../../lib/decimals';
import { Routes } from '../router-config';
import type { WithdrawPage_party_accounts } from './__generated__/WithdrawPage';
import { EthAddressInput } from './eth-address-input';
interface WithdrawFormProps {
accounts: WithdrawPage_party_accounts[];
currVegaKey: VegaKeyExtended;
connectedAddress: string;
}
export const WithdrawForm = ({
accounts,
currVegaKey,
connectedAddress,
}: WithdrawFormProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const [amountStr, setAmount] = React.useState('');
const [account, setAccount] = React.useState(accounts[0]);
const [status, submit] = useCreateWithdrawal(currVegaKey.pub);
const [destinationAddress, setDestinationAddress] =
React.useState(connectedAddress);
const amount = React.useMemo(
() => new BigNumber(amountStr || 0),
[amountStr]
);
const maximum = React.useMemo(() => {
if (account) {
return new BigNumber(account.balanceFormatted);
}
return new BigNumber(0);
}, [account]);
const valid = React.useMemo(() => {
if (
!destinationAddress ||
amount.isLessThanOrEqualTo(0) ||
amount.isGreaterThan(maximum)
) {
return false;
}
return true;
}, [destinationAddress, amount, maximum]);
const addressValid = React.useMemo(() => {
return ethers.utils.isAddress(destinationAddress);
}, [destinationAddress]);
// Navigate to complete withdrawals page once withdrawal
// creation is complete
React.useEffect(() => {
if (status === WithdrawStatus.Success) {
navigate(Routes.WITHDRAWALS);
}
}, [status, navigate]);
return (
<form
className="my-40"
onSubmit={(e) => {
e.preventDefault();
if (!valid || !addressValid) return;
submit(
removeDecimal(amount, account.asset.decimals),
account.asset.id,
destinationAddress
);
}}
>
<FormGroup label={t('withdrawFormAssetLabel')} labelFor="asset">
{accounts.length ? (
<Select
name="asset"
id="asset"
onChange={(e) => {
const account = accounts.find(
(a) => a.asset.id === e.currentTarget.value
);
if (!account) throw new Error('No account');
setAccount(account);
}}
>
{accounts.map((a) => (
<option value={a.asset.id}>
{a.asset.symbol} ({a.balanceFormatted})
</option>
))}
</Select>
) : (
<p className="text-white-60">{t('withdrawFormNoAsset')}</p>
)}
</FormGroup>
<div className="mb-24">
<Callout
title={t('withdrawPreparedWarningHeading')}
intent={Intent.Warning}
>
<p>{t('withdrawPreparedWarningText1')}</p>
<p className="mb-0">{t('withdrawPreparedWarningText2')}</p>
</Callout>
</div>
<EthAddressInput
onChange={setDestinationAddress}
address={destinationAddress}
connectedAddress={connectedAddress}
isValid={addressValid}
/>
<FormGroup label={t('withdrawFormAmountLabel')} labelFor="amount">
<AmountInput
amount={amountStr}
setAmount={setAmount}
maximum={maximum}
currency={'VEGA'}
/>
</FormGroup>
<StatefulButton
type="submit"
disabled={!addressValid || !valid || status === WithdrawStatus.Pending}
>
{status === WithdrawStatus.Pending ? (
<>
<Loader />
<span>{t('withdrawFormSubmitButtonPending')}</span>
</>
) : (
t('withdrawFormSubmitButtonIdle', {
amount: amountStr,
symbol: account?.asset.symbol,
})
)}
</StatefulButton>
</form>
);
};

View File

@ -1,105 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { WithdrawalStatus } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: WithdrawalsPage
// ====================================================
export interface WithdrawalsPage_party_withdrawals_asset {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The precision of the asset
*/
decimals: number;
}
export interface WithdrawalsPage_party_withdrawals_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
}
export interface WithdrawalsPage_party_withdrawals_details {
__typename: "Erc20WithdrawalDetails";
/**
* The ethereum address of the receiver of the asset funds
*/
receiverAddress: string;
}
export interface WithdrawalsPage_party_withdrawals {
__typename: "Withdrawal";
/**
* The Vega internal id of the withdrawal
*/
id: string;
/**
* The amount to be withdrawn
*/
amount: string;
/**
* The asset to be withdrawn
*/
asset: WithdrawalsPage_party_withdrawals_asset;
/**
* The PartyID initiating the withdrawal
*/
party: WithdrawalsPage_party_withdrawals_party;
/**
* The current status of the withdrawal
*/
status: WithdrawalStatus;
/**
* RFC3339Nano time at which the withdrawal was created
*/
createdTimestamp: string;
/**
* RFC3339Nano time at which the withdrawal was finalized
*/
withdrawnTimestamp: string | null;
/**
* Hash of the transaction on the foreign chain
*/
txHash: string | null;
/**
* Foreign chain specific details about the withdrawal
*/
details: WithdrawalsPage_party_withdrawals_details | null;
}
export interface WithdrawalsPage_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
/**
* The list of all withdrawals initiated by the party
*/
withdrawals: WithdrawalsPage_party_withdrawals[] | null;
}
export interface WithdrawalsPage {
/**
* An entity that is trading on the VEGA network
*/
party: WithdrawalsPage_party | null;
}
export interface WithdrawalsPageVariables {
partyId: string;
}

View File

@ -1,6 +1,4 @@
import { gql, useQuery } from '@apollo/client';
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { format } from 'date-fns';
import orderBy from 'lodash/orderBy';
import React from 'react';
@ -10,23 +8,16 @@ import { EtherscanLink } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../components/heading';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { SplashLoader } from '../../components/splash-loader';
import { TransactionButton } from '../../components/transaction-button';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
import type { VegaKeyExtended } from '@vegaprotocol/wallet';
import { useContracts } from '../../contexts/contracts/contracts-context';
import { TxState } from '../../hooks/transaction-reducer';
import { usePollERC20Approval } from '../../hooks/use-erc-poll20-approval';
import { useRefreshBalances } from '../../hooks/use-refresh-balances';
import { useTransaction } from '../../hooks/use-transaction';
import { BigNumber } from '../../lib/bignumber';
import { DATE_FORMAT_DETAILED } from '../../lib/date-formats';
import { addDecimal } from '../../lib/decimals';
import { truncateMiddle } from '../../lib/truncate-middle';
import type {
WithdrawalsPage,
WithdrawalsPage_party_withdrawals,
WithdrawalsPageVariables,
} from './__generated__/WithdrawalsPage';
import type { Withdrawals_party_withdrawals } from '@vegaprotocol/withdraws';
import { useCompleteWithdraw, useWithdrawals } from '@vegaprotocol/withdraws';
import { TransactionDialog } from '@vegaprotocol/web3';
import { WithdrawalStatus } from '../../__generated__/globalTypes';
const Withdrawals = () => {
const { t } = useTranslation();
@ -34,13 +25,6 @@ const Withdrawals = () => {
return (
<>
<Heading title={t('withdrawalsTitle')} />
<p>{t('withdrawalsText')}</p>
<Callout
title={t('withdrawalsPreparedWarningHeading')}
intent={Intent.Warning}
>
<p>{t('withdrawalsPreparedWarningText')}</p>
</Callout>
<VegaWalletContainer>
{(currVegaKey) => (
<WithdrawPendingContainer currVegaKey={currVegaKey} />
@ -54,51 +38,12 @@ interface WithdrawPendingContainerProps {
currVegaKey: VegaKeyExtended;
}
const WITHDRAWALS_PAGE_QUERY = gql`
query WithdrawalsPage($partyId: ID!) {
party(id: $partyId) {
id
withdrawals {
id
amount
asset {
id
symbol
decimals
}
party {
id
}
status
createdTimestamp
withdrawnTimestamp
txHash
details {
... on Erc20WithdrawalDetails {
receiverAddress
}
}
}
}
}
`;
const WithdrawPendingContainer = ({
currVegaKey,
}: WithdrawPendingContainerProps) => {
const { t } = useTranslation();
const { account } = useWeb3React();
const refreshBalances = useRefreshBalances(account || '');
const { data, loading, error, refetch } = useQuery<
WithdrawalsPage,
WithdrawalsPageVariables
>(WITHDRAWALS_PAGE_QUERY, {
variables: { partyId: currVegaKey.pub },
// This must be network-only because you are navigated to this page automatically after the withdrawal is created,
// if you have already visited this page the query result is cached with 0 withdrawals, so we need to refetch every
// time to ensure the withdrawal is shown immediately
fetchPolicy: 'network-only',
});
const { transaction, submit } = useCompleteWithdraw();
const { data, loading, error } = useWithdrawals();
const withdrawals = React.useMemo(() => {
if (!data?.party?.withdrawals?.length) return [];
@ -132,61 +77,60 @@ const WithdrawPendingContainer = ({
}
return (
<ul role="list">
{withdrawals.map((w) => (
<li key={w.id} className="mb-28">
<Withdrawal
withdrawal={w}
refetchWithdrawals={refetch}
refetchBalances={refreshBalances}
/>
</li>
))}
</ul>
<>
<h2>{t('withdrawalsPreparedWarningHeading')}</h2>
<p>{t('withdrawalsText')}</p>
<p>{t('withdrawalsPreparedWarningText')}</p>
<ul role="list">
{withdrawals.map((w) => (
<li key={w.id}>
<Withdrawal withdrawal={w} complete={submit} />
</li>
))}
</ul>
<TransactionDialog name="withdraw" {...transaction} />
</>
);
};
interface WithdrawalProps {
withdrawal: WithdrawalsPage_party_withdrawals;
refetchWithdrawals: () => void;
refetchBalances: () => void;
withdrawal: Withdrawals_party_withdrawals;
complete: (withdrawalId: string) => void;
}
export const Withdrawal = ({
withdrawal,
refetchWithdrawals,
refetchBalances,
}: WithdrawalProps) => {
export const Withdrawal = ({ withdrawal, complete }: WithdrawalProps) => {
const { t } = useTranslation();
const erc20Approval = usePollERC20Approval(withdrawal.id);
const { erc20Bridge } = useContracts();
const { state, perform, reset } = useTransaction(() => {
if (!erc20Approval) {
throw new Error('Withdraw needs approval object');
}
if (!withdrawal.details?.receiverAddress) {
throw new Error('Missing receiver address');
const renderStatus = ({
id,
status,
txHash,
pendingOnForeignChain,
}: Withdrawals_party_withdrawals) => {
if (pendingOnForeignChain) {
return t('Pending');
}
return erc20Bridge.withdraw({
assetSource: erc20Approval.assetSource,
amount: erc20Approval.amount,
nonce: erc20Approval.nonce,
signatures: erc20Approval.signatures,
// TODO: switch when targetAddress is populated and deployed to mainnet data.erc20WithdrawalApproval.targetAddress,
targetAddress: withdrawal.details.receiverAddress,
});
});
React.useEffect(() => {
// Once complete we need to refetch the withdrawals so that pending withdrawal
// is updated to have a txHash indicating it is complete. Updating your account balance
// is already handled by the query in the VegaWallet that polls
if (state.txState === TxState.Complete) {
refetchWithdrawals();
refetchBalances();
if (status === WithdrawalStatus.Finalized) {
if (txHash) {
return t('Complete');
} else {
return (
<>
{t('Incomplete')}{' '}
<button
className="text-white underline"
onClick={() => complete(id)}
>
{t('withdrawalsCompleteButton')}
</button>
</>
);
}
}
}, [state, refetchWithdrawals, refetchBalances]);
return status;
};
return (
<div>
@ -201,10 +145,6 @@ export const Withdrawal = ({
{withdrawal.asset.symbol}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
{t('from')}
<span>{truncateMiddle(withdrawal.party.id)}</span>
</KeyValueTableRow>
<KeyValueTableRow>
{t('toEthereum')}
<span>
@ -226,27 +166,23 @@ export const Withdrawal = ({
</span>
</KeyValueTableRow>
<KeyValueTableRow>
{t('Signature')}
<span title={erc20Approval?.signatures}>
{!erc20Approval?.signatures
? t('Loading')
: truncateMiddle(erc20Approval.signatures)}
{t('withdrawalTransaction', { foreignChain: 'Ethereum' })}
<span>
{withdrawal.txHash ? (
<EtherscanLink
tx={withdrawal.txHash}
text={truncateMiddle(withdrawal.txHash)}
/>
) : (
'-'
)}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
{t('status')}
{renderStatus(withdrawal)}
</KeyValueTableRow>
</KeyValueTable>
<TransactionButton
text={
!erc20Approval
? t('withdrawalsPreparingButton')
: t('withdrawalsCompleteButton')
}
transactionState={state}
forceTxState={withdrawal.txHash ? TxState.Complete : undefined}
forceTxHash={withdrawal.txHash}
disabled={!erc20Approval}
start={perform}
reset={reset}
/>
</div>
);
};

View File

@ -8,10 +8,12 @@ Feature: Markets page
Scenario: Select active market
Given I am on the markets page
And I can view markets
When I click on "Active" mocked market
Then trading page for "active" market is displayed
Scenario: Select suspended market
Given I am on the markets page
And I can view markets
When I click on "Suspended" mocked market
Then trading page for "suspended" market is displayed

View File

@ -10,6 +10,9 @@ export default class MarketPage extends BasePage {
marketStateColId = 'data';
validateMarketsAreDisplayed() {
// We need this to ensure that ag-grid is fully rendered before asserting
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
cy.get('.ag-root-wrapper').should('be.visible');
}

View File

@ -15,21 +15,19 @@ const mockMarkets = () => {
});
};
beforeEach(() => {
mockMarkets();
});
Then('I navigate to markets page', () => {
mockMarkets();
marketsPage.navigateToMarkets();
cy.wait('@Markets');
});
Given('I am on the markets page', () => {
mockMarkets();
cy.visit('/markets');
cy.wait('@Markets');
});
Then('I can view markets', () => {
cy.wait('@Markets');
marketsPage.validateMarketsAreDisplayed();
});

View File

@ -1,18 +0,0 @@
import type { AssetFields } from './__generated__/AssetFields';
export interface ERC20Asset extends AssetFields {
source: {
__typename: 'ERC20';
contractAddress: string;
};
}
type UnknownAsset = Pick<AssetFields, '__typename' | 'source'>;
// Type guard to ensure an asset is an ERC20 token
export const isERC20Asset = (asset: UnknownAsset): asset is ERC20Asset => {
if (asset.source.__typename === 'ERC20') {
return true;
}
return false;
};

View File

@ -6,7 +6,6 @@ import { DepositManager } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/react-helpers';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { ASSET_FRAGMENT } from '../../../lib/query-fragments';
import { isERC20Asset } from '../../../lib/assets';
const DEPOSIT_PAGE_QUERY = gql`
${ASSET_FRAGMENT}
@ -45,7 +44,7 @@ export const DepositContainer = ({
<DepositManager
bridgeAddress={ethereumConfig.collateral_bridge_contract.address}
requiredConfirmations={ethereumConfig.confirmations}
assets={data.assets.filter(isERC20Asset)}
assets={data.assets}
initialAssetId={assetId}
/>
);

View File

@ -6,7 +6,6 @@ import { WithdrawManager } from '@vegaprotocol/withdraws';
import { ASSET_FRAGMENT } from '../../../lib/query-fragments';
import Link from 'next/link';
import { PageQueryContainer } from '../../../components/page-query-container';
import { isERC20Asset } from '../../../lib/assets';
import type {
WithdrawPageQuery,
WithdrawPageQueryVariables,
@ -85,7 +84,7 @@ export const WithdrawPageContainer = ({
</p>
) : null}
<WithdrawManager
assets={data.assets.filter(isERC20Asset)}
assets={data.assets}
accounts={data.party?.accounts || []}
initialAssetId={assetId}
/>

View File

@ -82,8 +82,8 @@ export const DepositForm = ({
});
const onDeposit = async (fields: FormFields) => {
if (!selectedAsset) {
throw new Error('Asset not selected');
if (!selectedAsset || selectedAsset.source.__typename !== 'ERC20') {
throw new Error('Invalid asset');
}
submitDeposit({
@ -153,11 +153,13 @@ export const DepositForm = ({
<FormGroup label={t('Asset')} labelFor="asset" className="relative">
<Select {...register('asset', { validate: { required } })} id="asset">
<option value="">{t('Please select')}</option>
{assets.map((a) => (
<option key={a.id} value={a.id}>
{a.name}
</option>
))}
{assets
.filter((a) => a.source.__typename === 'ERC20')
.map((a) => (
<option key={a.id} value={a.id}>
{a.name}
</option>
))}
</Select>
{errors.asset?.message && (
<InputError intent="danger" className="mt-4" forInput="asset">

View File

@ -10,16 +10,23 @@ import { useSubmitFaucet } from './use-submit-faucet';
import { EthTxStatus, TransactionDialog } from '@vegaprotocol/web3';
import { useTokenContract, useBridgeContract } from '@vegaprotocol/web3';
interface ERC20AssetSource {
__typename: 'ERC20';
contractAddress: string;
}
interface BuiltinAssetSource {
__typename: 'BuiltinAsset';
}
type AssetSource = ERC20AssetSource | BuiltinAssetSource;
export interface Asset {
__typename: 'Asset';
id: string;
symbol: string;
name: string;
decimals: number;
source: {
__typename: 'ERC20';
contractAddress: string;
};
source: AssetSource;
}
interface DepositManagerProps {
@ -44,7 +51,9 @@ export const DepositManager = ({
}, [assets, assetId]);
const tokenContract = useTokenContract(
asset?.source.contractAddress,
asset?.source.__typename === 'ERC20'
? asset.source.contractAddress
: undefined,
process.env['NX_VEGA_ENV'] !== 'MAINNET'
);
const bridgeContract = useBridgeContract();

View File

@ -14,7 +14,7 @@ export const useGetDepositLimits = (
asset?: Asset
): Limits | null => {
const getLimits = useCallback(async () => {
if (!contract || !asset) {
if (!contract || !asset || asset.source.__typename !== 'ERC20') {
return;
}

View File

@ -1,23 +1,10 @@
import { ethers } from 'ethers';
import { SHA3 } from 'sha3';
import { remove0x } from './remove-0x';
import { sha3_256 } from 'js-sha3';
/**
* This function creates an ID in the same way that core does on the backend. This way we
* Can match up the newly created order with incoming orders via a subscription
*/
export const determineId = (sig: string) => {
// Prepend 0x
if (sig.slice(0, 2) !== '0x') {
sig = '0x' + sig;
}
// Create the ID
const hash = new SHA3(256);
const bytes = ethers.utils.arrayify(sig);
hash.update(Buffer.from(bytes));
const id = ethers.utils.hexlify(hash.digest());
// Remove 0x as core doesn't keep them in the API
return remove0x(id);
return sha3_256(ethers.utils.arrayify('0x' + sig));
};

View File

@ -1,9 +1,8 @@
import type {
VegaKey,
TransactionResponse,
OrderSubmissionBody,
WithdrawSubmissionBody,
} from '@vegaprotocol/vegawallet-service-api-client';
import type { TransactionSubmission } from '../types';
export { RestConnector } from './rest-connector';
type ErrorResponse =
@ -26,6 +25,6 @@ export interface VegaConnector {
/** Send a TX to the network. Only support order submission for now */
sendTx: (
body: OrderSubmissionBody | WithdrawSubmissionBody
body: TransactionSubmission
) => Promise<TransactionResponse | ErrorResponse>;
}

View File

@ -1,8 +1,4 @@
import type {
Configuration,
OrderSubmissionBody,
WithdrawSubmissionBody,
} from '@vegaprotocol/vegawallet-service-api-client';
import type { Configuration } from '@vegaprotocol/vegawallet-service-api-client';
import {
createConfiguration,
DefaultApi,
@ -10,6 +6,7 @@ import {
import { LocalStorage } from '@vegaprotocol/react-helpers';
import { WALLET_CONFIG } from '../storage-keys';
import type { VegaConnector } from '.';
import type { TransactionSubmission } from '../types';
// Perhaps there should be a default ConnectorConfig that others can extend off. Do all connectors
// need to use local storage, I don't think so...
@ -88,7 +85,7 @@ export class RestConnector implements VegaConnector {
}
}
async sendTx(body: OrderSubmissionBody | WithdrawSubmissionBody) {
async sendTx(body: TransactionSubmission) {
try {
return await this.service.commandSyncPost(body);
} catch (err) {

View File

@ -5,10 +5,7 @@ import type { VegaKeyExtended, VegaWalletContextShape } from '.';
import type { VegaConnector } from './connectors';
import { VegaWalletContext } from './context';
import { WALLET_KEY } from './storage-keys';
import type {
OrderSubmissionBody,
WithdrawSubmissionBody,
} from '@vegaprotocol/vegawallet-service-api-client';
import type { TransactionSubmission } from './types';
interface VegaWalletProviderProps {
children: ReactNode;
@ -72,16 +69,13 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
}
}, []);
const sendTx = useCallback(
(body: OrderSubmissionBody | WithdrawSubmissionBody) => {
if (!connector.current) {
return null;
}
const sendTx = useCallback((body: TransactionSubmission) => {
if (!connector.current) {
return null;
}
return connector.current.sendTx(body);
},
[]
);
return connector.current.sendTx(body);
}, []);
// Current selected keypair derived from publicKey state
const keypair = useMemo(() => {

View File

@ -1,5 +1,8 @@
import type {
DelegateSubmissionBody,
OrderSubmissionBody,
UndelegateSubmissionBody,
VoteSubmissionBody,
WithdrawSubmissionBody,
} from '@vegaprotocol/vegawallet-service-api-client';
@ -25,4 +28,7 @@ export enum OrderTimeInForce {
// Will make Transaction a union type as other transactions are added
export type TransactionSubmission =
| OrderSubmissionBody
| WithdrawSubmissionBody;
| WithdrawSubmissionBody
| VoteSubmissionBody
| DelegateSubmissionBody
| UndelegateSubmissionBody;

View File

@ -4,3 +4,4 @@ export * from './lib/withdrawals-table';
export * from './lib/use-complete-withdraw';
export * from './lib/use-withdraw';
export * from './lib/use-withdrawals';
export * from './lib/__generated__/Withdrawals';

View File

@ -11,6 +11,7 @@ export const generateAsset = (override?: PartialDeep<Asset>) => {
name: 'asset-name',
decimals: 5,
source: {
__typename: 'ERC20',
contractAddress: 'contract-address',
},
};

View File

@ -1,13 +1,22 @@
import type { AccountType } from '@vegaprotocol/types';
interface ERC20AssetSource {
__typename: 'ERC20';
contractAddress: string;
}
interface BuiltinAssetSource {
__typename: 'BuiltinAsset';
}
type AssetSource = ERC20AssetSource | BuiltinAssetSource;
export interface Asset {
id: string;
symbol: string;
name: string;
decimals: number;
source: {
contractAddress: string;
};
source: AssetSource;
}
export interface Account {

View File

@ -90,11 +90,13 @@ export const WithdrawForm = ({
id="asset"
>
<option value="">{t('Please select')}</option>
{assets.map((a) => (
<option key={a.id} value={a.id}>
{a.name}
</option>
))}
{assets
.filter((a) => a.source.__typename === 'ERC20')
.map((a) => (
<option key={a.id} value={a.id}>
{a.name}
</option>
))}
</Select>
)}
/>

View File

@ -29,7 +29,7 @@
"@sentry/react": "^6.19.2",
"@sentry/tracing": "^6.19.2",
"@vegaprotocol/smart-contracts-sdk": "^1.6.0",
"@vegaprotocol/vegawallet-service-api-client": "^0.4.9",
"@vegaprotocol/vegawallet-service-api-client": "^0.4.11",
"@walletconnect/ethereum-provider": "^1.7.5",
"@web3-react/core": "8.0.20-beta.0",
"@web3-react/metamask": "8.0.16-beta.0",
@ -52,6 +52,7 @@
"i18next": "^20.3.5",
"i18next-browser-languagedetector": "^6.1.2",
"immer": "^9.0.12",
"js-sha3": "^0.8.0",
"lodash": "^4.17.21",
"next": "^12.0.7",
"pennant": "0.4.8",
@ -69,7 +70,6 @@
"react-window-infinite-loader": "^1.0.7",
"recharts": "^2.1.2",
"regenerator-runtime": "0.13.7",
"sha3": "^2.1.4",
"tailwindcss": "^3.0.23",
"tslib": "^2.0.0",
"uuid": "^8.3.2",

View File

@ -6205,10 +6205,10 @@
ethers "^5.5.2"
lodash "^4.17.21"
"@vegaprotocol/vegawallet-service-api-client@^0.4.9":
version "0.4.9"
resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.9.tgz#9c2566407e9c86248f2c170b1f495afdcb347d87"
integrity sha512-zqppuVu3VrsHpdcNBvu6G+qMMt0CBXi5wHBb/0ryiXdXD9dmQci5Qz8zoGx5syOupFMdUDNstmGOaFWbBOuvbg==
"@vegaprotocol/vegawallet-service-api-client@^0.4.11":
version "0.4.11"
resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.11.tgz#41a623afc9957dcf8b5425f74280ba7861e92b74"
integrity sha512-yiodc3YFWG+RGG+wjpTjYmNAECP/Nv244mVu8IGVtj8LZo02KC/LpNCgmMhGaK4ZcqVtxHv9t7OUCSEWZhSgOg==
dependencies:
es6-promise "^4.2.4"
url-parse "^1.4.3"
@ -8438,14 +8438,6 @@ buffer@5.6.0:
base64-js "^1.0.2"
ieee754 "^1.1.4"
buffer@6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
buffer@^4.3.0:
version "4.9.2"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
@ -13304,7 +13296,7 @@ identity-obj-proxy@3.0.0:
dependencies:
harmony-reflect "^1.4.6"
ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1:
ieee754@^1.1.13, ieee754@^1.1.4:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@ -14693,7 +14685,7 @@ jest@27.2.3:
import-local "^3.0.2"
jest-cli "^27.2.3"
js-sha3@0.8.0:
js-sha3@0.8.0, js-sha3@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
@ -19310,13 +19302,6 @@ sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8, sha.js@~2.4.4:
inherits "^2.0.1"
safe-buffer "^5.0.1"
sha3@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.4.tgz#000fac0fe7c2feac1f48a25e7a31b52a6492cc8f"
integrity sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==
dependencies:
buffer "6.0.3"
shallow-clone@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"