import React, { useEffect, useCallback, useState } from "react"; import { Box, FormControl, HStack, Pressable, Spinner, Text, VStack, WarningOutlineIcon } from "native-base"; import { CurrencyValue } from "@usedapp/core"; import { SupportedChains, useG$Amounts, useG$Balance } from "@gooddollar/web3sdk-v2"; import { isEmpty } from "lodash"; import { Web3ActionButton } from "../../advanced"; import { Image, TokenInput, TokenOutput } from "../../core"; import { BigNumber } from "ethers"; import { GdAmount } from "../../core/layout/BalanceGD"; import ArrowTabLightRight from "../../assets/svg/arrow-tab-light-right.svg"; import type { IFees, ILimits, MicroBridgeProps } from "./types"; import { useWizard } from "react-use-wizard"; const useBridgeEstimate = ({ limits, fees, sourceChain, inputWei }: { limits?: ILimits; fees?: IFees; sourceChain: string; inputWei: string; }): { expectedFee: CurrencyValue; expectedToReceive: CurrencyValue; minimumAmount: CurrencyValue; maximumAmount: CurrencyValue; bridgeFee: CurrencyValue; minFee: CurrencyValue; maxFee: CurrencyValue; // minAmountWei: CurrencyValue; } => { const chain = sourceChain === "celo" ? 42220 : 122; const { minimumAmount, maximumAmount, bridgeFee, minFee, maxFee, input } = useG$Amounts( { minimumAmount: limits?.[sourceChain]?.minAmount, maximumAmount: limits?.[sourceChain]?.txLimit, bridgeFee: fees?.[sourceChain]?.fee, minFee: fees?.[sourceChain]?.minFee, maxFee: fees?.[sourceChain]?.maxFee, input: BigNumber.from(inputWei) }, "G$", chain ); //bridge fee is in BPS so divide by 10000 const expectedFee = bridgeFee.mul(input.value).div(10000); const expectedToReceive = input.sub(expectedFee.gt(minFee) ? expectedFee : minFee); return { expectedFee, expectedToReceive, minimumAmount, maximumAmount, bridgeFee, minFee, maxFee }; }; export const MicroBridge = ({ useCanBridge, onSetChain, originChain, inputTransaction, pendingTransaction, limits, fees, bridgeStatus, relayStatus, selfRelayStatus, onBridgeStart, onBridgeFailed, onBridgeSuccess }: MicroBridgeProps) => { const [isBridging, setBridging] = useState(false); const { nextStep } = useWizard(); const [sourceChain, setSourceChain] = originChain; const targetChain = sourceChain === "fuse" ? "celo" : "fuse"; const [toggleState, setToggleState] = useState(false); // query balances every 5 blocks, so balance is updated after bridging const { G$: fuseBalance } = useG$Balance(5, 122); const { G$: celoBalance } = useG$Balance(5, 42220); const gdValue = sourceChain === "fuse" ? fuseBalance : celoBalance; const wei = gdValue.value.toString(); const [bridgeWeiAmount, setBridgeAmount] = inputTransaction; const [, setPendingTransaction] = pendingTransaction; const { isValid, reason } = useCanBridge(sourceChain, bridgeWeiAmount); const { minimumAmount, expectedToReceive } = useBridgeEstimate({ limits, fees, inputWei: bridgeWeiAmount, sourceChain }); const hasBalance = Number(bridgeWeiAmount) <= Number(wei); const isValidInput = isValid && hasBalance; const reasonOf = reason || (!hasBalance && "Not enough balance") || ""; const toggleChains = useCallback(() => { setSourceChain(targetChain); setToggleState(prevState => !prevState); onSetChain?.(targetChain); }, [setSourceChain, onSetChain, targetChain]); const triggerBridge = useCallback(async () => { setPendingTransaction({ bridgeWeiAmount, expectedToReceive }); onBridgeStart?.(); }, [setBridging, onBridgeStart, bridgeWeiAmount, sourceChain, expectedToReceive]); useEffect(() => { const { status = "", errorMessage, errorCode } = relayStatus ?? {}; const { status: selfStatus = "" } = selfRelayStatus ?? {}; // if self relay succeeded then we are done, other wise we need to wait for relayStatus const isSuccess = [status, selfStatus].includes("Success"); // if bridge relayer failed or succeeded then we are done const isFailed = ["Fail", "Exception"].includes(status); // when bridge is signing, mining or succeed but relay not done yet - we're still bridging // once relay has completed (success or fail), don't treat as bridging so user can start another const bridgeInProgress = ["Mining", "PendingSignature", "Success"].includes(bridgeStatus?.status ?? ""); const stillBridging = bridgeInProgress && !isFailed && !isSuccess; setBridging(stillBridging); // show next step only once user signs tx if (bridgeStatus?.status === "Mining") { void nextStep(); } if (isSuccess) { onBridgeSuccess?.(); } if (isFailed) { const exception = new Error(errorMessage ?? "Failed to bridge"); if (errorCode) { exception.name = `BridgeError #${errorCode}`; } onBridgeFailed?.(exception); } }, [relayStatus, bridgeStatus, selfRelayStatus, onBridgeSuccess, onBridgeFailed]); const reasonMinAmount = reason === "minAmount" ? ` Minimum amount is ${Number(minimumAmount.value) / (sourceChain === "fuse" ? 1e2 : 1e18)} G$` : undefined; const getActiveColor = useCallback( (chain: string) => { return sourceChain === chain ? "goodGrey.700" : "goodGrey.400"; }, [sourceChain] ); if (isEmpty(fuseBalance) || isEmpty(celoBalance)) return ; const celoActiveColor = getActiveColor("celo"); const fuseActiveColor = getActiveColor("fuse"); return ( G$ Celo G$ Fuse }> {reasonMinAmount ?? reasonOf} You will receive on {targetChain} Minimum amount to bridge:{" "} {(Number(minimumAmount.value) / (sourceChain === "fuse" ? 1e2 : 1e18)).toLocaleString()} G$ Bridge Fee: G$ 10 or 0.15% of transaction (See FAQs for more on Fees) ); };