import React, { FC, useCallback, useEffect, useState } from "react";
import {
G$Amount,
SupportedChains,
useBridge,
useBridgeHistory,
useGetBridgeData,
useGetEnvChainId,
useRelayTx,
useWithinBridgeLimits
} from "@gooddollar/web3sdk-v2";
import moment from "moment";
import { ethers } from "ethers";
import { useEthers } from "@usedapp/core";
import { noop } from "lodash";
import {
ArrowForwardIcon,
Box,
Button,
FlatList,
Flex,
Heading,
HStack,
Skeleton,
Spinner,
Stack,
VStack,
Text
} from "native-base";
import { GdAmount, Title } from "../../core/layout";
import { ExplorerLink } from "../../core/web3/ExplorerLink";
import { BridgeWizard } from "./wizard/BridgeWizard";
import { truncateMiddle } from "../../utils";
const FUSE_CHAIN_ID = 122;
const CELO_CHAIN_ID = 42220;
/** Maps bridge target chain id to source/target chain ids and display names. */
function getBridgeChainInfo(targetChainId: number) {
const isTargetFuse = targetChainId === FUSE_CHAIN_ID;
return {
sourceChainId: isTargetFuse ? CELO_CHAIN_ID : FUSE_CHAIN_ID,
targetChainId,
sourceChainName: isTargetFuse ? "Celo" : "Fuse",
targetChainName: isTargetFuse ? "Fuse" : "Celo"
};
}
const useCanBridge = (chain: "fuse" | "celo", amountWei: string) => {
const { chainId } = useGetEnvChainId(chain === "fuse" ? SupportedChains.FUSE : SupportedChains.CELO);
const { account } = useEthers();
const canBridge = useWithinBridgeLimits(chainId, account || "", amountWei);
return canBridge;
};
const BridgeHistoryWithRelay = () => {
const { historySorted } = useBridgeHistory() ?? {};
const relayTx = useRelayTx();
const [relaying, setRelaying] = useState<{ [key: string]: boolean }>({});
const triggerRelay = useCallback(
async (i: any) => {
setRelaying({ ...relaying, [i.transactionHash]: true });
try {
const { sourceChainId, targetChainId } = getBridgeChainInfo(i.data.targetChainId.toNumber());
const relayResult = await relayTx(sourceChainId, targetChainId, i.transactionHash);
if (!relayResult.relayPromise) {
setRelaying({ ...relaying, [i.transactionHash]: false });
}
} catch (e) {
//todo: add analytics error log
setRelaying({ ...relaying, [i.transactionHash]: false });
}
},
[setRelaying, relayTx]
);
return (
Transactions History
Transaction Hash
From
To
Amount
Status
{historySorted ? (
historySorted.map(i => {
const chainInfo = getBridgeChainInfo(i.data.targetChainId.toNumber());
return (
{chainInfo.sourceChainName}
{chainInfo.targetChainName}
{i.amount} G$
{(i as any).relayEvent ? (
) : (
)}
);
})
) : (
)}
);
};
/** Table column widths - fixed % so header and rows stay aligned */
const COL_WIDTH_DATE = "18%";
const COL_WIDTH_AMOUNT = "32%";
const COL_WIDTH_STATUS = "50%";
const TABLE_PADDING_X = 4;
const HistoryRowItem = ({ item, env }: { item: any; env: string }) => {
const { amount, data, relayEvent, transactionHash } = item;
const { targetChainId, timestamp } = data || {};
const { fee } = relayEvent?.data || {};
const date = moment(ethers.BigNumber.from(timestamp).toNumber() * 1000).utc();
const dateFormatted = date.format("DD.MM.YY");
const dateHours = date.format("HH:mm");
const targetChain = parseInt(targetChainId);
const { sourceChainId: sourceChain } = getBridgeChainInfo(targetChain);
const feeFormatted = fee && targetChain ? G$Amount("G$", ethers.BigNumber.from(fee), targetChain, env) : null;
return (
{dateFormatted}
{dateHours}
G$ {amount}
{feeFormatted ? (
Fees:{" "}
{`G$ `}
) : (
)}
{relayEvent?.transactionHash && targetChain ? (
<>
Completed
{`(Bridged to ${SupportedChains[targetChain]})`}
>
) : (
<>
{`Waiting for bridge relayers to relay to target chain... (Can take a few minutes)`}
{`(Bridging from ${SupportedChains[sourceChain]})`}
>
)}
);
};
const BridgeHistory = ({ env }: { env: string }) => {
const { historySorted } = useBridgeHistory() ?? {};
return (
Recent Transactions
It may take up to 30 seconds to load transactions.
{/* Table header - same widths and padding as rows for column alignment */}
Date
Amount
Status
{/* Table body - FlatList so each row aligns under the header */}
{!historySorted ? (
) : (
item.transactionHash ?? String(Math.random())}
renderItem={({ item }) => (
)}
maxH="250"
scrollEnabled={true}
_contentContainerStyle={{ width: "100%", minWidth: "100%" }}
/>
)}
);
};
interface IMicroBridgeControllerProps {
withRelay?: boolean;
withHistory?: boolean;
onBridgeStart?: () => void;
onBridgeSuccess?: () => void;
onBridgeFailed?: (e: Error) => void;
}
export const MicroBridgeController: FC = ({
withRelay = false,
withHistory = true,
// onBridgeStart = noop,
onBridgeSuccess = noop,
onBridgeFailed = noop
}: IMicroBridgeControllerProps) => {
const { chainId } = useEthers();
const { defaultEnv } = useGetEnvChainId();
const { sendBridgeRequest, bridgeRequestStatus, relayStatus, selfRelayStatus } = useBridge();
const { bridgeFees: fuseBridgeFees, bridgeLimits: fuseBridgeLimits } = useGetBridgeData(SupportedChains.FUSE, "");
const { bridgeFees: celoBridgeFees, bridgeLimits: celoBridgeLimits } = useGetBridgeData(SupportedChains.CELO, "");
const inputTransaction = useState("0");
const pendingTransaction = useState(false);
const originChain = useState<"fuse" | "celo">(chainId === 122 ? "fuse" : "celo");
const [error, setError] = useState(null);
const onBridgeStart = useCallback(async () => {
const [inputWei] = inputTransaction;
try {
await sendBridgeRequest(inputWei, originChain[0]);
} catch (e: any) {
onBridgeFailed(e);
}
}, [inputTransaction, originChain]);
useEffect(() => {
void (async () => {
if (bridgeRequestStatus.status === "Exception") {
setError(bridgeRequestStatus.errorMessage as string);
}
})();
}, [bridgeRequestStatus]);
if (!fuseBridgeFees || !celoBridgeFees) return ;
return (
<>
{withRelay && }
{withHistory && }
>
);
};