Feat/Use callStatic to improve error messaging (#831)
* feat: make use max only use account balance, add custom max messages * fix: withdraw threshold limit display * feat: add callstatic to ethereum transaction hook * feat: improve types for useTransaction hook * chore: fix types and remove ts-ignore * chore: convert all smart contract wrapper methods to match metaclass methods * fix: this context for calling tx * chore: fix comment and any type * chore: typo Co-authored-by: Edd <edd@vega.xyz> Co-authored-by: Edd <edd@vega.xyz>
This commit is contained in:
parent
1afdf4899d
commit
e47298761a
@ -40,8 +40,8 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
decimals,
|
||||
] = await Promise.all([
|
||||
token.totalSupply(),
|
||||
staking.totalStaked(),
|
||||
vesting.totalStaked(),
|
||||
staking.total_staked(),
|
||||
vesting.total_staked(),
|
||||
token.decimals(),
|
||||
]);
|
||||
|
||||
|
@ -41,9 +41,9 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
|
||||
if (!account || !config) return;
|
||||
try {
|
||||
const [b, w, stats, a] = await Promise.all([
|
||||
contracts.vesting.userTotalAllTranches(account),
|
||||
contracts.vesting.user_total_all_tranches(account),
|
||||
contracts.token.balanceOf(account),
|
||||
contracts.vesting.userStats(account),
|
||||
contracts.vesting.user_stats(account),
|
||||
contracts.token.allowance(
|
||||
account,
|
||||
config.staking_bridge_contract.address
|
||||
|
@ -54,7 +54,7 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
config.staking_bridge_contract.address,
|
||||
signer || provider
|
||||
);
|
||||
const vegaAddress = await staking.stakingToken();
|
||||
const vegaAddress = await staking.staking_token();
|
||||
|
||||
setContracts({
|
||||
token: new Token(vegaAddress, signer || provider),
|
||||
|
@ -37,8 +37,8 @@ export const useGetUserTrancheBalances = (
|
||||
const trancheIds = [0, ...userTranches.map((t) => t.tranche_id)];
|
||||
const promises = trancheIds.map(async (tId) => {
|
||||
const [t, v] = await Promise.all([
|
||||
vesting.getTrancheBalance(address, tId),
|
||||
vesting.getVestedForTranche(address, tId),
|
||||
vesting.get_tranche_balance(address, tId),
|
||||
vesting.get_vested_for_tranche(address, tId),
|
||||
]);
|
||||
|
||||
const total = toBigNum(t, decimals);
|
||||
|
@ -18,8 +18,8 @@ export function useRefreshAssociatedBalances() {
|
||||
async (ethAddress: string, vegaKey: string) => {
|
||||
const [walletAssociatedBalance, vestingAssociatedBalance] =
|
||||
await Promise.all([
|
||||
staking.stakeBalance(ethAddress, vegaKey),
|
||||
vesting.stakeBalance(ethAddress, vegaKey),
|
||||
staking.stake_balance(ethAddress, vegaKey),
|
||||
vesting.stake_balance(ethAddress, vegaKey),
|
||||
]);
|
||||
|
||||
appDispatch({
|
||||
|
@ -24,13 +24,13 @@ export const useRefreshBalances = (address: string) => {
|
||||
try {
|
||||
const [b, w, stats, a, walletStakeBalance, vestingStakeBalance] =
|
||||
await Promise.all([
|
||||
vesting.userTotalAllTranches(address),
|
||||
vesting.user_total_all_tranches(address),
|
||||
token.balanceOf(address),
|
||||
vesting.userStats(address),
|
||||
vesting.user_stats(address),
|
||||
token.allowance(address, config.staking_bridge_contract.address),
|
||||
// Refresh connected vega key balances as well if we are connected to a vega key
|
||||
keypair?.pub ? staking.stakeBalance(address, keypair.pub) : null,
|
||||
keypair?.pub ? vesting.stakeBalance(address, keypair.pub) : null,
|
||||
keypair?.pub ? staking.stake_balance(address, keypair.pub) : null,
|
||||
keypair?.pub ? vesting.stake_balance(address, keypair.pub) : null,
|
||||
]);
|
||||
|
||||
const balance = toBigNum(b, decimals);
|
||||
|
@ -41,7 +41,7 @@ export const RedeemFromTranche = () => {
|
||||
state: txState,
|
||||
perform,
|
||||
dispatch: txDispatch,
|
||||
} = useTransaction(() => vesting.withdrawFromTranche(numberId));
|
||||
} = useTransaction(() => vesting.withdraw_from_tranche(numberId));
|
||||
const { token } = useContracts();
|
||||
|
||||
const redeemedAmount = React.useMemo(() => {
|
||||
|
@ -29,7 +29,7 @@ export const useAddStake = (
|
||||
appState: { decimals },
|
||||
} = useAppState();
|
||||
const contractAdd = useTransaction(
|
||||
() => vesting.stakeTokens(removeDecimal(amount, decimals), vegaKey),
|
||||
() => vesting.stake_tokens(removeDecimal(amount, decimals), vegaKey),
|
||||
confirmations
|
||||
);
|
||||
const walletAdd = useTransaction(
|
||||
|
@ -21,10 +21,10 @@ export const useRemoveStake = (
|
||||
// which if staked > wallet balance means you cannot unstaked
|
||||
// even worse if you stake everything then you can't unstake anything!
|
||||
const contractRemove = useTransaction(() =>
|
||||
vesting.removeStake(removeDecimal(amount, appState.decimals), vegaKey)
|
||||
vesting.remove_stake(removeDecimal(amount, appState.decimals), vegaKey)
|
||||
);
|
||||
const walletRemove = useTransaction(() =>
|
||||
staking.removeStake(removeDecimal(amount, appState.decimals), vegaKey)
|
||||
staking.remove_stake(removeDecimal(amount, appState.decimals), vegaKey)
|
||||
);
|
||||
const refreshBalances = useRefreshBalances(address);
|
||||
const getAssociationBreakdown = useGetAssociationBreakdown(
|
||||
|
@ -38,13 +38,13 @@ export interface DepositFormProps {
|
||||
selectedAsset?: Asset;
|
||||
onSelectAsset: (assetId: string) => void;
|
||||
balance: BigNumber | undefined;
|
||||
submitApprove: () => Promise<void>;
|
||||
submitApprove: () => void;
|
||||
submitDeposit: (args: {
|
||||
assetSource: string;
|
||||
amount: string;
|
||||
vegaPublicKey: string;
|
||||
}) => Promise<void>;
|
||||
requestFaucet: () => Promise<void>;
|
||||
}) => void;
|
||||
requestFaucet: () => void;
|
||||
limits: {
|
||||
max: BigNumber;
|
||||
deposited: BigNumber;
|
||||
|
@ -23,7 +23,10 @@ export const DepositLimits = ({ limits, balance }: DepositLimitsProps) => {
|
||||
if (limits.deposited.isEqualTo(0)) {
|
||||
remaining = maxLimit;
|
||||
} else {
|
||||
remaining = limits.max.minus(limits.deposited).toString();
|
||||
const amountRemaining = limits.max.minus(limits.deposited);
|
||||
remaining = amountRemaining.isGreaterThan(1_000_000)
|
||||
? t('1m+')
|
||||
: amountRemaining.toString();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
useEthereumConfig,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { useTokenContract } from '@vegaprotocol/web3';
|
||||
import { removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface ERC20AssetSource {
|
||||
__typename: 'ERC20';
|
||||
@ -77,7 +78,7 @@ export const DepositManager = ({
|
||||
);
|
||||
|
||||
// Set up approve transaction
|
||||
const approve = useSubmitApproval(tokenContract, asset?.decimals);
|
||||
const approve = useSubmitApproval(tokenContract);
|
||||
|
||||
// Set up deposit transaction
|
||||
const { confirmationEvent, ...deposit } = useSubmitDeposit();
|
||||
@ -109,9 +110,15 @@ export const DepositManager = ({
|
||||
selectedAsset={asset}
|
||||
onSelectAsset={(id) => setAssetId(id)}
|
||||
assets={sortBy(assets, 'name')}
|
||||
submitApprove={approve.perform}
|
||||
submitDeposit={deposit.perform}
|
||||
requestFaucet={faucet.perform}
|
||||
submitApprove={() => {
|
||||
if (!asset || !config) return;
|
||||
const amount = removeDecimal('1000000', asset.decimals);
|
||||
approve.perform(config.collateral_bridge_contract.address, amount);
|
||||
}}
|
||||
submitDeposit={(args) => {
|
||||
deposit.perform(args.assetSource, args.amount, args.vegaPublicKey);
|
||||
}}
|
||||
requestFaucet={() => faucet.perform()}
|
||||
limits={limits}
|
||||
allowance={allowance}
|
||||
isFaucetable={isFaucetable}
|
||||
|
@ -20,7 +20,7 @@ export const useGetDepositLimits = (asset?: Asset) => {
|
||||
return;
|
||||
}
|
||||
|
||||
return contract.getDepositMaximum(asset.source.contractAddress);
|
||||
return contract.get_deposit_maximum(asset.source.contractAddress);
|
||||
}, [asset, contract]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,22 +1,10 @@
|
||||
import { removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
import type { Token } from '@vegaprotocol/smart-contracts';
|
||||
import { useEthereumConfig, useEthereumTransaction } from '@vegaprotocol/web3';
|
||||
|
||||
export const useSubmitApproval = (
|
||||
contract: Token | null,
|
||||
decimals: number | undefined
|
||||
) => {
|
||||
const { config } = useEthereumConfig();
|
||||
|
||||
const transaction = useEthereumTransaction(() => {
|
||||
if (!contract || !config || decimals === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const amount = removeDecimal('1000000', decimals);
|
||||
|
||||
return contract.approve(config.collateral_bridge_contract.address, amount);
|
||||
});
|
||||
import { useEthereumTransaction } from '@vegaprotocol/web3';
|
||||
|
||||
export const useSubmitApproval = (contract: Token | null) => {
|
||||
const transaction = useEthereumTransaction<Token, 'approve'>(
|
||||
contract,
|
||||
'approve'
|
||||
);
|
||||
return transaction;
|
||||
};
|
||||
|
@ -12,6 +12,11 @@ import {
|
||||
useEthereumConfig,
|
||||
useEthereumTransaction,
|
||||
} from '@vegaprotocol/web3';
|
||||
import type {
|
||||
CollateralBridge,
|
||||
CollateralBridgeNew,
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import { prepend0x } from '@vegaprotocol/smart-contracts';
|
||||
|
||||
const DEPOSIT_EVENT_SUB = gql`
|
||||
subscription DepositEvent($partyId: ID!) {
|
||||
@ -35,26 +40,10 @@ export const useSubmitDeposit = () => {
|
||||
// Store public key from contract arguments for use in the subscription,
|
||||
// NOTE: it may be different from the users connected key
|
||||
const [partyId, setPartyId] = useState<string | null>(null);
|
||||
|
||||
const { transaction, perform } = useEthereumTransaction<{
|
||||
assetSource: string;
|
||||
amount: string;
|
||||
vegaPublicKey: string;
|
||||
}>((args) => {
|
||||
if (!contract) {
|
||||
return null;
|
||||
}
|
||||
// New deposit started clear old confirmation event and start
|
||||
// tracking deposits for the new public key
|
||||
setConfirmationEvent(null);
|
||||
setPartyId(args.vegaPublicKey);
|
||||
|
||||
return contract.depositAsset(
|
||||
args.assetSource,
|
||||
args.amount,
|
||||
args.vegaPublicKey
|
||||
);
|
||||
}, config?.confirmations);
|
||||
const { transaction, perform } = useEthereumTransaction<
|
||||
CollateralBridgeNew | CollateralBridge,
|
||||
'deposit_asset'
|
||||
>(contract, 'deposit_asset', config?.confirmations);
|
||||
|
||||
useSubscription<DepositEvent, DepositEventVariables>(DEPOSIT_EVENT_SUB, {
|
||||
variables: { partyId: partyId ? remove0x(partyId) : '' },
|
||||
@ -90,7 +79,12 @@ export const useSubmitDeposit = () => {
|
||||
|
||||
return {
|
||||
...transaction,
|
||||
perform,
|
||||
perform: (...args: Parameters<typeof perform>) => {
|
||||
setConfirmationEvent(null);
|
||||
setPartyId(args[2]);
|
||||
const publicKey = prepend0x(args[2]);
|
||||
perform(args[0], args[1], publicKey);
|
||||
},
|
||||
confirmationEvent,
|
||||
};
|
||||
};
|
||||
|
@ -1,14 +1,10 @@
|
||||
import { Token } from '@vegaprotocol/smart-contracts';
|
||||
import type { TokenFaucetable } from '@vegaprotocol/smart-contracts';
|
||||
import type { Token, TokenFaucetable } from '@vegaprotocol/smart-contracts';
|
||||
import { useEthereumTransaction } from '@vegaprotocol/web3';
|
||||
|
||||
export const useSubmitFaucet = (contract: Token | TokenFaucetable | null) => {
|
||||
const transaction = useEthereumTransaction(() => {
|
||||
if (!contract || contract instanceof Token) {
|
||||
return null;
|
||||
}
|
||||
return contract.faucet();
|
||||
});
|
||||
|
||||
const transaction = useEthereumTransaction<TokenFaucetable, 'faucet'>(
|
||||
contract,
|
||||
'faucet'
|
||||
);
|
||||
return transaction;
|
||||
};
|
||||
|
@ -50,11 +50,14 @@ export const useOrderEdit = () => {
|
||||
orderAmendment: {
|
||||
orderId: order.id,
|
||||
marketId: order.market.id,
|
||||
// @ts-ignore fix me please!
|
||||
price: {
|
||||
value: removeDecimal(order.price, order.market?.decimalPlaces),
|
||||
},
|
||||
timeInForce: VegaWalletOrderTimeInForce[order.timeInForce],
|
||||
// @ts-ignore fix me please!
|
||||
sizeDelta: 0,
|
||||
// @ts-ignore fix me please!
|
||||
expiresAt: order.expiresAt
|
||||
? {
|
||||
value:
|
||||
|
@ -1,7 +1,6 @@
|
||||
import type { BigNumber } from 'ethers';
|
||||
import { ethers } from 'ethers';
|
||||
import abi from '../abis/erc20_bridge_new_abi.json';
|
||||
import { prepend0x } from '../utils';
|
||||
|
||||
export class CollateralBridgeNew {
|
||||
public contract: ethers.Contract;
|
||||
@ -14,32 +13,28 @@ export class CollateralBridgeNew {
|
||||
this.contract = new ethers.Contract(address, abi, signerOrProvider);
|
||||
}
|
||||
|
||||
depositAsset(assetSource: string, amount: string, vegaPublicKey: string) {
|
||||
return this.contract.deposit_asset(
|
||||
assetSource,
|
||||
amount,
|
||||
prepend0x(vegaPublicKey)
|
||||
);
|
||||
deposit_asset(assetSource: string, amount: string, vegaPublicKey: string) {
|
||||
return this.contract.deposit_asset(assetSource, amount, vegaPublicKey);
|
||||
}
|
||||
getAssetSource(vegaAssetId: string) {
|
||||
get_asset_source(vegaAssetId: string) {
|
||||
return this.contract.get_asset_source(vegaAssetId);
|
||||
}
|
||||
getDepositMaximum(assetSource: string): Promise<BigNumber> {
|
||||
get_deposit_maximum(assetSource: string): Promise<BigNumber> {
|
||||
return this.contract.get_asset_deposit_lifetime_limit(assetSource);
|
||||
}
|
||||
getMultisigControlAddres() {
|
||||
get_multisig_control_address() {
|
||||
return this.contract.get_multisig_control_address();
|
||||
}
|
||||
getVegaAssetId(address: string) {
|
||||
get_vega_asset_id(address: string) {
|
||||
return this.contract.get_vega_asset_id(address);
|
||||
}
|
||||
isAssetListed(address: string) {
|
||||
is_asset_listed(address: string) {
|
||||
return this.contract.is_asset_listed(address);
|
||||
}
|
||||
getWithdrawThreshold(assetSource: string) {
|
||||
get_withdraw_threshold(assetSource: string) {
|
||||
return this.contract.get_withdraw_threshold(assetSource);
|
||||
}
|
||||
withdrawAsset(
|
||||
withdraw_asset(
|
||||
assetSource: string,
|
||||
amount: string,
|
||||
target: string,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import type { BigNumber } from 'ethers';
|
||||
import { ethers } from 'ethers';
|
||||
import abi from '../abis/erc20_bridge_abi.json';
|
||||
import { prepend0x } from '../utils';
|
||||
|
||||
export class CollateralBridge {
|
||||
public contract: ethers.Contract;
|
||||
@ -16,35 +15,31 @@ export class CollateralBridge {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
depositAsset(assetSource: string, amount: string, vegaPublicKey: string) {
|
||||
return this.contract.deposit_asset(
|
||||
assetSource,
|
||||
amount,
|
||||
prepend0x(vegaPublicKey)
|
||||
);
|
||||
deposit_asset(assetSource: string, amount: string, vegaPublicKey: string) {
|
||||
return this.contract.deposit_asset(assetSource, amount, vegaPublicKey);
|
||||
}
|
||||
getAssetSource(vegaAssetId: string) {
|
||||
get_asset_source(vegaAssetId: string) {
|
||||
return this.contract.get_asset_source(vegaAssetId);
|
||||
}
|
||||
getDepositMaximum(assetSource: string): Promise<BigNumber> {
|
||||
get_deposit_maximum(assetSource: string): Promise<BigNumber> {
|
||||
return this.contract.get_deposit_maximum(assetSource);
|
||||
}
|
||||
getDepositMinimum(assetSource: string): Promise<BigNumber> {
|
||||
get_deposit_minimum(assetSource: string): Promise<BigNumber> {
|
||||
return this.contract.get_deposit_minimum(assetSource);
|
||||
}
|
||||
getMultisigControlAddres() {
|
||||
get_multisig_control_address() {
|
||||
return this.contract.get_multisig_control_address();
|
||||
}
|
||||
getVegaAssetId(address: string) {
|
||||
get_vega_asset_id(address: string) {
|
||||
return this.contract.get_vega_asset_id(address);
|
||||
}
|
||||
isAssetListed(address: string) {
|
||||
is_asset_listed(address: string) {
|
||||
return this.contract.is_asset_listed(address);
|
||||
}
|
||||
getWithdrawThreshold(assetSource: string) {
|
||||
get_withdraw_threshold(assetSource: string) {
|
||||
return this.contract.get_withdraw_threshold(assetSource);
|
||||
}
|
||||
withdrawAsset(
|
||||
withdraw_asset(
|
||||
assetSource: string,
|
||||
amount: string,
|
||||
target: string,
|
||||
|
@ -17,23 +17,23 @@ export class StakingBridge {
|
||||
stake(amount: string, vegaPublicKey: string) {
|
||||
return this.contract.stake(amount, prepend0x(vegaPublicKey));
|
||||
}
|
||||
removeStake(amount: string, vegaPublicKey: string) {
|
||||
remove_stake(amount: string, vegaPublicKey: string) {
|
||||
return this.contract.remove_stake(amount, prepend0x(vegaPublicKey));
|
||||
}
|
||||
transferStake(amount: string, newAddress: string, vegaPublicKey: string) {
|
||||
transfer_stake(amount: string, newAddress: string, vegaPublicKey: string) {
|
||||
return this.contract.transfer_stake(
|
||||
amount,
|
||||
newAddress,
|
||||
prepend0x(vegaPublicKey)
|
||||
);
|
||||
}
|
||||
stakingToken() {
|
||||
staking_token() {
|
||||
return this.contract.staking_token();
|
||||
}
|
||||
stakeBalance(target: string, vegaPublicKey: string) {
|
||||
stake_balance(target: string, vegaPublicKey: string) {
|
||||
return this.contract.stake_balance(target, prepend0x(vegaPublicKey));
|
||||
}
|
||||
totalStaked() {
|
||||
total_staked() {
|
||||
return this.contract.total_staked();
|
||||
}
|
||||
}
|
||||
|
@ -14,31 +14,31 @@ export class TokenVesting {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
stakeTokens(amount: string, vegaPublicKey: string) {
|
||||
stake_tokens(amount: string, vegaPublicKey: string) {
|
||||
return this.contract.stake_tokens(amount, prepend0x(vegaPublicKey));
|
||||
}
|
||||
removeStake(amount: string, vegaPublicKey: string) {
|
||||
remove_stake(amount: string, vegaPublicKey: string) {
|
||||
return this.contract.remove_stake(amount, prepend0x(vegaPublicKey));
|
||||
}
|
||||
stakeBalance(address: string, vegaPublicKey: string) {
|
||||
stake_balance(address: string, vegaPublicKey: string) {
|
||||
return this.contract.stake_balance(address, prepend0x(vegaPublicKey));
|
||||
}
|
||||
totalStaked() {
|
||||
total_staked() {
|
||||
return this.contract.total_staked();
|
||||
}
|
||||
userStats(address: string) {
|
||||
user_stats(address: string) {
|
||||
return this.contract.user_stats(address);
|
||||
}
|
||||
getTrancheBalance(address: string, trancheId: number) {
|
||||
get_tranche_balance(address: string, trancheId: number) {
|
||||
return this.contract.get_tranche_balance(address, trancheId);
|
||||
}
|
||||
getVestedForTranche(address: string, trancheId: number) {
|
||||
get_vested_for_tranche(address: string, trancheId: number) {
|
||||
return this.contract.get_vested_for_tranche(address, trancheId);
|
||||
}
|
||||
userTotalAllTranches(address: string) {
|
||||
user_total_all_tranches(address: string) {
|
||||
return this.contract.user_total_all_tranches(address);
|
||||
}
|
||||
withdrawFromTranche(trancheId: number) {
|
||||
withdraw_from_tranche(trancheId: number) {
|
||||
return this.contract.withdraw_from_tranche(trancheId);
|
||||
}
|
||||
}
|
||||
|
@ -29,4 +29,7 @@ export class Token {
|
||||
decimals(): Promise<number> {
|
||||
return this.contract.decimals();
|
||||
}
|
||||
faucet() {
|
||||
/* No op */
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
export class EthereumError extends Error {
|
||||
code: number;
|
||||
reason: string;
|
||||
|
||||
constructor(message: string, code: number) {
|
||||
constructor(message: string, code: number, reason: string) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.name = 'EthereumError';
|
||||
this.code = code;
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ it('Opens when tx starts and closes if the user rejects the tx', () => {
|
||||
rerender(
|
||||
generateJsx({
|
||||
status: EthTxStatus.Error,
|
||||
error: new EthereumError('User rejected', 4001),
|
||||
error: new EthereumError('User rejected', 4001, 'reason'),
|
||||
})
|
||||
);
|
||||
|
||||
@ -82,11 +82,15 @@ it('Dialog states', () => {
|
||||
expect(screen.getByText('Ethereum transaction complete')).toBeInTheDocument();
|
||||
|
||||
const errorMsg = 'Something went wrong';
|
||||
const reason = 'Transaction failed';
|
||||
rerender(
|
||||
generateJsx({ status: EthTxStatus.Error, error: new Error(errorMsg) })
|
||||
generateJsx({
|
||||
status: EthTxStatus.Error,
|
||||
error: new EthereumError(errorMsg, 1, reason),
|
||||
})
|
||||
);
|
||||
expect(screen.getByText(`${props.name} failed`)).toBeInTheDocument();
|
||||
expect(screen.getByText(errorMsg)).toBeInTheDocument();
|
||||
expect(screen.getByText(`Error: ${reason}`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Success state waits for confirmation event if provided', () => {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import { isExpectedEthereumError } from '../ethereum-error';
|
||||
import { isEthereumError, isExpectedEthereumError } from '../ethereum-error';
|
||||
import type { TxError } from '../use-ethereum-transaction';
|
||||
import { EthTxStatus } from '../use-ethereum-transaction';
|
||||
import { ConfirmRow, TxRow, ConfirmationEventRow } from './dialog-rows';
|
||||
import { DialogWrapper } from './dialog-wrapper';
|
||||
@ -9,7 +10,7 @@ import { DialogWrapper } from './dialog-wrapper';
|
||||
export interface TransactionDialogProps {
|
||||
name: string;
|
||||
status: EthTxStatus;
|
||||
error: Error | null;
|
||||
error: TxError | null;
|
||||
confirmations: number;
|
||||
txHash: string | null;
|
||||
requiredConfirmations?: number;
|
||||
@ -32,9 +33,26 @@ export const TransactionDialog = ({
|
||||
|
||||
const renderContent = () => {
|
||||
if (status === EthTxStatus.Error) {
|
||||
const classNames = 'break-all text-black dark:text-white';
|
||||
if (isEthereumError(error)) {
|
||||
return (
|
||||
<p className={classNames}>
|
||||
{t('Error')}: {error.reason}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return (
|
||||
<p className={classNames}>
|
||||
{t('Error')}: {error.message}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<p className="break-all text-black dark:text-white">
|
||||
{error && error.message}
|
||||
<p className={classNames}>
|
||||
{t('Error')}: {t('Unknown error')}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,7 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { renderHook, act } from '@testing-library/react-hooks/dom';
|
||||
import { EthTxStatus } from './use-ethereum-transaction';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEthereumTransaction } from './use-ethereum-transaction';
|
||||
import type { ethers } from 'ethers';
|
||||
import { EthereumError } from './ethereum-error';
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
@ -17,53 +13,52 @@ afterAll(() => {
|
||||
|
||||
class MockContract {
|
||||
static txHash = 'tx-hash';
|
||||
confirmations = 0;
|
||||
depositAsset(args: {
|
||||
assetSource: string;
|
||||
amount: string;
|
||||
vegaPublicKey: string;
|
||||
}): Promise<ethers.ContractTransaction> {
|
||||
contract = {
|
||||
callStatic: {
|
||||
deposit_asset() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, 10);
|
||||
});
|
||||
},
|
||||
},
|
||||
} as unknown as ethers.Contract;
|
||||
|
||||
deposit_asset(assetSource: string, amount: string, vegaPublicKey: string) {
|
||||
return Promise.resolve({
|
||||
hash: MockContract.txHash,
|
||||
wait: () => {
|
||||
this.confirmations++;
|
||||
confirmations++;
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(
|
||||
() =>
|
||||
resolve({
|
||||
from: 'foo',
|
||||
confirmations: this.confirmations,
|
||||
} as ethers.ContractReceipt),
|
||||
100
|
||||
);
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
from: 'foo',
|
||||
confirmations,
|
||||
} as ethers.ContractReceipt);
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
} as ethers.ContractTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
let confirmations = 0;
|
||||
const mockContract = new MockContract();
|
||||
const requiredConfirmations = 3;
|
||||
|
||||
function setup(perform: () => void) {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<MockedProvider>{children}</MockedProvider>
|
||||
);
|
||||
return renderHook(
|
||||
// @ts-ignore force MockContract
|
||||
() => useEthereumTransaction(perform, requiredConfirmations),
|
||||
{ wrapper }
|
||||
function setup(methodName: 'deposit_asset' = 'deposit_asset') {
|
||||
return renderHook(() =>
|
||||
useEthereumTransaction<MockContract, 'deposit_asset'>(
|
||||
mockContract,
|
||||
methodName,
|
||||
requiredConfirmations
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
it('Ethereum transaction flow', async () => {
|
||||
const { result } = setup(() => {
|
||||
return mockContract.depositAsset({
|
||||
assetSource: 'asset-source',
|
||||
amount: '100',
|
||||
vegaPublicKey: 'vega-key',
|
||||
});
|
||||
});
|
||||
const { result } = setup();
|
||||
|
||||
expect(result.current).toEqual({
|
||||
transaction: {
|
||||
@ -77,27 +72,30 @@ it('Ethereum transaction flow', async () => {
|
||||
reset: expect.any(Function),
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.perform();
|
||||
});
|
||||
result.current.perform('asset-source', '100', 'vega-key');
|
||||
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Requested);
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Default); // still default as we await result of static call
|
||||
expect(result.current.transaction.confirmations).toBe(0);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Pending);
|
||||
expect(result.current.transaction.txHash).toEqual(MockContract.txHash);
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(10);
|
||||
});
|
||||
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Pending);
|
||||
expect(result.current.transaction.txHash).toEqual(MockContract.txHash);
|
||||
expect(result.current.transaction.confirmations).toBe(0);
|
||||
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(100);
|
||||
});
|
||||
|
||||
expect(result.current.transaction.confirmations).toBe(1);
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Pending);
|
||||
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(100);
|
||||
});
|
||||
|
||||
expect(result.current.transaction.confirmations).toBe(2);
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Pending);
|
||||
|
||||
@ -114,18 +112,14 @@ it('Ethereum transaction flow', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Error handling', async () => {
|
||||
const { result } = setup(() => {
|
||||
throw new EthereumError(errorMsg, 500);
|
||||
describe('error handling', () => {
|
||||
it('ensures correct method is used', async () => {
|
||||
const { result } = setup('non-existing-method' as 'deposit_asset');
|
||||
|
||||
act(() => {
|
||||
result.current.perform('asset-rouce', '100', 'vega-key');
|
||||
});
|
||||
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Error);
|
||||
});
|
||||
|
||||
const errorMsg = 'test-error';
|
||||
|
||||
act(() => {
|
||||
result.current.perform();
|
||||
});
|
||||
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Error);
|
||||
expect(result.current.transaction.error instanceof EthereumError).toBe(true);
|
||||
expect(result.current.transaction.error?.message).toBe(errorMsg);
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { ethers } from 'ethers';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { EthereumError, isEthereumError } from './ethereum-error';
|
||||
import type { EthereumError } from './ethereum-error';
|
||||
import { isEthereumError } from './ethereum-error';
|
||||
|
||||
export enum EthTxStatus {
|
||||
Default = 'Default',
|
||||
@ -28,10 +29,16 @@ export const initialState = {
|
||||
confirmations: 0,
|
||||
};
|
||||
|
||||
export const useEthereumTransaction = <TArgs = void>(
|
||||
performTransaction: (
|
||||
args: TArgs
|
||||
) => Promise<ethers.ContractTransaction> | null,
|
||||
type DefaultContract = {
|
||||
contract: ethers.Contract;
|
||||
};
|
||||
|
||||
export const useEthereumTransaction = <
|
||||
TContract extends DefaultContract,
|
||||
TMethod extends string
|
||||
>(
|
||||
contract: TContract | null,
|
||||
methodName: keyof TContract,
|
||||
requiredConfirmations = 1
|
||||
) => {
|
||||
const [transaction, _setTransaction] = useState<EthTxState>(initialState);
|
||||
@ -44,7 +51,27 @@ export const useEthereumTransaction = <TArgs = void>(
|
||||
}, []);
|
||||
|
||||
const perform = useCallback(
|
||||
async (args: TArgs) => {
|
||||
// @ts-ignore TS errors here as TMethod doesn't satisfy the constraints on TContract
|
||||
// its a tricky one to fix but does enforce the correct types when calling perform
|
||||
async (...args: Parameters<TContract[TMethod]>) => {
|
||||
try {
|
||||
if (
|
||||
!contract ||
|
||||
typeof contract[methodName] !== 'function' ||
|
||||
typeof contract.contract.callStatic[methodName as string] !==
|
||||
'function'
|
||||
) {
|
||||
throw new Error('method not found on contract');
|
||||
}
|
||||
await contract.contract.callStatic[methodName as string](...args);
|
||||
} catch (err) {
|
||||
setTransaction({
|
||||
status: EthTxStatus.Error,
|
||||
error: err as EthereumError,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setTransaction({
|
||||
status: EthTxStatus.Requested,
|
||||
error: null,
|
||||
@ -52,14 +79,13 @@ export const useEthereumTransaction = <TArgs = void>(
|
||||
});
|
||||
|
||||
try {
|
||||
const res = performTransaction(args);
|
||||
const method = contract[methodName];
|
||||
|
||||
if (res === null) {
|
||||
setTransaction({ status: EthTxStatus.Default });
|
||||
return;
|
||||
if (!method || typeof method !== 'function') {
|
||||
throw new Error('method not found on contract');
|
||||
}
|
||||
|
||||
const tx = await res;
|
||||
const tx = await method.call(contract, ...args);
|
||||
|
||||
let receipt: ethers.ContractReceipt | null = null;
|
||||
|
||||
@ -67,22 +93,21 @@ export const useEthereumTransaction = <TArgs = void>(
|
||||
|
||||
for (let i = 1; i <= requiredConfirmations; i++) {
|
||||
receipt = await tx.wait(i);
|
||||
setTransaction({ confirmations: receipt.confirmations });
|
||||
setTransaction({
|
||||
confirmations: receipt
|
||||
? receipt.confirmations
|
||||
: requiredConfirmations,
|
||||
});
|
||||
}
|
||||
|
||||
if (!receipt) {
|
||||
throw new Error('No receipt after confirmations are met');
|
||||
throw new Error('no receipt after confirmations are met');
|
||||
}
|
||||
|
||||
setTransaction({ status: EthTxStatus.Complete, receipt });
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
if (err instanceof Error || isEthereumError(err)) {
|
||||
setTransaction({ status: EthTxStatus.Error, error: err });
|
||||
} else if (isEthereumError(err)) {
|
||||
setTransaction({
|
||||
status: EthTxStatus.Error,
|
||||
error: new EthereumError(err.message, err.code),
|
||||
});
|
||||
} else {
|
||||
setTransaction({
|
||||
status: EthTxStatus.Error,
|
||||
@ -91,7 +116,7 @@ export const useEthereumTransaction = <TArgs = void>(
|
||||
}
|
||||
}
|
||||
},
|
||||
[performTransaction, requiredConfirmations, setTransaction]
|
||||
[contract, methodName, requiredConfirmations, setTransaction]
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
|
@ -11,7 +11,10 @@ import * as sentry from '@sentry/react';
|
||||
import type { Erc20ApprovalNew_erc20WithdrawalApproval } from './__generated__/Erc20ApprovalNew';
|
||||
|
||||
jest.mock('@vegaprotocol/web3', () => ({
|
||||
useBridgeContract: jest.fn(),
|
||||
useBridgeContract: jest.fn().mockReturnValue({
|
||||
withdraw_asset: jest.fn(),
|
||||
isNewContract: true,
|
||||
}),
|
||||
useEthereumTransaction: jest.fn(),
|
||||
}));
|
||||
|
||||
@ -56,7 +59,14 @@ it('Should perform the Ethereum transaction with the fetched approval', async ()
|
||||
result.current.submit(withdrawalId);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(mockPerform).toHaveBeenCalledWith(erc20WithdrawalApproval);
|
||||
expect(mockPerform).toHaveBeenCalledWith(
|
||||
erc20WithdrawalApproval.assetSource,
|
||||
erc20WithdrawalApproval.amount,
|
||||
erc20WithdrawalApproval.targetAddress,
|
||||
erc20WithdrawalApproval.creation,
|
||||
erc20WithdrawalApproval.nonce,
|
||||
erc20WithdrawalApproval.signatures
|
||||
);
|
||||
expect(result.current.withdrawalId).toBe(withdrawalId);
|
||||
});
|
||||
});
|
||||
|
@ -21,58 +21,22 @@ export const PENDING_WITHDRAWAL_FRAGMMENT = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export interface NewWithdrawTransactionArgs {
|
||||
assetSource: string;
|
||||
amount: string;
|
||||
nonce: string;
|
||||
signatures: string;
|
||||
targetAddress: string;
|
||||
creation: string;
|
||||
}
|
||||
|
||||
export interface WithdrawTransactionArgs {
|
||||
assetSource: string;
|
||||
amount: string;
|
||||
nonce: string;
|
||||
signatures: string;
|
||||
targetAddress: string;
|
||||
}
|
||||
|
||||
export const useCompleteWithdraw = (isNewContract: boolean) => {
|
||||
const { query, cache } = useApolloClient();
|
||||
const contract = useBridgeContract(isNewContract);
|
||||
const [id, setId] = useState('');
|
||||
const { transaction, perform } = useEthereumTransaction<
|
||||
WithdrawTransactionArgs | NewWithdrawTransactionArgs
|
||||
>((args) => {
|
||||
if (!contract) {
|
||||
return null;
|
||||
}
|
||||
if (contract.isNewContract) {
|
||||
const withdrawalData = args as NewWithdrawTransactionArgs;
|
||||
return (contract as CollateralBridgeNew).withdrawAsset(
|
||||
withdrawalData.assetSource,
|
||||
withdrawalData.amount,
|
||||
withdrawalData.targetAddress,
|
||||
withdrawalData.creation,
|
||||
withdrawalData.nonce,
|
||||
withdrawalData.signatures
|
||||
);
|
||||
} else {
|
||||
return (contract as CollateralBridge).withdrawAsset(
|
||||
args.assetSource,
|
||||
args.amount,
|
||||
args.targetAddress,
|
||||
args.nonce,
|
||||
args.signatures
|
||||
);
|
||||
}
|
||||
});
|
||||
CollateralBridgeNew | CollateralBridge,
|
||||
'withdraw_asset'
|
||||
>(contract, 'withdraw_asset');
|
||||
|
||||
const submit = useCallback(
|
||||
async (withdrawalId: string) => {
|
||||
setId(withdrawalId);
|
||||
try {
|
||||
if (!contract) {
|
||||
return;
|
||||
}
|
||||
const res = await query<
|
||||
Erc20Approval | Erc20ApprovalNew,
|
||||
Erc20ApprovalVariables
|
||||
@ -83,16 +47,34 @@ export const useCompleteWithdraw = (isNewContract: boolean) => {
|
||||
variables: { withdrawalId },
|
||||
});
|
||||
|
||||
if (!res.data.erc20WithdrawalApproval) {
|
||||
const approval = res.data.erc20WithdrawalApproval;
|
||||
if (!approval) {
|
||||
throw new Error('Could not retrieve withdrawal approval');
|
||||
}
|
||||
|
||||
perform(res.data.erc20WithdrawalApproval);
|
||||
if (contract.isNewContract && 'creation' in approval) {
|
||||
perform(
|
||||
approval.assetSource,
|
||||
approval.amount,
|
||||
approval.targetAddress,
|
||||
approval.creation,
|
||||
approval.nonce,
|
||||
approval.signatures
|
||||
);
|
||||
} else {
|
||||
perform(
|
||||
approval.assetSource,
|
||||
approval.amount,
|
||||
approval.targetAddress,
|
||||
approval.nonce,
|
||||
approval.signatures
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
captureException(err);
|
||||
}
|
||||
},
|
||||
[query, isNewContract, perform]
|
||||
[contract, query, isNewContract, perform]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -11,7 +11,7 @@ export const useGetWithdrawLimits = (asset?: Asset) => {
|
||||
return;
|
||||
}
|
||||
|
||||
return contract.getWithdrawThreshold(asset.source.contractAddress);
|
||||
return contract.get_withdraw_threshold(asset.source.contractAddress);
|
||||
}, [asset, contract]);
|
||||
|
||||
const {
|
||||
|
@ -10,7 +10,10 @@ import { useWithdraw } from './use-withdraw';
|
||||
import type { Erc20ApprovalNew } from './__generated__/Erc20ApprovalNew';
|
||||
|
||||
jest.mock('@vegaprotocol/web3', () => ({
|
||||
useBridgeContract: jest.fn(),
|
||||
useBridgeContract: jest.fn().mockReturnValue({
|
||||
withdraw_asset: jest.fn(),
|
||||
isNewContract: true,
|
||||
}),
|
||||
useEthereumTransaction: jest.fn(),
|
||||
}));
|
||||
|
||||
@ -141,9 +144,15 @@ it('Creates withdrawal and immediately submits Ethereum transaction', async () =
|
||||
// @ts-ignore MockedRespones types inteferring
|
||||
mockERC20Approval.result.data.erc20WithdrawalApproval
|
||||
);
|
||||
// @ts-ignore MockedRespones types inteferring
|
||||
const withdrawal = mockERC20Approval.result.data.erc20WithdrawalApproval;
|
||||
expect(mockPerform).toHaveBeenCalledWith(
|
||||
// @ts-ignore MockedRespones types inteferring
|
||||
mockERC20Approval.result.data.erc20WithdrawalApproval
|
||||
withdrawal.assetSource,
|
||||
withdrawal.amount,
|
||||
withdrawal.targetAddress,
|
||||
withdrawal.creation,
|
||||
withdrawal.nonce,
|
||||
withdrawal.signatures
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -4,20 +4,19 @@ import { useBridgeContract, useEthereumTransaction } from '@vegaprotocol/web3';
|
||||
import { useVegaTransaction, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { ERC20_APPROVAL_QUERY, ERC20_APPROVAL_QUERY_NEW } from './queries';
|
||||
import type {
|
||||
NewWithdrawTransactionArgs,
|
||||
WithdrawTransactionArgs,
|
||||
} from './use-complete-withdraw';
|
||||
import type {
|
||||
Erc20Approval,
|
||||
Erc20ApprovalVariables,
|
||||
Erc20Approval_erc20WithdrawalApproval,
|
||||
} from './__generated__/Erc20Approval';
|
||||
import type {
|
||||
Erc20ApprovalNew,
|
||||
Erc20ApprovalNew_erc20WithdrawalApproval,
|
||||
} from './__generated__/Erc20ApprovalNew';
|
||||
import type {
|
||||
CollateralBridge,
|
||||
CollateralBridgeNew,
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import type { Erc20ApprovalNew } from './__generated__/Erc20ApprovalNew';
|
||||
|
||||
export interface WithdrawalFields {
|
||||
amount: string;
|
||||
@ -27,8 +26,11 @@ export interface WithdrawalFields {
|
||||
|
||||
export const useWithdraw = (cancelled: boolean, isNewContract: boolean) => {
|
||||
const [withdrawalId, setWithdrawalId] = useState<string | null>(null);
|
||||
const [approval, setApproval] =
|
||||
useState<Erc20Approval_erc20WithdrawalApproval | null>(null);
|
||||
const [approval, setApproval] = useState<
|
||||
| Erc20Approval_erc20WithdrawalApproval
|
||||
| Erc20ApprovalNew_erc20WithdrawalApproval
|
||||
| null
|
||||
>(null);
|
||||
|
||||
const contract = useBridgeContract(isNewContract);
|
||||
const { keypair } = useVegaWallet();
|
||||
@ -42,30 +44,10 @@ export const useWithdraw = (cancelled: boolean, isNewContract: boolean) => {
|
||||
transaction: ethTx,
|
||||
perform,
|
||||
reset: resetEthTx,
|
||||
} = useEthereumTransaction<WithdrawTransactionArgs>((args) => {
|
||||
if (!contract) {
|
||||
return null;
|
||||
}
|
||||
if (contract.isNewContract) {
|
||||
const withdrawalArguments = args as NewWithdrawTransactionArgs;
|
||||
return (contract as CollateralBridgeNew).withdrawAsset(
|
||||
withdrawalArguments.assetSource,
|
||||
withdrawalArguments.amount,
|
||||
withdrawalArguments.targetAddress,
|
||||
withdrawalArguments.creation,
|
||||
withdrawalArguments.nonce,
|
||||
withdrawalArguments.signatures
|
||||
);
|
||||
} else {
|
||||
return (contract as CollateralBridge).withdrawAsset(
|
||||
args.assetSource,
|
||||
args.amount,
|
||||
args.targetAddress,
|
||||
args.nonce,
|
||||
args.signatures
|
||||
);
|
||||
}
|
||||
});
|
||||
} = useEthereumTransaction<
|
||||
CollateralBridgeNew | CollateralBridge,
|
||||
'withdraw_asset'
|
||||
>(contract, 'withdraw_asset');
|
||||
|
||||
const { data, stopPolling } = useQuery<
|
||||
Erc20Approval | Erc20ApprovalNew,
|
||||
@ -114,11 +96,28 @@ export const useWithdraw = (cancelled: boolean, isNewContract: boolean) => {
|
||||
}, [data, stopPolling]);
|
||||
|
||||
useEffect(() => {
|
||||
if (approval && !cancelled) {
|
||||
perform(approval);
|
||||
if (approval && contract && !cancelled) {
|
||||
if (contract.isNewContract && 'creation' in approval) {
|
||||
perform(
|
||||
approval.assetSource,
|
||||
approval.amount,
|
||||
approval.targetAddress,
|
||||
approval.creation,
|
||||
approval.nonce,
|
||||
approval.signatures
|
||||
);
|
||||
} else {
|
||||
perform(
|
||||
approval.assetSource,
|
||||
approval.amount,
|
||||
approval.targetAddress,
|
||||
approval.nonce,
|
||||
approval.signatures
|
||||
);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [approval]);
|
||||
}, [approval, contract]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
resetVegaTx();
|
||||
|
@ -4,6 +4,7 @@ import type { VegaTxState } from '@vegaprotocol/wallet';
|
||||
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { EthTxState } from '@vegaprotocol/web3';
|
||||
import { isEthereumError } from '@vegaprotocol/web3';
|
||||
import { EthTxStatus } from '@vegaprotocol/web3';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { Erc20Approval_erc20WithdrawalApproval } from './__generated__/Erc20Approval';
|
||||
@ -132,7 +133,11 @@ const getProps = (
|
||||
intent: Intent.Danger,
|
||||
children: (
|
||||
<Step>
|
||||
{ethTx.error ? ethTx.error.message : t('Something went wrong')}
|
||||
{isEthereumError(ethTx.error)
|
||||
? `Error: ${ethTx.error.reason}`
|
||||
: ethTx.error instanceof Error
|
||||
? t(`Error: ${ethTx.error.message}`)
|
||||
: t('Something went wrong')}
|
||||
</Step>
|
||||
),
|
||||
},
|
||||
|
@ -76,7 +76,7 @@ it('Expected Ethereum error closes the dialog', async () => {
|
||||
ethTx: {
|
||||
...useWithdrawValue.ethTx,
|
||||
status: EthTxStatus.Error,
|
||||
error: new EthereumError('User rejected transaction', 4001),
|
||||
error: new EthereumError('User rejected transaction', 4001, 'reason'),
|
||||
},
|
||||
});
|
||||
rerender(generateJsx(props));
|
||||
|
Loading…
Reference in New Issue
Block a user