diff --git a/packages/web/config/ibc-assets.ts b/packages/web/config/ibc-assets.ts index 4b61a84da6..651d5236e0 100644 --- a/packages/web/config/ibc-assets.ts +++ b/packages/web/config/ibc-assets.ts @@ -38,8 +38,6 @@ export const IBCAssetInfos: (IBCAsset & { wallets: ["metamask" as const, "walletconnect" as const], method: "deposit-address" as const, sourceChains: [AxelarSourceChainConfigs.usdc.ethereum], - tokenMinDenom: IS_TESTNET ? "uausdc" : "uusdc", // test: "uausdc" - transferFeeMinAmount: IS_TESTNET ? "150000" : "10500000", // From https://docs.axelar.dev/resources/mainnet#cross-chain-relayer-gas-fee }, }, { @@ -56,8 +54,6 @@ export const IBCAssetInfos: (IBCAsset & { wallets: ["metamask" as const, "walletconnect" as const], method: "deposit-address" as const, sourceChains: [AxelarSourceChainConfigs.weth.ethereum], - tokenMinDenom: IS_TESTNET ? "weth-wei" : "weth-wei", - transferFeeMinAmount: IS_TESTNET ? "60000000000000" : "6300000000000000", }, }, { diff --git a/packages/web/hooks/ui-config/index.ts b/packages/web/hooks/ui-config/index.ts index 2bae34021b..65e1fca05c 100644 --- a/packages/web/hooks/ui-config/index.ts +++ b/packages/web/hooks/ui-config/index.ts @@ -5,3 +5,4 @@ export * from "./use-fake-fee-config"; export * from "./use-remove-liquidity-config"; export * from "./use-slippage-config"; export * from "./use-trade-token-in-config"; +export * from "./use-transfer-config"; diff --git a/packages/web/hooks/ui-config/use-transfer-config.ts b/packages/web/hooks/ui-config/use-transfer-config.ts new file mode 100644 index 0000000000..4dddb187c9 --- /dev/null +++ b/packages/web/hooks/ui-config/use-transfer-config.ts @@ -0,0 +1,35 @@ +import { useState, useEffect } from "react"; +import { AccountSetBase } from "@keplr-wallet/stores"; +import { + ObservableAssets, + ObservableTransferUIConfig, +} from "../../stores/assets"; +import { makeLocalStorageKVStore } from "../../stores/kv-store"; +import { useWindowSize } from "../window"; + +export function useTransferConfig( + assetsStore: ObservableAssets, + account: AccountSetBase +) { + const { isMobile } = useWindowSize(); + + const [transferConfig, setTransferConfig] = + useState(null); + const [transferKvStore] = useState(() => + makeLocalStorageKVStore("transfer-ui-config") + ); + useEffect( + () => + setTransferConfig( + new ObservableTransferUIConfig( + assetsStore, + account, + transferKvStore, + isMobile + ) + ), + [assetsStore, account, isMobile] + ); + + return transferConfig; +} diff --git a/packages/web/integrations/axelar/hooks/use-deposit-address.ts b/packages/web/integrations/axelar/hooks/use-deposit-address.ts index 0bf223325a..8e4df2022a 100644 --- a/packages/web/integrations/axelar/hooks/use-deposit-address.ts +++ b/packages/web/integrations/axelar/hooks/use-deposit-address.ts @@ -5,7 +5,7 @@ export function useDepositAddress( sourceChain: string, destChain: string, address: string | undefined, - tokenMinDenom: string, + coinMinimalDenom: string, generateOnMount = true, environment = Environment.MAINNET ): { @@ -20,7 +20,7 @@ export function useDepositAddress( if (address && depositAddress === null) { setIsLoading(true); new AxelarAssetTransfer({ environment }) - .getDepositAddress(sourceChain, destChain, address, tokenMinDenom) + .getDepositAddress(sourceChain, destChain, address, coinMinimalDenom) .then((generatedAddress) => { setDepositAddress(generatedAddress); }) @@ -36,7 +36,7 @@ export function useDepositAddress( address, sourceChain, destChain, - tokenMinDenom, + coinMinimalDenom, setIsLoading, ]); diff --git a/packages/web/integrations/axelar/source-chain-config.ts b/packages/web/integrations/axelar/source-chain-config.ts index cde2c689ef..7c1ca64d0c 100644 --- a/packages/web/integrations/axelar/source-chain-config.ts +++ b/packages/web/integrations/axelar/source-chain-config.ts @@ -17,31 +17,37 @@ export const SourceChainConfigs: { ? "0x526f0A95EDC3DF4CBDB7bb37d4F7Ed451dB8e369" : "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // test: 'aUSDC' on metamask/etherscan logoUrl: "/networks/ethereum.svg", + transferFeeMinAmount: IS_TESTNET ? "150000" : "10500000", // From https://docs.axelar.dev/resources/mainnet#cross-chain-relayer-gas-fee }, bnbChain: { id: "Binance" as const, erc20ContractAddress: "0x4268B8F0B87b6Eae5d897996E6b845ddbD99Adf3", logoUrl: "/networks/binance.svg", + transferFeeMinAmount: IS_TESTNET ? "150000" : "1500000", }, avalanche: { id: "Avalanche" as const, erc20ContractAddress: "0xfaB550568C688d5D8A52C7d794cb93Edc26eC0eC", logoUrl: "/networks/avalanche.svg", + transferFeeMinAmount: IS_TESTNET ? "150000" : "1500000", }, polygon: { id: "Polygon" as const, erc20ContractAddress: "0x750e4C4984a9e0f12978eA6742Bc1c5D248f40ed", logoUrl: "/networks/polygon.svg", + transferFeeMinAmount: IS_TESTNET ? "150000" : "1500000", }, fantom: { id: "Fantom" as const, erc20ContractAddress: "0x1B6382DBDEa11d97f24495C9A90b7c88469134a4", logoUrl: "/networks/fantom.svg", + transferFeeMinAmount: IS_TESTNET ? "150000" : "1500000", }, moonbeam: { id: "Moonbeam" as const, erc20ContractAddress: "0xCa01a1D0993565291051daFF390892518ACfAD3A", logoUrl: "/networks/moonbeam.svg", + transferFeeMinAmount: IS_TESTNET ? "150000" : "1500000", }, }, weth: { @@ -53,6 +59,7 @@ export const SourceChainConfigs: { ? "0xc778417E063141139Fce010982780140Aa0cD5Ab" : "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", logoUrl: "/networks/ethereum.svg", + transferFeeMinAmount: IS_TESTNET ? "60000000000000" : "6300000000000000", }, }, }; diff --git a/packages/web/integrations/axelar/transfer.tsx b/packages/web/integrations/axelar/transfer.tsx index 9ae6b7748c..4a86eb2e19 100644 --- a/packages/web/integrations/axelar/transfer.tsx +++ b/packages/web/integrations/axelar/transfer.tsx @@ -56,8 +56,6 @@ const AxelarTransfer: FunctionComponent< selectedSourceChainKey, onRequestClose, onRequestSwitchWallet, - tokenMinDenom, - transferFeeMinAmount, sourceChains, isTestNet = process.env.NEXT_PUBLIC_IS_TESTNET === "true", connectCosmosWalletButtonOverride, @@ -98,9 +96,11 @@ const AxelarTransfer: FunctionComponent< EthClientChainIds_AxelarChainIdsMap[selectedSourceChainKey] ?? selectedSourceChainKey; - const erc20ContractAddress = sourceChains.find( - ({ id }) => id === selectedSourceChainKey - )?.erc20ContractAddress; + const sourceChainConfig = sourceChains.find( ({ id }) => id === selectedSourceChainKey ); + + const erc20ContractAddress = sourceChainConfig?.erc20ContractAddress; + const transferFeeMinAmount = sourceChainConfig?.transferFeeMinAmount ?? "0"; + const axelarChainId = chainStore.getChainFromCurrency(originCurrency.coinDenom)?.chainId || "axelar-dojo-1"; @@ -222,7 +222,7 @@ const AxelarTransfer: FunctionComponent< sourceChain, destChain, isWithdraw || correctChainSelected ? address : undefined, - tokenMinDenom, + originCurrency.coinMinimalDenom, undefined, isTestNet ? Environment.TESTNET : Environment.MAINNET ); @@ -230,7 +230,7 @@ const AxelarTransfer: FunctionComponent< // notify user they are withdrawing into a different account then they last deposited to const [lastDepositAccountAddress, setLastDepositAccountAddress] = useLocalStorageState( - `axelar-last-deposit-addr-${tokenMinDenom}`, + `axelar-last-deposit-addr-${originCurrency.coinMinimalDenom}`, null ); const warnOfDifferentDepositAddress = diff --git a/packages/web/integrations/axelar/types.ts b/packages/web/integrations/axelar/types.ts index 2f4d217b31..854fef816e 100644 --- a/packages/web/integrations/axelar/types.ts +++ b/packages/web/integrations/axelar/types.ts @@ -12,12 +12,6 @@ export interface AxelarBridgeConfig { /** Ex: `uusdc`. NOTE: Will get currency info from `originCurrency` on the IBC balance (from registrar). * See: https://docs.axelar.dev/resources/mainnet#assets */ - tokenMinDenom: string; - /** Amount of Axelar transfer fee in `originCurrency`. - * TODO: use `useTransferFeeQuery` should fees become dynamic and once APIs become production ready. - * See calculator tool on Axelar docs to get current fee constants: https://docs.axelar.dev/resources/mainnet#cross-chain-relayer-gas-fee. - */ - transferFeeMinAmount: string; } /** See: https://docs.axelar.dev/dev/build/chain-names/mainnet @@ -61,4 +55,11 @@ export type SourceChainConfig = { }; logoUrl: string; + + /** Amount of Axelar transfer fee in `originCurrency`. + * TODO: use `useTransferFeeQuery` should fees become dynamic and once APIs become production ready. + * See calculator tool on Axelar docs to get current fee constants: https://docs.axelar.dev/resources/mainnet#cross-chain-relayer-gas-fee. + */ + transferFeeMinAmount: string; + }; diff --git a/packages/web/integrations/ethereum/metamask.ts b/packages/web/integrations/ethereum/metamask.ts index e6603dc07c..34f0b582dd 100644 --- a/packages/web/integrations/ethereum/metamask.ts +++ b/packages/web/integrations/ethereum/metamask.ts @@ -21,6 +21,7 @@ const IS_TESTNET = process.env.NEXT_PUBLIC_IS_TESTNET === "true"; export class ObservableMetamask implements EthWallet { readonly key: WalletKey = "metamask"; + readonly mobileEnabled = false; readonly displayInfo: WalletDisplay = { iconUrl: "/icons/metamask-fox.svg", diff --git a/packages/web/integrations/ethereum/walletconnect.ts b/packages/web/integrations/ethereum/walletconnect.ts index 73bc1a9dfa..a53dbdd254 100644 --- a/packages/web/integrations/ethereum/walletconnect.ts +++ b/packages/web/integrations/ethereum/walletconnect.ts @@ -17,7 +17,8 @@ const CONNECTED_ACCOUNT_KEY = "wc-eth-connected-account"; const CONNECTED_ACCOUNT_CHAINID = "wc-eth-connected-chainId"; export class ObservableWalletConnect implements EthWallet { - key: WalletKey = "walletconnect"; + readonly key: WalletKey = "walletconnect"; + readonly mobileEnabled = false; displayInfo: WalletDisplay = { iconUrl: "/icons/walletconnect.svg", diff --git a/packages/web/integrations/wallets.ts b/packages/web/integrations/wallets.ts index 974cd7594b..e1a571c658 100644 --- a/packages/web/integrations/wallets.ts +++ b/packages/web/integrations/wallets.ts @@ -19,6 +19,9 @@ export interface Wallet< readonly key: WalletKey; readonly displayInfo: WalletDisplay; + /** Works on mobile browsers. */ + readonly mobileEnabled: boolean; + readonly accountAddress?: string; /** Human readable chain, falls back to hex ID (`0x...`) if unknown. */ readonly chainId?: string; diff --git a/packages/web/modals/connect-non-ibc-wallet.tsx b/packages/web/modals/connect-non-ibc-wallet.tsx index f542f9f95f..3d2246f87c 100644 --- a/packages/web/modals/connect-non-ibc-wallet.tsx +++ b/packages/web/modals/connect-non-ibc-wallet.tsx @@ -15,11 +15,14 @@ export const ConnectNonIbcWallet: FunctionComponent< onSelectWallet: (key: string) => void; } > = observer((props) => { + const { initiallySelectedWalletId, isWithdraw, wallets, onSelectWallet } = + props; + const [selectedWalletKey, setSelectedWalletId] = useState( - props.initiallySelectedWalletId ?? null + initiallySelectedWalletId ?? null ); - const selectedWallet = props.wallets.find((w) => w.key === selectedWalletKey); + const selectedWallet = wallets.find((w) => w.key === selectedWalletKey); const canOnboardSelectedWallet = selectedWallet && !selectedWallet.isInstalled && selectedWallet.onboard; @@ -28,12 +31,13 @@ export const ConnectNonIbcWallet: FunctionComponent< className: "h-14 md:w-full w-96 mt-3 mx-auto !px-1", size: "lg", disabled: - props.initiallySelectedWalletId === undefined && !selectedWalletKey, + (initiallySelectedWalletId === undefined && !selectedWalletKey) || + wallets.length === 0, onClick: () => { if (canOnboardSelectedWallet) { selectedWallet!.onboard?.(); } else if (selectedWalletKey) { - props.onSelectWallet(selectedWalletKey); + onSelectWallet(selectedWalletKey); } else { console.error( "Wallet selection invalid state: selectedWalletKey undefined" @@ -44,6 +48,8 @@ export const ConnectNonIbcWallet: FunctionComponent<
{canOnboardSelectedWallet ? `Install ${selectedWallet.displayInfo.displayName}` + : wallets.length === 0 + ? "None available" : "Next"}
), @@ -56,19 +62,30 @@ export const ConnectNonIbcWallet: FunctionComponent<
- {props.wallets.map((wallet, i) => ( + {props.wallets.length === 0 ? ( setSelectedWalletId(wallet.key)} + className="opacity-30" + id="placeholder" + displayName="None" + iconUrl="/icons/error-x.svg" /> - ))} + ) : ( + <> + {wallets.map((wallet, i) => ( + setSelectedWalletId(wallet.key)} + /> + ))} + + )}
{accountActionButton}
diff --git a/packages/web/pages/assets/index.tsx b/packages/web/pages/assets/index.tsx index 4098fb7ed6..0fd30733ac 100644 --- a/packages/web/pages/assets/index.tsx +++ b/packages/web/pages/assets/index.tsx @@ -9,9 +9,7 @@ import { } from "react"; import { PricePretty } from "@keplr-wallet/unit"; import { ObservableQueryPool } from "@osmosis-labs/stores"; -import { makeLocalStorageKVStore } from "../../stores/kv-store"; import { useStore } from "../../stores"; -import { ObservableTransferUIConfig } from "../../stores/assets"; import { Overview } from "../../components/overview"; import { AssetsTable } from "../../components/table/assets-table"; import { DepoolingTable } from "../../components/table/depooling-table"; @@ -26,7 +24,11 @@ import { TransferAssetSelectModal, } from "../../modals"; import { ConnectNonIbcWallet, PreTransferModal } from "../../modals"; -import { useWindowSize, useAmplitudeAnalytics } from "../../hooks"; +import { + useWindowSize, + useAmplitudeAnalytics, + useTransferConfig, +} from "../../hooks"; import { WalletConnectQRModal } from "../../modals"; import { EventName } from "../../config"; @@ -47,15 +49,7 @@ const Assets: NextPage = observer(() => { const { setUserProperty } = useAmplitudeAnalytics({ onLoadEvent: [EventName.Assets.pageViewed], }); - - const [transferConfig] = useState( - () => - new ObservableTransferUIConfig( - assetsStore, - account, - makeLocalStorageKVStore("transfer-ui-config") - ) - ); + const transferConfig = useTransferConfig(assetsStore, account); // mobile only const [preTransferModalProps, setPreTransferModalProps] = @@ -77,7 +71,7 @@ const Assets: NextPage = observer(() => { isUnstable: ibcBalance.isUnstable, onSelectToken: launchPreTransferModal, onWithdraw: () => { - transferConfig.transferAsset( + transferConfig?.transferAsset( "withdraw", ibcBalance.chainInfo.chainId, coinDenom @@ -85,7 +79,7 @@ const Assets: NextPage = observer(() => { setPreTransferModalProps(null); }, onDeposit: () => { - transferConfig.transferAsset( + transferConfig?.transferAsset( "deposit", ibcBalance.chainInfo.chainId, coinDenom @@ -110,25 +104,25 @@ const Assets: NextPage = observer(() => { return (
transferConfig.startTransfer("deposit")} - onWithdrawIntent={() => transferConfig.startTransfer("withdraw")} + onDepositIntent={() => transferConfig?.startTransfer("deposit")} + onWithdrawIntent={() => transferConfig?.startTransfer("withdraw")} /> {isMobile && preTransferModalProps && ( )} - {transferConfig.assetSelectModal && ( + {transferConfig?.assetSelectModal && ( )} - {transferConfig.connectNonIbcWalletModal && ( + {transferConfig?.connectNonIbcWalletModal && ( )} - {transferConfig.ibcTransferModal && ( + {transferConfig?.ibcTransferModal && ( )} - {transferConfig.bridgeTransferModal && ( + {transferConfig?.bridgeTransferModal && ( )} - {transferConfig.walletConnectEth.sessionConnectUri && ( + {transferConfig?.walletConnectEth.sessionConnectUri && ( { transferConfig.startTransfer("deposit")} - onWithdrawIntent={() => transferConfig.startTransfer("withdraw")} + onDepositIntent={() => transferConfig?.startTransfer("deposit")} + onWithdrawIntent={() => transferConfig?.startTransfer("withdraw")} onDeposit={(chainId, coinDenom, externalDepositUrl) => { if (!externalDepositUrl) { isMobile ? launchPreTransferModal(coinDenom) - : transferConfig.transferAsset("deposit", chainId, coinDenom); + : transferConfig?.transferAsset("deposit", chainId, coinDenom); } }} onWithdraw={(chainId, coinDenom, externalWithdrawUrl) => { if (!externalWithdrawUrl) { - transferConfig.transferAsset("withdraw", chainId, coinDenom); + transferConfig?.transferAsset("withdraw", chainId, coinDenom); } }} /> diff --git a/packages/web/stores/assets/transfer-ui-config.ts b/packages/web/stores/assets/transfer-ui-config.ts index 2e2f0a1b53..46ad39216c 100644 --- a/packages/web/stores/assets/transfer-ui-config.ts +++ b/packages/web/stores/assets/transfer-ui-config.ts @@ -69,7 +69,8 @@ export class ObservableTransferUIConfig { constructor( protected readonly assetsStore: ObservableAssets, protected readonly account: AccountSetBase, - protected readonly kvStore: KVStore + protected readonly kvStore: KVStore, + protected readonly isMobile: boolean ) { makeObservable(this); } @@ -84,7 +85,9 @@ export class ObservableTransferUIConfig { ); protected get _ethClientWallets(): EthWallet[] { - return [this.metamask, this.walletConnectEth]; + return [this.metamask, this.walletConnectEth].filter((wallet) => + this.isMobile ? wallet.mobileEnabled : true + ); } /** ### GLOBAL DEPOSIT/WITHDRAW BUTTONS AT TOP @@ -172,13 +175,8 @@ export class ObservableTransferUIConfig { this.launchWalletSelectModal(direction, balance, sourceChainKey); } ); - } else if (applicableWallets.length > 0) { - this.launchWalletSelectModal(direction, balance, sourceChainKey); } else { - console.warn( - "No non-Keplr wallets found for this bridged asset:", - balance.balance.currency.coinDenom - ); + this.launchWalletSelectModal(direction, balance, sourceChainKey); } } else { this.launchIbcTransferModal(direction, balance);