Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consume minSend amounts on frontend #319

Merged
merged 16 commits into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions frontend/src/components/AccountReceiveTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ import AccountReceiveTableWarning from 'components/AccountReceiveTableWarning.vu
import AccountReceiveTableWithdrawConfirmation from 'components/AccountReceiveTableWithdrawConfirmation.vue';
import BaseTooltip from 'src/components/BaseTooltip.vue';
import WithdrawForm from 'components/WithdrawForm.vue';
import { ConfirmedITXStatusResponse, FeeEstimateResponse } from 'components/models';
import { ConfirmedRelayerStatusResponse, FeeEstimateResponse } from 'components/models';
import { formatAddress, lookupOrFormatAddresses, toAddress, isAddressSafe } from 'src/utils/address';
import { MAINNET_PROVIDER } from 'src/utils/constants';
import { getEtherscanUrl } from 'src/utils/utils';
Expand Down Expand Up @@ -386,7 +386,7 @@ function useReceivedFundsTable(announcements: UserAnnouncement[], spendingKeyPai
const isLoading = ref(false);
const isFeeLoading = ref(false);
const isWithdrawInProgress = ref(false);
const txHashIfEth = ref(''); // if withdrawing native token, show the transaction hash (if token, we have an ITX ID)
const txHashIfEth = ref(''); // if withdrawing native token, show the transaction hash (if token, we have a relayer tx ID)

// Define table columns
const sortByTime = (a: Block, b: Block) => b.timestamp - a.timestamp;
Expand Down Expand Up @@ -554,14 +554,14 @@ function useReceivedFundsTable(announcements: UserAnnouncement[], spendingKeyPai
};

if (chainId === 137) {
// No ITX support on this network, so this is a regular transaction hash
// No relayer support on this network, so this is a regular transaction hash
mds1 marked this conversation as resolved.
Show resolved Hide resolved
console.log(`Relayed with transaction hash ${relayTransactionHash}`);
const receipt = await provider.value.waitForTransaction(relayTransactionHash);
console.log('Withdraw successful. Receipt:', receipt);
} else {
// Received an ITX relay transaction hash, wait for withdraw transaction to be mined
console.log(`Relayed with ITX ID ${relayTransactionHash}`);
const { receipt } = (await relayer.value?.waitForId(relayTransactionHash)) as ConfirmedITXStatusResponse;
// Received a relayer transaction hash, wait for withdraw transaction to be mined
console.log(`Relayed with relayer ID ${relayTransactionHash}`);
const { receipt } = (await relayer.value?.waitForId(relayTransactionHash)) as ConfirmedRelayerStatusResponse;
console.log('Withdraw successful. Receipt:', receipt);
}
}
Expand Down
36 changes: 24 additions & 12 deletions frontend/src/components/models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ethers } from 'ethers';
import { BigNumber } from 'src/utils/ethers';
import { TransactionReceipt, JsonRpcSigner, Web3Provider } from 'src/utils/ethers';
import type { TokenList, TokenInfo } from '@uniswap/token-lists/dist/types';
import { UmbraLogger } from 'components/logger';
Expand All @@ -9,8 +9,10 @@ export { BigNumber, Network, TransactionResponse } from 'src/utils/ethers';
export type Signer = JsonRpcSigner;
export type Provider = Web3Provider;

export const NATIVE_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';

export interface MulticallResponse {
blockNumber: ethers.BigNumber;
blockNumber: BigNumber;
returnData: string[];
}

Expand All @@ -21,7 +23,7 @@ export type Chain = {
nativeCurrency: {
// The address and logoURI fields are not part of the EIP-3085 spec but are added to make this field
// compatible with type TokenInfo
address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
address: typeof NATIVE_TOKEN_ADDRESS;
name: string;
symbol: string;
decimals: 18;
Expand All @@ -41,7 +43,7 @@ export const supportedChains: Array<Chain> = [
chainId: '0x1',
chainName: 'Mainnet',
nativeCurrency: {
address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
address: NATIVE_TOKEN_ADDRESS,
name: 'Ether',
symbol: 'ETH',
decimals: 18,
Expand All @@ -56,7 +58,7 @@ export const supportedChains: Array<Chain> = [
chainId: '0x4',
chainName: 'Rinkeby',
nativeCurrency: {
address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
address: NATIVE_TOKEN_ADDRESS,
name: 'Ether',
symbol: 'ETH',
decimals: 18,
Expand All @@ -71,7 +73,7 @@ export const supportedChains: Array<Chain> = [
chainId: '0xa', // 10 as hex
chainName: 'Optimism',
nativeCurrency: {
address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
address: NATIVE_TOKEN_ADDRESS,
name: 'Ether',
symbol: 'OETH',
decimals: 18,
Expand All @@ -86,7 +88,7 @@ export const supportedChains: Array<Chain> = [
chainId: '0x89', // 137 as hex
chainName: 'Polygon',
nativeCurrency: {
address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
address: NATIVE_TOKEN_ADDRESS,
name: 'Matic',
symbol: 'MATIC',
decimals: 18,
Expand All @@ -101,7 +103,7 @@ export const supportedChains: Array<Chain> = [
chainId: '0xa4b1', // 42161 as hex
chainName: 'Arbitrum One',
nativeCurrency: {
address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
address: NATIVE_TOKEN_ADDRESS,
name: 'Ether',
symbol: 'AETH',
decimals: 18,
Expand All @@ -115,7 +117,7 @@ export const supportedChains: Array<Chain> = [
];

// Set comprised of intersection of Chain IDs present for all contracts in src/contracts, supported by umbra-js, and by relayer
export type SupportedChainIds = '1' | '4' | '10' | '137' | '42161'; // strings for indexing into JSON files
export type SupportedChainId = '1' | '4' | '10' | '137' | '42161'; // strings for indexing into JSON files
export const supportedChainIds = supportedChains.map((chain) => Number(chain.chainId)); // numbers for verifying the chainId user is connected to

// CNS names owned by wallet are queried from The Graph, so these types help parse the response
Expand All @@ -128,7 +130,15 @@ export interface CnsQueryResponse {

// Relayer types
export type ApiError = { error: string };
export type TokenListResponse = TokenList | ApiError;
export interface TokenInfoExtended extends TokenInfo {
minSendAmount: string;
}
// Omit the TokenList.tokens type so we can override it with our own.
export interface TokenListSuccessResponse extends Omit<TokenList, 'tokens'> {
nativeTokenMinSendAmount: string;
mds1 marked this conversation as resolved.
Show resolved Hide resolved
tokens: TokenInfoExtended[];
}
export type TokenListResponse = TokenListSuccessResponse | ApiError;
export type FeeEstimate = { fee: string; token: TokenInfo };
export type FeeEstimateResponse = FeeEstimate | ApiError;
export type WithdrawalInputs = {
Expand All @@ -138,8 +148,10 @@ export type WithdrawalInputs = {
sponsorFee: string;
};
export type RelayResponse = { relayTransactionHash: string } | ApiError;
export type ITXStatusResponse = { receivedTime: string; broadcasts?: any[]; receipt?: TransactionReceipt } | ApiError;
export type ConfirmedITXStatusResponse = { receivedTime: string; broadcasts: any[]; receipt: TransactionReceipt };
export type RelayerStatusResponse =
| { receivedTime: string; broadcasts?: any[]; receipt?: TransactionReceipt }
| ApiError;
export type ConfirmedRelayerStatusResponse = { receivedTime: string; broadcasts: any[]; receipt: TransactionReceipt };

// Logger type added to window
declare global {
Expand Down
51 changes: 24 additions & 27 deletions frontend/src/pages/AccountSend.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
:disable="isSending"
filled
label="Token"
:options="tokenOptions"
:options="tokenList"
option-label="symbol"
/>

Expand Down Expand Up @@ -169,23 +169,24 @@ import useSettingsStore from 'src/store/settings';
import useWalletStore from 'src/store/wallet';
// --- Other ---
import { txNotify } from 'src/utils/alerts';
import { BigNumber, Contract, getAddress, MaxUint256, parseUnits, Zero } from 'src/utils/ethers';
import { humanizeTokenAmount, humanizeArithmeticResult } from 'src/utils/utils';
import { BigNumber, Contract, getAddress, MaxUint256, parseUnits, formatUnits, Zero } from 'src/utils/ethers';
import { humanizeTokenAmount, humanizeMinSendAmount, humanizeArithmeticResult } from 'src/utils/utils';
import { generatePaymentLink, parsePaymentLink } from 'src/utils/payment-links';
import { Provider, TokenInfo } from 'components/models';
import { Provider, TokenInfoExtended } from 'components/models';
import { ERC20_ABI } from 'src/utils/constants';

function useSendForm() {
const { advancedMode } = useSettingsStore();
const {
balances,
chainId,
currentChain,
getTokenBalances,
isLoading,
NATIVE_TOKEN,
provider,
signer,
tokens: tokenOptions,
tokens: tokenList,
umbra,
userAddress,
} = useWalletStore();
Expand All @@ -199,7 +200,7 @@ function useSendForm() {
const recipientIdBaseInputRef = ref<Vue>();
const useNormalPubKey = ref(false);
const shouldUseNormalPubKey = computed(() => advancedMode.value && useNormalPubKey.value); // only use normal public key if advanced mode is on
const token = ref<TokenInfo | null>();
const token = ref<TokenInfoExtended | null>();
const tokenBaseInputRef = ref<Vue>();
const humanAmount = ref<string>();
const humanAmountBaseInputRef = ref<Vue>();
Expand All @@ -225,7 +226,7 @@ function useSendForm() {
// message is hidden if the user checks the block after entering an address. We do this by checking if the
// checkbox toggle was changed, and if so re-validating the form. The rest of this watcher is for handling
// async validation rules
[isLoading, shouldUseNormalPubKey, recipientId, token, humanAmount],
[isLoading, shouldUseNormalPubKey, recipientId, token, humanAmount, tokenList],
async (
[isLoadingValue, useNormalPubKey, recipientIdValue, tokenValue, humanAmountValue],
[prevIsLoadingValue, prevUseNormalPubKey, prevRecipientIdValue, prevTokenValue, prevHumanAmountValue]
Expand All @@ -250,10 +251,10 @@ function useSendForm() {

// Reset token and amount if token is not supported on the network
if (
tokenOptions.value.length &&
!tokenOptions.value.some((tokenOption) => tokenOption.symbol === (tokenValue as TokenInfo)?.symbol)
tokenList.value.length &&
!tokenList.value.some((tokenOption) => tokenOption.symbol === (tokenValue as TokenInfoExtended)?.symbol)
) {
token.value = tokenOptions.value[0];
token.value = tokenList.value[0];
humanAmount.value = undefined;
}

Expand Down Expand Up @@ -282,7 +283,7 @@ function useSendForm() {

// For token, we always default to the chain's native token if none was selected
if (paymentToken?.symbol) token.value = paymentToken;
else token.value = tokenOptions.value[0];
else token.value = tokenList.value[0];
});

// Validators
Expand All @@ -304,21 +305,16 @@ function useSendForm() {
}

const isNativeToken = (address: string) => getAddress(address) === NATIVE_TOKEN.value.address;
const getMinSendAmount = (tokenAddress: string) => {
const chainId = BigNumber.from(currentChain.value?.chainId).toNumber();
// Polygon
if (chainId === 137) {
if (isNativeToken(tokenAddress)) {
return 1.0;
} else if (tokenAddress === '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619') {
return 0.001; // weth token minimum
} else {
return 3; // stablecoin token minimum
}
}
// Mainnet, Rinkeby, and other networks have higher ETH and stablecoin minimums due to higher fees
if (isNativeToken(tokenAddress)) return 0.01;
else return 100; // stablecoin token minimum

const getMinSendAmount = (tokenAddress: string): number => {
mds1 marked this conversation as resolved.
Show resolved Hide resolved
const tokenInfo = tokenList.value.filter((token) => token.address === tokenAddress)[0];
if (!tokenInfo) throw new Error(`token info unavailable for ${tokenAddress}`); // this state should not be possible
const tokenMinSendInWei = parseUnits(tokenInfo.minSendAmount, 'wei');
// We don't need to worry about fallbacks: native tokens have hardcoded fallbacks
// defined in the wallet store. For any other tokens, we wouldn't have info about them
// unless we got it from the relayer, which includes minSend amounts for all tokens.
const minSend = Number(formatUnits(tokenMinSendInWei, tokenInfo.decimals));
return humanizeMinSendAmount(minSend);
};

function isValidTokenAmount(val: string | undefined) {
Expand Down Expand Up @@ -403,6 +399,7 @@ function useSendForm() {

return {
advancedMode,
chainId,
currentChain,
humanAmount,
humanToll,
Expand All @@ -417,7 +414,7 @@ function useSendForm() {
recipientId,
sendFormRef,
token,
tokenOptions,
tokenList,
toll,
useNormalPubKey,
userAddress,
Expand Down
Loading