diff --git a/apps/namadillo/src/App/Transfer/CustomAddressForm.tsx b/apps/namadillo/src/App/Transfer/CustomAddressForm.tsx index 981386511..adefb417b 100644 --- a/apps/namadillo/src/App/Transfer/CustomAddressForm.tsx +++ b/apps/namadillo/src/App/Transfer/CustomAddressForm.tsx @@ -1,5 +1,10 @@ import { Input, Stack } from "@namada/components"; +import { chain as osmosis } from "chain-registry/mainnet/osmosis"; +import { useMemo } from "react"; +import namadaShieldedSvg from "./assets/namada-shielded.svg"; +import namadaTransparentSvg from "./assets/namada-transparent.svg"; + type CustomAddressFormProps = { onChangeAddress?: (address: string | undefined) => void; customAddress?: string; @@ -13,6 +18,13 @@ export const CustomAddressForm = ({ memo, onChangeMemo, }: CustomAddressFormProps): JSX.Element => { + const iconUrl = useMemo((): string | undefined => { + if (customAddress?.startsWith("osmo")) return osmosis.logo_URIs?.svg; + if (customAddress?.startsWith("znam")) return namadaShieldedSvg; + if (customAddress?.startsWith("tnam")) return namadaTransparentSvg; + return ""; + }, [customAddress]); + return ( {onChangeAddress && ( @@ -20,7 +32,13 @@ export const CustomAddressForm = ({ label="Recipient address" value={customAddress} onChange={(e) => onChangeAddress(e.target.value)} - /> + > + {iconUrl && ( + + + + )} + )} {onChangeMemo && ( { }, {}); }, []); - const sourceAssetList: Asset[] | undefined = useMemo(() => { + const availableAssets: Asset[] | undefined = useMemo(() => { if (!chainId) return; const config = sourceChainConfig.find( (config) => config[0].chain_id === chainId @@ -87,38 +87,44 @@ export const Example = (): JSX.Element => { } }, [chainId]); + const onChangeWallet = async (wallet: WalletProvider): Promise => { + try { + await integrations[wallet.id].connect(); + setWallet(wallet.id); + if (!chainId) { + setChainId(cosmos.chain_id); + } + } catch (err) { + console.error(err); + } + }; + return ( {}} - availableWallets={Object.values(wallets)} - sourceWallet={selectedWallet ? wallets[selectedWallet] : undefined} - onChangeWallet={async (wallet: WalletProvider) => { - try { - await integrations[wallet.id].connect(); - setWallet(wallet.id); - if (!chainId) { - setChainId(cosmos.chain_id); - } - } catch (err) { - console.error(err); - } + source={{ + connected: true, + wallet: selectedWallet ? wallets[selectedWallet] : undefined, + onChangeWallet, + availableWallets: Object.values(wallets), + onChangeChain: (chain) => setChainId(chain.chain_id), + onChangeSelectedAsset: setSelectedAsset, + availableChains: Object.values(sourceChains), + availableAmount: new BigNumber(100), + chain: selectedSourceChain, + availableAssets, + selectedAsset, }} - onChangeSourceChain={(chain) => { - setChainId(chain.chain_id); + destination={{ + connected: true, + chain: namadaChain as Chain, + availableWallets: [wallets.namada!], + wallet: wallets.namada, + isShielded, + onChangeShielded: setShielded, }} - onChangeSelectedAsset={setSelectedAsset} - availableSourceChains={Object.values(sourceChains)} - availableAssets={sourceAssetList} - selectedAsset={selectedAsset} - sourceChain={selectedSourceChain} - destinationChain={namadaChain as Chain} - destinationWallet={wallets.namada} - isShielded={isShielded} - onChangeShielded={setShielded} - availableAmount={new BigNumber(100) /* Change this */} transactionFee={new BigNumber(0.01)} + onSubmitTransfer={console.log} /> ); diff --git a/apps/namadillo/src/App/Transfer/ExampleFromNamada.tsx b/apps/namadillo/src/App/Transfer/ExampleFromNamada.tsx new file mode 100644 index 000000000..e10453065 --- /dev/null +++ b/apps/namadillo/src/App/Transfer/ExampleFromNamada.tsx @@ -0,0 +1,114 @@ +import BigNumber from "bignumber.js"; +import { useEffect, useState } from "react"; + +// This can be loaded async using @chain-registry/client +import { + chain as celestia, + assets as celestiaAssets, +} from "chain-registry/mainnet/celestia"; +import { + chain as cosmos, + assets as cosmosAssets, +} from "chain-registry/mainnet/cosmoshub"; +import { + chain as dydx, + assets as dydxAssets, +} from "chain-registry/mainnet/dydx"; +import { + chain as osmosis, + assets as osmosisAssets, +} from "chain-registry/mainnet/osmosis"; +import { + chain as stargaze, + assets as stargazeAssets, +} from "chain-registry/mainnet/stargaze"; +import { + chain as stride, + assets as strideAssets, +} from "chain-registry/mainnet/stride"; + +// This will be replaced by namada registry in the future +import namadaChain from "registry/namada.json"; + +import { Asset, Chain } from "@chain-registry/types"; +import { Panel } from "@namada/components"; +import { selectedIBCChainAtom, selectedIBCWallet } from "atoms/integrations"; +import { wallets } from "integrations"; +import { useAtom } from "jotai"; +import { useMemo } from "react"; +import { TransferModule } from "./TransferModule"; + +export const ExampleFromNamada = (): JSX.Element => { + const [selectedWallet] = useAtom(selectedIBCWallet); + const [selectedAsset, setSelectedAsset] = useState(); + const [chainId] = useAtom(selectedIBCChainAtom); + + const sourceChainConfig: [Chain, Asset[]][] = [ + [cosmos, cosmosAssets.assets], + [osmosis, osmosisAssets.assets], + [celestia, celestiaAssets.assets], + [dydx, dydxAssets.assets], + [stride, strideAssets.assets], + [stargaze, stargazeAssets.assets], + ]; + + const sourceChains: Record = useMemo(() => { + return sourceChainConfig.reduce((prev, current) => { + return { + ...prev, + [current[0].chain_id]: current[0], + }; + }, {}); + }, []); + + const availableAssets: Asset[] | undefined = useMemo(() => { + if (!chainId) return; + const config = sourceChainConfig.find( + (config) => config[0].chain_id === chainId + ); + if (config) { + return config[1]; + } + }, [chainId]); + + const selectedChain = + chainId && chainId in sourceChains ? sourceChains[chainId] : undefined; + + useEffect(() => { + const config = sourceChainConfig.find( + (config) => config[0].chain_id === chainId + ); + + if (config) { + setSelectedAsset(config[1][0]); + } + }, [chainId]); + + return ( + + + + ); +}; diff --git a/apps/namadillo/src/App/Transfer/SelectedWallet.tsx b/apps/namadillo/src/App/Transfer/SelectedWallet.tsx index d73d8189f..bc8197db5 100644 --- a/apps/namadillo/src/App/Transfer/SelectedWallet.tsx +++ b/apps/namadillo/src/App/Transfer/SelectedWallet.tsx @@ -46,7 +46,7 @@ export const SelectedWallet = ({ className={clsx( "flex justify-between items-center gap-2 text-sm text-neutral-500", "font-light text-right transition-colors", - { "hover:text-neutral-400": Boolean(onClick) } + { "hover:text-neutral-400": Boolean(onClick), "cursor-auto": !onClick } )} onClick={onClick} > diff --git a/apps/namadillo/src/App/Transfer/TransferDestination.tsx b/apps/namadillo/src/App/Transfer/TransferDestination.tsx index 47c29d66f..4a66fdd6e 100644 --- a/apps/namadillo/src/App/Transfer/TransferDestination.tsx +++ b/apps/namadillo/src/App/Transfer/TransferDestination.tsx @@ -1,11 +1,10 @@ import { Chain } from "@chain-registry/types"; +import { Stack } from "@namada/components"; import { NamCurrency } from "App/Common/NamCurrency"; import { TabSelector } from "App/Common/TabSelector"; import BigNumber from "bignumber.js"; import clsx from "clsx"; import { WalletProvider } from "types"; -import namadaShieldedSvg from "./assets/namada-shielded.svg"; -import namadaTransparentSvg from "./assets/namada-transparent.svg"; import { CustomAddressForm } from "./CustomAddressForm"; import { SelectedChain } from "./SelectedChain"; import { SelectedWallet } from "./SelectedWallet"; @@ -26,23 +25,6 @@ type TransferDestinationProps = { onChangeMemo?: (address: string) => void; }; -const parseChainInfo = ( - chain?: Chain, - isShielded?: boolean -): Chain | undefined => { - if (chain?.chain_name !== "namada") { - return chain; - } - return { - ...chain, - pretty_name: isShielded ? "Namada Shielded" : "Namada Transparent", - logo_URIs: { - ...chain.logo_URIs, - svg: isShielded ? namadaShieldedSvg : namadaTransparentSvg, - }, - }; -}; - export const TransferDestination = ({ chain, wallet, @@ -83,33 +65,45 @@ export const TransferDestination = ({ )} {onToggleCustomAddress && ( - onToggleCustomAddress(!customAddressActive)} - items={[ - { id: "my-address", text: "My Address", className: "text-white" }, - { id: "custom", text: "Custom Address", className: "text-white" }, - ]} - /> + )} -
- - {wallet && } -
+ {!customAddressActive && ( +
+ + {wallet && } +
+ )} {customAddressActive && ( - + + + + )} {transactionFee && ( diff --git a/apps/namadillo/src/App/Transfer/TransferModule.tsx b/apps/namadillo/src/App/Transfer/TransferModule.tsx index 35658dc7f..6bc1aa1d9 100644 --- a/apps/namadillo/src/App/Transfer/TransferModule.tsx +++ b/apps/namadillo/src/App/Transfer/TransferModule.tsx @@ -9,49 +9,41 @@ import { SelectWalletModal } from "./SelectWalletModal"; import { TransferArrow } from "./TransferArrow"; import { TransferDestination } from "./TransferDestination"; import { TransferSource } from "./TransferSource"; +import { parseChainInfo } from "./common"; -type TransferModuleProps = { - isConnected: boolean; - availableAmount?: BigNumber; +export type TransferModuleConfig = { + wallet?: WalletProvider; availableWallets: WalletProvider[]; - onSubmitTransfer: () => void; onChangeWallet?: (wallet: WalletProvider) => void; - sourceWallet?: WalletProvider; - availableSourceChains?: Chains; - sourceChain?: Chain; - onChangeSourceChain?: (chain: Chain) => void; - availableDestinationChains?: Chains; - destinationChain?: Chain; - destinationWallet?: WalletProvider; - onChangeDestinationChain?: (chain: Chain) => void; - selectedAsset?: Asset; + connected?: boolean; + availableChains?: Chains; + chain?: Chain; + onChangeChain?: (chain: Chain) => void; + isShielded?: boolean; +}; + +export type TransferSourceProps = TransferModuleConfig & { availableAssets?: Asset[]; + selectedAsset?: Asset; + availableAmount?: BigNumber; onChangeSelectedAsset?: (asset: Asset | undefined) => void; - isShielded?: boolean; - onChangeShielded?: (isShielded: boolean) => void; +}; + +type TransferDestinationProps = TransferModuleConfig & { enableCustomAddress?: boolean; + onChangeShielded?: (shielded: boolean) => void; +}; + +type TransferModuleProps = { + source: TransferSourceProps; + destination: TransferDestinationProps; transactionFee?: BigNumber; + onSubmitTransfer: () => void; }; export const TransferModule = ({ - isConnected, - selectedAsset, - availableAssets, - onChangeSelectedAsset, - availableSourceChains, - sourceChain, - onChangeSourceChain, - availableDestinationChains, - destinationChain, - destinationWallet, - onChangeDestinationChain, - isShielded, - onChangeShielded, - enableCustomAddress, - onChangeWallet, - availableWallets, - sourceWallet, - availableAmount, + source, + destination, transactionFee, }: TransferModuleProps): JSX.Element => { const [providerSelectorModalOpen, setProviderSelectorModalOpen] = @@ -67,11 +59,11 @@ export const TransferModule = ({ const validateTransfer = (): boolean => { if (!amount || amount.eq(0)) return false; - if (!sourceWallet || !sourceChain || !selectedAsset) return false; - if (!destinationWallet || !destinationChain) return false; + if (!source.wallet || !source.chain || !source.selectedAsset) return false; + if (!destination.wallet || !destination.chain) return false; if ( - !availableAmount || - availableAmount.lt(amount.plus(transactionFee || 0)) + !source.availableAmount || + source.availableAmount.lt(amount.plus(transactionFee || 0)) ) { return false; } @@ -88,28 +80,42 @@ export const TransferModule = ({
setProviderSelectorModalOpen(true)} - openChainSelector={() => setSourceChainModalOpen(true)} - openAssetSelector={() => setAssetSelectorModalOpen(true)} + isConnected={Boolean(source.connected)} + asset={source.selectedAsset} + chain={parseChainInfo(source.chain, source.isShielded)} + wallet={source.wallet} + availableAmount={source.availableAmount} amount={amount} - availableAmount={availableAmount} + openProviderSelector={ + source.onChangeWallet ? + () => setProviderSelectorModalOpen(true) + : undefined + } + openChainSelector={ + source.onChangeChain ? + () => setSourceChainModalOpen(true) + : undefined + } + openAssetSelector={ + source.onChangeSelectedAsset ? + () => setAssetSelectorModalOpen(true) + : undefined + } onChangeAmount={setAmount} /> - + - {sourceWallet ? "Submit" : "Select Wallet"} + {source.wallet ? "Submit" : "Select Wallet"}
- {providerSelectorModalOpen && onChangeWallet && ( + + {providerSelectorModalOpen && source.onChangeWallet && ( setProviderSelectorModalOpen(false)} - onConnect={onChangeWallet} + onConnect={source.onChangeWallet} /> )} - {sourceChainModalOpen && onChangeSourceChain && sourceWallet && ( + + {assetSelectorModalOpen && + source.onChangeSelectedAsset && + source.wallet && ( + setAssetSelectorModalOpen(false)} + assets={source.availableAssets || []} + onSelect={source.onChangeSelectedAsset} + /> + )} + + {sourceChainModalOpen && source.onChangeChain && source.wallet && ( setSourceChainModalOpen(false)} - chains={availableSourceChains || []} - onSelect={onChangeSourceChain} + chains={source.availableChains || []} + onSelect={source.onChangeChain} /> )} + {destinationChainModalOpen && - onChangeDestinationChain && - destinationWallet && ( + destination.onChangeChain && + destination.wallet && ( setDestinationChainModalOpen(false)} - chains={availableDestinationChains || []} - onSelect={onChangeDestinationChain} + chains={destination.availableChains || []} + onSelect={destination.onChangeChain} + /> + )} + + {assetSelectorModalOpen && + source.onChangeSelectedAsset && + source.wallet && ( + setAssetSelectorModalOpen(false)} + assets={source.availableAssets || []} + onSelect={source.onChangeSelectedAsset} /> )} - {assetSelectorModalOpen && onChangeSelectedAsset && sourceWallet && ( - setAssetSelectorModalOpen(false)} - assets={availableAssets || []} - onSelect={onChangeSelectedAsset} - /> - )} ); }; diff --git a/apps/namadillo/src/App/Transfer/__tests__/TransferDestination.test.tsx b/apps/namadillo/src/App/Transfer/__tests__/TransferDestination.test.tsx index 6cd71963d..1c2d5427a 100644 --- a/apps/namadillo/src/App/Transfer/__tests__/TransferDestination.test.tsx +++ b/apps/namadillo/src/App/Transfer/__tests__/TransferDestination.test.tsx @@ -7,6 +7,7 @@ import { import { TransferDestination } from "App/Transfer/TransferDestination"; import BigNumber from "bignumber.js"; import { walletMock } from "../__mocks__/providers"; +import { parseChainInfo } from "../common"; describe("TransferDestination", () => { it("should render the component with the default props", () => { @@ -19,7 +20,7 @@ describe("TransferDestination", () => { ); expect(screen.getByText("Shielded")).toBeInTheDocument(); @@ -35,7 +36,7 @@ describe("TransferDestination", () => { render( ); @@ -46,7 +47,7 @@ describe("TransferDestination", () => { render( ); @@ -57,7 +58,7 @@ describe("TransferDestination", () => { render( ); @@ -69,7 +70,7 @@ describe("TransferDestination", () => { render( ); diff --git a/apps/namadillo/src/App/Transfer/common.ts b/apps/namadillo/src/App/Transfer/common.ts new file mode 100644 index 000000000..4aee45843 --- /dev/null +++ b/apps/namadillo/src/App/Transfer/common.ts @@ -0,0 +1,23 @@ +import { Chain } from "@chain-registry/types"; +import namadaShieldedSvg from "./assets/namada-shielded.svg"; +import namadaTransparentSvg from "./assets/namada-transparent.svg"; + +export const parseChainInfo = ( + chain?: Chain, + isShielded?: boolean +): Chain | undefined => { + if (!chain) return undefined; + + if (chain.chain_name !== "namada") { + return chain; + } + + return { + ...chain, + pretty_name: isShielded ? "Namada Shielded" : "Namada Transparent", + logo_URIs: { + ...chain.logo_URIs, + svg: isShielded ? namadaShieldedSvg : namadaTransparentSvg, + }, + }; +};