diff --git a/apps/ui/src/components/WormholeForm.tsx b/apps/ui/src/components/WormholeForm.tsx new file mode 100644 index 000000000..b036e04fa --- /dev/null +++ b/apps/ui/src/components/WormholeForm.tsx @@ -0,0 +1,232 @@ +import type { ChainId } from "@certusone/wormhole-sdk"; +import { CHAIN_ID_TO_NAME } from "@certusone/wormhole-sdk"; +import type { EuiSelectOption } from "@elastic/eui"; +import { + EuiButton, + EuiForm, + EuiFormRow, + EuiSelect, + EuiSpacer, +} from "@elastic/eui"; +import { findOrThrow } from "@swim-io/utils"; +import Decimal from "decimal.js"; +import type { ReactElement } from "react"; +import { useEffect, useMemo, useState } from "react"; + +import { wormholeTokens as rawWormholeTokens } from "../config"; +import { useWormholeTransfer } from "../hooks"; +import type { TxResult, WormholeToken, WormholeTokenDetails } from "../models"; +import { generateId } from "../models"; + +import { EuiFieldIntlNumber } from "./EuiFieldIntlNumber"; + +const getDetailsByChainId = ( + token: WormholeToken, + chainId: ChainId, +): WormholeTokenDetails => + findOrThrow( + [token.nativeDetails, ...token.wrappedDetails], + (details) => details.chainId === chainId, + ); + +export const WormholeForm = (): ReactElement => { + const wormholeTokens = rawWormholeTokens as readonly WormholeToken[]; + const [currentTokenSymbol, setCurrentTokenSymbol] = useState( + wormholeTokens[0].symbol, + ); + const currentToken = findOrThrow( + wormholeTokens, + (token) => token.symbol === currentTokenSymbol, + ); + const tokenOptions: readonly EuiSelectOption[] = wormholeTokens.map( + (token) => ({ + value: token.symbol, + text: `${token.displayName} (${token.symbol})`, + selected: token.symbol === currentTokenSymbol, + }), + ); + + const sourceChains = useMemo( + () => [ + currentToken.nativeDetails.chainId, + ...currentToken.wrappedDetails.map(({ chainId }) => chainId), + ], + [currentToken], + ); + const [sourceChainId, setSourceChainId] = useState(sourceChains[0]); + const sourceChainOptions = sourceChains.map((chainId) => ({ + value: chainId, + text: CHAIN_ID_TO_NAME[chainId], + selected: chainId === sourceChainId, + })); + + const targetChains = useMemo( + () => sourceChains.filter((option) => option !== sourceChainId), + [sourceChains, sourceChainId], + ); + const [targetChainId, setTargetChainId] = useState(targetChains[0]); + const targetChainOptions = targetChains.map((chainId) => ({ + value: chainId, + text: CHAIN_ID_TO_NAME[chainId], + selected: chainId === targetChainId, + })); + + const [formInputAmount, setFormInputAmount] = useState(""); + const [inputAmount, setInputAmount] = useState(new Decimal(0)); + + const [txResults, setTxResults] = useState([]); + + const { mutateAsync: transfer, isLoading } = useWormholeTransfer(); + + const handleTxResult = (txResult: TxResult): void => { + setTxResults((previousResults) => [...previousResults, txResult]); + }; + + const handleSubmit = () => { + (async (): Promise => { + setTxResults([]); + const sourceDetails = getDetailsByChainId(currentToken, sourceChainId); + const targetDetails = getDetailsByChainId(currentToken, targetChainId); + await transfer({ + interactionId: generateId(), + value: inputAmount, + sourceDetails, + targetDetails, + nativeDetails: currentToken.nativeDetails, + onTxResult: handleTxResult, + }); + })().catch(console.error); + }; + + useEffect(() => { + setSourceChainId(sourceChains[0]); + }, [sourceChains]); + + useEffect(() => { + if (targetChainId === sourceChainId) { + setTargetChainId(targetChains[0]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [targetChains]); + + return ( + + {/* These tables are only to show what data is available */} + + + + + + + + + + {currentToken.logo && ( + + + + + )} + + + + + + + + + + + + +
{"Symbol"}{currentToken.symbol}
{"Name"}{currentToken.displayName}
{"Logo"} + {currentToken.displayName} +
{"Source Chain"}{sourceChainId}
{"Target Chain"}{targetChainId}
{"Loading?"}{isLoading.toString()}
+ {txResults.length > 0 && ( + <> +

Tx results

+ + + + + + {txResults.map(({ chainId, txId }) => ( + + + + + ))} +
Chain IDTx ID
{chainId}{txId}
+ + )} + + + + + { + setCurrentTokenSymbol(event.target.value); + }} + /> + + + + + + { + const newSourceChainId = parseInt( + event.target.value, + 10, + ) as ChainId; + setSourceChainId(newSourceChainId); + }} + /> + + + + + + { + const newTargetChainId = parseInt( + event.target.value, + 10, + ) as ChainId; + setTargetChainId(newTargetChainId); + }} + /> + + + + + + { + setInputAmount(new Decimal(formInputAmount)); + }} + /> + + + + + + + {"Transfer"} + + +
+ ); +};