feat(#495): get smart contracts addresses from network params

* feat: unhardcode contract addresses

* fix: linting and tests

* feat: switch contract usage in token app to use unhardcoded addresses

* chore: remove other usage of hard coded contract addresses

* feat: convert contracts to classes, update claim contract to fix circular dependency

* feat: add hard coded contract addresses to contracts page

* fix: misc tidy up

* chore: rename ethers big num conversion func

* fix: remove pending transactions modal

* chore: add single toBigNum function that can accept number string or EthersBignNumber

* chore: delete unused tranche helpers and decimals functions from smart contracts lib

* feat: add faucetable token class

* fix: reset tx state after early exit from approve tx

* feat: re add transaction modal using zustand store

* fix: loader colors for eth wallet

* fix: pass ethereum config to gurantee existence before tx execution

* chore: lint smart contracts lib

* chore: fix web3container to use children and not render prop

* chore: lint

* fix: use background to mock ethereum wallet to avoid mocking globally for every test

* chore: move web3 mock to common steps and call from withdrawals feature tests
This commit is contained in:
Matthew Russell 2022-06-07 15:08:40 -07:00 committed by GitHub
parent a66be425be
commit e463bbe238
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 1011 additions and 1672 deletions

View File

@ -1,4 +1,5 @@
import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
import { useWeb3React } from '@web3-react/core';
@ -43,11 +44,16 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
vesting.totalStaked(),
token.decimals(),
]);
const totalSupply = toBigNum(supply, decimals);
const totalWallet = toBigNum(totalAssociatedWallet, decimals);
const totalVesting = toBigNum(totalAssociatedVesting, decimals);
appDispatch({
type: AppStateActionType.SET_TOKEN,
decimals,
totalSupply: supply,
totalAssociated: totalAssociatedWallet.plus(totalAssociatedVesting),
totalSupply,
totalAssociated: totalWallet.plus(totalVesting),
});
setBalancesLoaded(true);
} catch (err) {

View File

@ -1,15 +1,15 @@
import { useTranslation } from 'react-i18next';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import { useAddAssetSupported } from '../../hooks/use-add-asset-to-wallet';
import vegaVesting from '../../images/vega_vesting.png';
import { AddTokenButtonLink } from '../add-token-button/add-token-button';
import { Callout } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/react-helpers';
export const AddLockedTokenAddress = () => {
const { ADDRESSES } = useEnvironment();
const { t } = useTranslation();
const addSupported = useAddAssetSupported();
const { ADDRESSES } = useEnvironment();
return (
<Callout
title={t(

View File

@ -1,8 +1,8 @@
import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { useWeb3React } from '@web3-react/core';
import React from 'react';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import {
AppStateActionType,
useAppState,
@ -10,17 +10,19 @@ import {
import { useContracts } from '../../contexts/contracts/contracts-context';
import { useGetAssociationBreakdown } from '../../hooks/use-get-association-breakdown';
import { useGetUserTrancheBalances } from '../../hooks/use-get-user-tranche-balances';
import { BigNumber } from '../../lib/bignumber';
interface BalanceManagerProps {
children: React.ReactElement;
}
export const BalanceManager = ({ children }: BalanceManagerProps) => {
const { ADDRESSES } = useEnvironment();
const contracts = useContracts();
const { account } = useWeb3React();
const { appDispatch } = useAppState();
const {
appState: { decimals },
appDispatch,
} = useAppState();
const { config } = useEthereumConfig();
const getUserTrancheBalances = useGetUserTrancheBalances(
account || '',
@ -35,17 +37,26 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
// update balances on connect to Ethereum
React.useEffect(() => {
const updateBalances = async () => {
if (!account) return;
if (!account || !config) return;
try {
const [balance, walletBalance, lien, allowance] = await Promise.all([
contracts.vesting.getUserBalanceAllTranches(account),
const [b, w, stats, a] = await Promise.all([
contracts.vesting.userTotalAllTranches(account),
contracts.token.balanceOf(account),
contracts.vesting.getLien(account),
contracts.token.allowance(account, ADDRESSES.stakingBridge),
contracts.vesting.userStats(account),
contracts.token.allowance(
account,
config.staking_bridge_contract.address
),
]);
const balance = toBigNum(b, decimals);
const walletBalance = toBigNum(w, decimals);
const lien = toBigNum(stats.lien, decimals);
const allowance = toBigNum(a, decimals);
appDispatch({
type: AppStateActionType.UPDATE_ACCOUNT_BALANCES,
balance: new BigNumber(balance),
balance,
walletBalance,
lien,
allowance,
@ -57,11 +68,12 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
updateBalances();
}, [
decimals,
appDispatch,
contracts?.token,
contracts?.vesting,
account,
ADDRESSES.stakingBridge,
config,
]);
// This use effect hook is very expensive and is kept separate to prevent expensive reloading of data.

View File

@ -22,7 +22,7 @@ import {
WalletCardHeader,
WalletCardRow,
} from '../wallet-card';
import { Button, Loader } from '@vegaprotocol/ui-toolkit';
import { Loader } from '@vegaprotocol/ui-toolkit';
import { theme } from '@vegaprotocol/tailwindcss-config';
const Colors = theme.colors;
@ -189,12 +189,12 @@ export const EthWallet = () => {
<WalletCardHeader>
<h1 className="text-h3 uppercase">{t('ethereumKey')}</h1>
{account && (
<div className="font-mono px-4 text-right">
<div>{truncateMiddle(account)}</div>
<div className="px-4 text-right">
<div className="font-mono">{truncateMiddle(account)}</div>
{pendingTxs && (
<div>
<Button
className="flex gap-2 justify-between p-4, bg-black text-white flex-nowrap whitespace-nowrap"
<button
className="flex items-center gap-4 p-4 border whitespace-nowrap"
data-testid="pending-transactions-btn"
onClick={() =>
appDispatch({
@ -203,9 +203,9 @@ export const EthWallet = () => {
})
}
>
<Loader size="small" />
<Loader size="small" forceTheme="light" />
{t('pendingTransactions')}
</Button>
</button>
</div>
)}
</div>

View File

@ -1,4 +1,3 @@
import type { TxData } from '@vegaprotocol/smart-contracts';
import { Dialog, Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import React from 'react';
@ -8,9 +7,10 @@ import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
import { useContracts } from '../../contexts/contracts/contracts-context';
import { truncateMiddle } from '../../lib/truncate-middle';
import { Tick } from '../icons';
import type { TxData } from '../../stores/transactions';
import { useTransactionStore } from '../../stores/transactions';
const TransactionModalTh = ({ children }: { children: React.ReactNode }) => (
<th className="border-b border-black-25 text-black-60 text-left font-normal">
@ -31,7 +31,7 @@ const TransactionModalStatus = ({
export const TransactionModal = () => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation();
const { transactions } = useContracts();
const { transactions } = useTransactionStore();
const { appState, appDispatch } = useAppState();
const renderStatus = (txObj: TxData) => {
@ -84,8 +84,8 @@ export const TransactionModal = () => {
<TransactionModalTd>
<Link
title={t('View transaction on Etherscan')}
href={`${ETHERSCAN_URL}/tx/${transaction.tx.hash}`}
target="_blank"
href={`${ETHERSCAN_URL}/tx/${transaction.tx.hash}`}
>
{truncateMiddle(transaction.tx.hash)}
</Link>

View File

@ -1,4 +1,4 @@
import { Networks } from '@vegaprotocol/smart-contracts';
import { Networks } from '@vegaprotocol/react-helpers';
interface VegaNode {
url: string;

View File

@ -1,20 +1,18 @@
import type {
TxData,
VegaClaim,
VegaErc20Bridge,
VegaStaking,
ERC20Token,
VegaVesting,
Claim,
CollateralBridge,
Token,
TokenVesting,
StakingBridge,
} from '@vegaprotocol/smart-contracts';
import React from 'react';
export interface ContractsContextShape {
token: ERC20Token;
staking: VegaStaking;
vesting: VegaVesting;
claim: VegaClaim;
erc20Bridge: VegaErc20Bridge;
transactions: TxData[];
token: Token;
staking: StakingBridge;
vesting: TokenVesting;
claim: Claim;
erc20Bridge: CollateralBridge;
}
export const ContractsContext = React.createContext<

View File

@ -1,29 +1,28 @@
import type { TxData } from '@vegaprotocol/smart-contracts';
import {
VegaClaim,
VegaErc20Bridge,
VegaStaking,
ERC20Token,
VegaVesting,
Token,
TokenVesting,
Claim,
CollateralBridge,
StakingBridge,
} from '@vegaprotocol/smart-contracts';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core';
import uniqBy from 'lodash/uniqBy';
import React from 'react';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import { SplashLoader } from '../../components/splash-loader';
import type { ContractsContextShape } from './contracts-context';
import { ContractsContext } from './contracts-context';
import { defaultProvider } from '../../lib/web3-connectors';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { useEnvironment } from '@vegaprotocol/react-helpers';
/**
* Provides Vega Ethereum contract instances to its children.
*/
export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
const { ADDRESSES, VEGA_ENV } = useEnvironment();
const { provider: activeProvider, account } = useWeb3React();
const [txs, setTxs] = React.useState<TxData[]>([]);
const { config } = useEthereumConfig();
const { VEGA_ENV, ADDRESSES } = useEnvironment();
const [contracts, setContracts] = React.useState<Pick<
ContractsContextShape,
'token' | 'staking' | 'vesting' | 'claim' | 'erc20Bridge'
@ -45,49 +44,25 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
signer = provider.getSigner();
}
if (provider) {
if (provider && config) {
setContracts({
token: new ERC20Token(
ADDRESSES.vegaTokenAddress,
// @ts-ignore Cant accept JsonRpcProvider provider
provider,
signer
token: new Token(ADDRESSES.vegaTokenAddress, signer || provider),
staking: new StakingBridge(
config.staking_bridge_contract.address,
signer || provider
),
// @ts-ignore Cant accept JsonRpcProvider provider
staking: new VegaStaking(VEGA_ENV, provider, signer),
// @ts-ignore Cant accept JsonRpcProvider provider
vesting: new VegaVesting(VEGA_ENV, provider, signer),
// @ts-ignore Cant accept JsonRpcProvider provider
claim: new VegaClaim(VEGA_ENV, provider, signer),
erc20Bridge: new VegaErc20Bridge(
VEGA_ENV,
// @ts-ignore Cant accept JsonRpcProvider provider
provider,
signer
vesting: new TokenVesting(
config.token_vesting_contract.address,
signer || provider
),
claim: new Claim(ADDRESSES.claimAddress, signer || provider),
erc20Bridge: new CollateralBridge(
config.collateral_bridge_contract.address,
signer || provider
),
});
}
}, [activeProvider, account, ADDRESSES.vegaTokenAddress, VEGA_ENV]);
React.useEffect(() => {
if (!contracts) return;
const mergeTxs = (existing: TxData[], incoming: TxData[]) => {
return uniqBy([...incoming, ...existing], 'tx.hash');
};
contracts.staking.listen((txs) => {
setTxs((curr) => mergeTxs(curr, txs));
});
contracts.vesting.listen((txs) => {
setTxs((curr) => mergeTxs(curr, txs));
});
}, [contracts]);
React.useEffect(() => {
setTxs([]);
}, [account]);
}, [activeProvider, account, config, ADDRESSES, VEGA_ENV]);
if (!contracts) {
return (
@ -98,7 +73,7 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
}
return (
<ContractsContext.Provider value={{ ...contracts, transactions: txs }}>
<ContractsContext.Provider value={contracts}>
{children}
</ContractsContext.Provider>
);

View File

@ -1,9 +1,8 @@
import React from 'react';
import * as Sentry from '@sentry/react';
import { Networks, useEnvironment } from '@vegaprotocol/react-helpers';
import { useWeb3React } from '@web3-react/core';
import { MetaMask } from '@web3-react/metamask';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import { Networks } from '@vegaprotocol/smart-contracts';
export const useAddAssetSupported = () => {
const { connector } = useWeb3React();

View File

@ -1,36 +0,0 @@
import * as Sentry from '@sentry/react';
import React from 'react';
import { NetworkParams } from '../config';
import { useNetworkParam } from './use-network-param';
export const useEthereumConfig = () => {
const { data: ethereumConfigJSON, loading } = useNetworkParam([
NetworkParams.ETHEREUM_CONFIG,
]);
const ethereumConfig = React.useMemo(() => {
if (!ethereumConfigJSON && !loading) {
Sentry.captureMessage(
`No ETH config found for network param ${NetworkParams.ETHEREUM_CONFIG}`
);
return null;
} else if (!ethereumConfigJSON) {
return null;
}
try {
const [configJson] = ethereumConfigJSON;
return JSON.parse(configJson);
} catch {
Sentry.captureMessage('Ethereum config JSON is invalid');
return null;
}
}, [ethereumConfigJSON, loading]);
if (!ethereumConfig) {
return null;
}
return {
confirmations: ethereumConfig.confirmations,
};
};

View File

@ -1,24 +1,33 @@
import React from 'react';
import type { ethers } from 'ethers';
import * as Sentry from '@sentry/react';
import type { VegaStaking, VegaVesting } from '@vegaprotocol/smart-contracts';
import { addDecimal } from '@vegaprotocol/react-helpers';
import type {
StakingBridge,
TokenVesting,
} from '@vegaprotocol/smart-contracts';
import {
AppStateActionType,
useAppState,
} from '../contexts/app-state/app-state-context';
import BigNumber from 'bignumber.js';
export function useGetAssociationBreakdown(
ethAddress: string,
staking: VegaStaking,
vesting: VegaVesting
staking: StakingBridge,
vesting: TokenVesting
): () => Promise<void> {
const { appDispatch } = useAppState();
const {
appState: { decimals },
appDispatch,
} = useAppState();
const getAssociationBreakdown = React.useCallback(async () => {
try {
const [stakingAssociations, vestingAssociations] = await Promise.all([
staking.userTotalStakedByVegaKey(ethAddress),
vesting.userTotalStakedByVegaKey(ethAddress),
userTotalStakedByVegaKey(staking, ethAddress, decimals),
userTotalStakedByVegaKey(vesting, ethAddress, decimals),
]);
appDispatch({
@ -31,7 +40,59 @@ export function useGetAssociationBreakdown(
} catch (err) {
Sentry.captureException(err);
}
}, [ethAddress, staking, vesting, appDispatch]);
}, [ethAddress, staking, vesting, decimals, appDispatch]);
return getAssociationBreakdown;
}
async function userTotalStakedByVegaKey(
contract: StakingBridge | TokenVesting,
ethereumAccount: string,
decimals: number
): Promise<{ [vegaKey: string]: BigNumber }> {
const addFilter = contract.contract.filters.Stake_Deposited(ethereumAccount);
const removeFilter = contract.contract.filters.Stake_Removed(ethereumAccount);
const addEvents = await contract.contract.queryFilter(addFilter);
const removeEvents = await contract.contract.queryFilter(removeFilter);
const res = combineStakeEventsByVegaKey(
[...addEvents, ...removeEvents],
decimals
);
return res;
}
function combineStakeEventsByVegaKey(
events: ethers.Event[],
decimals: number
): { [vegaKey: string]: BigNumber } {
const res = events.reduce((obj, e) => {
const vegaKey = e.args?.vega_public_key;
const amount = parseEventAmount(e, decimals);
const isDeposit = e.event === 'Stake_Deposited';
const isRemove = e.event === 'Stake_Removed';
if (!isDeposit && !isRemove) return obj;
if (Object.prototype.hasOwnProperty.call(obj, vegaKey)) {
if (isDeposit) {
obj[vegaKey] = obj[vegaKey].plus(amount);
} else {
obj[vegaKey] = obj[vegaKey].minus(amount);
}
} else {
if (isDeposit) {
obj[vegaKey] = amount;
} else {
obj[vegaKey] = new BigNumber(0);
}
}
return obj;
}, {} as { [vegaKey: string]: BigNumber });
return res;
}
function parseEventAmount(e: ethers.Event, decimals: number) {
const rawAmount = new BigNumber(e.args?.amount.toString() || 0);
return new BigNumber(addDecimal(rawAmount.toString(), decimals));
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import * as Sentry from '@sentry/react';
import type { VegaVesting } from '@vegaprotocol/smart-contracts';
import type { TokenVesting } from '@vegaprotocol/smart-contracts';
import {
AppStateActionType,
@ -8,12 +8,16 @@ import {
} from '../contexts/app-state/app-state-context';
import { BigNumber } from '../lib/bignumber';
import { useTranches } from './use-tranches';
import { toBigNum } from '@vegaprotocol/react-helpers';
export const useGetUserTrancheBalances = (
address: string,
vesting: VegaVesting
vesting: TokenVesting
) => {
const { appDispatch } = useAppState();
const {
appState: { decimals },
appDispatch,
} = useAppState();
const { tranches } = useTranches();
return React.useCallback(async () => {
appDispatch({
@ -32,10 +36,14 @@ export const useGetUserTrancheBalances = (
);
const trancheIds = [0, ...userTranches.map((t) => t.tranche_id)];
const promises = trancheIds.map(async (tId) => {
const [total, vested] = await Promise.all([
vesting.userTrancheTotalBalance(address, tId),
vesting.userTrancheVestedBalance(address, tId),
const [t, v] = await Promise.all([
vesting.getTrancheBalance(address, tId),
vesting.getVestedForTranche(address, tId),
]);
const total = toBigNum(t, decimals);
const vested = toBigNum(v, decimals);
return {
id: tId,
locked: tId === 0 ? total : total.minus(vested),
@ -56,5 +64,5 @@ export const useGetUserTrancheBalances = (
error: e as Error,
});
}
}, [address, appDispatch, tranches, vesting]);
}, [address, decimals, appDispatch, tranches, vesting]);
};

View File

@ -1,9 +1,8 @@
import React from 'react';
import { useContracts } from '../contexts/contracts/contracts-context';
import { useTransactionStore } from '../stores/transactions';
export const usePendingTransactions = () => {
const { transactions } = useContracts();
const { transactions } = useTransactionStore();
return React.useMemo(() => {
return transactions.some((tx) => tx.pending);

View File

@ -14,8 +14,8 @@ export function useRefreshAssociatedBalances() {
async (ethAddress: string, vegaKey: string) => {
const [walletAssociatedBalance, vestingAssociatedBalance] =
await Promise.all([
staking.stakeBalance(ethAddress, vegaKey),
vesting.stakeBalance(ethAddress, vegaKey),
staking.stakeBalance(ethAddress, `0x${vegaKey}`),
vesting.stakeBalance(ethAddress, `0x${vegaKey}`),
]);
appDispatch({

View File

@ -1,8 +1,9 @@
import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useEthereumConfig } from '@vegaprotocol/web3';
import React from 'react';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import {
AppStateActionType,
useAppState,
@ -10,29 +11,35 @@ import {
import { useContracts } from '../contexts/contracts/contracts-context';
export const useRefreshBalances = (address: string) => {
const { ADDRESSES } = useEnvironment();
const { appDispatch } = useAppState();
const {
appState: { decimals },
appDispatch,
} = useAppState();
const { keypair } = useVegaWallet();
const { token, staking, vesting } = useContracts();
const { config } = useEthereumConfig();
return React.useCallback(async () => {
if (!config) return;
try {
const [
balance,
walletBalance,
lien,
allowance,
walletAssociatedBalance,
vestingAssociatedBalance,
] = await Promise.all([
vesting.getUserBalanceAllTranches(address),
token.balanceOf(address),
vesting.getLien(address),
token.allowance(address, ADDRESSES.stakingBridge),
// 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,
]);
const [b, w, stats, a, walletStakeBalance, vestingStakeBalance] =
await Promise.all([
vesting.userTotalAllTranches(address),
token.balanceOf(address),
vesting.userStats(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,
]);
const balance = toBigNum(b, decimals);
const walletBalance = toBigNum(w, decimals);
const lien = toBigNum(stats.lien, decimals);
const allowance = toBigNum(a, decimals);
const walletAssociatedBalance = toBigNum(walletStakeBalance, decimals);
const vestingAssociatedBalance = toBigNum(vestingStakeBalance, decimals);
appDispatch({
type: AppStateActionType.REFRESH_BALANCES,
balance,
@ -47,11 +54,12 @@ export const useRefreshBalances = (address: string) => {
}
}, [
address,
decimals,
appDispatch,
keypair?.pub,
staking,
token,
vesting,
ADDRESSES.stakingBridge,
config,
]);
};

View File

@ -1,5 +1,6 @@
import type { Networks } from '@vegaprotocol/react-helpers';
import { useFetch } from '@vegaprotocol/react-helpers';
import type { Networks, Tranche } from '@vegaprotocol/smart-contracts';
import type { Tranche } from '@vegaprotocol/smart-contracts';
import React, { useEffect } from 'react';
import { useEnvironment } from '@vegaprotocol/react-helpers';

View File

@ -2,19 +2,20 @@ import React from 'react';
import * as Sentry from '@sentry/react';
import { useTranslation } from 'react-i18next';
import type { ethers } from 'ethers';
import { isUnexpectedError, isUserRejection } from '../lib/web3-utils';
import {
initialState,
TransactionActionType,
transactionReducer,
} from './transaction-reducer';
import { useTransactionStore } from '../stores/transactions';
export const useTransaction = (
performTransaction: () => Promise<ethers.ContractTransaction>,
requiredConfirmations = 1
) => {
const { t } = useTranslation();
const store = useTransactionStore();
const [state, dispatch] = React.useReducer(transactionReducer, {
...initialState,
requiredConfirmations,
@ -62,10 +63,12 @@ export const useTransaction = (
try {
const tx = await performTransaction();
store.add({ tx, receipt: null, pending: true, requiredConfirmations });
dispatch({
type: TransactionActionType.TX_SUBMITTED,
txHash: tx.hash,
});
Sentry.addBreadcrumb({
type: 'Transaction',
level: Sentry.Severity.Log,
@ -83,6 +86,7 @@ export const useTransaction = (
for (let i = 1; i <= requiredConfirmations; i++) {
receipt = await tx.wait(i);
store.update({ tx, receipt, pending: true, requiredConfirmations });
dispatch({
type: TransactionActionType.TX_CONFIRMATION,
confirmations: receipt.confirmations,
@ -93,11 +97,13 @@ export const useTransaction = (
throw new Error('No receipt after confirmations are met');
}
store.update({ tx, receipt, pending: false, requiredConfirmations });
dispatch({
type: TransactionActionType.TX_COMPLETE,
receipt,
confirmations: receipt.confirmations,
});
Sentry.addBreadcrumb({
type: 'Transaction',
level: Sentry.Severity.Log,
@ -114,7 +120,7 @@ export const useTransaction = (
} catch (err) {
handleError(err as Error);
}
}, [performTransaction, requiredConfirmations, handleError]);
}, [performTransaction, requiredConfirmations, handleError, store]);
const reset = () => {
dispatch({ type: TransactionActionType.TX_RESET });

View File

@ -1,17 +0,0 @@
/**
* From:
* https://github.com/ChainSafe/web3.js/blob/436e77a8eaa061fbaa183a9f73ca590c2e1d7697/packages/web3-utils/src/index.js
*/
export const asciiToHex = (str: string) => {
if (!str) return '0x00';
let hex = '';
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
const n = code.toString(16);
hex += n.length < 2 ? '0' + n : n;
}
return '0x' + hex;
};

View File

@ -6,6 +6,7 @@ export function addDecimal(value: BigNumber, decimals: number): string {
.decimalPlaces(decimals)
.toString();
}
export function removeDecimal(value: BigNumber, decimals: number): string {
return value.times(Math.pow(10, decimals)).toFixed(0);
}

View File

@ -1,8 +1,14 @@
import { useContracts } from '../../contexts/contracts/contracts-context';
import { useTransaction } from '../../hooks/use-transaction';
import type { IClaimTokenParams } from '@vegaprotocol/smart-contracts';
import { removeDecimal } from '@vegaprotocol/react-helpers';
import { useAppState } from '../../contexts/app-state/app-state-context';
export const useClaim = (claimData: IClaimTokenParams, address: string) => {
const {
appState: { decimals },
} = useAppState();
const claimArgs = {
...claimData,
...claimData.signature,
@ -10,6 +16,7 @@ export const useClaim = (claimData: IClaimTokenParams, address: string) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
country: claimData.country!,
account: address,
amount: removeDecimal(claimData.claim.amount.toString(), decimals),
};
const { claim } = useContracts();
return useTransaction(() => claim.claim(claimArgs));

View File

@ -1,22 +1,60 @@
import { useTranslation } from 'react-i18next';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import { t, useEnvironment } from '@vegaprotocol/react-helpers';
import { Link, Splash } from '@vegaprotocol/ui-toolkit';
import type { EthereumConfig } from '@vegaprotocol/web3';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { Heading } from '../../components/heading';
import { SplashLoader } from '../../components/splash-loader';
const Contracts = () => {
const { config } = useEthereumConfig();
const { ADDRESSES, ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation();
if (!config) {
return (
<Splash>
<SplashLoader />
</Splash>
);
}
return (
<section>
<Heading title={'Contracts'} />
<hr />
{[
'collateral_bridge_contract',
'multisig_control_contract',
'staking_bridge_contract',
'token_vesting_contract',
].map((key) => {
const contract = config[key as keyof EthereumConfig] as {
address: string;
};
return (
<div
key={key}
style={{ display: 'flex', justifyContent: 'space-between' }}
>
<div>{key}:</div>
<Link
title={t('View address on Etherscan')}
href={`${ETHERSCAN_URL}/address/${contract.address}`}
>
{config.collateral_bridge_contract.address}
</Link>
</div>
);
})}
{Object.entries(ADDRESSES).map(([key, value]) => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div
key={key}
style={{ display: 'flex', justifyContent: 'space-between' }}
>
<div>{key}:</div>
<Link
title={t('View on Etherscan (opens in a new tab)')}
title={t('View address on Etherscan')}
href={`${ETHERSCAN_URL}/address/${value}`}
target="_blank"
>
{value}
</Link>

View File

@ -8,6 +8,7 @@ import type { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number';
import { TokenDetailsCirculating } from './token-details-circulating';
import { SplashLoader } from '../../../components/splash-loader';
import { useEthereumConfig } from '@vegaprotocol/web3';
export const TokenDetails = ({
totalSupply,
@ -20,6 +21,7 @@ export const TokenDetails = ({
const { t } = useTranslation();
const { tranches, loading, error } = useTranches();
const { config } = useEthereumConfig();
if (error) {
return (
@ -29,7 +31,7 @@ export const TokenDetails = ({
);
}
if (!tranches || loading) {
if (!tranches || loading || !config) {
return (
<Splash>
<SplashLoader />
@ -57,10 +59,9 @@ export const TokenDetails = ({
data-testid="token-contract"
title={t('View on Etherscan (opens in a new tab)')}
className="font-mono"
href={`${ETHERSCAN_URL}/address/${ADDRESSES.vestingAddress}`}
target="_blank"
href={`${ETHERSCAN_URL}/address/${config.token_vesting_contract.address}`}
>
{ADDRESSES.vestingAddress}
{config.token_vesting_contract.address}
</Link>
</KeyValueTableRow>
<KeyValueTableRow>

View File

@ -1,44 +1,28 @@
import React from 'react';
import { useEthereumConfig } from '../../../hooks/use-ethereum-config';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { StakingWalletsContainer } from '../staking-wallets-container';
import { AssociatePage } from './associate-page';
import { AssociatePageNoVega } from './associate-page-no-vega';
export const NetworkParamsContainer = ({
children,
}: {
children: (data: { confirmations: number }) => React.ReactElement;
}) => {
const config = useEthereumConfig();
export const AssociateContainer = () => {
const { config } = useEthereumConfig();
if (!config) {
return null;
}
return children({
confirmations: config.confirmations,
});
};
export const AssociateContainer = () => {
return (
<NetworkParamsContainer>
{({ confirmations }) => (
<StakingWalletsContainer>
{({ address, currVegaKey }) =>
currVegaKey ? (
<AssociatePage
address={address}
vegaKey={currVegaKey}
requiredConfirmations={confirmations}
/>
) : (
<AssociatePageNoVega />
)
}
</StakingWalletsContainer>
)}
</NetworkParamsContainer>
<StakingWalletsContainer>
{({ address, currVegaKey }) =>
currVegaKey ? (
<AssociatePage
address={address}
vegaKey={currVegaKey}
ethereumConfig={config}
/>
) : (
<AssociatePageNoVega />
)
}
</StakingWalletsContainer>
);
};

View File

@ -1,5 +1,6 @@
import { Callout, Intent } from '@vegaprotocol/ui-toolkit';
import type { VegaKeyExtended } from '@vegaprotocol/wallet';
import type { EthereumConfig } from '@vegaprotocol/web3';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -18,11 +19,11 @@ import { WalletAssociate } from './wallet-associate';
export const AssociatePage = ({
address,
vegaKey,
requiredConfirmations,
ethereumConfig,
}: {
address: string;
vegaKey: VegaKeyExtended;
requiredConfirmations: number;
ethereumConfig: EthereumConfig;
}) => {
const { t } = useTranslation();
const params = useSearchParams();
@ -45,7 +46,7 @@ export const AssociatePage = ({
amount,
vegaKey.pub,
selectedStakingMethod,
requiredConfirmations
ethereumConfig.confirmations
);
const linking = usePollForStakeLinking(vegaKey.pub, txState.txData.hash);
@ -81,7 +82,7 @@ export const AssociatePage = ({
vegaKey={vegaKey.pub}
state={txState}
dispatch={txDispatch}
requiredConfirmations={requiredConfirmations}
requiredConfirmations={ethereumConfig.confirmations}
linking={linking}
/>
);
@ -128,6 +129,7 @@ export const AssociatePage = ({
perform={txPerform}
amount={amount}
setAmount={setAmount}
ethereumConfig={ethereumConfig}
/>
))}
</section>

View File

@ -1,6 +1,5 @@
import { gql, useApolloClient } from '@apollo/client';
import * as Sentry from '@sentry/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import { StakeLinkingStatus } from '../../../__generated__/globalTypes';
@ -15,6 +14,8 @@ import type {
PartyStakeLinkings_party_stake_linkings,
PartyStakeLinkingsVariables,
} from './__generated__/PartyStakeLinkings';
import { useAppState } from '../../../contexts/app-state/app-state-context';
import { removeDecimal } from '@vegaprotocol/react-helpers';
export const useAddStake = (
address: string,
@ -24,12 +25,15 @@ export const useAddStake = (
confirmations: number
) => {
const { staking, vesting } = useContracts();
const {
appState: { decimals },
} = useAppState();
const contractAdd = useTransaction(
() => vesting.addStake(new BigNumber(amount), vegaKey, confirmations),
() => vesting.stakeTokens(removeDecimal(amount, decimals), vegaKey),
confirmations
);
const walletAdd = useTransaction(
() => staking.addStake(new BigNumber(amount), vegaKey, confirmations),
() => staking.stake(removeDecimal(amount, decimals), vegaKey),
confirmations
);
const refreshBalances = useRefreshBalances(address);

View File

@ -12,7 +12,8 @@ import { useTransaction } from '../../../hooks/use-transaction';
import { BigNumber } from '../../../lib/bignumber';
import { AssociateInfo } from './associate-info';
import type { VegaKeyExtended } from '@vegaprotocol/wallet';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import { toBigNum } from '@vegaprotocol/react-helpers';
import type { EthereumConfig } from '@vegaprotocol/web3';
export const WalletAssociate = ({
perform,
@ -20,18 +21,19 @@ export const WalletAssociate = ({
amount,
setAmount,
address,
ethereumConfig,
}: {
perform: () => void;
amount: string;
setAmount: React.Dispatch<React.SetStateAction<string>>;
vegaKey: VegaKeyExtended;
address: string;
ethereumConfig: EthereumConfig;
}) => {
const { ADDRESSES } = useEnvironment();
const { t } = useTranslation();
const {
appDispatch,
appState: { walletBalance, allowance, walletAssociatedBalance },
appState: { walletBalance, allowance, walletAssociatedBalance, decimals },
} = useAppState();
const { token } = useContracts();
@ -40,16 +42,22 @@ export const WalletAssociate = ({
state: approveState,
perform: approve,
dispatch: approveDispatch,
} = useTransaction(() => token.approve(ADDRESSES.stakingBridge));
} = useTransaction(() =>
token.approve(
ethereumConfig.staking_bridge_contract.address,
Number.MAX_SAFE_INTEGER.toString()
)
);
// Once they have approved deposits then we need to refresh their allowance
React.useEffect(() => {
const run = async () => {
if (approveState.txState === TxState.Complete) {
const allowance = await token.allowance(
const a = await token.allowance(
address,
ADDRESSES.stakingBridge
ethereumConfig.staking_bridge_contract.address
);
const allowance = toBigNum(a, decimals);
appDispatch({
type: AppStateActionType.SET_ALLOWANCE,
allowance,
@ -62,7 +70,8 @@ export const WalletAssociate = ({
appDispatch,
approveState.txState,
token,
ADDRESSES.stakingBridge,
decimals,
ethereumConfig,
]);
let pageContent = null;

View File

@ -1,4 +1,5 @@
import BigNumber from 'bignumber.js';
import { removeDecimal } from '@vegaprotocol/react-helpers';
import { useAppState } from '../../../contexts/app-state/app-state-context';
import React from 'react';
import { StakingMethod } from '../../../components/staking-method-radio';
@ -14,15 +15,16 @@ export const useRemoveStake = (
vegaKey: string,
stakingMethod: StakingMethod | null
) => {
const { appState } = useAppState();
const { staking, vesting } = useContracts();
// Cannot use call on these as they check wallet balance
// 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(new BigNumber(amount), vegaKey)
vesting.removeStake(removeDecimal(amount, appState.decimals), vegaKey)
);
const walletRemove = useTransaction(() =>
staking.removeStake(new BigNumber(amount), vegaKey)
staking.removeStake(removeDecimal(amount, appState.decimals), vegaKey)
);
const refreshBalances = useRefreshBalances(address);
const getAssociationBreakdown = useGetAssociationBreakdown(

View File

@ -1,10 +1,5 @@
import { render } from '@testing-library/react';
import {
EthereumChainIds,
EnvironmentConfig,
Networks,
} from '@vegaprotocol/smart-contracts';
import type { TrancheLabelProps } from './tranche-label';
import { TrancheLabel } from './tranche-label';
@ -12,14 +7,13 @@ let props: TrancheLabelProps;
beforeEach(() => {
props = {
chainId: EthereumChainIds.Mainnet,
contract: EnvironmentConfig[Networks.MAINNET].vestingAddress,
chainId: 1,
id: 5,
};
});
it('Renders null for right contract address, wrong network', () => {
const WRONG_CHAIN = EthereumChainIds.Goerli;
const WRONG_CHAIN = 3;
const { container } = render(
<TrancheLabel {...props} chainId={WRONG_CHAIN} />
);
@ -27,16 +21,6 @@ it('Renders null for right contract address, wrong network', () => {
expect(container).toBeEmptyDOMElement();
});
it('Renders null for right network, wrong contract address', () => {
const WRONG_ADDRESS = '0x0';
const { container } = render(
<TrancheLabel {...props} contract={WRONG_ADDRESS} />
);
expect(container).toBeEmptyDOMElement();
});
it('Renders null for right network, right contract address, tranche without a name', () => {
const UNNAMED_TRANCHE = 0;

View File

@ -1,7 +1,3 @@
import type { EthereumChainId } from '@vegaprotocol/smart-contracts';
import { EthereumChainIds } from '@vegaprotocol/smart-contracts';
import { useEnvironment } from '@vegaprotocol/react-helpers';
const TRANCHE_LABELS: Record<number, string[]> = {
'5': ['Coinlist Option 1', 'Community Whitelist'],
'6': ['Coinlist Option 2'],
@ -11,8 +7,7 @@ const TRANCHE_LABELS: Record<number, string[]> = {
};
export interface TrancheLabelProps {
chainId: EthereumChainId | null;
contract: string;
chainId: number | undefined;
id: number;
}
@ -26,14 +21,9 @@ export interface TrancheLabelProps {
* @param chainId The ID of the chain this contract is on
* @param id The tranche ID on this contract
*/
export const TrancheLabel = ({ contract, chainId, id }: TrancheLabelProps) => {
const { ADDRESSES } = useEnvironment();
export const TrancheLabel = ({ chainId, id }: TrancheLabelProps) => {
// Only mainnet tranches on the known vesting contract have useful name
if (
chainId &&
chainId === EthereumChainIds.Mainnet &&
contract === ADDRESSES.vestingAddress
) {
if (chainId === 1) {
// Only some tranches have titles worth showing
if (TRANCHE_LABELS[id]) {
return (

View File

@ -1,7 +1,6 @@
import type {
Tranche as ITranche,
EthereumChainId,
} from '@vegaprotocol/smart-contracts';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import type { Tranche as ITranche } from '@vegaprotocol/smart-contracts';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -9,8 +8,6 @@ import { useParams } from 'react-router';
import { Navigate } from 'react-router-dom';
import { useOutletContext } from 'react-router-dom';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number';
import { TrancheItem } from '../redemption/tranche-item';
@ -29,7 +26,7 @@ const TrancheProgressContents = ({
export const Tranche = () => {
const tranches = useOutletContext<ITranche[]>();
const { ADDRESSES, ETHERSCAN_URL } = useEnvironment();
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation();
const { trancheId } = useParams<{ trancheId: string }>();
const { chainId } = useWeb3React();
@ -58,11 +55,7 @@ export const Tranche = () => {
unlocked={tranche.total_added.minus(tranche.locked_amount)}
total={tranche.total_added}
secondaryHeader={
<TrancheLabel
contract={ADDRESSES.vestingAddress}
chainId={`0x${chainId}` as EthereumChainId}
id={tranche.tranche_id}
/>
<TrancheLabel chainId={chainId} id={tranche.tranche_id} />
}
/>
<div

View File

@ -1,5 +1,5 @@
import { useOutletContext } from 'react-router-dom';
import type { Tranche, EthereumChainId } from '@vegaprotocol/smart-contracts';
import type { Tranche } from '@vegaprotocol/smart-contracts';
import { useWeb3React } from '@web3-react/core';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -8,7 +8,7 @@ import { TrancheItem } from '../redemption/tranche-item';
import { TrancheLabel } from './tranche-label';
import { VestingChart } from './vesting-chart';
import { Button } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import { useEthereumConfig } from '@vegaprotocol/web3';
const trancheMinimum = 10;
@ -16,13 +16,17 @@ const shouldShowTranche = (t: Tranche) =>
!t.total_added.isLessThanOrEqualTo(trancheMinimum);
export const Tranches = () => {
const { ADDRESSES } = useEnvironment();
const tranches = useOutletContext<Tranche[]>();
const [showAll, setShowAll] = React.useState<boolean>(false);
const { t } = useTranslation();
const { chainId } = useWeb3React();
const { config } = useEthereumConfig();
const filteredTranches = tranches?.filter(shouldShowTranche) || [];
if (!config) {
return null;
}
return (
<section>
<h2 className="text-h4">{t('chartTitle')}</h2>
@ -41,11 +45,7 @@ export const Tranches = () => {
unlocked={tranche.total_added.minus(tranche.locked_amount)}
total={tranche.total_added}
secondaryHeader={
<TrancheLabel
contract={ADDRESSES.vestingAddress}
chainId={`0x${chainId}` as EthereumChainId}
id={tranche.tranche_id}
/>
<TrancheLabel chainId={chainId} id={tranche.tranche_id} />
}
/>
</React.Fragment>

View File

@ -0,0 +1,42 @@
import type ethers from 'ethers';
import type { GetState, SetState } from 'zustand';
import create from 'zustand';
export interface TxData {
tx: ethers.ContractTransaction;
receipt: ethers.ContractReceipt | null;
pending: boolean;
requiredConfirmations: number;
}
interface TransactionStore {
transactions: Array<TxData>;
add: (tx: TxData) => void;
update: (tx: TxData) => void;
remove: (tx: TxData) => void;
}
export const useTransactionStore = create(
(set: SetState<TransactionStore>, get: GetState<TransactionStore>) => ({
transactions: [],
add: (tx) => {
const { transactions } = get();
set({ transactions: [...transactions, tx] });
},
update: (tx) => {
const { transactions } = get();
set({
transactions: [
...transactions.filter((t) => t.tx.hash !== tx.tx.hash),
tx,
],
});
},
remove: (tx) => {
const { transactions } = get();
set({
transactions: transactions.filter((t) => t.tx.hash !== tx.tx.hash),
});
},
})
);

View File

@ -1,7 +1,8 @@
Feature: Deposits to vega wallet
Background:
Given I navigate to deposits page
Given I can connect to Ethereum
And I navigate to deposits page
# wallet is already connected before tests start and doesn't prompt the disconnected state
@ignore

View File

@ -1,7 +1,8 @@
Feature: Withdrawals to eth wallet
Background:
Given I navigate to withdrawal page
Given I can connect to Ethereum
And I navigate to withdrawal page
And I connect to Vega Wallet
Scenario: Succesfull withdrawal

View File

@ -19,3 +19,7 @@ Given('I am on the homepage', () => {
basePage.closeDialog();
marketPage.validateMarketsAreDisplayed();
});
Given('I can connect to Ethereum', () => {
cy.mockWeb3Provider();
});

View File

@ -5,10 +5,6 @@ import DepositsPage from '../pages/deposits-page';
const depositsPage = new DepositsPage();
const ethWallet = new EthereumWallet();
beforeEach(() => {
cy.mockWeb3Provider();
});
Then('I navigate to deposits page', () => {
depositsPage.navigateToDeposits();
});

View File

@ -1,9 +1,10 @@
import { fireEvent, render, screen } from '@testing-library/react';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { NETWORK_PARAMS_QUERY, Web3Container } from './web3-container';
import type { NetworkParamsQuery } from './__generated__/NetworkParamsQuery';
import { Web3Container } from './web3-container';
import type { useWeb3React } from '@web3-react/core';
import type { NetworkParamsQuery } from '@vegaprotocol/web3';
import { NETWORK_PARAMS_QUERY } from '@vegaprotocol/web3';
const defaultHookValue = {
isActive: false,
@ -50,14 +51,12 @@ jest.mock('@web3-react/core', () => {
function setup(mock = networkParamsQueryMock) {
return render(
<MockedProvider mocks={[mock]}>
<Web3Container
render={({ ethereumConfig }) => (
<div>
<div>Child</div>
<div>{ethereumConfig.collateral_bridge_contract.address}</div>
</div>
)}
/>
<Web3Container>
<div>
<div>Child</div>
<div>{mockEthereumConfig.collateral_bridge_contract.address}</div>
</div>
</Web3Container>
</MockedProvider>
);
}
@ -140,29 +139,5 @@ it('Shows no config found message if the network parameter doesnt exist', async
};
setup(mock);
expect(screen.getByText('Loading...')).toBeInTheDocument();
expect(
await screen.findByText('No ethereum config found')
).toBeInTheDocument();
});
it('Shows message if ethereum config could not be parsed', async () => {
const mock: MockedResponse<NetworkParamsQuery> = {
request: {
query: NETWORK_PARAMS_QUERY,
},
result: {
data: {
networkParameters: [
{
__typename: 'NetworkParameter',
key: 'blockchains.ethereumConfig',
value: '"something invalid }',
},
],
},
},
};
setup(mock);
expect(screen.getByText('Loading...')).toBeInTheDocument();
expect(await screen.findByText('Could not parse config')).toBeInTheDocument();
expect(await screen.findByText('No data')).toBeInTheDocument();
});

View File

@ -1,96 +1,42 @@
import { gql } from '@apollo/client';
import type { NetworkParamsQuery } from './__generated__/NetworkParamsQuery';
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import { Web3Provider, Web3ConnectDialog } from '@vegaprotocol/web3';
import { AsyncRenderer, Button, Splash } from '@vegaprotocol/ui-toolkit';
import {
Web3Provider,
Web3ConnectDialog,
useEthereumConfig,
} from '@vegaprotocol/web3';
import { useWeb3React } from '@web3-react/core';
import type { ReactNode } from 'react';
import { useEffect, useState } from 'react';
import { Connectors } from '../../lib/web3-connectors';
import { PageQueryContainer } from '../page-query-container';
import { t } from '@vegaprotocol/react-helpers';
export interface EthereumConfig {
network_id: string;
chain_id: string;
confirmations: number;
collateral_bridge_contract: {
address: string;
};
multisig_control_contract: {
address: string;
deployment_block_height: number;
};
staking_bridge_contract: {
address: string;
deployment_block_height: number;
};
token_vesting_contract: {
address: string;
deployment_block_height: number;
};
}
export const NETWORK_PARAMS_QUERY = gql`
query NetworkParamsQuery {
networkParameters {
key
value
}
}
`;
interface Web3ContainerProps {
render: (params: { ethereumConfig: EthereumConfig }) => ReactNode;
children: ReactNode;
}
export const Web3Container = ({ render }: Web3ContainerProps) => {
export const Web3Container = ({ children }: Web3ContainerProps) => {
const [dialogOpen, setDialogOpen] = useState(false);
const { config, loading, error } = useEthereumConfig();
return (
<PageQueryContainer<NetworkParamsQuery>
query={NETWORK_PARAMS_QUERY}
render={(data) => {
const ethereumConfigParam = data.networkParameters?.find(
(np) => np.key === 'blockchains.ethereumConfig'
);
if (!ethereumConfigParam) {
return (
<Splash>
<p>{t('No ethereum config found')}</p>
</Splash>
);
}
let ethereumConfig: EthereumConfig;
try {
ethereumConfig = JSON.parse(ethereumConfigParam.value);
} catch {
return (
<Splash>
<p>{t('Could not parse config')}</p>
</Splash>
);
}
return (
<Web3Provider connectors={Connectors}>
<Web3Content
appChainId={Number(ethereumConfig.chain_id)}
setDialogOpen={setDialogOpen}
>
{render({ ethereumConfig })}
</Web3Content>
<Web3ConnectDialog
connectors={Connectors}
dialogOpen={dialogOpen}
setDialogOpen={setDialogOpen}
desiredChainId={Number(ethereumConfig.chain_id)}
/>
</Web3Provider>
);
}}
/>
<AsyncRenderer data={config} loading={loading} error={error}>
{config ? (
<Web3Provider connectors={Connectors}>
<Web3Content
appChainId={Number(config.chain_id)}
setDialogOpen={setDialogOpen}
>
{children}
</Web3Content>
<Web3ConnectDialog
connectors={Connectors}
dialogOpen={dialogOpen}
setDialogOpen={setDialogOpen}
desiredChainId={Number(config.chain_id)}
/>
</Web3Provider>
) : null}
</AsyncRenderer>
);
};

View File

@ -1,4 +1,3 @@
import type { EthereumConfig } from '../../../components/web3-container/web3-container';
import { gql } from '@apollo/client';
import { PageQueryContainer } from '../../../components/page-query-container';
import type { DepositPage } from './__generated__/DepositPage';
@ -17,17 +16,13 @@ const DEPOSIT_PAGE_QUERY = gql`
`;
interface DepositContainerProps {
ethereumConfig: EthereumConfig;
assetId?: string;
}
/**
* Fetches data required for the Deposit page
*/
export const DepositContainer = ({
ethereumConfig,
assetId,
}: DepositContainerProps) => {
export const DepositContainer = ({ assetId }: DepositContainerProps) => {
const { VEGA_ENV } = useEnvironment();
return (
@ -44,8 +39,6 @@ export const DepositContainer = ({
return (
<DepositManager
bridgeAddress={ethereumConfig.collateral_bridge_contract.address}
requiredConfirmations={ethereumConfig.confirmations}
assets={data.assets}
initialAssetId={assetId}
isFaucetable={VEGA_ENV !== 'MAINNET'}

View File

@ -20,14 +20,12 @@ const Deposit = () => {
}, [query]);
return (
<Web3Container
render={({ ethereumConfig }) => (
<div className="max-w-[420px] p-24 mx-auto">
<h1 className="text-h3 mb-12">Deposit</h1>
<DepositContainer ethereumConfig={ethereumConfig} assetId={assetId} />
</div>
)}
/>
<Web3Container>
<div className="max-w-[420px] p-24 mx-auto">
<h1 className="text-h3 mb-12">Deposit</h1>
<DepositContainer assetId={assetId} />
</div>
</Web3Container>
);
};

View File

@ -12,68 +12,66 @@ const Portfolio = () => {
const tabClassName = 'p-[16px] pl-[316px]';
return (
<Web3Container
render={() => (
<div className="h-full text-ui">
<main className="relative h-[calc(100%-200px)]">
<aside className="absolute px-[8px] py-[16px] w-[300px] mt-[28px] h-[calc(100%-28px)] w-[300px] overflow-auto">
<h2 className="text-h4 text-black dark:text-white">
{t('Filters')}
</h2>
</aside>
<section>
<GridTabs group="portfolio">
<GridTab id="positions" name={t('Positions')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('Positions')}
</h4>
<PositionsContainer />
</div>
</GridTab>
<GridTab id="orders" name={t('Orders')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('Orders')}
</h4>
<OrderListContainer />
</div>
</GridTab>
<GridTab id="fills" name={t('Fills')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('Fills')}
</h4>
</div>
</GridTab>
<GridTab id="history" name={t('History')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('History')}
</h4>
</div>
</GridTab>
</GridTabs>
</section>
</main>
<section className="fixed bottom-0 left-0 w-full h-[200px]">
<GridTabs group="collaterals">
<GridTab id="collateral" name={t('Collateral')}>
<AccountsContainer />
<Web3Container>
<div className="h-full text-ui">
<main className="relative h-[calc(100%-200px)]">
<aside className="absolute px-[8px] py-[16px] w-[300px] mt-[28px] h-[calc(100%-28px)] w-[300px] overflow-auto">
<h2 className="text-h4 text-black dark:text-white">
{t('Filters')}
</h2>
</aside>
<section>
<GridTabs group="portfolio">
<GridTab id="positions" name={t('Positions')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('Positions')}
</h4>
<PositionsContainer />
</div>
</GridTab>
<GridTab id="deposits" name={t('Deposits')}>
<AnchorButton data-testid="deposit" href="/portfolio/deposit">
{t('Deposit')}
</AnchorButton>
<GridTab id="orders" name={t('Orders')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('Orders')}
</h4>
<OrderListContainer />
</div>
</GridTab>
<GridTab id="withdrawals" name={t('Withdrawals')}>
<WithdrawalsContainer />
<GridTab id="fills" name={t('Fills')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('Fills')}
</h4>
</div>
</GridTab>
<GridTab id="history" name={t('History')}>
<div className={tabClassName}>
<h4 className="text-h4 text-black dark:text-white">
{t('History')}
</h4>
</div>
</GridTab>
</GridTabs>
</section>
</div>
)}
/>
</main>
<section className="fixed bottom-0 left-0 w-full h-[200px]">
<GridTabs group="collaterals">
<GridTab id="collateral" name={t('Collateral')}>
<AccountsContainer />
</GridTab>
<GridTab id="deposits" name={t('Deposits')}>
<AnchorButton data-testid="deposit" href="/portfolio/deposit">
{t('Deposit')}
</AnchorButton>
</GridTab>
<GridTab id="withdrawals" name={t('Withdrawals')}>
<WithdrawalsContainer />
</GridTab>
</GridTabs>
</section>
</div>
</Web3Container>
);
};

View File

@ -23,14 +23,12 @@ const Withdraw = () => {
return (
<VegaWalletContainer>
<Web3Container
render={() => (
<div className="max-w-[420px] p-24 mx-auto">
<h1 className="text-h3 mb-12">{t('Withdraw')}</h1>
<WithdrawPageContainer assetId={assetId} />
</div>
)}
/>
<Web3Container>
<div className="max-w-[420px] p-24 mx-auto">
<h1 className="text-h3 mb-12">{t('Withdraw')}</h1>
<WithdrawPageContainer assetId={assetId} />
</div>
</Web3Container>
</VegaWalletContainer>
);
};

View File

@ -7,19 +7,17 @@ import { WithdrawalsContainer } from './withdrawals-container';
const Withdrawals = () => {
return (
<VegaWalletContainer>
<Web3Container
render={() => (
<div className="h-full grid grid grid-rows-[min-content,1fr]">
<header className="flex justify-between p-24">
<h1 className="text-h3">{t('Withdrawals')}</h1>
<AnchorButton href="/portfolio/withdraw">
{t('Start withdrawal')}
</AnchorButton>
</header>
<WithdrawalsContainer />
</div>
)}
/>
<Web3Container>
<div className="h-full grid grid grid-rows-[min-content,1fr]">
<header className="flex justify-between p-24">
<h1 className="text-h3">{t('Withdrawals')}</h1>
<AnchorButton href="/portfolio/withdraw">
{t('Start withdrawal')}
</AnchorButton>
</header>
<WithdrawalsContainer />
</div>
</Web3Container>
</VegaWalletContainer>
);
};

View File

@ -7,8 +7,13 @@ import { useSubmitApproval } from './use-submit-approval';
import { useGetDepositLimits } from './use-get-deposit-limits';
import { useGetAllowance } from './use-get-allowance';
import { useSubmitFaucet } from './use-submit-faucet';
import { EthTxStatus, TransactionDialog } from '@vegaprotocol/web3';
import { useTokenContract, useBridgeContract } from '@vegaprotocol/web3';
import {
EthTxStatus,
TransactionDialog,
useEthereumConfig,
useTokenDecimals,
} from '@vegaprotocol/web3';
import { useTokenContract } from '@vegaprotocol/web3';
interface ERC20AssetSource {
__typename: 'ERC20';
@ -30,16 +35,12 @@ export interface Asset {
}
interface DepositManagerProps {
requiredConfirmations: number;
bridgeAddress: string;
assets: Asset[];
initialAssetId?: string;
isFaucetable?: boolean;
}
export const DepositManager = ({
requiredConfirmations,
bridgeAddress,
assets,
initialAssetId,
isFaucetable,
@ -52,31 +53,34 @@ export const DepositManager = ({
return asset;
}, [assets, assetId]);
const { config } = useEthereumConfig();
const tokenContract = useTokenContract(
asset?.source.__typename === 'ERC20'
? asset.source.contractAddress
: undefined,
isFaucetable
);
const bridgeContract = useBridgeContract();
const decimals = useTokenDecimals(tokenContract);
// Get users balance of the erc20 token selected
const { balanceOf, refetch } = useGetBalanceOfERC20Token(tokenContract);
const { balance, refetch } = useGetBalanceOfERC20Token(
tokenContract,
decimals
);
// Get temporary deposit limits
const limits = useGetDepositLimits(bridgeContract, asset);
const limits = useGetDepositLimits(asset, decimals);
// Get allowance (approved spending limit of brdige contract) for the selected asset
const allowance = useGetAllowance(tokenContract, bridgeAddress);
const allowance = useGetAllowance(tokenContract, decimals);
// Set up approve transaction
const approve = useSubmitApproval(tokenContract, bridgeAddress);
const approve = useSubmitApproval(tokenContract);
// Set up deposit transaction
const { confirmationEvent, ...deposit } = useSubmitDeposit(
bridgeContract,
requiredConfirmations
);
const { confirmationEvent, ...deposit } = useSubmitDeposit();
// Set up faucet transaction
const faucet = useSubmitFaucet(tokenContract);
@ -94,7 +98,7 @@ export const DepositManager = ({
return (
<>
<DepositForm
available={balanceOf}
available={balance}
selectedAsset={asset}
onSelectAsset={(id) => setAssetId(id)}
assets={sortBy(assets, 'name')}
@ -112,7 +116,7 @@ export const DepositManager = ({
name="deposit"
confirmed={Boolean(confirmationEvent)}
// Must wait for additional confirmations for Vega to pick up the Ethereum transaction
requiredConfirmations={requiredConfirmations}
requiredConfirmations={config?.confirmations}
/>
</>
);

View File

@ -1,24 +1,31 @@
import type { ERC20Token } from '@vegaprotocol/smart-contracts';
import type { Token } from '@vegaprotocol/smart-contracts';
import { useWeb3React } from '@web3-react/core';
import { useCallback } from 'react';
import { useEthereumReadContract } from '@vegaprotocol/web3';
import { useEthereumConfig, useEthereumReadContract } from '@vegaprotocol/web3';
import BigNumber from 'bignumber.js';
import { addDecimal } from '@vegaprotocol/react-helpers';
export const useGetAllowance = (
contract: ERC20Token | null,
bridgeAddress: string
) => {
export const useGetAllowance = (contract: Token | null, decimals?: number) => {
const { account } = useWeb3React();
const { config } = useEthereumConfig();
const getAllowance = useCallback(() => {
if (!contract || !account) {
if (!contract || !account || !config) {
return;
}
return contract.allowance(account, bridgeAddress);
}, [contract, account, bridgeAddress]);
return contract.allowance(
account,
config.collateral_bridge_contract.address
);
}, [contract, account, config]);
const {
state: { data },
} = useEthereumReadContract(getAllowance);
return data;
if (!data || !decimals) return;
const allowance = new BigNumber(addDecimal(data.toString(), decimals));
return allowance;
};

View File

@ -1,9 +1,14 @@
import { useEthereumReadContract } from '@vegaprotocol/web3';
import type { ERC20Token } from '@vegaprotocol/smart-contracts';
import type { Token } from '@vegaprotocol/smart-contracts';
import { useWeb3React } from '@web3-react/core';
import { useCallback } from 'react';
import BigNumber from 'bignumber.js';
import { addDecimal } from '@vegaprotocol/react-helpers';
export const useGetBalanceOfERC20Token = (contract: ERC20Token | null) => {
export const useGetBalanceOfERC20Token = (
contract: Token | null,
decimals: number | undefined
) => {
const { account } = useWeb3React();
const getBalance = useCallback(() => {
@ -16,5 +21,10 @@ export const useGetBalanceOfERC20Token = (contract: ERC20Token | null) => {
const { state, refetch } = useEthereumReadContract(getBalance);
return { balanceOf: state.data, refetch };
const balance =
state.data && decimals
? new BigNumber(addDecimal(state.data?.toString(), decimals))
: undefined;
return { balance, refetch };
};

View File

@ -1,37 +1,33 @@
import type BigNumber from 'bignumber.js';
import { useCallback } from 'react';
import type { VegaErc20Bridge } from '@vegaprotocol/smart-contracts';
import type { Asset } from './deposit-manager';
import { useEthereumReadContract } from '@vegaprotocol/web3';
import { useBridgeContract, useEthereumReadContract } from '@vegaprotocol/web3';
import BigNumber from 'bignumber.js';
import { addDecimal } from '@vegaprotocol/react-helpers';
interface Limits {
min: BigNumber;
max: BigNumber;
}
export const useGetDepositLimits = (
contract: VegaErc20Bridge | null,
asset?: Asset
): Limits | null => {
export const useGetDepositLimits = (asset?: Asset, decimals?: number) => {
const contract = useBridgeContract();
const getLimits = useCallback(async () => {
if (!contract || !asset || asset.source.__typename !== 'ERC20') {
return;
}
return Promise.all([
contract.getDepositMinimum(asset.source.contractAddress, asset.decimals),
contract.getDepositMaximum(asset.source.contractAddress, asset.decimals),
contract.getDepositMinimum(asset.source.contractAddress),
contract.getDepositMaximum(asset.source.contractAddress),
]);
}, [asset, contract]);
const {
state: { data },
} = useEthereumReadContract<[BigNumber, BigNumber] | undefined>(getLimits);
} = useEthereumReadContract(getLimits);
if (!data) return null;
if (!data || !decimals) return null;
const min = new BigNumber(addDecimal(data[0].toString(), decimals));
const max = new BigNumber(addDecimal(data[1].toString(), decimals));
return {
min: data[0],
max: data[1],
min,
max: max.isEqualTo(0) ? new BigNumber(Infinity) : max,
};
};

View File

@ -1,15 +1,17 @@
import type { ERC20Token } from '@vegaprotocol/smart-contracts';
import { useEthereumTransaction } from '@vegaprotocol/web3';
import type { Token } from '@vegaprotocol/smart-contracts';
import { useEthereumConfig, useEthereumTransaction } from '@vegaprotocol/web3';
export const useSubmitApproval = (contract: Token | null) => {
const { config } = useEthereumConfig();
export const useSubmitApproval = (
contract: ERC20Token | null,
bridgeAddress: string
) => {
const transaction = useEthereumTransaction(() => {
if (!contract) {
if (!contract || !config) {
return null;
}
return contract.approve(bridgeAddress);
return contract.approve(
config.collateral_bridge_contract.address,
Number.MAX_SAFE_INTEGER.toString()
);
});
return transaction;

View File

@ -7,8 +7,11 @@ import type {
import { DepositStatus } from '@vegaprotocol/types';
import { useState } from 'react';
import { remove0x } from '@vegaprotocol/react-helpers';
import { useEthereumTransaction } from '@vegaprotocol/web3';
import type { VegaErc20Bridge } from '@vegaprotocol/smart-contracts';
import {
useBridgeContract,
useEthereumConfig,
useEthereumTransaction,
} from '@vegaprotocol/web3';
const DEPOSIT_EVENT_SUB = gql`
subscription DepositEvent($partyId: ID!) {
@ -24,10 +27,9 @@ const DEPOSIT_EVENT_SUB = gql`
}
`;
export const useSubmitDeposit = (
contract: VegaErc20Bridge | null,
confirmations: number
) => {
export const useSubmitDeposit = () => {
const { config } = useEthereumConfig();
const contract = useBridgeContract();
const [confirmationEvent, setConfirmationEvent] =
useState<DepositEvent_busEvents_event_Deposit | null>(null);
// Store public key from contract arguments for use in the subscription,
@ -52,7 +54,7 @@ export const useSubmitDeposit = (
args.amount,
args.vegaPublicKey
);
}, confirmations);
}, config?.confirmations);
useSubscription<DepositEvent, DepositEventVariables>(DEPOSIT_EVENT_SUB, {
variables: { partyId: partyId ? remove0x(partyId) : '' },

View File

@ -1,9 +1,10 @@
import type { ERC20Token } from '@vegaprotocol/smart-contracts';
import { Token } from '@vegaprotocol/smart-contracts';
import type { TokenFaucetable } from '@vegaprotocol/smart-contracts';
import { useEthereumTransaction } from '@vegaprotocol/web3';
export const useSubmitFaucet = (contract: ERC20Token | null) => {
export const useSubmitFaucet = (contract: Token | TokenFaucetable | null) => {
const transaction = useEthereumTransaction(() => {
if (!contract) {
if (!contract || contract instanceof Token) {
return null;
}
return contract.faucet();

View File

@ -1,15 +1,53 @@
import type { ReactNode } from 'react';
import type { Networks } from '@vegaprotocol/smart-contracts';
import { EnvironmentConfig } from '@vegaprotocol/smart-contracts';
import { createContext, useContext } from 'react';
import type { Networks } from '../lib/environment';
declare global {
interface Window {
_ENV?: RawEnvironment;
}
}
const customVegaTokenAddress = process.env['CUSTOM_TOKEN_ADDRESS'] as string;
const customClaimAddress = process.env['CUSTOM_CLAIM_ADDRESS'] as string;
const customLockedAddress = process.env['CUSTOM_LOCKED_ADDRESS'] as string;
interface VegaContracts {
vegaTokenAddress: string;
claimAddress: string;
lockedAddress: string;
}
type VegaContracts = typeof EnvironmentConfig[Networks];
export const ContractAddresses: { [key in Networks]: VegaContracts } = {
CUSTOM: {
vegaTokenAddress: customVegaTokenAddress,
claimAddress: customClaimAddress,
lockedAddress: customLockedAddress,
},
DEVNET: {
vegaTokenAddress: '0xc93137f9F4B820Ca85FfA3C7e84cCa6Ebc7bB517',
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994',
lockedAddress: '0x0',
},
STAGNET: {
vegaTokenAddress: '0x547cbA83a7eb82b546ee5C7ff0527F258Ba4546D',
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
STAGNET2: {
vegaTokenAddress: '0xd8fa193B93a179DdCf51FFFDe5320E0872cdcf44',
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
TESTNET: {
vegaTokenAddress: '0xDc335304979D378255015c33AbFf09B60c31EBAb',
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
MAINNET: {
vegaTokenAddress: '0xcB84d72e61e383767C4DFEb2d8ff7f4FB89abc6e',
claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3',
lockedAddress: '0x78344c7305d73a7a0ac3c94cd9960f4449a1814e',
},
};
type EnvironmentProviderProps = {
definintions?: Partial<RawEnvironment>;
@ -106,7 +144,7 @@ export const EnvironmentProvider = ({
<EnvironmentContext.Provider
value={{
...environment,
ADDRESSES: EnvironmentConfig[environment['VEGA_ENV']],
ADDRESSES: ContractAddresses[environment['VEGA_ENV']],
}}
>
{children}

View File

@ -1,5 +1,6 @@
export * from './hooks';
export * from './lib/context';
export * from './lib/environment';
export * from './lib/format';
export * from './lib/generic-data-provider';
export * from './lib/grid';

View File

@ -1,4 +1,5 @@
import { BigNumber } from 'bignumber.js';
import { BigNumber as EthersBigNumber } from 'ethers';
import memoize from 'lodash/memoize';
import { getUserLocale } from './utils';
@ -6,15 +7,22 @@ export function toDecimal(numberOfDecimals: number) {
return Math.pow(10, -numberOfDecimals);
}
export function toBigNum(
rawValue: string | number | EthersBigNumber,
decimals: number
): BigNumber {
return new BigNumber(
rawValue instanceof EthersBigNumber ? rawValue.toString() : rawValue || 0
).dividedBy(Math.pow(10, decimals));
}
export function addDecimal(
value: string | number,
value: string | number | EthersBigNumber,
decimals: number,
decimalPrecision = decimals
): string {
if (!decimals) return value.toString();
return new BigNumber(value || 0)
.dividedBy(Math.pow(10, decimals))
.toFixed(decimalPrecision);
return toBigNum(value, decimals).toFixed(decimalPrecision);
}
export function removeDecimal(value: string, decimals: number): string {

View File

@ -1,126 +0,0 @@
import { Networks } from './vega';
const customVegaTokenAddress = process.env.CUSTOM_TOKEN_ADDRESS as string;
const customClaimAddress = process.env.CUSTOM_CLAIM_ADDRESS as string;
const customLockedAddress = process.env.CUSTOM_LOCKED_ADDRESS as string;
const customVestingAddress = process.env.CUSTOM_VESTING_ADDRESS as string;
const customStakingBridge = process.env.CUSTOM_STAKING_BRIDGE as string;
const customErc20Bridge = process.env.CUSTOM_ERC20_BRIDGE as string;
export type EthereumChainId = '0x1' | '0x3' | '0x4' | '0x5' | '0x2a';
export type EthereumChainName =
| 'Mainnet'
| 'Ropsten'
| 'Rinkeby'
| 'Goerli'
| 'Kovan';
export const EthereumChainNames: Record<EthereumChainId, EthereumChainName> = {
'0x1': 'Mainnet',
'0x3': 'Ropsten',
'0x4': 'Rinkeby',
'0x5': 'Goerli',
'0x2a': 'Kovan',
};
export const EthereumChainIds: Record<EthereumChainName, EthereumChainId> = {
Mainnet: '0x1',
Ropsten: '0x3',
Rinkeby: '0x4',
Goerli: '0x5',
Kovan: '0x2a',
};
export const ChainIdMap: Record<EthereumChainId, number> = {
'0x1': 1,
'0x3': 3,
'0x4': 4,
'0x5': 5,
'0x2a': 42,
};
interface VegaContracts {
vestingAddress: string;
vegaTokenAddress: string;
claimAddress: string;
lockedAddress: string;
stakingBridge: string;
erc20Bridge: string;
}
export const EnvironmentConfig: { [key in Networks]: VegaContracts } = {
[Networks.CUSTOM]: {
vegaTokenAddress: customVegaTokenAddress,
claimAddress: customClaimAddress,
lockedAddress: customLockedAddress,
vestingAddress: customVestingAddress,
stakingBridge: customStakingBridge,
erc20Bridge: customErc20Bridge,
},
[Networks.DEVNET]: {
vegaTokenAddress: '0xc93137f9F4B820Ca85FfA3C7e84cCa6Ebc7bB517',
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994',
lockedAddress: '0x0',
vestingAddress: '0xd1216AAb948f5FC706Df73df6d71c64CcaA8550a',
stakingBridge: '0xf2cD5C8b8c52f96293338A0AF463a0Bfc602D5bc',
erc20Bridge: '0xE43013C3c2A134AB3782ADEb258669A8566DAD57',
},
[Networks.STAGNET]: {
vegaTokenAddress: '0x547cbA83a7eb82b546ee5C7ff0527F258Ba4546D',
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
vestingAddress: '0xfCe6eB272D3d4146A96bC28de71212b327F575fa',
stakingBridge: '0x7D88CD817227D599815d407D929af18Bb8D57176',
erc20Bridge: '0xc0835e6dEf177F8ba2561C4e4216827A3798c6B9',
},
[Networks.STAGNET2]: {
vegaTokenAddress: '0xd8fa193B93a179DdCf51FFFDe5320E0872cdcf44',
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
vestingAddress: '0x9F10cBeEf03A564Fb914c2010c0Cd55E9BB11406',
stakingBridge: '0x7896C9491962D5839783CB6e0492ECebd34Bb35F',
erc20Bridge: '0xffC1eb64e22fd5E29816c633eE84088EEEe879E5',
},
[Networks.TESTNET]: {
vegaTokenAddress: '0xDc335304979D378255015c33AbFf09B60c31EBAb',
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
vestingAddress: '0xe2deBB240b43EDfEBc9c38B67c0894B9A92Bf07c',
stakingBridge: '0xF5A3830F002BE78dd801214F5316b677E0355c60',
erc20Bridge: '0xF009C66c6afC9661143fD7cE1eDb02c1961a6510',
},
[Networks.MAINNET]: {
vegaTokenAddress: '0xcB84d72e61e383767C4DFEb2d8ff7f4FB89abc6e',
claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3',
lockedAddress: '0x78344c7305d73a7a0ac3c94cd9960f4449a1814e',
vestingAddress: '0x23d1bFE8fA50a167816fBD79D7932577c06011f4',
stakingBridge: '0x195064D33f09e0c42cF98E665D9506e0dC17de68',
erc20Bridge: '0xCd403f722b76366f7d609842C589906ca051310f',
},
};
// No concept of dev/staging/test for these right now.
export const RewardsAddresses = {
[EthereumChainIds.Mainnet]: {
'SushiSwap VEGA/ETH': '0x285de24077440c53b1661287D170e3ae22de0a44',
'SushiSwap VEGA/USDC': '0x49407c243c26f109b3c77c41dd83742164c20b5f',
} as { [key: string]: string },
[EthereumChainIds.Ropsten]: {
'SushiSwap VEGA/ETH': '0xa93dd6912897c5fe8503a82234d829bc7905714b',
'SushiSwap VEGA/USDC': '0xa93dd6912897c5fe8503a82234d829bc7905714b',
} as { [key: string]: string },
};
export const RewardsPoolAddresses = {
[EthereumChainIds.Mainnet]: {
'0x285de24077440c53b1661287D170e3ae22de0a44':
'0x29c827ce49accf68a1a278c67c9d30c52fbbc348',
'0x49407c243c26f109b3c77c41dd83742164c20b5f':
'0x42b7B8f8F83fA5cbf0176f8c24Ad51EbcD4B5F17',
} as { [key: string]: string },
[EthereumChainIds.Ropsten]: {
// Only one deployed to this environment
'0xa93dd6912897c5fe8503a82234d829bc7905714b':
'0x29c827ce49accf68a1a278c67c9d30c52fbbc348',
} as { [key: string]: string },
};

View File

@ -1,2 +0,0 @@
export * from './ethereum';
export * from './vega';

View File

@ -1,73 +0,0 @@
import type { ethers } from 'ethers';
import type { TxData } from '.';
export class BaseContract {
public signer: ethers.Signer | null = null;
public provider: ethers.providers.Provider;
public transactions: TxData[] = [];
public listeners: ((transactions: TxData[]) => void)[] = [];
constructor(provider: ethers.providers.Provider, signer?: ethers.Signer) {
this.provider = provider;
this.signer = signer || null;
}
async handleEvent(event: ethers.Event, requiredConfirmations = 1) {
const tx = await event.getTransaction();
// start tracking transaction if its not already in the transactions array
const existing = this.transactions.find((t) => t.tx.hash === tx.hash);
if (!existing) {
this.trackTransaction(tx, requiredConfirmations);
}
}
async trackTransaction(
tx: ethers.providers.TransactionResponse,
requiredConfirmations = 1
) {
this.mergeTransaction({
tx,
receipt: null,
pending: true,
requiredConfirmations,
});
let receipt = null;
for (let i = 1; i <= requiredConfirmations; i++) {
receipt = await tx.wait(i);
this.mergeTransaction({
tx,
receipt,
pending: true,
requiredConfirmations,
});
}
this.mergeTransaction({
tx,
receipt,
pending: false,
requiredConfirmations,
});
}
private mergeTransaction(tx: TxData) {
this.transactions = [
// Replace any existing transaction in the array with this one
...this.transactions.filter((t) => t.tx.hash !== tx.tx.hash),
tx,
];
this.emit();
}
emit() {
this.listeners.forEach((ln) => {
ln(this.transactions);
});
}
listen(cb: (txs: TxData[]) => void) {
this.listeners.push(cb);
}
}

View File

@ -1,11 +1,6 @@
import type BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import { EnvironmentConfig } from '../config/ethereum';
import type { Networks } from '../config/vega';
import claimAbi from '../abis/claim_abi.json';
import tokenAbi from '../abis/vega_token_abi.json';
import { asciiToHex, removeDecimal } from '../utils';
import { BaseContract } from './base-contract';
import abi from '../abis/claim_abi.json';
import { asciiToHex } from '../utils';
export const UNSPENT_CODE = '0x0000000000000000000000000000000000000000';
export const SPENT_CODE = '0x0000000000000000000000000000000000000001';
@ -16,55 +11,27 @@ export const SPENT_CODE = '0x0000000000000000000000000000000000000001';
* const provider = new Web3.providers.HttpProvider(
* "https://ropsten.infura.io/v3/5aff9e61ad844bcf982d0d0c3f1d29f1"
* );
* const web3 = new Web3(provider);
*
* const web3 = new Web3(provider)
* // Ropsten address
* const contract = new VegaClaim(web3, "0xAf5dC1772714b2F4fae3b65eb83100f1Ea677b21")
* contract.isCountryBlocked("US").then(console.log)
* contract.isClaimValid({ claimCode: "0x...", expiry: 0, nonce: "0x00", account: "0x00" })
* ```
*/
export class VegaClaim extends BaseContract {
export class Claim {
public contract: ethers.Contract;
public tokenContract: ethers.Contract;
public dp: Promise<number>;
constructor(
network: Networks,
provider: ethers.providers.Web3Provider,
signer?: ethers.Signer
address: string,
signerOrProvider: ethers.Signer | ethers.providers.Provider
) {
super(provider, signer);
this.contract = new ethers.Contract(
EnvironmentConfig[network].claimAddress,
claimAbi,
this.signer || this.provider
);
const tokenContract = new ethers.Contract(
EnvironmentConfig[network].vegaTokenAddress,
tokenAbi,
this.signer || this.provider
);
this.tokenContract = tokenContract;
this.dp = (async () => {
const val = await tokenContract.decimals();
return Number(val);
})();
this.contract = new ethers.Contract(address, abi, signerOrProvider);
}
/** Execute contracts commit_untargeted function */
async commit(
s: string,
confirmations = 1
): Promise<ethers.ContractTransaction> {
const tx = await this.contract.commit_untargeted(s);
this.trackTransaction(tx, confirmations);
return tx;
public commit(s: string): Promise<ethers.ContractTransaction> {
return this.contract.commit_untargeted(s);
}
/**
@ -73,36 +40,32 @@ export class VegaClaim extends BaseContract {
* was performed and mined beforehand
* @return {Promise<boolean>}
*/
public async claim(
{
amount,
tranche,
expiry,
target,
country,
v,
r,
s,
}: {
amount: BigNumber;
tranche: number;
expiry: number;
target?: string;
country: string;
v: number;
r: string;
s: string;
},
confirmations = 1
): Promise<ethers.ContractTransaction> {
const convertedAmount = removeDecimal(amount, await this.dp).toString();
const tx = await this.contract[
public claim({
amount,
tranche,
expiry,
target,
country,
v,
r,
s,
}: {
amount: string;
tranche: number;
expiry: number;
target?: string;
country: string;
v: number;
r: string;
s: string;
}): Promise<ethers.ContractTransaction> {
return this.contract[
target != null ? 'claim_targeted' : 'claim_untargeted'
](
...[
{ r, s, v },
{
amount: convertedAmount,
amount,
tranche,
expiry,
},
@ -110,18 +73,14 @@ export class VegaClaim extends BaseContract {
target,
].filter(Boolean)
);
this.trackTransaction(tx, confirmations);
return tx;
}
/**
* Check if this code was already committed to by this account
* @return {Promise<boolean>}
*/
async isCommitted({ s }: { s: string }): Promise<string> {
return await this.contract.commitments(s);
isCommitted({ s }: { s: string }): Promise<string> {
return this.contract.commitments(s);
}
/**
@ -130,7 +89,7 @@ export class VegaClaim extends BaseContract {
* @returns Promise<boolean>
*/
async isExpired(expiry: number): Promise<boolean> {
return expiry < (await this.provider.getBlock('latest')).timestamp;
return expiry < (await this.contract.provider.getBlock('latest')).timestamp;
}
/**

View File

@ -0,0 +1,51 @@
import type { BigNumber } from 'ethers';
import { ethers } from 'ethers';
import abi from '../abis/erc20_bridge_abi.json';
export class CollateralBridge {
public contract: ethers.Contract;
constructor(
address: string,
signerOrProvider: ethers.Signer | ethers.providers.Provider
) {
this.contract = new ethers.Contract(address, abi, signerOrProvider);
}
depositAsset(assetSource: string, amount: string, vegaPublicKey: string) {
return this.contract.deposit_asset(assetSource, amount, vegaPublicKey);
}
getAssetSource(vegaAssetId: string) {
return this.contract.get_asset_source(vegaAssetId);
}
getDepositMaximum(assetSource: string): Promise<BigNumber> {
return this.contract.get_deposit_maximum(assetSource);
}
getDepositMinimum(assetSource: string): Promise<BigNumber> {
return this.contract.get_deposit_minimum(assetSource);
}
getMultisigControlAddres() {
return this.contract.get_multisig_control_address();
}
getVegaAssetId(address: string) {
return this.contract.get_vega_asset_id(address);
}
isAssetListed(address: string) {
return this.contract.is_asset_listed(address);
}
withdrawAsset(
assetSource: string,
amount: string,
target: string,
nonce: string,
signatures: string
) {
return this.contract.withdraw_asset(
assetSource,
amount,
target,
nonce,
signatures
);
}
}

View File

@ -1,111 +0,0 @@
import type { BigNumber as EthersBigNumber } from 'ethers';
import { ethers } from 'ethers';
import erc20Abi from '../abis/erc20_abi.json';
import erc20AbiFaucet from '../abis/erc20_abi_faucet.json';
import BigNumber from 'bignumber.js';
import { addDecimal, removeDecimal } from '../utils';
import { BaseContract } from './base-contract';
export class ERC20Token extends BaseContract {
public contract: ethers.Contract;
public dp: Promise<number>;
private faucetable: boolean;
constructor(
address: string,
provider: ethers.providers.Web3Provider,
signer?: ethers.Signer,
faucetable = false
) {
super(provider, signer);
this.faucetable = faucetable;
const contract = new ethers.Contract(
address,
faucetable ? erc20AbiFaucet : erc20Abi,
signer || provider
);
this.contract = contract;
this.dp = (async () => {
const val = await contract.decimals();
return Number(val);
})();
}
/** Gets Vega token total supply */
async totalSupply(): Promise<BigNumber> {
const res: EthersBigNumber = await this.contract.totalSupply();
const value = addDecimal(new BigNumber(res.toString()), await this.dp);
return value;
}
/** Gets number tokens an Ethereum account owns */
async balanceOf(address: string): Promise<BigNumber> {
const res: EthersBigNumber = await this.contract.balanceOf(address);
const value = addDecimal(new BigNumber(res.toString()), await this.dp);
return value;
}
async transfer(
from: string,
to: string,
amount: BigNumber,
confirmations = 1
) {
const value = removeDecimal(amount, await this.dp).toString();
const tx = await this.contract.transfer(from, to, value);
this.trackTransaction(tx, confirmations);
return tx;
}
async transferFrom(
sender: string,
recipient: string,
amount: BigNumber,
confirmations = 1
) {
const value = removeDecimal(amount, await this.dp).toString();
const tx = await this.contract.transferFrom(sender, recipient, value);
this.trackTransaction(tx, confirmations);
return tx;
}
/** Gets Ethereum account's permitted allowance */
async allowance(address: string, spender: string): Promise<BigNumber> {
const res: EthersBigNumber = await this.contract.allowance(
address,
spender
);
const value = addDecimal(new BigNumber(res.toString()), await this.dp);
return value;
}
/** Executs contracts approve function */
async approve(
spender: string,
confirmations = 1
): Promise<ethers.ContractTransaction> {
const amount = removeDecimal(
new BigNumber(Number.MAX_SAFE_INTEGER),
await this.dp
).toString();
const tx = await this.contract.approve(spender, amount);
this.trackTransaction(tx, confirmations);
return tx;
}
async faucet(confirmations = 1): Promise<ethers.ContractTransaction> {
if (!this.faucetable) {
throw new Error('Faucet function can not be called on this contract');
}
const tx = await this.contract.faucet();
this.trackTransaction(tx, confirmations);
return tx;
}
/** Gets number of decimals used by token */
async decimals(): Promise<number> {
const res: number = await this.contract.decimals();
return Number(res);
}
}

View File

@ -1,8 +1,8 @@
export * from './stake-helpers';
export * from './tranche-helpers';
export * from './vega-claim';
export * from './vega-erc20-bridge';
export * from './vega-staking';
export * from './vega-vesting';
export * from './vega-web3-types';
export * from './erc20-token';
export * from './claim';
export * from './collateral-bridge';
export * from './staking-bridge';
export * from './token-vesting';
export * from './token';
export * from './token-faucetable';

View File

@ -1,40 +0,0 @@
import type { ethers } from 'ethers';
import BigNumber from 'bignumber.js';
import { addDecimal } from '../utils/decimals';
export function combineStakeEventsByVegaKey(
events: ethers.Event[],
decimals: number
): { [vegaKey: string]: BigNumber } {
const res = events.reduce((obj, e) => {
const vegaKey = e.args?.vega_public_key;
const amount = parseEventAmount(e, decimals);
const isDeposit = e.event === 'Stake_Deposited';
const isRemove = e.event === 'Stake_Removed';
if (!isDeposit && !isRemove) return obj;
if (Object.prototype.hasOwnProperty.call(obj, vegaKey)) {
if (isDeposit) {
obj[vegaKey] = obj[vegaKey].plus(amount);
} else {
obj[vegaKey] = obj[vegaKey].minus(amount);
}
} else {
if (isDeposit) {
obj[vegaKey] = amount;
} else {
obj[vegaKey] = new BigNumber(0);
}
}
return obj;
}, {} as { [vegaKey: string]: BigNumber });
return res;
}
function parseEventAmount(e: ethers.Event, decimals: number) {
const rawAmount = new BigNumber(e.args?.amount.toString() || 0);
return addDecimal(rawAmount, decimals);
}

View File

@ -0,0 +1,32 @@
import { ethers } from 'ethers';
import abi from '../abis/staking_abi.json';
export class StakingBridge {
public contract: ethers.Contract;
constructor(
address: string,
signerOrProvider: ethers.Signer | ethers.providers.Provider
) {
this.contract = new ethers.Contract(address, abi, signerOrProvider);
}
stake(amount: string, vegaPublicKey: string) {
return this.contract.stake(amount, `0x${vegaPublicKey}`);
}
removeStake(amount: string, vegaPublicKey: string) {
return this.contract.remove_stake(amount, `0x${vegaPublicKey}`);
}
transferStake(amount: string, newAddress: string, vegaPublicKey: string) {
return this.contract.transfer_stake(amount, newAddress, vegaPublicKey);
}
stakingToken() {
return this.contract.staking_token();
}
stakeBalance(target: string, vegaPublicKey: string) {
return this.contract.stake_balance(target, vegaPublicKey);
}
totalStaked() {
return this.contract.total_staked();
}
}

View File

@ -0,0 +1,37 @@
import type { BigNumber } from 'ethers';
import { ethers } from 'ethers';
import erc20AbiFaucetable from '../abis/erc20_abi_faucet.json';
export class TokenFaucetable {
public contract: ethers.Contract;
constructor(
address: string,
signerOrProvider: ethers.Signer | ethers.providers.Provider
) {
this.contract = new ethers.Contract(
address,
erc20AbiFaucetable,
signerOrProvider
);
}
totalSupply() {
return this.contract.totalSupply();
}
balanceOf(account: string): Promise<BigNumber> {
return this.contract.balanceOf(account);
}
allowance(owner: string, spender: string): Promise<BigNumber> {
return this.contract.allowance(owner, spender);
}
approve(spender: string, amount: string) {
return this.contract.approve(spender, amount);
}
decimals(): Promise<number> {
return this.contract.decimals();
}
faucet() {
return this.contract.faucet();
}
}

View File

@ -0,0 +1,41 @@
import { ethers } from 'ethers';
import abi from '../abis/vesting_abi.json';
export class TokenVesting {
public contract: ethers.Contract;
constructor(
address: string,
signerOrProvider: ethers.Signer | ethers.providers.Provider
) {
this.contract = new ethers.Contract(address, abi, signerOrProvider);
}
stakeTokens(amount: string, vegaPublicKey: string) {
return this.contract.stake_tokens(amount, vegaPublicKey);
}
removeStake(amount: string, vegaPublicKey: string) {
return this.contract.remove_stake(amount, vegaPublicKey);
}
stakeBalance(address: string, vegaPublicKey: string) {
return this.contract.stake_balance(address, vegaPublicKey);
}
totalStaked() {
return this.contract.total_staked();
}
userStats(address: string) {
return this.contract.user_stats(address);
}
getTrancheBalance(address: string, trancheId: number) {
return this.contract.get_tranche_balance(address, trancheId);
}
getVestedForTranche(address: string, trancheId: number) {
return this.contract.get_vested_for_tranche(address, trancheId);
}
userTotalAllTranches(address: string) {
return this.contract.user_total_all_tranches(address);
}
withdrawFromTranche(trancheId: number) {
return this.contract.withdraw_from_tranche(trancheId);
}
}

View File

@ -0,0 +1,30 @@
import type { BigNumber } from 'ethers';
import { ethers } from 'ethers';
import erc20Abi from '../abis/erc20_abi.json';
export class Token {
public contract: ethers.Contract;
constructor(
address: string,
signerOrProvider: ethers.Signer | ethers.providers.Provider
) {
this.contract = new ethers.Contract(address, erc20Abi, signerOrProvider);
}
totalSupply() {
return this.contract.totalSupply();
}
balanceOf(account: string): Promise<BigNumber> {
return this.contract.balanceOf(account);
}
allowance(owner: string, spender: string): Promise<BigNumber> {
return this.contract.allowance(owner, spender);
}
approve(spender: string, amount: string) {
return this.contract.approve(spender, amount);
}
decimals(): Promise<number> {
return this.contract.decimals();
}
}

View File

@ -1,165 +0,0 @@
import BigNumber from 'bignumber.js';
import type { ethers } from 'ethers';
import uniq from 'lodash/uniq';
import { addDecimal } from '../utils/decimals';
import type { Tranche, TrancheUser } from './vega-web3-types';
import { TrancheEvents } from './vega-web3-types';
export function createUserTransactions(
events: ethers.Event[],
decimals: number
) {
return events.map((event) => {
return {
amount: addDecimal(
new BigNumber(event.args?.amount.toString()),
decimals
),
user: event.args?.user,
tranche_id: event.args?.tranche_id,
tx: event.transactionHash,
};
});
}
export function getUsersInTranche(
balanceAddedEvents: ethers.Event[],
balanceRemovedEvents: ethers.Event[],
addresses: string[],
decimals: number
): TrancheUser[] {
return addresses.map((address) => {
const userDeposits = balanceAddedEvents.filter(
(event) => event.args?.user === address
);
const userWithdraws = balanceRemovedEvents.filter(
(event) => event.args?.user === address
);
const deposits = createUserTransactions(userDeposits, decimals);
const withdrawals = createUserTransactions(userWithdraws, decimals);
const total_tokens = deposits.reduce(
(pre, cur) => pre.plus(cur.amount),
new BigNumber(0)
);
const withdrawn_tokens = withdrawals.reduce(
(pre, cur) => pre.plus(cur.amount),
new BigNumber(0)
);
const remaining_tokens = total_tokens.minus(withdrawn_tokens);
return {
address,
deposits,
withdrawals,
total_tokens,
withdrawn_tokens,
remaining_tokens,
};
});
}
export function sumFromEvents(events: ethers.Event[], decimals: number) {
const amounts = events.map((e) =>
addDecimal(new BigNumber(e.args?.amount.toString()), decimals)
);
// Start with a 0 so if there are none there is no NaN
return BigNumber.sum.apply(null, [new BigNumber(0), ...amounts]);
}
export function getLockedAmount(
totalAdded: BigNumber,
cliffStart: number,
trancheDuration: number
) {
let amount = new BigNumber(0);
const ts = Math.round(new Date().getTime() / 1000);
const tranche_progress = (ts - cliffStart) / trancheDuration;
if (tranche_progress < 0) {
amount = totalAdded;
} else if (tranche_progress < 1) {
amount = totalAdded.times(1 - tranche_progress);
}
return amount;
}
export function createTransactions(events: ethers.Event[], decimals: number) {
return events.map((event) => {
return {
amount: addDecimal(
new BigNumber(event.args?.amount.toString()),
decimals
),
user: event.args?.user,
tx: event.transactionHash,
};
});
}
export function getTranchesFromHistory(
createEvents: ethers.Event[],
addEvents: ethers.Event[],
removeEvents: ethers.Event[],
decimals: number
): Tranche[] {
return createEvents.map((event) => {
const tranche_id = event.args?.tranche_id;
const balanceAddedEvents = addEvents.filter(
(e) =>
e.event === TrancheEvents.BalanceAdded &&
e.args?.tranche_id === tranche_id
);
const balanceRemovedEvents = removeEvents.filter(
(e) =>
e.event === TrancheEvents.BalanceRemoved &&
e.args?.tranche_id === tranche_id
);
//get tranche start and end dates
const tranche_duration = event.args?.duration;
const cliff_start = event.args?.cliff_start;
const tranche_start = new Date(cliff_start.mul(1000).toNumber());
const tranche_end = new Date(
cliff_start.add(tranche_duration).mul(1000).toNumber()
);
// get added and removed values
const total_added = sumFromEvents(balanceAddedEvents, decimals);
const total_removed = sumFromEvents(balanceRemovedEvents, decimals);
// get locked amount
const locked_amount = getLockedAmount(
total_added,
cliff_start,
tranche_duration
);
// get all deposits and withdrawals
const deposits = createTransactions(balanceAddedEvents, decimals);
const withdrawals = createTransactions(balanceRemovedEvents, decimals);
// get all users
const uniqueAddresses = uniq(
balanceAddedEvents.map((event) => event.args?.user)
);
const users = getUsersInTranche(
balanceAddedEvents,
balanceRemovedEvents,
uniqueAddresses,
decimals
);
return {
tranche_id: parseInt(tranche_id),
tranche_start,
tranche_end,
total_added,
total_removed,
locked_amount,
deposits,
withdrawals,
users,
};
});
}

View File

@ -1,109 +0,0 @@
import type { BigNumber as EthersBigNumber } from 'ethers';
import { ethers } from 'ethers';
import { EnvironmentConfig } from '../config/ethereum';
import type { Networks } from '../config/vega';
import erc20BridgeAbi from '../abis/erc20_bridge_abi.json';
import { BaseContract } from './base-contract';
import BigNumber from 'bignumber.js';
import { addDecimal } from '../utils';
export class VegaErc20Bridge extends BaseContract {
private contract: ethers.Contract;
constructor(
network: Networks,
provider: ethers.providers.Web3Provider,
signer?: ethers.Signer
) {
super(provider, signer);
this.contract = new ethers.Contract(
EnvironmentConfig[network].erc20Bridge,
erc20BridgeAbi,
this.signer || this.provider
);
}
/** Executes contracts withdraw_asset function */
async withdraw(
approval: {
assetSource: string;
amount: string;
nonce: string;
signatures: string;
targetAddress: string;
},
confirmations = 1
): Promise<ethers.ContractTransaction> {
const tx = await this.contract.withdraw_asset(
approval.assetSource,
approval.amount, // No need to remove decimals as this value is already set and not manipulated by the user
approval.targetAddress,
approval.nonce,
approval.signatures
);
this.trackTransaction(tx, confirmations);
return tx;
}
async depositAsset(
assetSource: string,
amount: string,
vegaPublicKey: string,
confirmations = 1
) {
const tx = await this.contract.deposit_asset(
assetSource,
amount,
vegaPublicKey
);
this.trackTransaction(tx, confirmations);
return tx;
}
async getAssetSource(vegaAssetId: string): Promise<string> {
const res = await this.contract.get_asset_source(vegaAssetId);
return res;
}
async getDepositMaximum(
assetSource: string,
decimals: number
): Promise<BigNumber> {
const res: EthersBigNumber = await this.contract.get_deposit_maximum(
assetSource
);
const value = addDecimal(new BigNumber(res.toString()), decimals);
return value;
}
async getDepositMinimum(
assetSource: string,
decimals: number
): Promise<BigNumber> {
const res: EthersBigNumber = await this.contract.get_deposit_minimum(
assetSource
);
const value = addDecimal(new BigNumber(res.toString()), decimals);
return value;
}
async getMultisigControlAddress(): Promise<string> {
const res = await this.contract.get_multisig_control_address();
return res;
}
async getVegaAssetId(): Promise<string> {
const res = await this.contract.get_vega_asset_id();
return res;
}
async isAssetListed(assetSource: string): Promise<boolean> {
const res = await this.contract.is_asset_listed(assetSource);
return res;
}
}

View File

@ -1,131 +0,0 @@
import type { BigNumber as EthersBigNumber } from 'ethers';
import { ethers } from 'ethers';
import stakingAbi from '../abis/staking_abi.json';
import tokenAbi from '../abis/vega_token_abi.json';
import { combineStakeEventsByVegaKey } from './stake-helpers';
import BigNumber from 'bignumber.js';
import { BaseContract } from './base-contract';
import { EnvironmentConfig } from '../config/ethereum';
import type { Networks } from '../config/vega';
import { addDecimal, hexadecimalify, removeDecimal } from '../utils';
export class VegaStaking extends BaseContract {
public contract: ethers.Contract;
public tokenContract: ethers.Contract;
public dp: Promise<number>;
constructor(
network: Networks,
provider: ethers.providers.Web3Provider,
signer?: ethers.Signer
) {
super(provider, signer);
const tokenContract = new ethers.Contract(
EnvironmentConfig[network].vegaTokenAddress,
tokenAbi,
this.signer || this.provider
);
this.tokenContract = tokenContract;
this.contract = new ethers.Contract(
EnvironmentConfig[network].stakingBridge,
stakingAbi,
this.signer || this.provider
);
this.dp = (async () => {
const val = await tokenContract.decimals();
return Number(val);
})();
}
/** Executes staking contracts stake function */
async addStake(
amount: BigNumber,
vegaKey: string,
confirmations = 1
): Promise<ethers.ContractTransaction> {
const convertedAmount = removeDecimal(amount, await this.dp).toString();
const tx = await this.contract.stake(
convertedAmount,
hexadecimalify(vegaKey)
);
// store and track the transaction in BaseContract
this.trackTransaction(tx, confirmations);
return tx;
}
/** Executes staking contracts remove_stake function */
async removeStake(
amount: BigNumber,
vegaKey: string,
confirmations = 1
): Promise<ethers.ContractTransaction> {
const convertedAmount = removeDecimal(amount, await this.dp).toString();
const tx = await this.contract.remove_stake(
convertedAmount,
hexadecimalify(vegaKey)
);
this.trackTransaction(tx, confirmations);
return tx;
}
/** Executes staking contracts transfer_stake function */
async transferStake(
amount: BigNumber,
newAddress: string,
vegaKey: string,
confirmations = 1
): Promise<ethers.ContractTransaction> {
const convertedAmount = removeDecimal(amount, await this.dp).toString();
const tx = await this.contract.transfer_stake(
convertedAmount,
newAddress,
hexadecimalify(vegaKey)
);
this.trackTransaction(tx, confirmations);
return tx;
}
/** Returns the amount staked for given Vega public key */
async stakeBalance(address: string, vegaKey: string): Promise<BigNumber> {
const res: EthersBigNumber = await this.contract.stake_balance(
address,
hexadecimalify(vegaKey)
);
const value = addDecimal(new BigNumber(res.toString()), await this.dp);
return value;
}
/** Returns the total amount currently staked */
async totalStaked(): Promise<BigNumber> {
const res: EthersBigNumber = await this.contract.total_staked();
const value = addDecimal(new BigNumber(res.toString()), await this.dp);
return value;
}
/** Returns amounts staked across all Vega keys for single Ethereum account */
async userTotalStakedByVegaKey(
ethereumAccount: string
): Promise<{ [vegaKey: string]: BigNumber }> {
const addFilter = this.contract.filters.Stake_Deposited(ethereumAccount);
const removeFilter = this.contract.filters.Stake_Removed(ethereumAccount);
const addEvents = await this.contract.queryFilter(addFilter);
const removeEvents = await this.contract.queryFilter(removeFilter);
const res = combineStakeEventsByVegaKey(
[...addEvents, ...removeEvents],
await this.dp
);
return res;
}
}

View File

@ -1,182 +0,0 @@
import BigNumber from 'bignumber.js';
import type { BigNumber as EthersBigNumber } from 'ethers';
import { ethers } from 'ethers';
import { EnvironmentConfig } from '../config/ethereum';
import type { Networks } from '../config/vega';
import vestingAbi from '../abis/vesting_abi.json';
import tokenAbi from '../abis/vega_token_abi.json';
import { BaseContract } from './base-contract';
import { combineStakeEventsByVegaKey } from './stake-helpers';
import { getTranchesFromHistory } from './tranche-helpers';
import type { Tranche } from './vega-web3-types';
import { addDecimal, hexadecimalify, removeDecimal } from '../utils';
export class VegaVesting extends BaseContract {
public contract: ethers.Contract;
public tokenContract: ethers.Contract;
public dp: Promise<number>;
constructor(
network: Networks,
provider: ethers.providers.Web3Provider,
signer?: ethers.Signer
) {
super(provider, signer);
const tokenContract = new ethers.Contract(
EnvironmentConfig[network].vegaTokenAddress,
tokenAbi,
this.signer || this.provider
);
this.tokenContract = tokenContract;
this.contract = new ethers.Contract(
EnvironmentConfig[network].vestingAddress,
vestingAbi,
this.signer || this.provider
);
this.dp = (async () => {
const val = await tokenContract.decimals();
return Number(val);
})();
}
/** Executes vesting contracts stake_tokens function */
async addStake(
amount: BigNumber,
vegaKey: string,
confirmations = 1
): Promise<ethers.ContractTransaction> {
const convertedAmount = removeDecimal(amount, await this.dp).toString();
const tx = await this.contract.stake_tokens(
convertedAmount,
hexadecimalify(vegaKey)
);
this.trackTransaction(tx, confirmations);
return tx;
}
/** Executes vesting contracts remove_stake function */
async removeStake(
amount: BigNumber,
vegaKey: string,
confirmations = 1
): Promise<ethers.ContractTransaction> {
const convertedAmount = removeDecimal(amount, await this.dp).toString();
const tx = await this.contract.remove_stake(
convertedAmount,
hexadecimalify(vegaKey)
);
this.trackTransaction(tx, confirmations);
return tx;
}
/** Returns the amount staked for a given Vega public key */
async stakeBalance(address: string, vegaKey: string): Promise<BigNumber> {
const res: EthersBigNumber = await this.contract.stake_balance(
address,
hexadecimalify(vegaKey)
);
const value = addDecimal(new BigNumber(res.toString()), await this.dp);
return value;
}
/** Returns the total amount currently staked */
async totalStaked(): Promise<BigNumber> {
const res: EthersBigNumber = await this.contract.total_staked();
const value = addDecimal(new BigNumber(res.toString()), await this.dp);
return value;
}
/** Returns the amount of locked tokens in the vesting contract */
async getLien(address: string): Promise<BigNumber> {
const {
lien,
}: {
lien: EthersBigNumber;
total_in_all_tranches: EthersBigNumber;
} = await this.contract.user_stats(address);
const value = addDecimal(new BigNumber(lien.toString()), await this.dp);
return value;
}
/** Returns the amount a user has in a specific tranche */
async userTrancheTotalBalance(
address: string,
tranche: number
): Promise<BigNumber> {
const amount: EthersBigNumber = await this.contract.get_tranche_balance(
address,
tranche
);
const value = addDecimal(new BigNumber(amount.toString()), await this.dp);
return value;
}
/** Returns vested amount for a given tranche */
async userTrancheVestedBalance(
address: string,
tranche: number
): Promise<BigNumber> {
const amount: EthersBigNumber = await this.contract.get_vested_for_tranche(
address,
tranche
);
const value = addDecimal(new BigNumber(amount.toString()), await this.dp);
return value;
}
/** Returns the users total tokens across all tranches */
async getUserBalanceAllTranches(account: string): Promise<BigNumber> {
const amount: EthersBigNumber = await this.contract.user_total_all_tranches(
account
);
const value = addDecimal(new BigNumber(amount.toString()), await this.dp);
return value;
}
/** Gets all tranche data */
async getAllTranches(): Promise<Tranche[]> {
const [created, added, removed] = await Promise.all([
this.contract.queryFilter(this.contract.filters.Tranche_Created()),
this.contract.queryFilter(this.contract.filters.Tranche_Balance_Added()),
this.contract.queryFilter(
this.contract.filters.Tranche_Balance_Removed()
),
]);
const dp = await this.dp;
return getTranchesFromHistory(created, added, removed, dp);
}
/** Executes contracts withdraw_from_tranche function */
async withdrawFromTranche(
trancheId: number,
confirmations = 1
): Promise<ethers.ContractTransaction> {
const tx = await this.contract.withdraw_from_tranche(trancheId);
this.trackTransaction(tx, confirmations);
return tx;
}
/** Returns amounts staked across all Vega keys for single Ethereum account */
async userTotalStakedByVegaKey(address: string) {
const addFilter = this.contract.filters.Stake_Deposited(address);
const removeFilter = this.contract.filters.Stake_Removed(address);
const addEvents = await this.contract.queryFilter(addFilter);
const removeEvents = await this.contract.queryFilter(removeFilter);
const res = combineStakeEventsByVegaKey(
[...addEvents, ...removeEvents],
await this.dp
);
return res;
}
}

View File

@ -1,4 +1,3 @@
import type { ethers } from 'ethers';
import type BigNumber from 'bignumber.js';
export interface Tranche {
@ -74,10 +73,3 @@ export interface EpochDetails {
startSeconds: BigNumber;
endSeconds: BigNumber;
}
export interface TxData {
tx: ethers.ContractTransaction;
receipt: ethers.ContractReceipt | null;
pending: boolean;
requiredConfirmations: number;
}

View File

@ -1,3 +1,2 @@
export * from './config';
export * from './contracts';
export * from './utils';

View File

@ -1,22 +0,0 @@
import BigNumber from 'bignumber.js';
import { addDecimal, removeDecimal } from './decimals';
test('Do not pad numbers with 0s when the number length is less than the specified DPs', () => {
expect(addDecimal(new BigNumber(10000), 10).toString()).toEqual('0.000001');
});
test('Handles large numbers correctly', () => {
const claimCode = new BigNumber('20000000000000000000000000');
const decimals = 18;
const decimalised = addDecimal(claimCode, decimals);
expect(decimalised.toString()).toEqual('20000000');
const undecimalised = removeDecimal(claimCode, decimals);
expect(undecimalised.toString()).toEqual(
'20000000000000000000000000000000000000000000'
);
const mangled = removeDecimal(addDecimal(claimCode, decimals), decimals);
expect(mangled.toString()).toEqual('20000000000000000000000000');
});

View File

@ -1,10 +0,0 @@
import BigNumber from 'bignumber.js';
BigNumber.config({ EXPONENTIAL_AT: 20000 });
export function addDecimal(value: BigNumber, decimals: number): BigNumber {
return value.dividedBy(Math.pow(10, decimals)).decimalPlaces(decimals);
}
export function removeDecimal(value: BigNumber, decimals: number): BigNumber {
return value.times(Math.pow(10, decimals));
}

View File

@ -1,3 +1,2 @@
export * from './decimals';
export * from './ascii-to-hex';
export * from './hexadecimalify';

View File

@ -1,10 +1,12 @@
import classNames from 'classnames';
import { useEffect, useState } from 'react';
interface LoaderProps {
size?: 'small' | 'large';
forceTheme?: 'dark' | 'light';
}
export const Loader = ({ size = 'large' }: LoaderProps) => {
export const Loader = ({ size = 'large', forceTheme }: LoaderProps) => {
const [, forceRender] = useState(false);
useEffect(() => {
@ -15,8 +17,14 @@ export const Loader = ({ size = 'large' }: LoaderProps) => {
return () => clearInterval(interval);
}, []);
const itemClasses = classNames({
'dark:bg-white bg-black': !forceTheme,
'bg-white': forceTheme === 'dark',
'bg-black': forceTheme === 'light',
'w-16 h-16': size === 'large',
'w-[5px] h-[5px]': size === 'small',
});
const wrapperClasses = size === 'small' ? 'w-[15px] h-[15px]' : 'w-64 h-64';
const gridItemClasses = size === 'small' ? 'w-[5px] h-[5px]' : 'w-16 h-16';
const items = size === 'small' ? 9 : 16;
return (
@ -25,7 +33,7 @@ export const Loader = ({ size = 'large' }: LoaderProps) => {
{new Array(items).fill(null).map((_, i) => {
return (
<div
className={`${gridItemClasses} dark:bg-white bg-black`}
className={itemClasses}
key={i}
style={{
opacity: Math.random() > 0.75 ? 1 : 0,

View File

@ -1,8 +1,11 @@
export * from './lib/web3-provider';
export * from './lib/web3-connect-dialog';
export * from './lib/ethereum-error';
export * from './lib/__generated__/NetworkParamsQuery';
export * from './lib/use-bridge-contract';
export * from './lib/use-token-contract';
export * from './lib/use-token-decimals';
export * from './lib/use-ethereum-config';
export * from './lib/use-ethereum-read-contract';
export * from './lib/use-ethereum-transaction';
export * from './lib/transaction-dialog';
export * from './lib/web3-provider';
export * from './lib/web3-connect-dialog';

View File

@ -1,18 +1,24 @@
import { VegaErc20Bridge } from '@vegaprotocol/smart-contracts';
import { CollateralBridge } from '@vegaprotocol/smart-contracts';
import { useWeb3React } from '@web3-react/core';
import { useMemo } from 'react';
import { useEnvironment } from '@vegaprotocol/react-helpers';
import { useEthereumConfig } from './use-ethereum-config';
export const useBridgeContract = () => {
const { VEGA_ENV } = useEnvironment();
const { provider } = useWeb3React();
const { config } = useEthereumConfig();
const contract = useMemo(() => {
if (!provider) {
if (!provider || !config) {
return null;
}
return new VegaErc20Bridge(VEGA_ENV, provider, provider?.getSigner());
}, [provider, VEGA_ENV]);
const signer = provider.getSigner();
return new CollateralBridge(
config.collateral_bridge_contract.address,
signer || provider
);
}, [provider, config]);
return contract;
};

View File

@ -0,0 +1,64 @@
import { gql, useQuery } from '@apollo/client';
import { useMemo } from 'react';
import type { NetworkParamsQuery } from './__generated__/NetworkParamsQuery';
export interface EthereumConfig {
network_id: string;
chain_id: string;
confirmations: number;
collateral_bridge_contract: {
address: string;
};
multisig_control_contract: {
address: string;
deployment_block_height: number;
};
staking_bridge_contract: {
address: string;
deployment_block_height: number;
};
token_vesting_contract: {
address: string;
deployment_block_height: number;
};
}
export const NETWORK_PARAMS_QUERY = gql`
query NetworkParamsQuery {
networkParameters {
key
value
}
}
`;
export const useEthereumConfig = () => {
const { data, loading, error } =
useQuery<NetworkParamsQuery>(NETWORK_PARAMS_QUERY);
const config = useMemo(() => {
if (!data) {
return null;
}
const param = data.networkParameters?.find(
(np) => np.key === 'blockchains.ethereumConfig'
);
if (!param) {
return null;
}
let parsedConfig: EthereumConfig;
try {
parsedConfig = JSON.parse(param.value);
} catch {
return null;
}
return parsedConfig;
}, [data]);
return { config, loading, error };
};

View File

@ -1,22 +1,25 @@
import { ERC20Token } from '@vegaprotocol/smart-contracts';
import { Token, TokenFaucetable } from '@vegaprotocol/smart-contracts';
import { useWeb3React } from '@web3-react/core';
import { useMemo } from 'react';
export const useTokenContract = (
contractAddress?: string,
faucetable = false
): ERC20Token | null => {
) => {
const { provider } = useWeb3React();
const contract = useMemo(() => {
if (!provider || !contractAddress) {
return null;
}
return new ERC20Token(
contractAddress,
provider,
provider.getSigner(),
faucetable
);
const signer = provider.getSigner();
if (faucetable) {
return new TokenFaucetable(contractAddress, signer || provider);
} else {
return new Token(contractAddress, signer || provider);
}
}, [provider, contractAddress, faucetable]);
return contract;

View File

@ -0,0 +1,20 @@
import type { Token } from '@vegaprotocol/smart-contracts';
import { useCallback } from 'react';
import { useEthereumReadContract } from './use-ethereum-read-contract';
export const useTokenDecimals = (contract: Token | null) => {
const getDecimals = useCallback(async () => {
if (!contract) {
return;
}
const value = await contract.decimals();
const decimals = Number(value);
return decimals;
}, [contract]);
const { state } = useEthereumReadContract(getDecimals);
return state.data;
};

View File

@ -33,7 +33,13 @@ export const useCompleteWithdraw = () => {
if (!contract) {
return null;
}
return contract.withdraw(args);
return contract.withdrawAsset(
args.assetSource,
args.amount,
args.targetAddress,
args.nonce,
args.signatures
);
});
const submit = useCallback(

View File

@ -38,7 +38,13 @@ export const useWithdraw = (cancelled: boolean) => {
if (!contract) {
return null;
}
return contract.withdraw(args);
return contract.withdrawAsset(
args.assetSource,
args.amount,
args.targetAddress,
args.nonce,
args.signatures
);
});
const { data, stopPolling } = useQuery<Erc20Approval, Erc20ApprovalVariables>(