Compare commits

...
Author SHA1 Message Date
Bill He b6c048e2e2 address nits 2023-09-22 14:17:32 -07:00
Bill He 86227a43eb use dydx gas token 2023-09-22 14:07:31 -07:00
Bill He ad058d8b5f address comment 2023-09-22 11:47:03 -07:00
Bill He 1aaf97478b Use abacus for simulate transactions and balance polling 2023-09-21 22:48:17 -07:00
15 changed files with 175 additions and 223 deletions
+1 -1
View File
@@ -37,7 +37,7 @@
"@cosmjs/proto-signing": "^0.31.0", "@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0", "@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0", "@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^0.5.0", "@dydxprotocol/v4-abacus": "^0.5.4",
"@dydxprotocol/v4-client-js": "^0.36.1", "@dydxprotocol/v4-client-js": "^0.36.1",
"@dydxprotocol/v4-localization": "^0.1.11", "@dydxprotocol/v4-localization": "^0.1.11",
"@ethersproject/providers": "^5.7.2", "@ethersproject/providers": "^5.7.2",
+4 -4
View File
@@ -27,8 +27,8 @@ dependencies:
specifier: ^0.31.0 specifier: ^0.31.0
version: 0.31.0 version: 0.31.0
'@dydxprotocol/v4-abacus': '@dydxprotocol/v4-abacus':
specifier: ^0.5.0 specifier: ^0.5.4
version: 0.5.0 version: 0.5.4
'@dydxprotocol/v4-client-js': '@dydxprotocol/v4-client-js':
specifier: ^0.36.1 specifier: ^0.36.1
version: 0.36.1 version: 0.36.1
@@ -979,8 +979,8 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==} resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true dev: true
/@dydxprotocol/v4-abacus@0.5.0: /@dydxprotocol/v4-abacus@0.5.4:
resolution: {integrity: sha512-K/aLJeOWzmToCn6p9vjBqfwn79/nuKxjZLSzaj1mKNxw25KBFh2q93njNbONFRA72zBElD0K+gw8J/skTpGg1Q==} resolution: {integrity: sha512-LMwmyXCih2bBdLX3vO5OwbdGWg3oZ8WSGj+4nqr8tn/bn1qCSKdYdZZ9/U20Yir2rMAcoC0jmznMcBwthlSp/w==}
dev: false dev: false
/@dydxprotocol/v4-client-js@0.36.1: /@dydxprotocol/v4-client-js@0.36.1:
+5
View File
@@ -122,6 +122,7 @@ export const InputSelectionOption = Abacus.exchange.dydx.abacus.output.input.Sel
// ------ Wallet ------ // // ------ Wallet ------ //
export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet; export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet;
export type AccountBalance = Abacus.exchange.dydx.abacus.output.AccountBalance;
export type Subaccount = Abacus.exchange.dydx.abacus.output.Subaccount; export type Subaccount = Abacus.exchange.dydx.abacus.output.Subaccount;
export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPosition; export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPosition;
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder; export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
@@ -188,6 +189,10 @@ export type HumanReadablePlaceOrderPayload =
Abacus.exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload; Abacus.exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload;
export type HumanReadableCancelOrderPayload = export type HumanReadableCancelOrderPayload =
Abacus.exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload; Abacus.exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload;
export type HumanReadableWithdrawPayload =
Abacus.exchange.dydx.abacus.state.manager.HumanReadableWithdrawPayload;
export type HumanReadableTransferPayload =
Abacus.exchange.dydx.abacus.state.manager.HumanReadableTransferPayload;
// ------ Helpers ------ // // ------ Helpers ------ //
export const AbacusHelper = Abacus.exchange.dydx.abacus.utils.AbacusHelper; export const AbacusHelper = Abacus.exchange.dydx.abacus.utils.AbacusHelper;
+8 -2
View File
@@ -383,12 +383,18 @@ export const DYDX_CHAIN_INFO: Parameters<typeof suggestChain>[0] = {
features: [], features: [],
}; };
// TODO: export this type from abacus instead
export enum DydxChainAsset { export enum DydxChainAsset {
USDC = 'USDC', USDC = 'usdc',
DYDX = 'Dv4TNT', DYDX = 'dydx',
} }
export const DYDX_CHAIN_ASSET_COIN_DENOM: Record<DydxChainAsset, string> = { export const DYDX_CHAIN_ASSET_COIN_DENOM: Record<DydxChainAsset, string> = {
[DydxChainAsset.USDC]: USDC_DENOM, [DydxChainAsset.USDC]: USDC_DENOM,
[DydxChainAsset.DYDX]: DYDX_DENOM, [DydxChainAsset.DYDX]: DYDX_DENOM,
}; };
export const DYDX_CHAIN_ASSET_TAGS: Record<DydxChainAsset, string> = {
[DydxChainAsset.USDC]: 'USDC',
[DydxChainAsset.DYDX]: 'Dv4TNT',
};
+7 -5
View File
@@ -1,10 +1,12 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useSelector } from 'react-redux'; import { shallowEqual, useSelector } from 'react-redux';
import { useBalance } from 'wagmi'; import { useBalance } from 'wagmi';
import { StargateClient } from '@cosmjs/stargate'; import { StargateClient } from '@cosmjs/stargate';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { formatUnits } from 'viem'; import { formatUnits } from 'viem';
import { USDC_DENOM, DYDX_DENOM } from '@dydxprotocol/v4-client-js';
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks'; import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
import { QUANTUM_MULTIPLIER } from '@/constants/numbers'; import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { EvmAddress } from '@/constants/wallets'; import { EvmAddress } from '@/constants/wallets';
@@ -12,11 +14,10 @@ import { EvmAddress } from '@/constants/wallets';
import { convertBech32Address } from '@/lib/addressUtils'; import { convertBech32Address } from '@/lib/addressUtils';
import { MustBigNumber } from '@/lib/numbers'; import { MustBigNumber } from '@/lib/numbers';
import { getBalances } from '@/state/accountSelectors';
import { getSelectedNetwork } from '@/state/appSelectors'; import { getSelectedNetwork } from '@/state/appSelectors';
import { useAccounts } from './useAccounts'; import { useAccounts } from './useAccounts';
import { usePollNativeTokenBalance } from './usePollNativeTokenBalance';
import { usePollUSDCBalance } from './usePollUSDCBalance';
type UseAccountBalanceProps = { type UseAccountBalanceProps = {
// Token Items // Token Items
@@ -50,6 +51,7 @@ export const useAccountBalance = ({
const { evmAddress, dydxAddress } = useAccounts(); const { evmAddress, dydxAddress } = useAccounts();
const selectedNetwork = useSelector(getSelectedNetwork); const selectedNetwork = useSelector(getSelectedNetwork);
const balances = useSelector(getBalances, shallowEqual);
const evmChainId = Number(CLIENT_NETWORK_CONFIGS[selectedNetwork].ethereumChainId); const evmChainId = Number(CLIENT_NETWORK_CONFIGS[selectedNetwork].ethereumChainId);
const evmQuery = useBalance({ const evmQuery = useBalance({
@@ -92,12 +94,12 @@ export const useAccountBalance = ({
const { formatted: evmBalance } = evmQuery.data || {}; const { formatted: evmBalance } = evmQuery.data || {};
const balance = !assetSymbol ? '0' : isCosmosChain ? cosmosQuery.data : evmBalance; const balance = !assetSymbol ? '0' : isCosmosChain ? cosmosQuery.data : evmBalance;
const nativeTokenCoinBalance = usePollNativeTokenBalance({ dydxAddress }); const nativeTokenCoinBalance = balances?.[DYDX_DENOM];
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount) const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount)
.div(QUANTUM_MULTIPLIER) .div(QUANTUM_MULTIPLIER)
.toNumber(); .toNumber();
const usdcCoinBalance = usePollUSDCBalance({ dydxAddress }); const usdcCoinBalance = balances?.[USDC_DENOM];
const usdcBalance = MustBigNumber(usdcCoinBalance?.amount).div(QUANTUM_MULTIPLIER).toNumber(); const usdcBalance = MustBigNumber(usdcCoinBalance?.amount).div(QUANTUM_MULTIPLIER).toNumber();
return { return {
-31
View File
@@ -1,31 +0,0 @@
import { useQuery } from 'react-query';
import { useAccounts } from '@/hooks';
import { DydxAddress } from '@/constants/wallets';
import { DYDX_DENOM } from '@dydxprotocol/v4-client-js';
const ACCOUNT_BALANCE_POLLING_INTERVAL = 60_000;
export const usePollNativeTokenBalance = ({
dydxAddress,
interval = ACCOUNT_BALANCE_POLLING_INTERVAL,
}: {
dydxAddress?: DydxAddress;
interval?: number;
}) => {
const { getAccountBalance } = useAccounts();
const { data } = useQuery({
enabled: dydxAddress !== undefined,
queryKey: ['usePollNativeTokenBalance', { dydxAddress }],
queryFn: async () => {
if (!dydxAddress) return;
return await getAccountBalance({ dydxAddress, denom: DYDX_DENOM });
},
refetchInterval: interval,
staleTime: interval,
});
return data;
};
-30
View File
@@ -1,30 +0,0 @@
import { useQuery } from 'react-query';
import { useAccounts } from '@/hooks';
import { DydxAddress } from '@/constants/wallets';
const ACCOUNT_BALANCE_POLLING_INTERVAL = 60_000;
export const usePollUSDCBalance = ({
dydxAddress,
interval = ACCOUNT_BALANCE_POLLING_INTERVAL,
}: {
dydxAddress?: DydxAddress;
interval?: number;
}) => {
const { getAccountBalance } = useAccounts();
const { data } = useQuery({
enabled: dydxAddress !== undefined,
queryKey: ['usePollUSDCBalance', { dydxAddress }],
queryFn: async () => {
if (!dydxAddress) return;
return await getAccountBalance({ dydxAddress });
},
refetchInterval: interval,
staleTime: interval,
});
return data;
};
+12 -86
View File
@@ -1,5 +1,5 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux'; import { shallowEqual, useSelector, useDispatch } from 'react-redux';
import type { Nullable } from '@dydxprotocol/v4-abacus'; import type { Nullable } from '@dydxprotocol/v4-abacus';
import Long from 'long'; import Long from 'long';
import type { IndexedTx } from '@cosmjs/stargate'; import type { IndexedTx } from '@cosmjs/stargate';
@@ -11,10 +11,10 @@ import {
SubaccountClient, SubaccountClient,
DYDX_DENOM, DYDX_DENOM,
USDC_DENOM, USDC_DENOM,
GAS_PRICE_DYDX_DENOM,
} from '@dydxprotocol/v4-client-js'; } from '@dydxprotocol/v4-client-js';
import type { import type {
AccountBalance,
HumanReadablePlaceOrderPayload, HumanReadablePlaceOrderPayload,
ParsingError, ParsingError,
SubAccountHistoricalPNLs, SubAccountHistoricalPNLs,
@@ -26,6 +26,7 @@ import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { DydxAddress } from '@/constants/wallets'; import { DydxAddress } from '@/constants/wallets';
import { setSubaccount, setHistoricalPnl, removeUncommittedOrderClientId } from '@/state/account'; import { setSubaccount, setHistoricalPnl, removeUncommittedOrderClientId } from '@/state/account';
import { getBalances } from '@/state/accountSelectors';
import abacusStateManager from '@/lib/abacus'; import abacusStateManager from '@/lib/abacus';
import { track } from '@/lib/analytics'; import { track } from '@/lib/analytics';
@@ -34,7 +35,7 @@ import { log } from '@/lib/telemetry';
import { useAccounts } from './useAccounts'; import { useAccounts } from './useAccounts';
import { useDydxClient } from './useDydxClient'; import { useDydxClient } from './useDydxClient';
import { usePollUSDCBalance } from './usePollUSDCBalance';
type SubaccountContextType = ReturnType<typeof useSubaccountContext>; type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType); const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType);
@@ -70,10 +71,8 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
const { const {
depositToSubaccount, depositToSubaccount,
withdrawFromSubaccount, withdrawFromSubaccount,
simulateWithdrawFromSubaccount,
transferFromSubaccountToAddress, transferFromSubaccountToAddress,
transferNativeToken, transferNativeToken,
simulateTransferNativeToken,
sendSquidWithdrawFromSubaccount, sendSquidWithdrawFromSubaccount,
} = useMemo( } = useMemo(
() => ({ () => ({
@@ -95,31 +94,6 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
amount: number; amount: number;
}) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount), }) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount),
simulateWithdrawFromSubaccount: async ({
subaccountClient,
amount,
recipient,
}: {
subaccountClient: SubaccountClient;
amount: number;
recipient?: string;
}) => {
return await compositeClient?.simulate(
subaccountClient?.wallet,
() =>
new Promise((resolve) => {
const msg = compositeClient?.withdrawFromSubaccountMessage(
subaccountClient,
amount,
recipient
);
resolve([msg]);
}),
undefined
);
},
transferFromSubaccountToAddress: async ({ transferFromSubaccountToAddress: async ({
subaccountClient, subaccountClient,
assetId = 0, assetId = 0,
@@ -170,31 +144,6 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
Method.BroadcastTxCommit Method.BroadcastTxCommit
), ),
simulateTransferNativeToken: async ({
subaccountClient,
amount,
recipient,
}: {
subaccountClient: SubaccountClient;
amount: number;
recipient: string;
}) =>
await compositeClient?.simulate(
subaccountClient?.wallet,
() =>
new Promise((resolve) => {
const msg = compositeClient?.validatorClient.post.composer.composeMsgSendToken(
subaccountClient.address,
recipient,
DYDX_DENOM,
Long.fromNumber(amount * QUANTUM_MULTIPLIER)
);
resolve([msg]);
}),
GAS_PRICE_DYDX_DENOM,
undefined
),
sendSquidWithdrawFromSubaccount: async ({ sendSquidWithdrawFromSubaccount: async ({
subaccountClient, subaccountClient,
amount, amount,
@@ -244,10 +193,10 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
// ------ Deposit/Withdraw Methods ------ // // ------ Deposit/Withdraw Methods ------ //
const depositFunds = useCallback( const depositFunds = useCallback(
async (balance?: Coin) => { async (balance: AccountBalance) => {
if (!localDydxWallet) return; if (!localDydxWallet) return;
const amountAfterDust = MustBigNumber(balance?.amount) const amountAfterDust = MustBigNumber(balance.amount)
.minus(AMOUNT_RESERVED_FOR_GAS_USDC) // keep 0.1 USDC in user's wallet for gas .minus(AMOUNT_RESERVED_FOR_GAS_USDC) // keep 0.1 USDC in user's wallet for gas
.toString(); .toString();
@@ -261,11 +210,14 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
[localDydxWallet, depositToSubaccount] [localDydxWallet, depositToSubaccount]
); );
const balance = usePollUSDCBalance({ dydxAddress }); const balances = useSelector(getBalances, shallowEqual);
const usdcCoinBalance = balances?.[USDC_DENOM];
useEffect(() => { useEffect(() => {
depositFunds(balance); if (usdcCoinBalance) {
}, [balance]); depositFunds(usdcCoinBalance);
}
}, [usdcCoinBalance]);
const deposit = useCallback( const deposit = useCallback(
async (amount: Long) => { async (amount: Long) => {
@@ -304,30 +256,6 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
[subaccountClient, transferFromSubaccountToAddress, transferNativeToken] [subaccountClient, transferFromSubaccountToAddress, transferNativeToken]
); );
const simulateTransfer = useCallback(
async (amount: number, recipient: string, coinDenom: string) => {
if (!subaccountClient) {
return;
}
return await (coinDenom === USDC_DENOM
? simulateWithdrawFromSubaccount
: simulateTransferNativeToken)({ subaccountClient, amount, recipient });
},
[subaccountClient, simulateWithdrawFromSubaccount, simulateTransferNativeToken]
);
const simulateWithdraw = useCallback(
async (amount: number) => {
if (!subaccountClient) {
return;
}
return await simulateWithdrawFromSubaccount({ subaccountClient, amount });
},
[subaccountClient, simulateWithdrawFromSubaccount]
);
const sendSquidWithdraw = useCallback( const sendSquidWithdraw = useCallback(
async (amount: number, payload: string) => { async (amount: number, payload: string) => {
if (!subaccountClient) { if (!subaccountClient) {
@@ -437,9 +365,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
requestFaucetFunds, requestFaucetFunds,
// Transfer Methods // Transfer Methods
simulateTransfer,
transfer, transfer,
simulateWithdraw,
sendSquidWithdraw, sendSquidWithdraw,
// Trading Methods // Trading Methods
+93
View File
@@ -14,6 +14,8 @@ import {
OrderSide, OrderSide,
OrderTimeInForce, OrderTimeInForce,
OrderExecution, OrderExecution,
DYDX_DENOM,
GAS_PRICE_DYDX_DENOM,
} from '@dydxprotocol/v4-client-js'; } from '@dydxprotocol/v4-client-js';
import { import {
@@ -24,10 +26,13 @@ import {
type TransactionTypes, type TransactionTypes,
type HumanReadablePlaceOrderPayload, type HumanReadablePlaceOrderPayload,
type HumanReadableCancelOrderPayload, type HumanReadableCancelOrderPayload,
type HumanReadableWithdrawPayload,
type HumanReadableTransferPayload,
} from '@/constants/abacus'; } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade'; import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { RootStore } from '@/state/_store'; import { RootStore } from '@/state/_store';
import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account'; import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account';
@@ -229,6 +234,76 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
} }
} }
async simulateWithdrawTransaction(params: HumanReadableWithdrawPayload): Promise<string> {
if (!this.compositeClient || !this.localWallet) {
throw new Error('Missing compositeClient or localWallet');
}
const { subaccountNumber, amount } = params ?? {};
const compositeClient = this.compositeClient;
const subaccountClient = new SubaccountClient(this.localWallet, subaccountNumber);
try {
const tx = await compositeClient.simulate(
this.localWallet,
() =>
new Promise((resolve) => {
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount);
resolve([msg]);
}),
);
const parsedTx = this.parseToPrimitives(tx);
return JSON.stringify(parsedTx);
} catch (error) {
log('DydxChainTransactions/simulateWithdrawTransaction', error);
return JSON.stringify({
error,
});
}
}
async simulateTransferNativeTokenTransaction(params: HumanReadableTransferPayload): Promise<string> {
if (!this.compositeClient || !this.localWallet) {
throw new Error('Missing compositeClient or localWallet');
}
const { subaccountNumber, amount, recipient } = params ?? {};
const compositeClient = this.compositeClient;
const subaccountClient = new SubaccountClient(this.localWallet, subaccountNumber);
try {
const tx = await compositeClient.simulate(
this.localWallet,
() =>
new Promise((resolve) => {
const msg = compositeClient?.validatorClient.post.composer.composeMsgSendToken(
subaccountClient.address,
recipient,
DYDX_DENOM,
Long.fromNumber(amount * QUANTUM_MULTIPLIER)
);
resolve([msg]);
}),
GAS_PRICE_DYDX_DENOM,
);
const parsedTx = this.parseToPrimitives(tx);
return JSON.stringify(parsedTx);
} catch (error) {
log('DydxChainTransactions/simulateTransferNativeTokenTransaction', error);
return JSON.stringify({
error,
});
}
}
async transaction( async transaction(
type: TransactionTypes, type: TransactionTypes,
paramsInJson: Abacus.Nullable<string>, paramsInJson: Abacus.Nullable<string>,
@@ -248,6 +323,16 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
callback(result); callback(result);
break; break;
} }
case TransactionType.simulateWithdraw: {
const result = await this.simulateWithdrawTransaction(params);
callback(result);
break;
}
case TransactionType.simulateTransferNativeToken: {
const result = await this.simulateTransferNativeTokenTransaction(params);
callback(result);
break;
}
default: { default: {
break; break;
} }
@@ -310,6 +395,14 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
const parsedUserStats = this.parseToPrimitives(userStats); const parsedUserStats = this.parseToPrimitives(userStats);
callback(JSON.stringify(parsedUserStats)); callback(JSON.stringify(parsedUserStats));
break; break;
case QueryType.GetAccountBalances:
if (!this.localWallet?.address) throw new Error('Missing localWallet');
const accountBalances = await this.compositeClient?.validatorClient.get.getAccountBalances(
this.localWallet.address
);
const parsedAccountBalances = this.parseToPrimitives(accountBalances);
callback(JSON.stringify(parsedAccountBalances));
break;
// Do not implement Transfers (yet) // Do not implement Transfers (yet)
case QueryType.Transfers: case QueryType.Transfers:
default: default:
+12
View File
@@ -1,6 +1,7 @@
import { kollections } from '@dydxprotocol/v4-abacus'; import { kollections } from '@dydxprotocol/v4-abacus';
import type { import type {
AccountBalance,
AbacusApiState, AbacusApiState,
AbacusNotification, AbacusNotification,
AbacusStateNotificationProtocol, AbacusStateNotificationProtocol,
@@ -18,6 +19,7 @@ import { Changes } from '@/constants/abacus';
import type { RootStore } from '@/state/_store'; import type { RootStore } from '@/state/_store';
import { import {
setBalances,
setFills, setFills,
setFundingPayments, setFundingPayments,
setHistoricalPnl, setHistoricalPnl,
@@ -72,6 +74,16 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
); );
} }
if (changes.has(Changes.accountBalances)) {
if (updatedState.account?.balances) {
const balances: Record<string, AccountBalance> = {}
for (const { k, v } of updatedState.account.balances.toArray()) {
balances[k] = v;
}
dispatch(setBalances(balances));
}
}
if (changes.has(Changes.configs)) { if (changes.has(Changes.configs)) {
dispatch(setConfigs(updatedState.configs)); dispatch(setConfigs(updatedState.configs));
} }
+6
View File
@@ -1,6 +1,7 @@
import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { import type {
AccountBalance,
SubaccountFill, SubaccountFill,
Nullable, Nullable,
Subaccount, Subaccount,
@@ -35,6 +36,7 @@ export type AccountState = {
wallet?: Nullable<Wallet>; wallet?: Nullable<Wallet>;
walletType?: WalletType; walletType?: WalletType;
historicalPnlPeriod?: HistoricalPnlPeriods; historicalPnlPeriod?: HistoricalPnlPeriods;
balances?: Record<string, AccountBalance>;
}; };
const initialState: AccountState = { const initialState: AccountState = {
@@ -149,6 +151,9 @@ export const accountSlice = createSlice({
viewedOrders: (state) => { viewedOrders: (state) => {
state.hasUnseenOrderUpdates = false; state.hasUnseenOrderUpdates = false;
}, },
setBalances: (state, action: PayloadAction<Record<string, AccountBalance>>) => {
state.balances = action.payload;
},
addUncommittedOrderClientId: (state, action: PayloadAction<number>) => { addUncommittedOrderClientId: (state, action: PayloadAction<number>) => {
state.uncommittedOrderClientIds.push(action.payload); state.uncommittedOrderClientIds.push(action.payload);
}, },
@@ -173,6 +178,7 @@ export const {
setWallet, setWallet,
viewedFills, viewedFills,
viewedOrders, viewedOrders,
setBalances,
addUncommittedOrderClientId, addUncommittedOrderClientId,
removeUncommittedOrderClientId, removeUncommittedOrderClientId,
} = accountSlice.actions; } = accountSlice.actions;
+5
View File
@@ -332,3 +332,8 @@ export const getUserStats = (state: RootState) => ({
makerVolume30D: state.account?.wallet?.user?.makerVolume30D, makerVolume30D: state.account?.wallet?.user?.makerVolume30D,
takerVolume30D: state.account?.wallet?.user?.takerVolume30D, takerVolume30D: state.account?.wallet?.user?.takerVolume30D,
}); });
/**
* @returns user wallet balances
*/
export const getBalances = (state: RootState) => state.account?.balances;
@@ -43,7 +43,7 @@ export const WithdrawForm = () => {
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { simulateWithdraw, sendSquidWithdraw } = useSubaccount(); const { sendSquidWithdraw } = useSubaccount();
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {}; const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
// User input // User input
@@ -97,16 +97,10 @@ export const WithdrawForm = () => {
field: TransferInputField.usdcSize, field: TransferInputField.usdcSize,
}); });
} else { } else {
const stdFee = await simulateWithdraw(parseFloat(debouncedAmount));
const amount =
parseFloat(debouncedAmount) -
parseFloat(stdFee?.amount[0]?.amount || '0') / QUANTUM_MULTIPLIER;
abacusStateManager.setTransferValue({ abacusStateManager.setTransferValue({
value: amount.toString(), value: debouncedAmount,
field: TransferInputField.usdcSize, field: TransferInputField.usdcSize,
}); });
setError(null); setError(null);
} }
} catch (error) { } catch (error) {
+16 -52
View File
@@ -13,7 +13,7 @@ import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks'; import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers'; import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { DYDX_CHAIN_ASSET_COIN_DENOM, DydxChainAsset } from '@/constants/wallets'; import { DYDX_CHAIN_ASSET_COIN_DENOM, DYDX_CHAIN_ASSET_TAGS, DydxChainAsset } from '@/constants/wallets';
import { import {
useAccountBalance, useAccountBalance,
@@ -52,28 +52,6 @@ type TransferFormProps = {
className?: string; className?: string;
}; };
const debouncedEstimateFee = debounce(
async ({ amount, recipientAddress, asset, setFees, simulateTransfer }) => {
if (!amount || !recipientAddress) {
return;
}
try {
const coinDenom = DYDX_CHAIN_ASSET_COIN_DENOM[asset as DydxChainAsset];
const stdFee: StdFee = await simulateTransfer(amount, recipientAddress, coinDenom);
const fee = stdFee?.amount.find((coin) => coin.denom === coinDenom)?.amount;
const feeAmount = MustBigNumber(fee).div(QUANTUM_MULTIPLIER).toNumber();
setFees(feeAmount);
} catch (error) {
console.error('TransferForm > : debouncedEstimateFee > ', error);
}
},
1000,
{ trailing: true }
);
export const TransferForm = ({ export const TransferForm = ({
selectedAsset = DydxChainAsset.DYDX, selectedAsset = DydxChainAsset.DYDX,
onDone, onDone,
@@ -82,8 +60,8 @@ export const TransferForm = ({
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {}; const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { dydxAddress } = useAccounts(); const { dydxAddress } = useAccounts();
const { address: recipientAddress, size } = useSelector(getTransferInputs, shallowEqual) || {}; const { address: recipientAddress, size, fee } = useSelector(getTransferInputs, shallowEqual) || {};
const { transfer, simulateTransfer } = useSubaccount(); const { transfer } = useSubaccount();
const { nativeTokenBalance, usdcBalance } = useAccountBalance(); const { nativeTokenBalance, usdcBalance } = useAccountBalance();
const { selectedNetwork } = useSelectedNetwork(); const { selectedNetwork } = useSelectedNetwork();
@@ -93,7 +71,6 @@ export const TransferForm = ({
// Form states // Form states
const [error, setError] = useState<Error | undefined>(); const [error, setError] = useState<Error | undefined>();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [fees, setFees] = useState<number>();
const balance = asset === DydxChainAsset.USDC ? freeCollateral?.current : nativeTokenBalance; const balance = asset === DydxChainAsset.USDC ? freeCollateral?.current : nativeTokenBalance;
const newBalance = const newBalance =
@@ -104,7 +81,7 @@ export const TransferForm = ({
.toNumber(); .toNumber();
const amount = asset === DydxChainAsset.USDC ? size?.usdcSize : size?.size; const amount = asset === DydxChainAsset.USDC ? size?.usdcSize : size?.size;
const showNotEnoughGasWarning = fees && asset === DydxChainAsset.USDC && usdcBalance < fees; const showNotEnoughGasWarning = fee && asset === DydxChainAsset.USDC && usdcBalance < fee;
// BN // BN
const amountBN = MustBigNumber(amount); const amountBN = MustBigNumber(amount);
@@ -134,34 +111,21 @@ export const TransferForm = ({
useEffect(() => { useEffect(() => {
setError(undefined); setError(undefined);
debouncedEstimateFee.cancel(); abacusStateManager.setTransferValue({
value: asset,
if (isAmountValid && isAddressValid) { field: TransferInputField.token,
debouncedEstimateFee({ });
amount,
recipientAddress,
asset,
setFees,
simulateTransfer,
});
} else {
setFees(undefined);
}
}, [asset, amount, recipientAddress]);
useEffect(() => {
setError(undefined);
}, [asset]); }, [asset]);
const onTransfer = async () => { const onTransfer = async () => {
if (!isAmountValid || !isAddressValid || !fees) return; if (!isAmountValid || !isAddressValid || !fee) return;
setIsLoading(true); setIsLoading(true);
setError(undefined); setError(undefined);
try { try {
// Subtract fees from amount if sending native tokens // Subtract fees from amount if sending native tokens
const amountToTransfer = ( const amountToTransfer = (
asset === DydxChainAsset.DYDX ? amountBN.minus(fees) : amountBN asset === DydxChainAsset.DYDX ? amountBN.minus(fee) : amountBN
).toNumber(); ).toNumber();
const txResponse = await transfer( const txResponse = await transfer(
@@ -212,7 +176,7 @@ export const TransferForm = ({
value: DydxChainAsset.USDC, value: DydxChainAsset.USDC,
label: ( label: (
<Styled.InlineRow> <Styled.InlineRow>
<AssetIcon symbol="USDC" /> USDC <AssetIcon symbol="USDC" /> {DYDX_CHAIN_ASSET_TAGS[DydxChainAsset.USDC]}
</Styled.InlineRow> </Styled.InlineRow>
), ),
}, },
@@ -221,7 +185,7 @@ export const TransferForm = ({
label: ( label: (
<Styled.InlineRow> <Styled.InlineRow>
{/* <AssetIcon symbol="DYDX" /> */} {/* <AssetIcon symbol="DYDX" /> */}
Dv4TNT {DYDX_CHAIN_ASSET_TAGS[DydxChainAsset.DYDX]}
</Styled.InlineRow> </Styled.InlineRow>
), ),
}, },
@@ -243,7 +207,7 @@ export const TransferForm = ({
key: 'amount', key: 'amount',
label: ( label: (
<span> <span>
{stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{asset}</Tag> {stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{DYDX_CHAIN_ASSET_TAGS[asset]}</Tag>
</span> </span>
), ),
value: ( value: (
@@ -375,9 +339,9 @@ export const TransferForm = ({
<Styled.Footer> <Styled.Footer>
<TransferButtonAndReceipt <TransferButtonAndReceipt
selectedAsset={asset} selectedAsset={asset}
fees={fees} fees={fee || undefined}
isDisabled={!isAmountValid || !isAddressValid || !fees} isDisabled={!isAmountValid || !isAddressValid || !fee}
isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !fees)} isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !fee)}
/> />
</Styled.Footer> </Styled.Footer>
</Styled.Form> </Styled.Form>
@@ -3,7 +3,7 @@ import { shallowEqual, useSelector } from 'react-redux';
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons'; import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { NumberSign } from '@/constants/numbers'; import { NumberSign } from '@/constants/numbers';
import { DydxChainAsset } from '@/constants/wallets'; import { DYDX_CHAIN_ASSET_TAGS, DydxChainAsset } from '@/constants/wallets';
import { useAccountBalance, useStringGetter } from '@/hooks'; import { useAccountBalance, useStringGetter } from '@/hooks';
@@ -60,7 +60,7 @@ export const TransferButtonAndReceipt = ({
key: 'fees', key: 'fees',
label: ( label: (
<span> <span>
{stringGetter({ key: STRING_KEYS.FEES })} <Tag>{selectedAsset}</Tag> {stringGetter({ key: STRING_KEYS.FEES })} <Tag>{DYDX_CHAIN_ASSET_TAGS[selectedAsset]}</Tag>
</span> </span>
), ),
value: <Output type={OutputType.Asset} value={fees} />, value: <Output type={OutputType.Asset} value={fees} />,
@@ -69,7 +69,7 @@ export const TransferButtonAndReceipt = ({
key: 'balance', key: 'balance',
label: ( label: (
<span> <span>
{stringGetter({ key: STRING_KEYS.BALANCE })} <Tag>{selectedAsset}</Tag> {stringGetter({ key: STRING_KEYS.BALANCE })} <Tag>{DYDX_CHAIN_ASSET_TAGS[selectedAsset]}</Tag>
</span> </span>
), ),
value: ( value: (