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