import { ConnectButton } from "@rainbow-me/rainbowkit"; import zilliqa from "./assets/zilliqa.png"; import { fromBech32Address, toBech32Address } from "@zilliqa-js/crypto"; import { validation } from "@zilliqa-js/util"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faArrowRight, faArrowUpRightFromSquare, faChevronDown, faRepeat, } from "@fortawesome/free-solid-svg-icons"; import { useEffect, useState } from "react"; import { Chains, TokenConfig, chainConfigs } from "./config/config"; import { erc20ABI, useAccount, useContractRead, useContractWrite, useNetwork, usePrepareContractWrite, useSwitchNetwork, useWaitForTransaction, } from "wagmi"; import { formatEther, formatUnits, parseUnits } from "viem"; import { Id, toast } from "react-toastify"; import { tokenManagerAbi } from "./abi/TokenManager"; type TxnType = "approve" | "bridge"; function App() { const [fromChain, setFromChain] = useState( Object.values(chainConfigs)[0].chain ); const { address: account } = useAccount(); const [toChain, setToChain] = useState( Object.values(chainConfigs)[1].chain ); const [amount, setAmount] = useState(""); const isAmountNonZero = Number(amount) > 0; const fromChainConfig = chainConfigs[fromChain]!; const toChainConfig = chainConfigs[toChain]!; const { switchNetwork } = useSwitchNetwork(); const { chain } = useNetwork(); const [latestTxn, setLatestTxn] = useState<[TxnType, `0x${string}`]>(); const [loadingId, setLoadingId] = useState(); const [recipient, setRecipient] = useState(); const [token, selectedToken] = useState( Object.values(chainConfigs)[0].tokens[0] ); useEffect(() => { switchNetwork && switchNetwork(fromChainConfig.chainId); }, [fromChainConfig, switchNetwork]); useEffect(() => { selectedToken(fromChainConfig.tokens[0]); }, [fromChain, fromChainConfig.tokens]); useEffect(() => { setRecipient(account); }, [account]); useEffect(() => { if (chain !== fromChainConfig.wagmiChain) { const newFromChain = Object.values(chainConfigs).find( (chainConfig) => chainConfig.chainId == chain?.id ); if (!newFromChain?.chain) { return; } if (newFromChain === toChainConfig) { setToChain(fromChain); } setFromChain(newFromChain?.chain); } }, [chain, fromChain, fromChainConfig.wagmiChain, toChainConfig]); const { data: decimals } = useContractRead({ abi: erc20ABI, functionName: "decimals", address: token.address, enabled: !!token.address, }); const { data: fees } = useContractRead({ abi: tokenManagerAbi, functionName: "getFees", address: fromChainConfig.tokenManagerAddress, enabled: !!fromChainConfig.tokenManagerAddress, }); const { data: paused } = useContractRead({ abi: tokenManagerAbi, functionName: "paused", address: fromChainConfig.tokenManagerAddress, enabled: !!fromChainConfig.tokenManagerAddress, }); const { data: balance } = useContractRead({ abi: erc20ABI, functionName: "balanceOf", args: account ? [account!] : undefined, address: token.address, enabled: !!account && !!token.address, watch: true, }); const { data: allowance } = useContractRead({ abi: erc20ABI, functionName: "allowance", address: token.address, args: [account!, fromChainConfig.tokenManagerAddress], enabled: !!account && !!token.address && !!fromChainConfig.tokenManagerAddress, watch: true, }); const hasEnoughAllowance = !!decimals && isAmountNonZero ? (allowance ?? 0n) >= parseUnits(amount!, decimals) : true; const hasEnoughBalance = decimals && balance ? parseUnits(amount!, decimals) <= balance : false; const validBech32Address = recipient && validation.isBech32(recipient); const validEthAddress = recipient && validation.isAddress(recipient); const hasValidAddress = recipient ? validBech32Address != validEthAddress : true; const ethRecipient = validBech32Address ? fromBech32Address(recipient) : recipient; const { config: transferConfig } = usePrepareContractWrite({ address: fromChainConfig.tokenManagerAddress, abi: tokenManagerAbi, args: [ token.address, BigInt(toChainConfig.chainId), ethRecipient as `0x${string}`, parseUnits(amount!, decimals ?? 0), ], functionName: "transfer", value: fees ?? 0n, enabled: hasEnoughAllowance, }); const { writeAsync: bridge, isLoading: isLoadingBridge } = useContractWrite(transferConfig); // From Zilliqa Bridging const { writeAsync: bridgeFromZilliqa, isLoading: isLoadingBridgeFromZilliqa, } = useContractWrite({ mode: "prepared", request: { address: fromChainConfig.tokenManagerAddress, chain: fromChainConfig.wagmiChain, account: account!, abi: tokenManagerAbi, value: fees ?? 0n, args: [ token.address, BigInt(toChainConfig.chainId), ethRecipient as `0x${string}`, parseUnits(amount!, decimals ?? 0), ], functionName: "transfer", gas: 600_000n, type: "legacy", }, }); // Approvals const { config: approveConfig } = usePrepareContractWrite({ address: token.address, abi: erc20ABI, args: [ fromChainConfig.tokenManagerAddress, parseUnits(amount!, decimals ?? 0), ], functionName: "approve", gas: fromChainConfig.isZilliqa ? 400_000n : undefined, type: fromChainConfig.isZilliqa ? "legacy" : "eip1559", }); const { writeAsync: approve, isLoading: isLoadingApprove } = useContractWrite(approveConfig); const canBridge = amount && hasValidAddress && hasEnoughAllowance && hasEnoughBalance && !paused && (fromChainConfig.isZilliqa ? !isLoadingBridgeFromZilliqa : !isLoadingBridge); const { data: txnReceipt, isLoading: isWaitingForTxn, error, refetch, } = useWaitForTransaction({ hash: latestTxn?.[1], enabled: !!latestTxn?.[1], }); useEffect(() => { if (error) { // Little hack to get Zilliqa to refetch the pending txns refetch(); } }, [error, refetch]); useEffect(() => { if (txnReceipt && loadingId && latestTxn) { let description; if (latestTxn[0] === "bridge") { description = (
Bridge request txn sent. From {fromChainConfig.name} to{" "} {toChainConfig.name} {amount} {token.name} tokens. View on{" "} window.open( `${fromChainConfig.blockExplorer}${txnReceipt.transactionHash}`, "_blank" ) } > block explorer
); setAmount(""); } else if (latestTxn[0] === "approve") { description = ( ); } else { return; } toast.update(loadingId, { render: description, type: "success", isLoading: false, }); setLoadingId(undefined); setLatestTxn(undefined); } }, [ isWaitingForTxn, txnReceipt, loadingId, latestTxn, fromChainConfig.name, fromChainConfig.blockExplorer, toChainConfig.name, amount, token.name, ]); useEffect(() => { if (!loadingId && isWaitingForTxn && latestTxn) { const id = toast.loading("Transaction being processed..."); setLoadingId(id); } }, [isWaitingForTxn, latestTxn, loadingId]); const showLoadingButton = isLoadingBridgeFromZilliqa || isLoadingBridge || isLoadingApprove || isWaitingForTxn; return ( <>
Zilliqa Logo

BRIDGE

Networks

{fromChainConfig.name}

    {Object.values(chainConfigs) .filter( (config) => config.chain !== fromChainConfig.chain ) .map(({ chain, name }) => (
  • { if (chain === toChain) { setToChain(fromChain); } setFromChain(chain); }} > {name}
  • ))}

{toChainConfig.name}

    {Object.values(chainConfigs) .filter(({ chain }) => chain !== toChainConfig.chain) .map(({ chain, name }) => (
  • { if (chain === fromChain) { setFromChain(toChain); } setToChain(chain); blur(); }} > {name}
  • ))}
Recipient
setRecipient(target.value)} />
{!hasValidAddress && (
Invalid Address
)}
Token Balance:{" "} {balance !== undefined && decimals ? formatUnits(balance, decimals) : null}
setAmount(target.value)} />
{!hasEnoughBalance && isAmountNonZero ? (
Insufficient balance
) : ( !hasEnoughAllowance && isAmountNonZero && !!allowance && (
Insufficient allowance
) )}
{!!fees && !!amount && ( <>
)}
{!hasEnoughAllowance && hasEnoughBalance ? ( ) : ( )}
{paused && (
Warning: bridge is currently under maintenance.
)}
); } export default App;