diff --git a/src/components/Modal/tBTC/InitiateBridging.tsx b/src/components/Modal/tBTC/InitiateBridging.tsx new file mode 100644 index 000000000..eb3379061 --- /dev/null +++ b/src/components/Modal/tBTC/InitiateBridging.tsx @@ -0,0 +1,143 @@ +import { + BodyLg, + BodySm, + Button, + Divider, + H5, + List, + ModalBody, + ModalFooter, + ModalHeader, + Skeleton, + Box, +} from "@threshold-network/components" +import { FC } from "react" +import { BaseModalProps } from "../../../types" +import InfoBox from "../../InfoBox" +import { InlineTokenBalance } from "../../TokenBalance" +import { + TransactionDetailsAmountItem, + TransactionDetailsItem, +} from "../../TransactionDetails" +import ModalCloseButton from "../ModalCloseButton" +import withBaseModal from "../withBaseModal" +import { PosthogButtonId } from "../../../types/posthog" +import SubmitTxButton from "../../SubmitTxButton" +import { BridgeRoute } from "../../../threshold-ts/bridge" +import { BigNumber } from "ethers" +import { formatUnits } from "@ethersproject/units" + +type InitiateBridgingProps = { + amount: string + fromNetwork: string + toNetwork: string + bridgeRoute: BridgeRoute + estimatedFee: BigNumber | null + estimatedTime: number + onConfirm: () => Promise +} & BaseModalProps + +const InitiateBridgingBase: FC = ({ + closeModal, + amount, + fromNetwork, + toNetwork, + bridgeRoute, + estimatedFee, + estimatedTime, + onConfirm, +}) => { + const formatTime = (seconds: number): string => { + if (seconds < 3600) { + return `~${Math.round(seconds / 60)} minutes` + } else if (seconds < 86400) { + return `~${Math.round(seconds / 3600)} hours` + } else { + return `~${Math.round(seconds / 86400)} days` + } + } + + const handleConfirm = () => { + try { + // Close immediately, run in background + closeModal() + void onConfirm() + } catch (error) { + console.error("Bridge transaction failed:", error) + } + } + + const bridgeTypeText = + bridgeRoute === "ccip" ? "CCIP Bridge" : "Standard Bridge" + const feeAmount = estimatedFee ? formatUnits(estimatedFee, 18) : "0" + + return ( + <> + Initiate {bridgeTypeText} + + + +
+ You are bridging{" "} + +
+ + From {fromNetwork} to {toNetwork} using {bridgeTypeText}. This will + take approximately {formatTime(estimatedTime)}. + +
+ + + + + + + + + + {bridgeRoute === "ccip" ? ( + <> + Your tokens will be bridged using Chainlink CCIP. The transaction + will be viewable on the CCIP Explorer. + + ) : ( + <> + Your tokens will be bridged using the Standard Bridge. This + process takes 7 days to complete. + + )} + + +
+ + + Confirm Bridge + + + ) +} + +export const InitiateBridging = withBaseModal(InitiateBridgingBase) diff --git a/src/components/Modal/tBTC/index.ts b/src/components/Modal/tBTC/index.ts index d21aa8012..e48e09c11 100644 --- a/src/components/Modal/tBTC/index.ts +++ b/src/components/Modal/tBTC/index.ts @@ -1,3 +1,4 @@ export * from "./NewTBTCApp" export * from "./GenerateNewDepositAddress" export * from "./InitiateUnminting" +export * from "./InitiateBridging" diff --git a/src/components/Navbar/NetworkButton.tsx b/src/components/Navbar/NetworkButton.tsx index 46bb9ed58..b2bca49dc 100644 --- a/src/components/Navbar/NetworkButton.tsx +++ b/src/components/Navbar/NetworkButton.tsx @@ -50,20 +50,28 @@ const getNetworkIcon = (chainId: number, colorMode: string): NetworkIconMap => { icon: , bg: grayBackground, }, + [SupportedChainIds.Bob]: { + icon: , + bg: grayBackground, + }, } : { [SupportedChainIds.Sepolia]: { icon: , bg: grayBackground, }, - // [SupportedChainIds.ArbitrumSepolia]: { - // icon: , - // bg: grayBackground, - // }, - // [SupportedChainIds.BaseSepolia]: { - // icon: , - // bg: "blue.500", - // }, + [SupportedChainIds.ArbitrumSepolia]: { + icon: , + bg: grayBackground, + }, + [SupportedChainIds.BaseSepolia]: { + icon: , + bg: "blue.500", + }, + [SupportedChainIds.BobSepolia]: { + icon: , + bg: grayBackground, + }, }), } diff --git a/src/components/SubNavigationPills/index.tsx b/src/components/SubNavigationPills/index.tsx index f5f953d0b..90acff289 100644 --- a/src/components/SubNavigationPills/index.tsx +++ b/src/components/SubNavigationPills/index.tsx @@ -146,7 +146,7 @@ const getPathMatches = (pills: RouteProps[], locationPathname: string) => { : `${parentPathBase}/${path}` ) const match = matchPath( - { path: resolved.pathname, end: true }, + { path: resolved.pathname, end: false }, currentPathname ) pathMatches.push({ @@ -168,16 +168,15 @@ const getActivePillIndex = (pills: RouteProps[], locationPathname: string) => { if (matchedPaths.length === 0) return undefined if (matchedPaths.length === 1) return matchedPaths[0].index - const matchedElementWithLongestPathnameBase = matchedPaths.reduce( + const matchedElementWithLongestPath = matchedPaths.reduce( (maxElement, currentElement) => { - return currentElement.match.pathnameBase.length > - maxElement.match.pathnameBase.length + return currentElement.resolvedPath.length > maxElement.resolvedPath.length ? currentElement : maxElement } ) - return matchedElementWithLongestPathnameBase.index + return matchedElementWithLongestPath.index } export default SubNavigationPills diff --git a/src/components/tBTC/BridgeActivity.tsx b/src/components/tBTC/BridgeActivity.tsx index 1f7795353..1b7bd99b4 100644 --- a/src/components/tBTC/BridgeActivity.tsx +++ b/src/components/tBTC/BridgeActivity.tsx @@ -30,11 +30,23 @@ import { useIsActive } from "../../hooks/useIsActive" import { useNonEVMConnection } from "../../hooks/useNonEVMConnection" export type BridgeActivityProps = { - data: BridgeActivityType[] + data: (BridgeActivityType | BridgeActivityTypeExtended)[] isFetching: boolean emptyState?: ReactElement } +// Extended type to support bridge activities +export type BridgeActivityTypeExtended = BridgeActivityType & { + additionalData?: { + bridgeRoute?: string + fromNetwork?: string + toNetwork?: string + explorerUrl?: string + redeemerOutputScript?: string + walletPublicKeyHash?: string + } +} + type BridgeActivityContextValue = { [Property in keyof BridgeActivityProps]-?: BridgeActivityProps[Property] } & { isBridgeHistoryEmpty: boolean } @@ -101,7 +113,7 @@ export const BridgeActivityData: FC = (props) => { ) } -const ActivityItem: FC = ({ +const ActivityItem: FC = ({ amount, status, activityKey, @@ -111,20 +123,28 @@ const ActivityItem: FC = ({ }) => { const { account } = useIsActive() - const link = - bridgeProcess === "unmint" - ? RedemptionDetailsLinkBuilder.createFromTxHash(txHash) - .withRedeemer(account!) - .withRedeemerOutputScript( - (additionalData as UnmintBridgeActivityAdditionalData) - .redeemerOutputScript - ) - .withWalletPublicKeyHash( - (additionalData as UnmintBridgeActivityAdditionalData) - .walletPublicKeyHash - ) - .build() - : `/tBTC/mint/deposit/${activityKey}` + let link: string + let isExternal = false + + // Check if this is a bridge activity with explorerUrl + if (additionalData?.explorerUrl) { + link = additionalData.explorerUrl + isExternal = true + } else if (bridgeProcess === "unmint") { + link = RedemptionDetailsLinkBuilder.createFromTxHash(txHash) + .withRedeemer(account!) + .withRedeemerOutputScript( + (additionalData as UnmintBridgeActivityAdditionalData) + .redeemerOutputScript + ) + .withWalletPublicKeyHash( + (additionalData as UnmintBridgeActivityAdditionalData) + .walletPublicKeyHash + ) + .build() + } else { + link = `/tBTC/deposit/mint/deposit/${activityKey}` + } return ( @@ -134,6 +154,7 @@ const ActivityItem: FC = ({ _hover={{ textDecoration: "none" }} color="inherit" to={link} + isExternal={isExternal} > @@ -142,8 +163,13 @@ const ActivityItem: FC = ({ ) } -const renderActivityItem = (item: BridgeActivityType) => ( - +const renderActivityItem = ( + item: BridgeActivityType | BridgeActivityTypeExtended +) => ( + ) const bridgeActivityStatusToBadgeProps: Record< diff --git a/src/enums/modal.ts b/src/enums/modal.ts index bd03dc5fe..0d89b87ad 100644 --- a/src/enums/modal.ts +++ b/src/enums/modal.ts @@ -38,4 +38,5 @@ export enum ModalType { InitiateUnminting = "INITIATE_UNMINTING", TACoCommitment = "TACO_COMMITMENT", TACoCommitmentSuccess = "TACO_COMMITMENT_SUCCESS", + InitiateBridging = "INITIATE_BRIDGING", } diff --git a/src/hooks/tbtc/index.ts b/src/hooks/tbtc/index.ts index 22ee9ccd1..95d7f8cb8 100644 --- a/src/hooks/tbtc/index.ts +++ b/src/hooks/tbtc/index.ts @@ -13,3 +13,5 @@ export * from "./useTBTCVaultContract" export * from "./useSubscribeToRedemptionsCompletedEvent" export * from "./useFindRedemptionInBitcoinTx" export * from "./useStarknetTBTCBalance" +export * from "./useBridge" +export * from "./useBridgeActivity" diff --git a/src/hooks/tbtc/useBridge.ts b/src/hooks/tbtc/useBridge.ts new file mode 100644 index 000000000..f64e52ecd --- /dev/null +++ b/src/hooks/tbtc/useBridge.ts @@ -0,0 +1,271 @@ +import { useState, useEffect, useCallback, useMemo, useRef } from "react" +import { BigNumber } from "ethers" +import { parseUnits } from "@ethersproject/units" +import { useWeb3React } from "@web3-react/core" +import { useThreshold } from "../../contexts/ThresholdContext" +import { BridgeRoute, BridgeQuote } from "../../threshold-ts/bridge" +import { BridgeNetwork } from "../../pages/tBTC/BobBridge/components/NetworkSelector" +import { SupportedChainIds } from "../../networks/enums/networks" +import { getEthereumDefaultProviderChainId } from "../../utils/getEnvVariable" +import { useIsActive } from "../useIsActive" + +export const useBridge = () => { + const { bridge } = useThreshold() + const context = bridge.getContext() + const account = context.account + const { chainId } = useIsActive() + + // Determine default networks based on environment + const defaultChainId = getEthereumDefaultProviderChainId() + const isMainnet = defaultChainId === SupportedChainIds.Ethereum + + const defaultFromNetwork = isMainnet + ? SupportedChainIds.Ethereum + : SupportedChainIds.Sepolia + const defaultToNetwork = isMainnet + ? SupportedChainIds.Bob + : SupportedChainIds.BobSepolia + + // State + const [fromNetwork, setFromNetwork] = + useState(defaultFromNetwork) + const [toNetwork, setToNetwork] = useState(defaultToNetwork) + const [amount, setAmount] = useState("") + const [bridgeRoute, setBridgeRoute] = useState(null) + const [quote, setQuote] = useState(null) + const [isLoadingQuote, setIsLoadingQuote] = useState(false) + const [bridgingTime, setBridgingTime] = useState(null) + const [ccipAllowance, setCcipAllowance] = useState( + BigNumber.from(0) + ) + const [isLoadingAllowance, setIsLoadingAllowance] = useState(false) + + // Swap networks + const swapNetworks = useCallback(() => { + setFromNetwork(toNetwork) + setToNetwork(fromNetwork) + }, [fromNetwork, toNetwork]) + + // Handle network changes + const handleFromNetworkChange = useCallback((network: BridgeNetwork) => { + setFromNetwork(network) + // Auto-update toNetwork to maintain mainnet/testnet consistency + if ( + network === SupportedChainIds.Ethereum || + network === SupportedChainIds.Bob + ) { + // Mainnet + setToNetwork( + network === SupportedChainIds.Ethereum + ? SupportedChainIds.Bob + : SupportedChainIds.Ethereum + ) + } else { + // Testnet + setToNetwork( + network === SupportedChainIds.Sepolia + ? SupportedChainIds.BobSepolia + : SupportedChainIds.Sepolia + ) + } + }, []) + + const handleToNetworkChange = useCallback((network: BridgeNetwork) => { + setToNetwork(network) + // Auto-update fromNetwork to maintain mainnet/testnet consistency + if ( + network === SupportedChainIds.Ethereum || + network === SupportedChainIds.Bob + ) { + // Mainnet + setFromNetwork( + network === SupportedChainIds.Ethereum + ? SupportedChainIds.Bob + : SupportedChainIds.Ethereum + ) + } else { + // Testnet + setFromNetwork( + network === SupportedChainIds.Sepolia + ? SupportedChainIds.BobSepolia + : SupportedChainIds.Sepolia + ) + } + }, []) + + // Simple debounce implementation + const debounceTimerRef = useRef(null) + + // Update bridge route based on network selection + const updateBridgeRoute = useCallback(async () => { + if (!bridge) { + setBridgeRoute(null) + return + } + + const isDeposit = + fromNetwork === SupportedChainIds.Ethereum || + fromNetwork === SupportedChainIds.Sepolia + + try { + if (isDeposit) { + // For deposits, always use CCIP + setBridgeRoute("ccip") + } else { + const amountBN = amount ? parseUnits(amount, 18) : undefined + + // For withdrawals from Bob, determine best route + // Using a small amount just to determine the default + // route + const testAmount = parseUnits("0.01", 18) + const route = await bridge.pickPath(amountBN ?? testAmount) + setBridgeRoute(route) + } + } catch (error) { + console.error("Failed to determine bridge route:", error) + setBridgeRoute(null) + } + }, [amount, bridge, fromNetwork]) + + // Update quote based on amount and route + const updateQuote = useCallback(async () => { + if (!amount || !bridge || !bridgeRoute) { + setQuote(null) + return + } + + // Check if user is on the correct network + if (chainId !== fromNetwork) { + setQuote(null) + return + } + + try { + setIsLoadingQuote(true) + const amountBN = parseUnits(amount, 18) + + // Just get quote for the current route + const quote = await bridge.quoteFees(amountBN) + setQuote(quote) + } catch (error) { + console.error("Failed to get bridge quote:", error) + setQuote(null) + } finally { + setIsLoadingQuote(false) + } + }, [amount, bridge, bridgeRoute, fromNetwork, chainId]) + + // Update CCIP allowance + const updateCcipAllowance = useCallback(async () => { + if (!bridge || !account) { + setCcipAllowance(BigNumber.from(0)) + return + } + + try { + setIsLoadingAllowance(true) + const allowance = await bridge.getCcipAllowance() + setCcipAllowance(allowance) + } catch (error) { + console.error("Failed to get CCIP allowance:", error) + setCcipAllowance(BigNumber.from(0)) + } finally { + setIsLoadingAllowance(false) + } + }, [bridge, account]) + + // Execute bridge transaction + const executeBridge = useCallback(async () => { + if (!bridge || !amount || !bridgeRoute) { + throw new Error("Bridge not ready") + } + + const amountBN = parseUnits(amount, 18) + + // Determine if this is a deposit or withdrawal + const isDeposit = + fromNetwork === SupportedChainIds.Ethereum || + fromNetwork === SupportedChainIds.Sepolia + + if (isDeposit) { + // Execute deposit to Bob + return await bridge.depositToBob(amountBN) + } else { + // Execute withdrawal from Bob + return await bridge.withdrawFromBob(amountBN) + } + }, [bridge, amount, bridgeRoute, fromNetwork]) + + // Execute CCIP approval + const approveCcip = useCallback(async () => { + if (!bridge || !amount) { + throw new Error("Bridge not ready") + } + + const amountBN = parseUnits(amount, 18) + return await bridge.approveForCcip(amountBN) + }, [bridge, amount]) + + // Debounced quote update + const debouncedUpdateQuote = useCallback(() => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current) + } + + debounceTimerRef.current = setTimeout(() => { + updateQuote() + }, 500) + }, [updateQuote, amount, bridgeRoute]) + + // Update bridging time based on route and direction + useEffect(() => { + if (!bridge || !bridgeRoute) { + setBridgingTime(null) + return + } + + // For withdrawals (Bob -> Ethereum), get time based on route + const time = bridge.getBridgingTime(bridgeRoute) + setBridgingTime(time) + }, [bridge, bridgeRoute, fromNetwork]) + + // Update bridge route when networks change + useEffect(() => { + updateBridgeRoute() + }, [updateBridgeRoute]) + + // Effects + useEffect(() => { + debouncedUpdateQuote() + }, [amount, bridgeRoute, bridge]) + + useEffect(() => { + updateCcipAllowance() + }, [updateCcipAllowance]) + + return { + // Networks + fromNetwork, + toNetwork, + setFromNetwork: handleFromNetworkChange, + setToNetwork: handleToNetworkChange, + swapNetworks, + + // Amount + amount, + setAmount, + + // Bridge info + bridgeRoute, + quote, + isLoadingQuote, + bridgingTime, + ccipAllowance, + isLoadingAllowance, + updateCcipAllowance, + + // Actions + executeBridge, + approveCcip, + } +} diff --git a/src/hooks/tbtc/useBridgeActivity.ts b/src/hooks/tbtc/useBridgeActivity.ts new file mode 100644 index 000000000..8651f47da --- /dev/null +++ b/src/hooks/tbtc/useBridgeActivity.ts @@ -0,0 +1,100 @@ +import { useEffect, useState, useCallback } from "react" +import { useIsActive } from "../useIsActive" +import { useThreshold } from "../../contexts/ThresholdContext" +import { BridgeActivity } from "../../threshold-ts/bridge" + +// Re-export types from bridge module +export type { + BridgeActivityStatus, + BridgeActivity as BridgeActivityData, +} from "../../threshold-ts/bridge" + +export const useBridgeActivity = () => { + const { account, chainId } = useIsActive() + const threshold = useThreshold() + const [bridgeActivities, setBridgeActivities] = useState([]) + const [isFetching, setIsFetching] = useState(false) + const [refreshTrigger, setRefreshTrigger] = useState(0) + + useEffect(() => { + if (!account || !chainId || !threshold.bridge) { + setBridgeActivities([]) + return + } + + let mounted = true + + const fetchBridgeActivity = async () => { + if (!mounted) return + + setIsFetching(true) + try { + // Use the bridge interface method to fetch activities + // Try to fetch from a smaller block range - last ~5k blocks + const activities = await threshold.bridge.fetchBridgeActivities( + account, + -5000 + ) + + if (!mounted) return + + // Double-check filtering based on current chainId + const isBobNetwork = chainId === 60808 || chainId === 808813 + const filtered = activities.filter((activity) => { + if (isBobNetwork) { + // On BOB: show activities where BOB is either source or destination + return ( + activity.fromNetwork.includes("BOB") || + activity.toNetwork.includes("BOB") + ) + } else { + // On Ethereum: show activities where Ethereum is either source or destination + return ( + activity.fromNetwork.includes("Ethereum") || + activity.toNetwork.includes("Ethereum") + ) + } + }) + + if (mounted) { + setBridgeActivities(filtered) + } + } catch (error) { + if (mounted) { + setBridgeActivities([]) + } + } finally { + if (mounted) { + setIsFetching(false) + } + } + } + + fetchBridgeActivity() + + return () => { + mounted = false + } + }, [account, chainId, threshold.bridge, refreshTrigger]) + + // Add periodic refresh every 30 seconds + useEffect(() => { + if (!account || !chainId || !threshold.bridge) return + + const interval = setInterval(() => { + setRefreshTrigger((prev) => prev + 1) + }, 30000) // 30 seconds + + return () => clearInterval(interval) + }, [account, chainId, threshold.bridge]) + + const refetch = useCallback(() => { + setRefreshTrigger((prev) => prev + 1) + }, []) + + return { + data: bridgeActivities, + isFetching, + refetch, + } +} diff --git a/src/hooks/useIsActive.ts b/src/hooks/useIsActive.ts index af108d8ad..f22dc28fb 100644 --- a/src/hooks/useIsActive.ts +++ b/src/hooks/useIsActive.ts @@ -33,57 +33,60 @@ export const useIsActive = (): UseIsActiveResult => { const ledgerLiveAppEthChaindId = ethAccountChainId const { isEmbed } = useIsEmbed() - const switchNetwork = useCallback(async (chainId: number): Promise => { - if (_connector) { - const provider = await _connector.getProvider() - const desiredChainIdHex = toHex(chainId) + const switchNetwork = useCallback( + async (chainId: number): Promise => { + if (_connector) { + const provider = await _connector.getProvider() + const desiredChainIdHex = toHex(chainId) - await provider - .request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: desiredChainIdHex }], - }) - .catch(async (error: any) => { - const errorCode = - (error?.data as any)?.originalError?.code || error.code + await provider + .request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: desiredChainIdHex }], + }) + .catch(async (error: any) => { + const errorCode = + (error?.data as any)?.originalError?.code || error.code - // If the error code indicates that the chain is unrecognized (4902) - // or that the provider is disconnected from the specified chain (4901), - // then we try to add the chain to the wallet. - if (errorCode === 4902 || errorCode === 4901) { - if (!provider) throw new Error("No provider available") + // If the error code indicates that the chain is unrecognized (4902) + // or that the provider is disconnected from the specified chain (4901), + // then we try to add the chain to the wallet. + if (errorCode === 4902 || errorCode === 4901) { + if (!provider) throw new Error("No provider available") - const network = networks.find( - (network) => network.chainId === chainId - ) - if (!network || !network.chainParameters) { - throw new Error("Network parameters not found") - } + const network = networks.find( + (network) => network.chainId === chainId + ) + if (!network || !network.chainParameters) { + throw new Error("Network parameters not found") + } - // Add the chain to wallet - await provider.request({ - method: "wallet_addEthereumChain", - params: [ - { - ...network.chainParameters, - chainId: desiredChainIdHex, - }, - ], - }) + // Add the chain to wallet + await provider.request({ + method: "wallet_addEthereumChain", + params: [ + { + ...network.chainParameters, + chainId: desiredChainIdHex, + }, + ], + }) - // After adding, switch to the new chain - await provider.request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: desiredChainIdHex }], - }) + // After adding, switch to the new chain + await provider.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: desiredChainIdHex }], + }) - // If adding and switching succeed, simply return. - return - } - throw error - }) - } - }, []) + // If adding and switching succeed, simply return. + return + } + throw error + }) + } + }, + [_connector] + ) const isActive = useMemo(() => { if (isEmbed) { diff --git a/src/networks/enums/networks.ts b/src/networks/enums/networks.ts index a02c616c7..28e8e800b 100644 --- a/src/networks/enums/networks.ts +++ b/src/networks/enums/networks.ts @@ -10,10 +10,20 @@ export enum SupportedChainIds { Localhost = 1337, Arbitrum = 42161, Base = 8453, - BOBMainnet = 60808, - BOBTestnet = 808813, - // ArbitrumSepolia = 421614, - // BaseSepolia = 84532, + Bob = 60808, + BobSepolia = 808813, + ArbitrumSepolia = 421614, + BaseSepolia = 84532, +} + +export enum BobChainSelector { + Bob = "3849287863852499584", + BobSepolia = "5535534526963509396", +} + +export enum EthereumChainSelector { + Ethereum = "5009297550715157269", + Sepolia = "16015286601757825753", } export enum NonEVMNetworks { @@ -58,4 +68,6 @@ export enum PublicRpcUrls { ArbitrumSepolia = "https://arbitrum-sepolia.drpc.org", Base = "https://base.drpc.org", BaseSepolia = "https://base-sepolia.drpc.org", + Bob = "https://rpc.gobob.xyz/", + BobSepolia = "https://bob-sepolia.rpc.gobob.xyz/", } diff --git a/src/networks/types/networks.ts b/src/networks/types/networks.ts index eddd61f28..eecfff8bc 100644 --- a/src/networks/types/networks.ts +++ b/src/networks/types/networks.ts @@ -18,7 +18,7 @@ export interface EthereumRpcMap { } export type NetworkName = keyof typeof SupportedChainIds -export type MainNetworkName = keyof typeof Chains +export type MainNetworkName = "Bob" | keyof typeof Chains export interface Network { chainId: SupportedChainIds name: Exclude diff --git a/src/networks/utils/createExplorerLink.ts b/src/networks/utils/createExplorerLink.ts index 4978c5bed..4c6a28c7c 100644 --- a/src/networks/utils/createExplorerLink.ts +++ b/src/networks/utils/createExplorerLink.ts @@ -39,8 +39,10 @@ export const createExplorerPrefix = ( [SupportedChainIds.Sepolia]: "https://sepolia.etherscan.io", [SupportedChainIds.Arbitrum]: "https://arbiscan.io", [SupportedChainIds.Base]: "https://basescan.org", - // [SupportedChainIds.BaseSepolia]: "https://sepolia.basescan.org", - // [SupportedChainIds.ArbitrumSepolia]: "https://sepolia.arbiscan.io", + [SupportedChainIds.BaseSepolia]: "https://sepolia.basescan.org", + [SupportedChainIds.ArbitrumSepolia]: "https://sepolia.arbiscan.io", + [SupportedChainIds.Bob]: "https://explorer.gobob.xyz/", + [SupportedChainIds.BobSepolia]: "https://bob-sepolia.explorer.gobob.xyz", } return prefixMap[Number(chainId)] || "https://etherscan.io" diff --git a/src/networks/utils/networks.ts b/src/networks/utils/networks.ts index 48a262e36..4e87feacf 100644 --- a/src/networks/utils/networks.ts +++ b/src/networks/utils/networks.ts @@ -101,42 +101,78 @@ export const networks: Network[] = [ blockExplorerUrls: [createExplorerPrefix(SupportedChainIds.Sepolia)], }, }, - // { - // chainId: SupportedChainIds.ArbitrumSepolia, - // name: "Arbitrum", - // layer: Layer.L2, - // networkType: NetworkType.Testnet, - // alchemyName: AlchemyName.Arbitrum, - // chainParameters: { - // chainId: toHex(SupportedChainIds.ArbitrumSepolia), - // chainName: "Arbitrum Sepolia", - // nativeCurrency: { - // name: NativeCurrency.SepoliaEther, - // symbol: ETH_SYMBOL, - // decimals: DECIMALS, - // }, - // rpcUrls: [PublicRpcUrls.ArbitrumSepolia], - // blockExplorerUrls: [ - // createExplorerPrefix(SupportedChainIds.ArbitrumSepolia), - // ], - // }, - // }, - // { - // chainId: SupportedChainIds.BaseSepolia, - // name: "Base", - // layer: Layer.L2, - // networkType: NetworkType.Testnet, - // alchemyName: AlchemyName.Base, - // chainParameters: { - // chainId: toHex(SupportedChainIds.BaseSepolia), - // chainName: "Base Sepolia", - // nativeCurrency: { - // name: NativeCurrency.SepoliaEther, - // symbol: ETH_SYMBOL, - // decimals: DECIMALS, - // }, - // rpcUrls: [PublicRpcUrls.BaseSepolia], - // blockExplorerUrls: [createExplorerPrefix(SupportedChainIds.BaseSepolia)], - // }, - // }, + { + chainId: SupportedChainIds.ArbitrumSepolia, + name: "Arbitrum", + layer: Layer.L2, + networkType: NetworkType.Testnet, + alchemyName: AlchemyName.Arbitrum, + chainParameters: { + chainId: toHex(SupportedChainIds.ArbitrumSepolia), + chainName: "Arbitrum Sepolia", + nativeCurrency: { + name: NativeCurrency.SepoliaEther, + symbol: ETH_SYMBOL, + decimals: DECIMALS, + }, + rpcUrls: [PublicRpcUrls.ArbitrumSepolia], + blockExplorerUrls: [ + createExplorerPrefix(SupportedChainIds.ArbitrumSepolia), + ], + }, + }, + { + chainId: SupportedChainIds.BaseSepolia, + name: "Base", + layer: Layer.L2, + networkType: NetworkType.Testnet, + alchemyName: AlchemyName.Base, + chainParameters: { + chainId: toHex(SupportedChainIds.BaseSepolia), + chainName: "Base Sepolia", + nativeCurrency: { + name: NativeCurrency.SepoliaEther, + symbol: ETH_SYMBOL, + decimals: DECIMALS, + }, + rpcUrls: [PublicRpcUrls.BaseSepolia], + blockExplorerUrls: [createExplorerPrefix(SupportedChainIds.BaseSepolia)], + }, + }, + { + chainId: SupportedChainIds.Bob, + name: "Bob", + layer: Layer.L2, + networkType: NetworkType.Mainnet, + alchemyName: undefined, + chainParameters: { + chainId: toHex(SupportedChainIds.Bob), + chainName: "Bob Mainnet", + nativeCurrency: { + name: NativeCurrency.Ether, + symbol: ETH_SYMBOL, + decimals: DECIMALS, + }, + rpcUrls: [PublicRpcUrls.Bob], + blockExplorerUrls: [createExplorerPrefix(SupportedChainIds.Bob)], + }, + }, + { + chainId: SupportedChainIds.BobSepolia, + name: "Bob", + layer: Layer.L2, + networkType: NetworkType.Testnet, + alchemyName: undefined, + chainParameters: { + chainId: toHex(SupportedChainIds.BobSepolia), + chainName: "Bob Sepolia", + nativeCurrency: { + name: NativeCurrency.SepoliaEther, + symbol: ETH_SYMBOL, + decimals: DECIMALS, + }, + rpcUrls: [PublicRpcUrls.BobSepolia], + blockExplorerUrls: [createExplorerPrefix(SupportedChainIds.BobSepolia)], + }, + }, ] diff --git a/src/networks/utils/networksAlchemyConfig.ts b/src/networks/utils/networksAlchemyConfig.ts index 160380b12..c685c14f4 100644 --- a/src/networks/utils/networksAlchemyConfig.ts +++ b/src/networks/utils/networksAlchemyConfig.ts @@ -18,12 +18,12 @@ export const networksAlchemyConfig: NetworksAlchemyConfig = { name: AlchemyName.Base, type: NetworkType.Mainnet, }, - // [SupportedChainIds.BaseSepolia]: { - // name: AlchemyName.Base, - // type: NetworkType.Testnet, - // }, - // [SupportedChainIds.ArbitrumSepolia]: { - // name: AlchemyName.Arbitrum, - // type: NetworkType.Testnet, - // }, + [SupportedChainIds.BaseSepolia]: { + name: AlchemyName.Base, + type: NetworkType.Testnet, + }, + [SupportedChainIds.ArbitrumSepolia]: { + name: AlchemyName.Arbitrum, + type: NetworkType.Testnet, + }, } diff --git a/src/pages/tBTC/BobBridge/TBTCBridge.tsx b/src/pages/tBTC/BobBridge/TBTCBridge.tsx new file mode 100644 index 000000000..a2fbcbe8f --- /dev/null +++ b/src/pages/tBTC/BobBridge/TBTCBridge.tsx @@ -0,0 +1,35 @@ +import { Stack } from "@chakra-ui/react" +import BridgePanel from "./components/BridgePanel" +import TokenBalanceCard from "../../../components/TokenBalanceCard" +import { BridgeActivityCard } from "./components/BridgeActivityCard" +import { Token } from "../../../enums" +import { PageComponent } from "../../../types" +import { useBridgeActivity } from "../../../hooks/tbtc/useBridgeActivity" + +const TBTCBridge: PageComponent = () => { + const { data, isFetching, refetch } = useBridgeActivity() + + return ( + + + + + + + + ) +} + +TBTCBridge.route = { + path: "bob-bridge", + title: "Bob Bridge", + index: true, + isPageEnabled: true, +} + +export default TBTCBridge diff --git a/src/pages/tBTC/BobBridge/components/BridgeActivityCard.tsx b/src/pages/tBTC/BobBridge/components/BridgeActivityCard.tsx new file mode 100644 index 000000000..c221eea7e --- /dev/null +++ b/src/pages/tBTC/BobBridge/components/BridgeActivityCard.tsx @@ -0,0 +1,131 @@ +import { FC } from "react" +import { ComponentProps } from "react" +import { + Card, + LabelSm, + List, + Box, + Skeleton, + Stack, +} from "@threshold-network/components" +import { ActivityItemWrapper } from "../../../../components/tBTC" +import { InlineTokenBalance } from "../../../../components/TokenBalance" +import { BridgeActivity } from "../../../../threshold-ts/bridge" + +const BridgeTypeBadge: FC<{ bridgeRoute: string }> = ({ bridgeRoute }) => { + const label = bridgeRoute === "ccip" ? "CCIP" : "STANDARD BOB" + return ( + + {label} + + ) +} + +const BridgeActivityItem: FC<{ + amount: string + explorerUrl: string + bridgeRoute: string +}> = ({ amount, explorerUrl, bridgeRoute }) => { + return ( + + + + + + + ) +} + +const BridgeActivityHeader = () => { + return ( + + tBTC + bridge + + ) +} + +const BridgeActivityLoadingState = () => { + return ( + + + + + + ) +} + +interface BridgeActivityCardProps extends ComponentProps { + data: BridgeActivity[] + isFetching: boolean +} + +export const BridgeActivityCard: FC = (props) => { + const { data, isFetching, ...cardProps } = props + const isBridgeHistoryEmpty = data.length === 0 + + return ( + + bridge activity + + {isFetching ? ( + + ) : ( + + {isBridgeHistoryEmpty ? ( + <> + + + -.-- + + + -.-- + + + + + -.-- + + + -.-- + + + + ) : ( + data.map((item) => ( + + )) + )} + + )} + + ) +} diff --git a/src/pages/tBTC/BobBridge/components/BridgeAmountInput.tsx b/src/pages/tBTC/BobBridge/components/BridgeAmountInput.tsx new file mode 100644 index 000000000..aa7a09b68 --- /dev/null +++ b/src/pages/tBTC/BobBridge/components/BridgeAmountInput.tsx @@ -0,0 +1,121 @@ +import { FC } from "react" +import { + Box, + Flex, + HStack, + Icon, + Input, + InputGroup, + Text, + VStack, + useColorModeValue, +} from "@chakra-ui/react" +import { BodySm } from "@threshold-network/components" +import { useTokenBalance } from "../../../../hooks/useTokenBalance" +import { Token } from "../../../../enums" +import { formatTokenAmount } from "../../../../utils/formatAmount" +import { useToken } from "../../../../hooks/useToken" +import { parseUnits, formatUnits } from "@ethersproject/units" +import { tBTCFillBlack } from "../../../../static/icons/tBTCFillBlack" + +interface BridgeAmountInputProps { + amount: string + onChange: (amount: string) => void + tokenSymbol: string +} + +const BridgeAmountInput: FC = ({ + amount, + onChange, + tokenSymbol, +}) => { + const inputBg = useColorModeValue("gray.50", "gray.800") + const borderColor = useColorModeValue("gray.200", "gray.700") + + const balance = useTokenBalance(Token.TBTCV2) + const token = useToken(Token.TBTCV2) + + const formattedBalance = formatTokenAmount(balance, undefined, 18, 6) + + // Calculate USD value + const usdValue = (() => { + try { + if (!amount || amount === "0") return "0.00" + const parsedAmount = parseFloat(amount) + if (isNaN(parsedAmount)) return "0.00" + const usdConversion = token.usdConversion || 0 + const usdAmount = parsedAmount * usdConversion + return usdAmount.toFixed(2) + } catch { + return "0.00" + } + })() + + const handleMaxClick = () => { + // Convert balance to string with proper decimals + try { + const balanceStr = formatUnits(balance, 18) + onChange(balanceStr) + } catch (error) { + console.error("Error formatting balance:", error) + onChange("0") + } + } + + const handleInputChange = (e: React.ChangeEvent) => { + const value = e.target.value + // Allow only numbers and decimal point + if (value === "" || /^\d*\.?\d*$/.test(value)) { + onChange(value) + } + } + + return ( + + + + + Amount + + + + + + ${usdValue} + + + + + + {tokenSymbol} + + + Balance: {formattedBalance} + + + + + ) +} + +export default BridgeAmountInput diff --git a/src/pages/tBTC/BobBridge/components/BridgeButton.tsx b/src/pages/tBTC/BobBridge/components/BridgeButton.tsx new file mode 100644 index 000000000..785c97f52 --- /dev/null +++ b/src/pages/tBTC/BobBridge/components/BridgeButton.tsx @@ -0,0 +1,174 @@ +import { FC, useMemo, useState, useCallback } from "react" +import { BigNumber } from "ethers" +import { parseUnits } from "@ethersproject/units" +import { useWeb3React } from "@web3-react/core" +import { useIsActive } from "../../../../hooks/useIsActive" +import { useModal } from "../../../../hooks/useModal" +import { ModalType } from "../../../../enums" +import { BridgeRoute } from "../../../../threshold-ts/bridge" +import { BridgeNetwork } from "./NetworkSelector" +import { SupportedChainIds } from "../../../../networks/enums/networks" +import SubmitTxButton from "../../../../components/SubmitTxButton" +import { useTokenBalance } from "../../../../hooks/useTokenBalance" +import { Token } from "../../../../enums" + +interface BridgeButtonProps { + amount: string + fromNetwork: BridgeNetwork + toNetwork: BridgeNetwork + bridgeRoute: BridgeRoute | null + ccipAllowance: BigNumber + onBridgeAction?: () => Promise + isLoading?: boolean + size?: string +} + +const BridgeButton: FC = ({ + amount, + fromNetwork, + toNetwork, + bridgeRoute, + ccipAllowance, + onBridgeAction, + isLoading = false, + size, +}) => { + const { account, active } = useWeb3React() + const { chainId, switchNetwork } = useIsActive() + const { openModal } = useModal() + const [isSwitchingNetwork, setIsSwitchingNetwork] = useState(false) + const balance = useTokenBalance(Token.TBTCV2) + + // Parse amount safely + const amountBN = useMemo(() => { + try { + return amount ? parseUnits(amount, 18) : BigNumber.from(0) + } catch { + return BigNumber.from(0) + } + }, [amount]) + + // Helper function to get network name + const getNetworkName = (chainId: BridgeNetwork): string => { + switch (chainId) { + case SupportedChainIds.Ethereum: + return "Ethereum" + case SupportedChainIds.Sepolia: + return "Sepolia" + case SupportedChainIds.Bob: + return "BOB" + case SupportedChainIds.BobSepolia: + return "BOB Sepolia" + default: + return "Unknown" + } + } + + // Handle network switching + const handleNetworkSwitch = useCallback(async () => { + try { + setIsSwitchingNetwork(true) + await switchNetwork(fromNetwork) + } catch (error) { + console.error("Failed to switch network:", error) + } finally { + setIsSwitchingNetwork(false) + } + }, [fromNetwork, switchNetwork]) + + // Determine button state and text + const buttonState = useMemo(() => { + // Not connected + if (!active || !account) { + return { + text: "Connect Wallet", + disabled: false, + isLoading: false, + onClick: () => openModal(ModalType.SelectWallet), + } + } + + // Wrong network + if (chainId !== fromNetwork) { + return { + text: isSwitchingNetwork + ? "Switching Network..." + : `Switch to ${getNetworkName(fromNetwork)}`, + disabled: isSwitchingNetwork, + isLoading: isSwitchingNetwork, + onClick: handleNetworkSwitch, + } + } + + // No amount entered + if (!amount || amountBN.eq(0)) { + return { + text: "Enter Amount", + disabled: true, + isLoading: false, + onClick: () => {}, + } + } + + // Check if amount exceeds balance + if (amountBN.gt(balance)) { + return { + text: "Insufficient Balance", + disabled: true, + isLoading: false, + onClick: () => {}, + } + } + + // Check if CCIP approval needed (only for CCIP route) + const needsCCIPApproval = + bridgeRoute === "ccip" && ccipAllowance.lt(amountBN) + + if (needsCCIPApproval) { + return { + text: isLoading ? "Approving..." : "Approve CCIP", + disabled: isLoading, + isLoading: isLoading, + onClick: onBridgeAction || (() => {}), + } + } + + // Ready to bridge + return { + text: isLoading ? "Bridging..." : "Bridge Asset", + disabled: isLoading, + isLoading: isLoading, + onClick: onBridgeAction || (() => {}), + } + }, [ + active, + account, + chainId, + fromNetwork, + amount, + amountBN, + balance, + bridgeRoute, + ccipAllowance, + openModal, + isSwitchingNetwork, + handleNetworkSwitch, + onBridgeAction, + isLoading, + ]) + + return ( + + {buttonState.text} + + ) +} + +export default BridgeButton diff --git a/src/pages/tBTC/BobBridge/components/BridgeFees.tsx b/src/pages/tBTC/BobBridge/components/BridgeFees.tsx new file mode 100644 index 000000000..3bc8521ab --- /dev/null +++ b/src/pages/tBTC/BobBridge/components/BridgeFees.tsx @@ -0,0 +1,116 @@ +import { FC } from "react" +import { List, Tooltip, Box } from "@chakra-ui/react" +import { TransactionDetailsItem } from "../../../../components/TransactionDetails" +import { BridgeQuote } from "../../../../threshold-ts/bridge" +import { BridgeNetwork } from "./NetworkSelector" +import { formatUnits } from "@ethersproject/units" +import { SupportedChainIds } from "../../../../networks/enums/networks" +import { BigNumber } from "ethers" +import { BodySm } from "@threshold-network/components" + +interface BridgeFeesProps { + quote: BridgeQuote | null + isLoading: boolean + fromNetwork: BridgeNetwork + toNetwork: BridgeNetwork +} + +const BridgeFees: FC = ({ + quote, + isLoading, + fromNetwork, + toNetwork, +}) => { + // Determine if this is a deposit or withdrawal + const isDeposit = + (fromNetwork === SupportedChainIds.Ethereum || + fromNetwork === SupportedChainIds.Sepolia) && + (toNetwork === SupportedChainIds.Bob || + toNetwork === SupportedChainIds.BobSepolia) + + // Helper to format fee from BigNumber to decimal string + const formatFee = (fee: any): { display: string; full: string } => { + if (!fee) return { display: "0 ETH", full: "0 ETH" } + + try { + let feeValue: string + + // Handle BigNumber with _hex property + if (fee._hex) { + feeValue = fee._hex + } else if (fee.hex) { + feeValue = fee.hex + } else if (typeof fee === "string") { + feeValue = fee + } else { + feeValue = fee.toString() + } + + // Convert to decimal string with 18 decimals + const formatted = formatUnits(feeValue, 18) + const numValue = parseFloat(formatted) + + let display: string + if (numValue === 0) { + display = "0 ETH" + } else if (numValue < 0.000001) { + display = "< 0.000001 ETH" + } else if (numValue < 0.001) { + display = `${numValue.toFixed(6)} ETH` + } else if (numValue < 1) { + display = `${numValue.toFixed(4)} ETH` + } else { + display = `${numValue.toFixed(2)} ETH` + } + + return { + display, + full: `${formatted} ETH`, + } + } catch (error) { + console.error("Error formatting fee:", error, fee) + return { display: "0 ETH", full: "0 ETH" } + } + } + + const FeeDisplay: FC<{ fee: any }> = ({ fee }) => { + const { display, full } = formatFee(fee) + return ( + + + {display} + + + ) + } + + return ( + + + {quote ? ( + + ) : isLoading ? ( + Loading... + ) : ( + 0 ETH + )} + + + {quote?.route === "ccip" && quote.breakdown?.ccipFee && ( + + + + )} + + {quote?.route === "standard" && quote.breakdown?.standardFee && ( + + + + )} + + ) +} + +export default BridgeFees diff --git a/src/pages/tBTC/BobBridge/components/BridgePanel.tsx b/src/pages/tBTC/BobBridge/components/BridgePanel.tsx new file mode 100644 index 000000000..537b2768c --- /dev/null +++ b/src/pages/tBTC/BobBridge/components/BridgePanel.tsx @@ -0,0 +1,180 @@ +import { FC, useCallback, useState } from "react" +import { Stack } from "@chakra-ui/react" +import { Card, H5 } from "@threshold-network/components" +import NetworkSelector from "./NetworkSelector" +import BridgeTypeSelector from "./BridgeTypeSelector" +import BridgeAmountInput from "./BridgeAmountInput" +import BridgeFees from "./BridgeFees" +import BridgeButton from "./BridgeButton" +import { useBridge } from "../../../../hooks/tbtc/useBridge" +import { parseUnits } from "@ethersproject/units" +import { useModal } from "../../../../hooks/useModal" +import BridgeTxAlert from "./BridgeTxAlert" +import { ModalType } from "../../../../enums" + +interface BridgePanelProps { + onBridgeSuccess?: () => void +} + +const BridgePanel: FC = ({ onBridgeSuccess }) => { + const { + fromNetwork, + toNetwork, + bridgeRoute, + amount, + setAmount, + swapNetworks, + setFromNetwork, + setToNetwork, + ccipAllowance, + quote, + isLoadingQuote, + bridgingTime, + executeBridge, + approveCcip, + updateCcipAllowance, + } = useBridge() + + const { openModal } = useModal() + const [isTransacting, setIsTransacting] = useState(false) + const [successTx, setSuccessTx] = useState<{ + hash: string + route: "ccip" | "standard" + } | null>(null) + + const handleBridgeExecution = useCallback(async () => { + setIsTransacting(true) + try { + const bridgeTx = await executeBridge() + if (bridgeTx) { + setSuccessTx({ + hash: bridgeTx.hash, + route: bridgeRoute as "ccip" | "standard", + }) + // Wait a bit for the transaction to be indexed + setTimeout(() => { + onBridgeSuccess?.() + }, 3000) + } + return bridgeTx + } catch (error) { + console.error("Bridge execution failed:", error) + throw error + } finally { + setIsTransacting(false) + } + }, [executeBridge, bridgeRoute, onBridgeSuccess]) + + const handleBridgeAction = useCallback(async () => { + if (!amount || isTransacting) return + + setIsTransacting(true) + try { + const amountBN = parseUnits(amount, 18) + + // Check if CCIP approval is needed + if (bridgeRoute === "ccip" && ccipAllowance.lt(amountBN)) { + const tx = await approveCcip() + if (tx) { + await tx.wait() + console.log("tx: ", tx) + // Update allowance after approval + await updateCcipAllowance() + // Don't execute bridge here - let user click again after approval + return tx + } + } else { + // Open confirmation modal + openModal(ModalType.InitiateBridging, { + amount, + fromNetwork: + fromNetwork === 1 + ? "Ethereum" + : fromNetwork === 11155111 + ? "Ethereum Sepolia" + : fromNetwork === 60808 + ? "BOB" + : "BOB Sepolia", + toNetwork: + toNetwork === 1 + ? "Ethereum" + : toNetwork === 11155111 + ? "Ethereum Sepolia" + : toNetwork === 60808 + ? "BOB" + : "BOB Sepolia", + bridgeRoute, + estimatedFee: quote?.fee || null, + estimatedTime: bridgingTime || 0, + onConfirm: handleBridgeExecution, + }) + } + } catch (error) { + console.error("Bridge action failed:", error) + throw error + } finally { + setIsTransacting(false) + } + }, [ + amount, + bridgeRoute, + ccipAllowance, + approveCcip, + updateCcipAllowance, + isTransacting, + openModal, + fromNetwork, + toNetwork, + quote, + bridgingTime, + handleBridgeExecution, + ]) + + return ( + + +
tBTC Bridge
+ + {successTx && } + + + + + + + +
+
+ ) +} + +export default BridgePanel diff --git a/src/pages/tBTC/BobBridge/components/BridgeTxAlert.tsx b/src/pages/tBTC/BobBridge/components/BridgeTxAlert.tsx new file mode 100644 index 000000000..d05c0d5b1 --- /dev/null +++ b/src/pages/tBTC/BobBridge/components/BridgeTxAlert.tsx @@ -0,0 +1,41 @@ +import { FC } from "react" +import { + Alert, + AlertIcon, + Stack, + Text, + Link as ChakraLink, +} from "@chakra-ui/react" + +type BridgeTxAlertProps = { + tx: { + hash: string + route: "ccip" | "standard" + } +} + +const BridgeTxAlert: FC = ({ tx }) => { + const explorerUrl = + tx.route === "ccip" + ? `https://ccip.chain.link/tx/${tx.hash}` + : `https://explorer.gobob.xyz/tx/${tx.hash}` + + const explorerLabel = tx.route === "ccip" ? "CCIP Explorer" : "BOB Explorer" + + return ( + + + + Transaction submitted successfully! + + View on{" "} + + {explorerLabel} + + + + + ) +} + +export default BridgeTxAlert diff --git a/src/pages/tBTC/BobBridge/components/BridgeTypeSelector.tsx b/src/pages/tBTC/BobBridge/components/BridgeTypeSelector.tsx new file mode 100644 index 000000000..01945ec68 --- /dev/null +++ b/src/pages/tBTC/BobBridge/components/BridgeTypeSelector.tsx @@ -0,0 +1,160 @@ +import { FC, useEffect, useState } from "react" +import { + Alert, + AlertIcon, + Box, + Flex, + HStack, + Icon, + Text, + VStack, + useColorModeValue, +} from "@chakra-ui/react" +import { FiCheck } from "react-icons/fi" +import { BodySm } from "@threshold-network/components" +import { SupportedChainIds } from "../../../../networks/enums/networks" +import { BridgeRoute } from "../../../../threshold-ts/bridge" +import { BridgeNetwork } from "./NetworkSelector" +import { useIsActive } from "../../../../hooks/useIsActive" + +interface BridgeTypeSelectorProps { + fromNetwork: BridgeNetwork + toNetwork: BridgeNetwork + bridgeRoute: BridgeRoute | null + bridgingTime?: number +} + +const BridgeTypeSelector: FC = ({ + fromNetwork, + toNetwork, + bridgeRoute, + bridgingTime, +}) => { + const bgColor = useColorModeValue("gray.50", "gray.800") + const borderColor = useColorModeValue("gray.200", "gray.700") + const activeBg = useColorModeValue("brand.50", "brand.900") + const activeBorder = useColorModeValue("brand.500", "brand.400") + const disabledOpacity = 0.5 + + // Determine which bridges are available + const isEthereumToBob = + (fromNetwork === SupportedChainIds.Ethereum || + fromNetwork === SupportedChainIds.Sepolia) && + (toNetwork === SupportedChainIds.Bob || + toNetwork === SupportedChainIds.BobSepolia) + + const isBobToEthereum = + (fromNetwork === SupportedChainIds.Bob || + fromNetwork === SupportedChainIds.BobSepolia) && + (toNetwork === SupportedChainIds.Ethereum || + toNetwork === SupportedChainIds.Sepolia) + // For Ethereum to Bob, only CCIP is available + const ccipEnabled = + isEthereumToBob || (isBobToEthereum && bridgeRoute === "ccip") + const standardEnabled = isBobToEthereum && bridgeRoute === "standard" + + const BridgeBox = ({ + title, + isActive, + isDisabled, + }: { + title: string + isActive: boolean + isDisabled: boolean + }) => ( + + + {title} + {isActive && ( + + + + )} + + + ) + + const formatTime = (seconds?: number): string => { + if (!seconds) return "" + if (seconds <= 3600) { + return `~${Math.round(seconds / 60)} minutes` + } + const days = Math.floor(seconds / 86400) + return `${days} day${days > 1 ? "s" : ""}` + } + + return ( + + + + + + + {/* Show alert based on active bridge type */} + {standardEnabled && ( + + + + Standard Bridge: 7 days + + Your withdrawal will take 7 days to complete using the Standard + Bridge. This is a security feature of the Optimism protocol.{" "} + + Learn more + + + + + )} + + {ccipEnabled && ( + + + + + CCIP Bridge: {formatTime(bridgingTime)} + + + Your transfer will complete in approximately{" "} + {formatTime(bridgingTime)} using Chainlink's Cross-Chain + Interoperability Protocol (CCIP). + + + + )} + + ) +} + +export default BridgeTypeSelector diff --git a/src/pages/tBTC/BobBridge/components/NetworkSelector.tsx b/src/pages/tBTC/BobBridge/components/NetworkSelector.tsx new file mode 100644 index 000000000..f27a96a42 --- /dev/null +++ b/src/pages/tBTC/BobBridge/components/NetworkSelector.tsx @@ -0,0 +1,206 @@ +import { FC } from "react" +import { + Box, + Flex, + HStack, + Icon, + IconButton, + Menu, + MenuButton, + MenuItem, + MenuList, + Text, + VStack, + useColorModeValue, +} from "@chakra-ui/react" +import { ChevronDownIcon } from "@chakra-ui/icons" +import { FiArrowLeft, FiArrowRight } from "react-icons/fi" +import { BodySm, LineDivider } from "@threshold-network/components" +import { EthereumLight } from "../../../../static/icons/EthereumLight" +import { EthereumDark } from "../../../../static/icons/EthereumDark" +import { SupportedChainIds } from "../../../../networks/enums/networks" +import { useColorMode } from "@chakra-ui/react" + +export type BridgeNetwork = + | SupportedChainIds.Ethereum + | SupportedChainIds.Sepolia + | SupportedChainIds.Bob + | SupportedChainIds.BobSepolia + +interface NetworkSelectorProps { + fromNetwork: BridgeNetwork + toNetwork: BridgeNetwork + onSwap: () => void + onFromNetworkChange: (network: BridgeNetwork) => void + onToNetworkChange: (network: BridgeNetwork) => void +} + +const getNetworkIcon = (chainId: BridgeNetwork, colorMode: string) => { + const ethereumLogo = colorMode === "light" ? EthereumDark : EthereumLight + + switch (chainId) { + case SupportedChainIds.Ethereum: + case SupportedChainIds.Sepolia: + return + case SupportedChainIds.Bob: + case SupportedChainIds.BobSepolia: + // TODO: Add BOB icon when available + return + default: + return + } +} + +const getNetworkName = (chainId: BridgeNetwork): string => { + switch (chainId) { + case SupportedChainIds.Ethereum: + return "Ethereum" + case SupportedChainIds.Sepolia: + return "Sepolia" + case SupportedChainIds.Bob: + return "BOB" + case SupportedChainIds.BobSepolia: + return "BOB Sepolia" + default: + return "Unknown" + } +} + +const getAvailableNetworks = ( + currentNetwork: BridgeNetwork +): BridgeNetwork[] => { + // Mainnet networks + if ( + currentNetwork === SupportedChainIds.Ethereum || + currentNetwork === SupportedChainIds.Bob + ) { + return [SupportedChainIds.Ethereum, SupportedChainIds.Bob] + } + // Testnet networks + return [SupportedChainIds.Sepolia, SupportedChainIds.BobSepolia] +} + +const NetworkSelector: FC = ({ + fromNetwork, + toNetwork, + onSwap, + onFromNetworkChange, + onToNetworkChange, +}) => { + const { colorMode } = useColorMode() + const bgColor = useColorModeValue("gray.50", "gray.800") + const borderColor = useColorModeValue("gray.200", "gray.700") + const hoverBg = useColorModeValue("gray.100", "gray.700") + + const availableFromNetworks = getAvailableNetworks(fromNetwork) + const availableToNetworks = getAvailableNetworks(toNetwork) + + const NetworkBox = ({ + label, + network, + networks, + onChange, + }: { + label: string + network: BridgeNetwork + networks: BridgeNetwork[] + onChange: (network: BridgeNetwork) => void + }) => ( + + + + {label} + + + + + {getNetworkIcon(network, colorMode)} + + {getNetworkName(network)} + + + + + + {networks + .filter((n) => n !== network) + .map((n) => ( + onChange(n)} + > + {getNetworkName(n)} + + ))} + + + + + ) + + return ( + + + + + + + + } + variant="ghost" + onClick={onSwap} + size="md" + _hover={{ bg: hoverBg }} + /> + + + + + + + ) +} + +export default NetworkSelector diff --git a/src/pages/tBTC/BobBridge/components/index.ts b/src/pages/tBTC/BobBridge/components/index.ts new file mode 100644 index 000000000..d8c279606 --- /dev/null +++ b/src/pages/tBTC/BobBridge/components/index.ts @@ -0,0 +1,8 @@ +export { default as BridgePanel } from "./BridgePanel" +export { default as NetworkSelector } from "./NetworkSelector" +export { default as BridgeTypeSelector } from "./BridgeTypeSelector" +export { default as BridgeAmountInput } from "./BridgeAmountInput" +export { default as BridgeFees } from "./BridgeFees" +export { default as BridgeButton } from "./BridgeButton" + +export type { BridgeNetwork } from "./NetworkSelector" diff --git a/src/pages/tBTC/BobBridge/index.tsx b/src/pages/tBTC/BobBridge/index.tsx new file mode 100644 index 000000000..dcf21464b --- /dev/null +++ b/src/pages/tBTC/BobBridge/index.tsx @@ -0,0 +1,17 @@ +import TBTCBridge from "./TBTCBridge" +import PageLayout from "../../PageLayout" +import { PageComponent } from "../../../types" + +const BridgePage: PageComponent = (props) => { + return +} + +BridgePage.route = { + path: "bridges", + index: false, + pages: [TBTCBridge], + title: "Bridges", + isPageEnabled: true, +} + +export default BridgePage diff --git a/src/pages/tBTC/Bridge/BridgeActivityCard/index.tsx b/src/pages/tBTC/Deposit/BridgeActivityCard/index.tsx similarity index 100% rename from src/pages/tBTC/Bridge/BridgeActivityCard/index.tsx rename to src/pages/tBTC/Deposit/BridgeActivityCard/index.tsx diff --git a/src/pages/tBTC/Bridge/BridgeLayout.tsx b/src/pages/tBTC/Deposit/BridgeLayout.tsx similarity index 100% rename from src/pages/tBTC/Bridge/BridgeLayout.tsx rename to src/pages/tBTC/Deposit/BridgeLayout.tsx diff --git a/src/pages/tBTC/Bridge/DepositDetails.tsx b/src/pages/tBTC/Deposit/DepositDetails.tsx similarity index 100% rename from src/pages/tBTC/Bridge/DepositDetails.tsx rename to src/pages/tBTC/Deposit/DepositDetails.tsx diff --git a/src/pages/tBTC/Bridge/Mint.tsx b/src/pages/tBTC/Deposit/Mint.tsx similarity index 100% rename from src/pages/tBTC/Bridge/Mint.tsx rename to src/pages/tBTC/Deposit/Mint.tsx diff --git a/src/pages/tBTC/Bridge/MintUnmintNav.tsx b/src/pages/tBTC/Deposit/MintUnmintNav.tsx similarity index 100% rename from src/pages/tBTC/Bridge/MintUnmintNav.tsx rename to src/pages/tBTC/Deposit/MintUnmintNav.tsx diff --git a/src/pages/tBTC/Bridge/Minting/InitiateMinting.tsx b/src/pages/tBTC/Deposit/Minting/InitiateMinting.tsx similarity index 100% rename from src/pages/tBTC/Bridge/Minting/InitiateMinting.tsx rename to src/pages/tBTC/Deposit/Minting/InitiateMinting.tsx diff --git a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx b/src/pages/tBTC/Deposit/Minting/MakeDeposit.tsx similarity index 100% rename from src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx rename to src/pages/tBTC/Deposit/Minting/MakeDeposit.tsx diff --git a/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx b/src/pages/tBTC/Deposit/Minting/MintingFlowRouter.tsx similarity index 100% rename from src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx rename to src/pages/tBTC/Deposit/Minting/MintingFlowRouter.tsx diff --git a/src/pages/tBTC/Bridge/Minting/MintingSuccess.tsx b/src/pages/tBTC/Deposit/Minting/MintingSuccess.tsx similarity index 96% rename from src/pages/tBTC/Bridge/Minting/MintingSuccess.tsx rename to src/pages/tBTC/Deposit/Minting/MintingSuccess.tsx index 489d13707..2d37df1a3 100644 --- a/src/pages/tBTC/Bridge/Minting/MintingSuccess.tsx +++ b/src/pages/tBTC/Deposit/Minting/MintingSuccess.tsx @@ -37,7 +37,7 @@ const MintingSuccessComponent: FC = () => { return ( { } TBTCBridge.route = { - path: "", - index: false, - pathOverride: "*", + path: "deposit", + index: true, pages: [MintPage, UnmintPage], - title: "Bridge", + title: "Deposit", isPageEnabled: true, } diff --git a/src/pages/tBTC/HowItWorks/MintingTimelineCard.tsx b/src/pages/tBTC/HowItWorks/MintingTimelineCard.tsx index 5d0c14a46..20868fdb2 100644 --- a/src/pages/tBTC/HowItWorks/MintingTimelineCard.tsx +++ b/src/pages/tBTC/HowItWorks/MintingTimelineCard.tsx @@ -13,7 +13,7 @@ import { MintingTimelineStep1, MintingTimelineStep2, MintingTimelineStep3, -} from "../Bridge/Minting/MintingTimeline" +} from "../Deposit/Minting/MintingTimeline" import Link from "../../../components/Link" import { ExternalHref } from "../../../enums" import { Steps } from "../../../components/Step" diff --git a/src/pages/tBTC/index.tsx b/src/pages/tBTC/index.tsx index c46176eb1..271084db3 100644 --- a/src/pages/tBTC/index.tsx +++ b/src/pages/tBTC/index.tsx @@ -1,10 +1,11 @@ import PageLayout from "../PageLayout" import { PageComponent } from "../../types" import HowItWorksPage from "./HowItWorks" -import TBTCBridge from "./Bridge" +import TBTCBridge from "./Deposit" +import BobBridge from "./BobBridge" import { featureFlags } from "../../constants" import { ExplorerPage } from "./Explorer" -import { ResumeDepositPage } from "./Bridge/ResumeDeposit" +import { ResumeDepositPage } from "./Deposit/ResumeDeposit" const MainTBTCPage: PageComponent = (props) => { const externalLinks = [ @@ -26,7 +27,13 @@ const MainTBTCPage: PageComponent = (props) => { MainTBTCPage.route = { path: "tBTC", index: false, - pages: [HowItWorksPage, TBTCBridge, ExplorerPage, ResumeDepositPage], + pages: [ + HowItWorksPage, + TBTCBridge, + ExplorerPage, + ResumeDepositPage, + BobBridge, + ], title: "tBTC", isPageEnabled: featureFlags.TBTC_V2, } diff --git a/src/threshold-ts/bridge/context/NetworkContext.ts b/src/threshold-ts/bridge/context/NetworkContext.ts new file mode 100644 index 000000000..af255736b --- /dev/null +++ b/src/threshold-ts/bridge/context/NetworkContext.ts @@ -0,0 +1,707 @@ +import { BigNumber, Contract, ethers, providers, Signer } from "ethers" +import { TransactionResponse } from "@ethersproject/abstract-provider" +import { AddressZero } from "@ethersproject/constants" +import { EthereumConfig } from "../../types" +import { getContract, getArtifact } from "../../utils" +import { + BobChainSelector, + EthereumChainSelector, + SupportedChainIds, +} from "../../../networks/enums/networks" +import { BridgeOptions, BridgeRoute, BridgeQuote } from "../index" + +export type NetworkType = "ethereum" | "bob" + +export class NetworkContext { + protected readonly cfg: EthereumConfig + protected readonly provider: providers.Provider | Signer + public readonly account?: string + public readonly chainId: number + public readonly networkType: NetworkType | undefined + + // Common contracts + public token!: Contract + public ccipRouter!: Contract + public tokenPool!: Contract + + // Bob-specific contracts + public standardBridge?: Contract + + // Cache for legacy cap (Bob only) + private _legacyCapCache: { value: BigNumber; timestamp: number } | null = null + private readonly _cacheExpiry = 60000 // 1 minute + + constructor(cfg: EthereumConfig) { + this.cfg = cfg + this.provider = cfg.ethereumProviderOrSigner + this.account = cfg.account + this.chainId = Number(cfg.chainId) + this.networkType = this.determineNetworkType() + if (this.networkType) { + this.initContracts() + } + } + + private determineNetworkType(): NetworkType | undefined { + if ( + this.chainId === SupportedChainIds.Bob || + this.chainId === SupportedChainIds.BobSepolia + ) { + return "bob" + } else if ( + this.chainId === SupportedChainIds.Ethereum || + this.chainId === SupportedChainIds.Sepolia + ) { + return "ethereum" + } + console.warn( + `Unsupported network: chainId ${this.chainId}. Bridge only supports Bob (60808/808813) and Ethereum (1/11155111)` + ) + } + + protected initContracts(): void { + const { chainId, shouldUseTestnetDevelopmentContracts, account } = this.cfg + + const tokenPoolArtifact = getArtifact( + "TokenPool", + chainId, + shouldUseTestnetDevelopmentContracts + ) + const ccipRouterArtifact = getArtifact( + "CCIPRouter", + chainId, + shouldUseTestnetDevelopmentContracts + ) + const tokenArtifact = getArtifact( + this.networkType === "bob" ? "OptimismMintableUpgradableTBTC" : "TBTC", + chainId, + shouldUseTestnetDevelopmentContracts + ) + + if (!ccipRouterArtifact || !tokenArtifact || !tokenPoolArtifact) { + throw new Error( + `Bridge-related artifacts not found for ${this.networkType} network` + ) + } + + this.tokenPool = getContract( + tokenPoolArtifact.address, + tokenPoolArtifact.abi, + this.provider, + account + ) + this.ccipRouter = getContract( + ccipRouterArtifact.address, + ccipRouterArtifact.abi, + this.provider, + account + ) + this.token = getContract( + tokenArtifact.address, + tokenArtifact.abi, + this.provider, + account + ) + + // Load Standard Bridge - Bob only + if (this.networkType === "bob") { + const standardBridgeArtifact = getArtifact( + "StandardBridge", + chainId, + shouldUseTestnetDevelopmentContracts + ) + + if (!standardBridgeArtifact) { + throw new Error("Standard Bridge artifact not found for Bob network") + } + + this.standardBridge = getContract( + standardBridgeArtifact.address, + standardBridgeArtifact.abi, + this.provider, + account + ) + } + + console.log(`${this.networkType} network contracts initialized:`, { + token: !!this.token, + tokenAddress: this.token?.address, + ccipRouter: !!this.ccipRouter, + ccipRouterAddress: this.ccipRouter?.address, + tokenPool: !!this.tokenPool, + tokenPoolAddress: this.tokenPool?.address, + standardBridge: !!this.standardBridge, + standardBridgeAddress: this.standardBridge?.address, + }) + } + + protected isMainnet(): boolean { + return ( + this.chainId === SupportedChainIds.Bob || + this.chainId === SupportedChainIds.Ethereum + ) + } + + protected isTestnet(): boolean { + return ( + this.chainId === SupportedChainIds.BobSepolia || + this.chainId === SupportedChainIds.Sepolia + ) + } + + /** + * Get the CCIP chain selector for the destination network + * @param {number} [targetChainId] - Optional target chain ID. If not provided, determines based on current network + * @return {string} The CCIP chain selector for the destination network + */ + protected getDestinationChainSelector(targetChainId?: number): string { + // If no target specified, determine based on current network + if (!targetChainId) { + // If on Ethereum/Sepolia -> Bob/BobSepolia + if ( + this.chainId === SupportedChainIds.Ethereum || + this.chainId === SupportedChainIds.Sepolia + ) { + return this.isMainnet() + ? BobChainSelector.Bob + : BobChainSelector.BobSepolia + } + // If on Bob/BobSepolia -> Ethereum/Sepolia + else if ( + this.chainId === SupportedChainIds.Bob || + this.chainId === SupportedChainIds.BobSepolia + ) { + return this.isMainnet() + ? EthereumChainSelector.Ethereum + : EthereumChainSelector.Sepolia + } + } + + // If target specified, return its selector + switch (targetChainId) { + case SupportedChainIds.Ethereum: + return EthereumChainSelector.Ethereum + case SupportedChainIds.Sepolia: + return EthereumChainSelector.Sepolia + case SupportedChainIds.Bob: + return BobChainSelector.Bob + case SupportedChainIds.BobSepolia: + return BobChainSelector.BobSepolia + default: + throw new Error(`Unsupported target chain ID: ${targetChainId}`) + } + } + + /** + * Approves CCIP Router to spend tokens (works on both networks) + * @param {BigNumber} amount - Amount to approve + * @return {Promise} Transaction response or null if already approved + */ + async approveForCcip(amount: BigNumber): Promise { + if (!this.account) { + throw new Error("No account connected") + } + + try { + const currentAllowance = await this.getCcipAllowance() + + if (currentAllowance.gte(amount)) { + console.log( + `CCIP Router approval not needed. Current allowance: ${currentAllowance.toString()}` + ) + return null + } + console.log(`Approving CCIP Router for ${amount.toString()}`) + + const tx = await this.token.approve(this.ccipRouter.address, amount) + return tx + } catch (error: any) { + console.error("Failed to approve for CCIP Router:", error) + throw new Error(`CCIP Router approval failed: ${error.message}`) + } + } + + /** + * Gets current CCIP Router allowance + */ + async getCcipAllowance(): Promise { + if (!this.account) { + throw new Error("No account connected") + } + + const allowance = await this.token.allowance( + this.account, + this.ccipRouter.address + ) + return allowance + } + + /** + * Withdraws tBTC from Bob network to Ethereum L1. + * @param {BigNumber} amount - Amount of tBTC to withdraw. + * @param {BridgeOptions} [opts] - Optional transaction parameters. + * @throws Error if called on Ethereum network. + */ + async withdrawFromBob( + amount: BigNumber, + opts?: BridgeOptions + ): Promise { + if (this.networkType !== "bob") { + throw new Error( + "withdrawFromBob() can only be called from Bob network. Please switch to Bob network." + ) + } + + if (amount.lte(0)) { + throw new Error("Withdrawal amount must be greater than zero") + } + + // Determine the appropriate route + const route = await this.pickPath(amount) + + // Route to the appropriate withdrawal method + if (route === "ccip") { + return this._withdrawViaCcip(amount, opts) + } else { + return this._withdrawViaStandard(amount, opts) + } + } + + /** + * Deposits tBTC from Ethereum L1 to Bob network + * @param {BigNumber} amount - Amount of tBTC to deposit. + * @param {BridgeOptions} [opts] - Optional transaction parameters. + * @throws Error if called on Bob network + */ + async depositToBob( + amount: BigNumber, + opts?: BridgeOptions + ): Promise { + if (this.networkType !== "ethereum") { + throw new Error( + "depositToBob() can only be called from Ethereum L1. Please switch to Ethereum network." + ) + } + + // Validate amount + if (amount.lte(0)) { + throw new Error("Deposit amount must be greater than zero") + } + + if (!this.account) { + throw new Error("No account connected") + } + + const recipient = opts?.recipient || this.account + + try { + // Check and handle token approval + const approvalTx = await this.approveForCcip(amount) + if (approvalTx) { + console.log(`Waiting for CCIP Router approval tx: ${approvalTx.hash}`) + await approvalTx.wait() + } + + const destinationChainSelector = this.getDestinationChainSelector() + + // Build EVM2AnyMessage for CCIP + const tokenAmounts = [ + { + token: this.token.address, + amount: amount, + }, + ] + + // For L1 to Bob, we always use the standard encoded format + const encodedReceiver = ethers.utils.defaultAbiCoder.encode( + ["address"], + [recipient] + ) + + const message = { + receiver: encodedReceiver, + data: "0x", + tokenAmounts: tokenAmounts, + feeToken: AddressZero, + extraArgs: this.encodeExtraArgs(200000), + } + + // Calculate fees + const fees = await this._quoteCcipBridgingFees(amount, 60 * 60) + + // Build transaction parameters + // Add 10% buffer to the fee to avoid InsufficientFeeTokenAmount errors + const feeWithBuffer = fees.fee.mul(110).div(100) + const txParams: any = { + value: feeWithBuffer, + ...(opts?.gasLimit && { gasLimit: opts.gasLimit }), + ...(opts?.gasPrice && { gasPrice: opts.gasPrice }), + ...(opts?.maxFeePerGas && { maxFeePerGas: opts.maxFeePerGas }), + ...(opts?.maxPriorityFeePerGas && { + maxPriorityFeePerGas: opts.maxPriorityFeePerGas, + }), + } + + // Execute CCIP deposit + const tx = await this.ccipRouter.ccipSend( + destinationChainSelector, + message, + txParams + ) + + console.log(`L1 to Bob CCIP deposit initiated. Tx hash: ${tx.hash}`) + console.log(`Deposit will arrive on Bob in ~60 minutes`) + + return tx + } catch (error: any) { + console.error("L1 to Bob CCIP deposit failed:", error) + throw new Error(`CCIP deposit failed: ${error.message}`) + } + } + + /** + * Quotes fees for any bridge operation (deposits or withdrawals) + * @param {BigNumber} amount - Amount of tBTC to deposit or withdraw. + * @return {Promise} Fee quote with breakdown. + */ + async quoteFees(amount: BigNumber): Promise { + if (amount.lte(0)) { + throw new Error("Amount must be greater than zero") + } + + const route = await this.pickPath(amount) + const estimatedTime = this.getBridgingTime(route) + + if (route === "ccip") { + return this._quoteCcipBridgingFees(amount, estimatedTime) + } else { + return this._quoteStandardBridgingFees(amount, estimatedTime) + } + } + + /** + * Checks if amount can be withdrawn (Bob network only) + * @param {BigNumber} amount - Amount of tBTC to withdraw. + * @return {Promise} Withdrawal feasibility with canWithdraw, route and reason properties + * @throws Error if called on Ethereum network + */ + async canWithdraw(amount: BigNumber): Promise<{ + canWithdraw: boolean + route?: BridgeRoute + reason?: string + }> { + if (this.networkType !== "bob") { + throw new Error( + "canWithdraw() can only be called from Bob network. Cannot withdraw from Ethereum." + ) + } + + try { + const route = await this.pickPath(amount) + return { canWithdraw: true, route } + } catch (error: any) { + return { + canWithdraw: false, + reason: error.message, + } + } + } + + /** + * Determines the optimal withdrawal route (Bob network only) + * @param {BigNumber} amount - Amount of tBTC to withdraw. + * @return {Promise} Optimal withdrawal route. + * @throws Error if called on Ethereum network + */ + async pickPath(amount: BigNumber): Promise { + if (!this.networkType) { + throw new Error("Network type not determined") + } + + if (amount.lte(0)) { + throw new Error("Amount must be greater than zero") + } + + // Case 1: Ethereum network - CCIP always available + if (this.networkType === "ethereum") { + return "ccip" + } + + // Case 2: Bob network - check legacy cap + const legacyCapRemaining = await this.getLegacyCapRemaining() + + // Case 3: Legacy cap is exhausted - CCIP available + if (legacyCapRemaining.eq(0)) { + return "ccip" + } + + // Case 2: Amount fits within legacy cap - use standard bridge + if (amount.lte(legacyCapRemaining)) { + return "standard" + } + + // Case 3: Amount exceeds legacy cap but cap > 0 - blocked + throw new Error( + `Amount ${amount.toString()} exceeds legacy cap remaining ${legacyCapRemaining.toString()}. ` + + `Please wait for legacy cap to deplete or reduce your withdrawal amount.` + ) + } + + /** + * Gets the remaining legacy cap (Bob network only) + * @throws Error if called on Ethereum network + */ + async getLegacyCapRemaining(): Promise { + if (this.networkType !== "bob") { + throw new Error( + "getLegacyCapRemaining() can only be called from Bob network" + ) + } + + // Check cache validity + if ( + this._legacyCapCache && + Date.now() - this._legacyCapCache.timestamp < this._cacheExpiry + ) { + return this._legacyCapCache.value + } + + try { + const legacyCapRemaining = await this.token.legacyCapRemaining() + + // Update cache + this._legacyCapCache = { + value: legacyCapRemaining, + timestamp: Date.now(), + } + + return legacyCapRemaining + } catch (error: any) { + console.error("Failed to fetch legacyCapRemaining:", error) + + // If we have stale cache data, return it as fallback + if (this._legacyCapCache) { + console.warn("Returning stale cache data due to contract call failure") + return this._legacyCapCache.value + } + + throw new Error(`Failed to get legacy cap remaining: ${error.message}`) + } + } + + /** + * Get withdrawal time estimates + * @param {BridgeRoute} route - The withdrawal route. + * @return {number} The estimated withdrawal time in seconds. + */ + getBridgingTime(route: BridgeRoute): number { + switch (route) { + case "ccip": + return 60 * 60 // ~60 minutes in seconds + case "standard": + return 7 * 24 * 60 * 60 // 7 days in seconds + default: + throw new Error(`Unknown route: ${route}`) + } + } + + // Private methods for internal operations + + private async _withdrawViaCcip( + amount: BigNumber, + opts?: BridgeOptions + ): Promise { + if (!this.account) { + throw new Error("No account connected") + } + + const recipient = opts?.recipient || this.account + + try { + // Check and handle token approval + const approvalTx = await this.approveForCcip(amount) + if (approvalTx) { + console.log(`Waiting for CCIP Router approval tx: ${approvalTx.hash}`) + await approvalTx.wait() + } + + // Build EVM2AnyMessage for CCIP + const tokenAmounts = [ + { + token: this.token.address, + amount: amount, + }, + ] + + const destinationChainSelector = this.getDestinationChainSelector() + + const message = { + receiver: ethers.utils.defaultAbiCoder.encode(["address"], [recipient]), + data: "0x", + tokenAmounts: tokenAmounts, + feeToken: AddressZero, + extraArgs: this.encodeExtraArgs(200000), + } + + // Calculate fees + const fees = await this.ccipRouter.getFee( + destinationChainSelector, + message + ) + + // Send CCIP message + // Add 10% buffer to the fee to avoid InsufficientFeeTokenAmount errors + const feeWithBuffer = fees.mul(110).div(100) + + const txParams: any = { + value: feeWithBuffer, + ...(opts?.gasLimit && { gasLimit: opts.gasLimit }), + ...(opts?.gasPrice && { gasPrice: opts.gasPrice }), + ...(opts?.maxFeePerGas && { maxFeePerGas: opts.maxFeePerGas }), + ...(opts?.maxPriorityFeePerGas && { + maxPriorityFeePerGas: opts.maxPriorityFeePerGas, + }), + } + + const tx = await this.ccipRouter.ccipSend( + destinationChainSelector, + message, + txParams + ) + + console.log(`CCIP withdrawal initiated. Tx hash: ${tx.hash}`) + return tx + } catch (error: any) { + console.error("CCIP withdrawal failed:", error) + throw new Error(`CCIP withdrawal failed: ${error.message}`) + } + } + + private async _withdrawViaStandard( + amount: BigNumber, + opts?: BridgeOptions + ): Promise { + if (!this.account) { + throw new Error("No account connected") + } + + if (!this.standardBridge) { + throw new Error("Standard bridge not available") + } + + const recipient = opts?.recipient || this.account + + try { + const txParams: any = { + ...(opts?.gasLimit && { gasLimit: opts.gasLimit }), + ...(opts?.gasPrice && { gasPrice: opts.gasPrice }), + ...(opts?.maxFeePerGas && { maxFeePerGas: opts.maxFeePerGas }), + ...(opts?.maxPriorityFeePerGas && { + maxPriorityFeePerGas: opts.maxPriorityFeePerGas, + }), + } + + const tx = await this.standardBridge.withdrawTo( + this.token.address, + recipient, + amount, + opts?.deadline || 0, + "0x", + txParams + ) + + console.log( + `Standard Bridge withdrawal initiated (7-day delay). Tx hash: ${tx.hash}` + ) + return tx + } catch (error: any) { + console.error("Standard Bridge withdrawal failed:", error) + throw new Error(`Standard Bridge withdrawal failed: ${error.message}`) + } + } + + private async _quoteCcipBridgingFees( + amount: BigNumber, + estimatedTime: number + ): Promise { + try { + const tokenAmounts = [ + { + token: this.token.address, + amount: amount, + }, + ] + const destinationChainSelector = this.getDestinationChainSelector() + + const message = { + receiver: ethers.utils.defaultAbiCoder.encode( + ["address"], + [this.account || AddressZero] + ), + data: "0x", + tokenAmounts: tokenAmounts, + feeToken: AddressZero, + extraArgs: this.encodeExtraArgs(200000), + } + + const ccipFee = await this.ccipRouter.getFee( + destinationChainSelector, + message + ) + + return { + route: "ccip", + fee: ccipFee, + estimatedTime: estimatedTime, + breakdown: { + ccipFee: ccipFee, + standardFee: BigNumber.from(0), + }, + } + } catch (error) { + console.error("Failed to get CCIP fee:", error) + // Fallback to estimated fee if contract call fails + const estimatedFee = amount.mul(3).div(1000) // 0.3% + + return { + route: "ccip", + fee: estimatedFee, + estimatedTime: estimatedTime, + breakdown: { + ccipFee: estimatedFee, + standardFee: BigNumber.from(0), + }, + } + } + } + + private async _quoteStandardBridgingFees( + amount: BigNumber, + estimatedTime: number + ): Promise { + const gasPrice = await this.provider.getGasPrice() + const estimatedGas = BigNumber.from(200000) + const standardFee = gasPrice.mul(estimatedGas) + + return { + route: "standard", + fee: standardFee, + estimatedTime: estimatedTime, + breakdown: { + standardFee: standardFee, + ccipFee: BigNumber.from(0), + }, + } + } + + private encodeExtraArgs(gasLimit: number): string { + const abiCoder = new ethers.utils.AbiCoder() + // CCIP requires extraArgs to be prefixed with a version tag + // For EVMExtraArgsV1, we only encode gasLimit (uint256) + const encodedArgs = abiCoder.encode(["uint256"], [gasLimit]) + // EVMExtraArgsV1 tag is 0x97a657c9 (required by CCIP router) + const evmExtraArgsV1Tag = "0x97a657c9" + // Concatenate tag with encoded args (removing 0x prefix from encodedArgs) + return evmExtraArgsV1Tag + encodedArgs.slice(2) + } +} diff --git a/src/threshold-ts/bridge/index.ts b/src/threshold-ts/bridge/index.ts new file mode 100644 index 000000000..190fda2b9 --- /dev/null +++ b/src/threshold-ts/bridge/index.ts @@ -0,0 +1,643 @@ +import { BigNumber, Event } from "ethers" +import { TransactionResponse } from "@ethersproject/abstract-provider" +import { EthereumConfig, CrossChainConfig } from "../types" +import { IMulticall } from "../multicall" +import { NetworkContext } from "./context/NetworkContext" + +export interface IBridge { + // Core bridge methods + withdrawFromBob( + amount: BigNumber, + opts?: BridgeOptions + ): Promise + depositToBob( + amount: BigNumber, + opts?: BridgeOptions + ): Promise + + // Approval helpers + approveForCcip(amount: BigNumber): Promise + + // Routing and quotes + pickPath(amount: BigNumber): Promise + quoteFees(amount: BigNumber): Promise + + // Utilities + getLegacyCapRemaining(): Promise + canWithdraw(amount: BigNumber): Promise<{ + canWithdraw: boolean + route?: BridgeRoute + reason?: string + }> + getCcipAllowance(): Promise + getBridgingTime(route: BridgeRoute): number + getContext(): NetworkContext + fetchBridgeActivities( + account: string, + fromBlock?: number | string + ): Promise +} + +export type BridgeRoute = "ccip" | "standard" + +export type BridgeActivityStatus = "BRIDGED" | "PENDING" | "ERROR" + +export interface BridgeActivity { + amount: string + status: BridgeActivityStatus + activityKey: string + bridgeRoute: BridgeRoute + fromNetwork: string + toNetwork: string + txHash: string + timestamp: number + explorerUrl: string +} + +export interface BridgeOptions { + gasLimit?: BigNumber + gasPrice?: BigNumber + maxFeePerGas?: BigNumber + maxPriorityFeePerGas?: BigNumber + recipient?: string + slippage?: number + deadline?: number +} + +export interface BridgeQuote { + route: BridgeRoute + fee: BigNumber + estimatedTime: number // seconds + breakdown?: { + ccipAmount?: BigNumber + standardAmount?: BigNumber + ccipFee?: BigNumber + standardFee?: BigNumber + } +} + +export class Bridge implements IBridge { + private readonly context: NetworkContext + + constructor(ethereumConfig: EthereumConfig) { + // Create the appropriate context based on the current network + this.context = new NetworkContext(ethereumConfig) + } + + /** + * Withdraws tBTC from Bob network to Ethereum L1. + * This method can only be called when connected to Bob network. + * + * @param {BigNumber} amount - Amount of tBTC to withdraw + * @param {BridgeOptions} [opts] - Optional transaction parameters + * @return {Promise} Transaction response + * @throws Error if not on Bob network + */ + async withdrawFromBob( + amount: BigNumber, + opts?: BridgeOptions + ): Promise { + return this.context.withdrawFromBob(amount, opts) + } + + /** + * Deposits tBTC from Ethereum L1 to Bob network. + * This method can only be called when connected to Ethereum network. + * + * @param {BigNumber} amount - Amount of tBTC to deposit + * @param {BridgeOptions} [opts] - Optional transaction parameters + * @return {Promise} Transaction response + * @throws Error if not on Ethereum network + */ + async depositToBob( + amount: BigNumber, + opts?: BridgeOptions + ): Promise { + return this.context.depositToBob(amount, opts) + } + + /** + * Approves CCIP Router to spend tokens (works on both networks) + * @param {BigNumber} amount - Amount to approve + * @return {Promise} Transaction response or null if already approved + */ + async approveForCcip(amount: BigNumber): Promise { + return this.context.approveForCcip(amount) + } + + /** + * Determines the optimal withdrawal route (Bob network only) + * @param {BigNumber} amount - Amount to withdraw + * @return {Promise} Withdrawal route ("ccip" or "standard") + * @throws Error if not on Bob network + */ + async pickPath(amount: BigNumber): Promise { + return this.context.pickPath(amount) + } + + /** + * Quotes fees for withdrawals or deposits + * @param {BigNumber} amount - Amount to transfer + * @return {Promise} Fee quote with breakdown + */ + async quoteFees(amount: BigNumber): Promise { + return this.context.quoteFees(amount) + } + + /** + * Gets the remaining legacy cap (Bob network only) + * @return {Promise} Remaining legacy cap + * @throws Error if not on Bob network + */ + async getLegacyCapRemaining(): Promise { + return this.context.getLegacyCapRemaining() + } + + /** + * Checks if amount can be withdrawn (Bob network only) + * @param {BigNumber} amount - Amount to check + * @return {Promise} Withdrawal feasibility with canWithdraw, route and reason properties + * @throws Error if not on Bob network + */ + async canWithdraw(amount: BigNumber): Promise<{ + canWithdraw: boolean + route?: BridgeRoute + reason?: string + }> { + return this.context.canWithdraw(amount) + } + + /** + * Gets CCIP Router allowance (works on both networks) + * @return {Promise} Current allowance + */ + async getCcipAllowance(): Promise { + return this.context.getCcipAllowance() + } + + /** + * Gets withdrawal time estimate + * @param {BridgeRoute} route - Route type ("ccip" or "standard") + * @return {number} Time estimate in seconds + */ + getBridgingTime(route: BridgeRoute): number { + return this.context.getBridgingTime(route) + } + + getContext(): NetworkContext { + return this.context + } + + /** + * Fetches bridge activities for a given account + * @param {string} account - Account address + * @param {number | string} [fromBlock=-10000] - Starting block (default: last 10000 blocks) + * @return {Promise} Array of bridge activities + */ + async fetchBridgeActivities( + account: string, + fromBlock: number | string = -50000 + ): Promise { + const activities: BridgeActivity[] = [] + + if (!this.context || !this.context.tokenPool) { + console.log("Bridge contracts not initialized") + return activities + } + + // Import getContractPastEvents utility + const { getContractPastEvents } = await import("../utils") + + // Get the block number to start from + const currentBlock = await this.context.tokenPool.provider.getBlockNumber() + const startBlock = + typeof fromBlock === "number" && fromBlock < 0 + ? Math.max(0, currentBlock + fromBlock) + : fromBlock + + // fetching window computed using fromBlock + + // Check if contract has the expected events + // token pool filters check + + // Also log router filters if available + if (this.context.ccipRouter) { + // router available + } + try { + if (this.context.chainId === 1 || this.context.chainId === 11155111) { + // L1 (Ethereum/Sepolia) + // L1 events (Locked/Released) + // Locked events: user deposits (sends to L2) + // First try without filter to see if there are any events + const allLockedEvents = await getContractPastEvents( + this.context.tokenPool, + { + eventName: "Locked", + filterParams: [], // no filter first + fromBlock: startBlock, + toBlock: "latest", + } + ) + // total Locked events + + // Now filter by sender + // eslint-disable-next-line prefer-const + let lockedEvents = allLockedEvents.filter((event) => { + const matches = + event.args?.sender?.toLowerCase() === account.toLowerCase() + if (!matches && allLockedEvents.length < 10) { + // skip + } + return matches + }) + // matched Locked events for account + + // If no events found by indexed param, filter by transaction sender + if (lockedEvents.length === 0 && allLockedEvents.length > 0) { + // fallback: filter by transaction sender + for (const event of allLockedEvents) { + try { + const tx = await event.getTransaction() + if (tx.from.toLowerCase() === account.toLowerCase()) { + // found tx initiated by user + lockedEvents.push(event) + } + } catch (err) { + console.error("Error getting transaction:", err) + } + } + // matched by tx sender + } + // process Locked events + for (const event of lockedEvents) { + const activity = await this.parseTokenPoolEvent( + event, + "PENDING", + "deposit" + ) + if (activity) { + activities.push(activity) + } + } + + // Released events: user receives from L2 + const allReleasedEvents = await getContractPastEvents( + this.context.tokenPool, + { + eventName: "Released", + filterParams: [], // no filter first + fromBlock: startBlock, + toBlock: "latest", + } + ) + // total Released events + + // Filter by recipient + // eslint-disable-next-line prefer-const + let releasedEvents = allReleasedEvents.filter( + (event) => + event.args?.recipient?.toLowerCase() === account.toLowerCase() + ) + // matched Released events for account + + // If no events by recipient, check by transaction sender + if (releasedEvents.length === 0 && allReleasedEvents.length > 0) { + // fallback: filter Released by tx sender + for (const event of allReleasedEvents) { + try { + const tx = await event.getTransaction() + if (tx.from.toLowerCase() === account.toLowerCase()) { + // found user-initiated Released tx + releasedEvents.push(event) + } + } catch (err) { + console.error("Error getting transaction:", err) + } + } + // matched Released by tx sender + } + for (const event of releasedEvents) { + const activity = await this.parseTokenPoolEvent( + event, + "BRIDGED", + "withdrawal" + ) + if (activity) activities.push(activity) + } + } else { + // L2 (BOB/BOB Sepolia) + // L2 events (Burned/Minted) + // Burned events: user withdraws (sends to L1) + const allBurnedEvents = await getContractPastEvents( + this.context.tokenPool, + { + eventName: "Burned", + filterParams: [], // no filter first + fromBlock: startBlock, + toBlock: "latest", + } + ) + // total Burned events + + // Filter by sender + // eslint-disable-next-line prefer-const + let burnedEvents = allBurnedEvents.filter( + (event) => event.args?.sender?.toLowerCase() === account.toLowerCase() + ) + // matched Burned events for account + + // If no events by sender, check by transaction sender + if (burnedEvents.length === 0 && allBurnedEvents.length > 0) { + // fallback: filter Burned by tx sender + for (const event of allBurnedEvents) { + try { + const tx = await event.getTransaction() + if (tx.from.toLowerCase() === account.toLowerCase()) { + // found user-initiated Burned tx + burnedEvents.push(event) + } + } catch (err) { + console.error("Error getting transaction:", err) + } + } + // matched Burned by tx sender + } + for (const event of burnedEvents) { + const activity = await this.parseTokenPoolEvent( + event, + "PENDING", + "withdrawal" + ) + if (activity) activities.push(activity) + } + + // Minted events: user receives from L1 + const allMintedEvents = await getContractPastEvents( + this.context.tokenPool, + { + eventName: "Minted", + filterParams: [], // no filter first + fromBlock: startBlock, + toBlock: "latest", + } + ) + // total Minted events + + // Filter by recipient + // eslint-disable-next-line prefer-const + let mintedEvents = allMintedEvents.filter( + (event) => + event.args?.recipient?.toLowerCase() === account.toLowerCase() + ) + // matched Minted events for account + + // If no events by recipient, check by transaction sender + if (mintedEvents.length === 0 && allMintedEvents.length > 0) { + // fallback: filter Minted by tx sender + for (const event of allMintedEvents) { + try { + const tx = await event.getTransaction() + if (tx.from.toLowerCase() === account.toLowerCase()) { + // found user-initiated Minted tx + mintedEvents.push(event) + } + } catch (err) { + console.error("Error getting transaction:", err) + } + } + // matched Minted by tx sender + } + for (const event of mintedEvents) { + const activity = await this.parseTokenPoolEvent( + event, + "BRIDGED", + "deposit" + ) + if (activity) activities.push(activity) + } + } + } catch (error) { + // ignore token pool errors + } + + // Standard bridge (WithdrawalInitiated) on L2 when used + if (this.context.networkType === "bob" && this.context.standardBridge) { + try { + const getWithdrawalInitiated: any = ( + this.context.standardBridge.filters as any + ).WithdrawalInitiated + const withdrawFilter = getWithdrawalInitiated() + const withdrawEvents = await this.context.standardBridge.queryFilter( + withdrawFilter, + fromBlock, + "latest" + ) + + // Filter by indexed 'from' parameter + const matchedEvents = withdrawEvents.filter((event) => { + const fromParam = (event.args as any)?.from + return fromParam && fromParam.toLowerCase() === account.toLowerCase() + }) + + // If no matches by event param, try by transaction sender as fallback + if (matchedEvents.length === 0 && withdrawEvents.length > 0) { + for (const event of withdrawEvents) { + try { + const tx = await event.getTransaction() + if (tx.from.toLowerCase() === account.toLowerCase()) { + matchedEvents.push(event) + } + } catch { + // ignore tx fetch errors + } + } + } + + for (const event of matchedEvents) { + const activity = await this.parseStandardBridgeEvent(event) + if (activity) { + activities.push(activity) + } + } + } catch (error) { + // ignore standard bridge errors + } + } + + // Try to get CCIP messages from router if available + if (this.context.ccipRouter) { + try { + const messageEvents = await getContractPastEvents( + this.context.ccipRouter, + { + eventName: "MessageExecuted", + filterParams: [], + fromBlock: startBlock, + toBlock: "latest", + } + ) + // optionally correlate with user + } catch (error) { + // ignore router errors + } + } + + // Sort by timestamp descending + activities.sort((a, b) => b.timestamp - a.timestamp) + + // Return all activities - the hook will filter based on the current network + // This avoids issues with stale context when the bridge instance is recreated + return activities + } + + private async parseCCIPEvent(event: Event): Promise { + try { + const block = await event.getBlock() + const fromNetwork = this.getNetworkName(this.context.chainId) + const toNetwork = + this.context.networkType === "ethereum" ? "Bob" : "Ethereum" + + return { + amount: event.args?.tokenAmounts?.[0]?.amount?.toString() || "0", + status: "BRIDGED", + activityKey: `ccip-${event.transactionHash}-${event.logIndex}`, + bridgeRoute: "ccip", + fromNetwork, + toNetwork, + txHash: event.transactionHash, + timestamp: block.timestamp, + explorerUrl: `https://ccip.chain.link/tx/${event.transactionHash}`, + } + } catch (error) { + // ignore parse error + return null + } + } + + private async parseStandardBridgeEvent( + event: Event + ): Promise { + try { + const block = await event.getBlock() + const fromNetwork = this.getNetworkName(this.context.chainId) + const toNetwork = + this.context.chainId === 60808 ? "Ethereum" : "Ethereum Sepolia" + + // WithdrawalInitiated event args + const eventArgs = event.args as any + const amount = eventArgs?.amount || "0" + + return { + amount: amount.toString(), + status: "PENDING", // Standard bridge takes 7 days + activityKey: `standard-${event.transactionHash}-${event.logIndex}`, + bridgeRoute: "standard", + fromNetwork, + toNetwork, + txHash: event.transactionHash, + timestamp: block.timestamp, + explorerUrl: + this.context.chainId === 60808 + ? `https://explorer.gobob.xyz/tx/${event.transactionHash}` + : `https://bob-sepolia.explorer.gobob.xyz/tx/${event.transactionHash}`, + } + } catch (error) { + // ignore parse error + return null + } + } + + private async parseTokenPoolEvent( + event: Event, + status: BridgeActivityStatus, + direction: "deposit" | "withdrawal" + ): Promise { + try { + // parse token pool event + const block = await event.getBlock() + + // Determine from/to networks based on current chain and direction + let fromNetwork: string + let toNetwork: string + + if (this.context.chainId === 1 || this.context.chainId === 11155111) { + // On L1 + if (direction === "deposit") { + // Locked event: sending from L1 to L2 + fromNetwork = this.getNetworkName(this.context.chainId) + toNetwork = this.context.chainId === 1 ? "BOB" : "BOB Sepolia" + } else { + // Released event: received on L1 from L2 + fromNetwork = this.context.chainId === 1 ? "BOB" : "BOB Sepolia" + toNetwork = this.getNetworkName(this.context.chainId) + } + } else { + // On L2 + if (direction === "withdrawal") { + // Burned event: sending from L2 to L1 + fromNetwork = this.getNetworkName(this.context.chainId) + toNetwork = + this.context.chainId === 60808 ? "Ethereum" : "Ethereum Sepolia" + } else { + // Minted event: received on L2 from L1 + fromNetwork = + this.context.chainId === 60808 ? "Ethereum" : "Ethereum Sepolia" + toNetwork = this.getNetworkName(this.context.chainId) + } + } + + // Extract amount from event args + const amount = event.args?.amount || "0" + + const txHash = event.transactionHash + const logIndex = event.logIndex + + return { + amount: amount.toString(), + status, + activityKey: `${event.event}-${txHash}-${logIndex}`, + bridgeRoute: "ccip", + fromNetwork, + toNetwork, + txHash, + timestamp: block.timestamp, + explorerUrl: `https://ccip.chain.link/tx/${txHash}`, + } + } catch (error) { + console.error("Failed to parse token pool event:", error) + return null + } + } + + private getNetworkName(chainId: number): string { + switch (chainId) { + case 1: + return "Ethereum" + case 11155111: + return "Ethereum Sepolia" + case 60808: + return "BOB" + case 808813: + return "BOB Sepolia" + default: + return "Unknown" + } + } + + private getExplorerBaseUrl(chainId: number): string { + switch (chainId) { + case 1: + return "https://etherscan.io/tx/" + case 11155111: + return "https://sepolia.etherscan.io/tx/" + case 60808: + case 808813: + return "https://explorer.gobob.xyz/tx/" + default: + return "" + } + } +} + +// Types are exported at the top of the file diff --git a/src/threshold-ts/index.ts b/src/threshold-ts/index.ts index 78a4e0353..0884b6218 100644 --- a/src/threshold-ts/index.ts +++ b/src/threshold-ts/index.ts @@ -1,17 +1,20 @@ +import { VendingMachines, IVendingMachines } from "./vending-machine" import { MultiAppStaking } from "./mas" -import { IMulticall, Multicall } from "./multicall" -import { IStaking, Staking } from "./staking" -import { ITBTC, TBTC } from "./tbtc" +import { Staking, IStaking } from "./staking" +import { TBTC, ITBTC } from "./tbtc" +import { Bridge, IBridge } from "./bridge" +import { Multicall, IMulticall } from "./multicall" import { ThresholdConfig } from "./types" -import { IVendingMachines, VendingMachines } from "./vending-machine" export class Threshold { + // @ts-ignore config!: ThresholdConfig multicall!: IMulticall + vendingMachines!: IVendingMachines staking!: IStaking multiAppStaking!: MultiAppStaking - vendingMachines!: IVendingMachines tbtc!: ITBTC + bridge!: IBridge constructor(config: ThresholdConfig) { this._initialize(config) @@ -30,6 +33,7 @@ export class Threshold { ethereum ) this.tbtc = new TBTC(ethereum, bitcoin, crossChain) + this.bridge = new Bridge(ethereum) } updateConfig = async (config: ThresholdConfig) => { diff --git a/src/threshold-ts/multicall/index.ts b/src/threshold-ts/multicall/index.ts index 45bd69aa7..727e92068 100644 --- a/src/threshold-ts/multicall/index.ts +++ b/src/threshold-ts/multicall/index.ts @@ -46,9 +46,11 @@ export const MULTICALL_ADDRESSES = { [SupportedChainIds.Arbitrum]: "0xcA11bde05977b3631167028862bE2a173976CA11", [SupportedChainIds.Base]: "0xcA11bde05977b3631167028862bE2a173976CA11", [SupportedChainIds.Localhost]: process.env.REACT_APP_MULTICALL_ADDRESS, - // [SupportedChainIds.BaseSepolia]: "0xcA11bde05977b3631167028862bE2a173976CA11", - // [SupportedChainIds.ArbitrumSepolia]: - // "0xcA11bde05977b3631167028862bE2a173976CA11", + [SupportedChainIds.BaseSepolia]: "0xcA11bde05977b3631167028862bE2a173976CA11", + [SupportedChainIds.ArbitrumSepolia]: + "0xcA11bde05977b3631167028862bE2a173976CA11", + [SupportedChainIds.Bob]: "0xcA11bde05977b3631167028862bE2a173976CA11", + [SupportedChainIds.BobSepolia]: "0xcA11bde05977b3631167028862bE2a173976CA11", } as Record export class Multicall implements IMulticall { diff --git a/src/threshold-ts/tbtc/__test__/bridge.test.ts b/src/threshold-ts/tbtc/__test__/bridge.test.ts index f29e8242c..94fedd5d2 100644 --- a/src/threshold-ts/tbtc/__test__/bridge.test.ts +++ b/src/threshold-ts/tbtc/__test__/bridge.test.ts @@ -45,7 +45,7 @@ describe("Bridge", () => { } as unknown as providers.Provider mockEthereumConfig = { - chainId: 60808, // BOB mainnet + chainId: 60808, // Bob mainnet ethereumProviderOrSigner: mockProvider, account: "0x1234567890123456789012345678901234567890", shouldUseTestnetDevelopmentContracts: false, @@ -53,7 +53,7 @@ describe("Bridge", () => { mockCrossChainConfig = { isCrossChain: true, - chainName: "BOB", + chainName: "Bob", nonEVMProvider: null, } @@ -112,10 +112,10 @@ describe("Bridge", () => { mockMulticall ) - // Create a non-BOB bridge for testing uninitialized contracts + // Create a non-Bob bridge for testing uninitialized contracts const nonBobConfig = { ...mockEthereumConfig, - chainId: 1, // Ethereum mainnet, not BOB + chainId: 1, // Ethereum mainnet, not Bob } bridgeNonBob = new Bridge( nonBobConfig, @@ -355,7 +355,7 @@ describe("Bridge", () => { }) describe("contract initialization", () => { - it("should initialize contracts for BOB mainnet", () => { + it("should initialize contracts for Bob mainnet", () => { // Arrange const bobMainnetConfig = { ...mockEthereumConfig, @@ -375,7 +375,7 @@ describe("Bridge", () => { // We'll test actual contract loading in integration tests }) - it("should initialize contracts for BOB testnet", () => { + it("should initialize contracts for Bob testnet", () => { // Arrange const bobTestnetConfig = { ...mockEthereumConfig, @@ -393,7 +393,7 @@ describe("Bridge", () => { expect(bridge).toBeInstanceOf(Bridge) }) - it("should handle non-BOB networks gracefully", () => { + it("should handle non-Bob networks gracefully", () => { // Arrange const nonBobConfig = { ...mockEthereumConfig, @@ -409,14 +409,14 @@ describe("Bridge", () => { // Assert expect(bridge).toBeInstanceOf(Bridge) - // Contracts should remain null for non-BOB networks + // Contracts should remain null for non-Bob networks }) it("should ensure contracts are initialized before operations", async () => { // Arrange const nonBobConfig = { ...mockEthereumConfig, - chainId: 1, // Non-BOB network + chainId: 1, // Non-Bob network } const bridge = new Bridge( nonBobConfig, @@ -706,7 +706,7 @@ describe("Bridge", () => { // Arrange const nonBobConfig = { ...mockEthereumConfig, - chainId: 1, // Ethereum mainnet, not BOB + chainId: 1, // Ethereum mainnet, not Bob } const bridgeNoContracts = new Bridge( nonBobConfig, @@ -818,7 +818,7 @@ describe("Bridge", () => { // Arrange const nonBobConfig = { ...mockEthereumConfig, - chainId: 1, // Ethereum mainnet, not BOB + chainId: 1, // Ethereum mainnet, not Bob } const bridgeNoContracts = new Bridge( nonBobConfig, @@ -909,7 +909,7 @@ describe("Bridge", () => { // Arrange const nonBobConfig = { ...mockEthereumConfig, - chainId: 1, // Ethereum mainnet, not BOB + chainId: 1, // Ethereum mainnet, not Bob } const bridgeNoContracts = new Bridge( nonBobConfig, @@ -1002,7 +1002,7 @@ describe("Bridge", () => { // Arrange const nonBobConfig = { ...mockEthereumConfig, - chainId: 1, // Ethereum mainnet, not BOB + chainId: 1, // Ethereum mainnet, not Bob } const bridgeNoContracts = new Bridge( nonBobConfig, @@ -1272,7 +1272,7 @@ describe("Bridge", () => { // Arrange const nonBobConfig = { ...mockEthereumConfig, - chainId: 1, // Ethereum mainnet, not BOB + chainId: 1, // Ethereum mainnet, not Bob } const bridgeNoContracts = new Bridge( nonBobConfig, @@ -1283,7 +1283,7 @@ describe("Bridge", () => { // Act & Assert await expect(bridgeNoContracts.withdraw(amount)).rejects.toThrow( - "Bridge contracts not initialized. Ensure you're on BOB network." + "Bridge contracts not initialized. Ensure you're on Bob network." ) }) @@ -1623,7 +1623,7 @@ describe("Bridge", () => { ) }) - it("should calculate deposit fees for L1 to BOB", async () => { + it("should calculate deposit fees for L1 to Bob", async () => { // Arrange const amount = BigNumber.from("1000000000000000000") // 1 tBTC const l1GasPrice = BigNumber.from("30000000000") // 30 gwei @@ -1714,7 +1714,7 @@ describe("Bridge", () => { return { address: mockLinkTokenContract.address, abi: [] } } if (name === "OptimismMintableUpgradableTBTC") { - return { address: "0xBOBTokenAddress", abi: [] } + return { address: "0xBobTokenAddress", abi: [] } } return null }) @@ -1743,7 +1743,7 @@ describe("Bridge", () => { bridge = new Bridge(mainnetConfig, mockCrossChainConfig, mockMulticall) }) - it("should execute CCIP deposit from L1 to BOB", async () => { + it("should execute CCIP deposit from L1 to Bob", async () => { // Arrange const amount = BigNumber.from("1000000000000000000") // 1 tBTC @@ -1758,7 +1758,7 @@ describe("Bridge", () => { ) expect(mockL1CCIPRouterContract.getFee).toHaveBeenCalled() expect(mockL1CCIPRouterContract.ccipSend).toHaveBeenCalledWith( - "3849287863852499584", // BOB mainnet chain selector + "3849287863852499584", // Bob mainnet chain selector expect.objectContaining({ receiver: expect.any(String), data: "0x", @@ -1787,7 +1787,7 @@ describe("Bridge", () => { // Assert expect(mockL1CCIPRouterContract.ccipSend).toHaveBeenCalledWith( - "3849287863852499584", // BOB mainnet chain selector + "3849287863852499584", // Bob mainnet chain selector expect.objectContaining({ receiver: expect.stringContaining( customRecipient.slice(2).toLowerCase() @@ -1810,7 +1810,7 @@ describe("Bridge", () => { // Arrange const bobConfig = { ...mockEthereumConfig, - chainId: 60808, // BOB mainnet + chainId: 60808, // Bob mainnet } const bobBridge = new Bridge( bobConfig, @@ -1926,7 +1926,7 @@ describe("Bridge", () => { // Assert expect(result.hash).toBe("0xccipDepositTx") expect(mockL1CCIPRouterContract.ccipSend).toHaveBeenCalledWith( - "5535534526963509396", // BOB Sepolia chain selector + "5535534526963509396", // Bob Sepolia chain selector expect.any(Object), expect.any(Object) ) diff --git a/src/threshold-ts/tbtc/bob-artifacts/BurnFromMintTokenPool.json b/src/threshold-ts/tbtc/bob-artifacts/BurnFromMintTokenPool.json deleted file mode 100644 index 605ec809f..000000000 --- a/src/threshold-ts/tbtc/bob-artifacts/BurnFromMintTokenPool.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "mainnet": { - "address": "0x0000000000000000000000000000000000000000" - }, - "testnet": { - "address": "0x968ec7D09Ee42Ff0bf0Df6442D4B6124cD3d3EfB" - }, - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint8", - "name": "localTokenDecimals", - "type": "uint8" - }, - { - "internalType": "address[]", - "name": "allowlist", - "type": "address[]" - }, - { - "internalType": "address", - "name": "rmnProxy", - "type": "address" - }, - { - "internalType": "address", - "name": "router", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "getAllowListEnabled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "removes", - "type": "address[]" - }, - { - "internalType": "address[]", - "name": "adds", - "type": "address[]" - } - ], - "name": "applyAllowListUpdates", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getToken", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getRouter", - "outputs": [ - { - "internalType": "address", - "name": "router", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "pure", - "type": "function" - } - ] -} diff --git a/src/threshold-ts/tbtc/l1-artifacts/LockReleaseTokenPool.json b/src/threshold-ts/tbtc/bob-artifacts/BurnFromMintTokenPoolUpgradeable.json similarity index 58% rename from src/threshold-ts/tbtc/l1-artifacts/LockReleaseTokenPool.json rename to src/threshold-ts/tbtc/bob-artifacts/BurnFromMintTokenPoolUpgradeable.json index e5e4847d8..2c3757b2b 100644 --- a/src/threshold-ts/tbtc/l1-artifacts/LockReleaseTokenPool.json +++ b/src/threshold-ts/tbtc/bob-artifacts/BurnFromMintTokenPoolUpgradeable.json @@ -1,11 +1,100 @@ { - "mainnet": { - "address": "0x0000000000000000000000000000000000000000" - }, - "sepolia": { - "address": "0x0000000000000000000000000000000000000000" - }, + "address": "0xF248957E8FD81DdFc0d722899b8fFF284a3fCbc3", "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Burned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Minted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "getToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -23,19 +112,59 @@ "name": "rmnProxy", "type": "address" }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "uint64", + "name": "supportedRemoteChainId", + "type": "uint64" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "isSupportedChain", + "outputs": [ { "internalType": "bool", - "name": "acceptLiquidity", + "name": "", "type": "bool" - }, + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", - "name": "router", + "name": "token", "type": "address" } ], - "stateMutability": "nonpayable", - "type": "constructor" + "name": "isSupportedToken", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" }, { "inputs": [ @@ -65,6 +194,11 @@ "internalType": "address", "name": "localToken", "type": "address" + }, + { + "internalType": "bytes", + "name": "extraArgs", + "type": "bytes" } ], "internalType": "struct Pool.LockOrBurnInV1", @@ -95,6 +229,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -164,39 +311,33 @@ "type": "function" }, { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "provideLiquidity", + "inputs": [], + "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "s_rmnProxy", + "outputs": [ { - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "internalType": "address", + "name": "", + "type": "address" } ], - "name": "withdrawLiquidity", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "getLiquidity", + "name": "s_router", "outputs": [ { - "internalType": "uint256", + "internalType": "address", "name": "", - "type": "uint256" + "type": "address" } ], "stateMutability": "view", @@ -204,12 +345,12 @@ }, { "inputs": [], - "name": "owner", + "name": "s_supportedRemoteChainId", "outputs": [ { - "internalType": "address", + "internalType": "uint64", "name": "", - "type": "address" + "type": "uint64" } ], "stateMutability": "view", @@ -217,11 +358,11 @@ }, { "inputs": [], - "name": "getToken", + "name": "s_token", "outputs": [ { "internalType": "contract IERC20", - "name": "token", + "name": "", "type": "address" } ], @@ -246,6 +387,32 @@ ], "stateMutability": "pure", "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "typeAndVersion", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" } ] } diff --git a/src/threshold-ts/tbtc/bob-artifacts/CCIPRouter.json b/src/threshold-ts/tbtc/bob-artifacts/CCIPRouter.json index 42e435233..3aabd396f 100644 --- a/src/threshold-ts/tbtc/bob-artifacts/CCIPRouter.json +++ b/src/threshold-ts/tbtc/bob-artifacts/CCIPRouter.json @@ -1,13 +1,290 @@ { - "mainnet": { - "address": "0x827716e74F769AB7b6bb374A29235d9c2156932C", - "chainSelector": "3849287863852499584" - }, - "testnet": { - "address": "0x7808184405d6Cbc663764003dE21617fa640bc82", - "chainSelector": "5535534526963509396" - }, + "address": "0x827716e74F769AB7b6bb374A29235d9c2156932C", "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "wrappedNative", + "type": "address" + }, + { + "internalType": "address", + "name": "armProxy", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "BadARMSignal", + "type": "error" + }, + { + "inputs": [], + "name": "FailedToSendValue", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientFeeTokenAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMsgValue", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "InvalidRecipientAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyOffRamp", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + } + ], + "name": "UnsupportedDestinationChain", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "messageId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "calldataHash", + "type": "bytes32" + } + ], + "name": "MessageExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "onRamp", + "type": "address" + } + ], + "name": "OnRampSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_RET_BYTES", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "onRamp", + "type": "address" + } + ], + "internalType": "struct Router.OnRamp[]", + "name": "onRampUpdates", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "offRampRemoves", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "offRampAdds", + "type": "tuple[]" + } + ], + "name": "applyRampUpdates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -64,13 +341,26 @@ "outputs": [ { "internalType": "bytes32", - "name": "messageId", + "name": "", "type": "bytes32" } ], "stateMutability": "payable", "type": "function" }, + { + "inputs": [], + "name": "getArmProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -136,12 +426,24 @@ }, { "inputs": [], - "name": "getArmProxy", + "name": "getOffRamps", "outputs": [ { - "internalType": "address", - "name": "armProxy", - "type": "address" + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "", + "type": "tuple[]" } ], "stateMutability": "view", @@ -149,12 +451,31 @@ }, { "inputs": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + } + ], + "name": "getOnRamp", + "outputs": [ { "internalType": "address", - "name": "token", + "name": "", "type": "address" } ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], "name": "getSupportedTokens", "outputs": [ { @@ -166,6 +487,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getWrappedNative", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -178,12 +512,194 @@ "outputs": [ { "internalType": "bool", - "name": "supported", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "isOffRamp", + "outputs": [ + { + "internalType": "bool", + "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "messageId", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "sender", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Client.EVMTokenAmount[]", + "name": "destTokenAmounts", + "type": "tuple[]" + } + ], + "internalType": "struct Client.Any2EVMMessage", + "name": "message", + "type": "tuple" + }, + { + "internalType": "uint16", + "name": "gasForCallExactCheck", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "routeMessage", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "retData", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "gasUsed", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "wrappedNative", + "type": "address" + } + ], + "name": "setWrappedNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "typeAndVersion", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" } ] } diff --git a/src/threshold-ts/tbtc/bob-artifacts/StandardBridge.json b/src/threshold-ts/tbtc/bob-artifacts/StandardBridge.json index c01d85e17..d70720c12 100644 --- a/src/threshold-ts/tbtc/bob-artifacts/StandardBridge.json +++ b/src/threshold-ts/tbtc/bob-artifacts/StandardBridge.json @@ -1,11 +1,327 @@ { - "address": "0x2222222222222222222222222222222222222222", + "address": "0x4200000000000000000000000000000000000010", "abi": [ { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, "inputs": [ { + "indexed": true, "internalType": "address", - "name": "_l2Token", + "name": "l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "DepositFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "remoteToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20BridgeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "remoteToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20BridgeInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHBridgeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHBridgeInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "WithdrawalInitiated", + "type": "event" + }, + { + "inputs": [], + "name": "MESSENGER", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OTHER_BRIDGE", + "outputs": [ + { + "internalType": "contract StandardBridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "bridgeERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", "type": "address" }, { @@ -20,7 +336,7 @@ }, { "internalType": "uint32", - "name": "_deadline", + "name": "_minGasLimit", "type": "uint32" }, { @@ -29,7 +345,110 @@ "type": "bytes" } ], - "name": "withdrawTo", + "name": "bridgeERC20To", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "bridgeETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "bridgeETHTo", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "deposits", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "finalizeBridgeERC20", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -38,9 +457,138 @@ "inputs": [ { "internalType": "address", - "name": "_l1Token", + "name": "_from", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "finalizeBridgeETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract StandardBridge", + "name": "_otherBridge", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "l1TokenBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "otherBridge", + "outputs": [ + { + "internalType": "contract StandardBridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l2Token", "type": "address" }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "_l2Token", @@ -58,19 +606,23 @@ }, { "internalType": "uint32", - "name": "_l2Gas", + "name": "_minGasLimit", "type": "uint32" }, { "internalType": "bytes", - "name": "_data", + "name": "_extraData", "type": "bytes" } ], - "name": "depositERC20To", + "name": "withdrawTo", "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "payable", "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" } ] } diff --git a/src/threshold-ts/tbtc/bob-sepolia-artifacts/BurnFromMintTokenPoolUpgradeable.json b/src/threshold-ts/tbtc/bob-sepolia-artifacts/BurnFromMintTokenPoolUpgradeable.json new file mode 100644 index 000000000..137bdeac6 --- /dev/null +++ b/src/threshold-ts/tbtc/bob-sepolia-artifacts/BurnFromMintTokenPoolUpgradeable.json @@ -0,0 +1,1451 @@ +{ + "address": "0x7F9c05f01806D61a5092820b11d7dFF67b444AED", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AllowListNotEnabled", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "CallerIsNotARampOnRouter", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], + "name": "ChainAlreadyExists", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "ChainNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "CursedByRMN", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "expected", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "actual", + "type": "uint8" + } + ], + "name": "InvalidDecimalArgs", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "sourcePoolData", + "type": "bytes" + } + ], + "name": "InvalidRemoteChainDecimals", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "InvalidRemotePoolForChain", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "sourcePoolAddress", + "type": "bytes" + } + ], + "name": "InvalidSourcePoolAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "InvalidToken", + "type": "error" + }, + { + "inputs": [], + "name": "MismatchedArrayLengths", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "NonExistentChain", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "remoteDecimals", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "localDecimals", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "remoteAmount", + "type": "uint256" + } + ], + "name": "OverflowDetected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "PoolAlreadyAdded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "SenderNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "AllowListAdd", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "AllowListRemove", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Burned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "remoteToken", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct RateLimiter.Config", + "name": "outboundRateLimiterConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct RateLimiter.Config", + "name": "inboundRateLimiterConfig", + "type": "tuple" + } + ], + "name": "ChainAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct RateLimiter.Config", + "name": "outboundRateLimiterConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct RateLimiter.Config", + "name": "inboundRateLimiterConfig", + "type": "tuple" + } + ], + "name": "ChainConfigured", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "ChainRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Locked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Minted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "rateLimitAdmin", + "type": "address" + } + ], + "name": "RateLimitAdminSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Released", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "RemotePoolAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "RemotePoolRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldRouter", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newRouter", + "type": "address" + } + ], + "name": "RouterUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "addRemotePool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "removes", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "adds", + "type": "address[]" + } + ], + "name": "applyAllowListUpdates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64[]", + "name": "remoteChainSelectorsToRemove", + "type": "uint64[]" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes[]", + "name": "remotePoolAddresses", + "type": "bytes[]" + }, + { + "internalType": "bytes", + "name": "remoteTokenAddress", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config", + "name": "outboundRateLimiterConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config", + "name": "inboundRateLimiterConfig", + "type": "tuple" + } + ], + "internalType": "struct TokenPoolMutable.ChainUpdate[]", + "name": "chainsToAdd", + "type": "tuple[]" + } + ], + "name": "applyChainUpdates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllowList", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllowListEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "getCurrentInboundRateLimiterState", + "outputs": [ + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokens", + "type": "uint128" + }, + { + "internalType": "uint32", + "name": "lastUpdated", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.TokenBucket", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "getCurrentOutboundRateLimiterState", + "outputs": [ + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokens", + "type": "uint128" + }, + { + "internalType": "uint32", + "name": "lastUpdated", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.TokenBucket", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRateLimitAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "getRemotePools", + "outputs": [ + { + "internalType": "bytes[]", + "name": "", + "type": "bytes[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "getRemoteToken", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRmnProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRouter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSupportedChains", + "outputs": [ + { + "internalType": "uint64[]", + "name": "", + "type": "uint64[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getToken", + "outputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTokenDecimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint8", + "name": "_localTokenDecimals", + "type": "uint8" + }, + { + "internalType": "address[]", + "name": "_allowlist", + "type": "address[]" + }, + { + "internalType": "address", + "name": "_rmnProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "_router", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "isRemotePool", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "isSupportedChain", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + } + ], + "name": "isSupportedToken", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "originalSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraArgs", + "type": "bytes" + } + ], + "internalType": "struct Pool.LockOrBurnInV1", + "name": "lockOrBurnIn", + "type": "tuple" + } + ], + "name": "lockOrBurn", + "outputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "destTokenAddress", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "destPoolData", + "type": "bytes" + } + ], + "internalType": "struct Pool.LockOrBurnOutV1", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "originalSender", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "sourcePoolAddress", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "sourcePoolData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "offchainTokenData", + "type": "bytes" + } + ], + "internalType": "struct Pool.ReleaseOrMintInV1", + "name": "releaseOrMintIn", + "type": "tuple" + } + ], + "name": "releaseOrMint", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "destinationAmount", + "type": "uint256" + } + ], + "internalType": "struct Pool.ReleaseOrMintOutV1", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "removeRemotePool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config", + "name": "outboundConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config", + "name": "inboundConfig", + "type": "tuple" + } + ], + "name": "setChainRateLimiterConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64[]", + "name": "chainSelectors", + "type": "uint64[]" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config[]", + "name": "outboundConfigs", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config[]", + "name": "inboundConfigs", + "type": "tuple[]" + } + ], + "name": "setChainRateLimiterConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_rateLimitAdmin", + "type": "address" + } + ], + "name": "setRateLimitAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newRouter", + "type": "address" + } + ], + "name": "setRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "typeAndVersion", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } + ] +} diff --git a/src/threshold-ts/tbtc/bob-sepolia-artifacts/CCIPRouter.json b/src/threshold-ts/tbtc/bob-sepolia-artifacts/CCIPRouter.json new file mode 100644 index 000000000..64d263f49 --- /dev/null +++ b/src/threshold-ts/tbtc/bob-sepolia-artifacts/CCIPRouter.json @@ -0,0 +1,705 @@ +{ + "address": "0x7808184405d6Cbc663764003dE21617fa640bc82", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "wrappedNative", + "type": "address" + }, + { + "internalType": "address", + "name": "armProxy", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "BadARMSignal", + "type": "error" + }, + { + "inputs": [], + "name": "FailedToSendValue", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientFeeTokenAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMsgValue", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "InvalidRecipientAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyOffRamp", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + } + ], + "name": "UnsupportedDestinationChain", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "messageId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "calldataHash", + "type": "bytes32" + } + ], + "name": "MessageExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "onRamp", + "type": "address" + } + ], + "name": "OnRampSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_RET_BYTES", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "onRamp", + "type": "address" + } + ], + "internalType": "struct Router.OnRamp[]", + "name": "onRampUpdates", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "offRampRemoves", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "offRampAdds", + "type": "tuple[]" + } + ], + "name": "applyRampUpdates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destinationChainSelector", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Client.EVMTokenAmount[]", + "name": "tokenAmounts", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "feeToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraArgs", + "type": "bytes" + } + ], + "internalType": "struct Client.EVM2AnyMessage", + "name": "message", + "type": "tuple" + } + ], + "name": "ccipSend", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getArmProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destinationChainSelector", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Client.EVMTokenAmount[]", + "name": "tokenAmounts", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "feeToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraArgs", + "type": "bytes" + } + ], + "internalType": "struct Client.EVM2AnyMessage", + "name": "message", + "type": "tuple" + } + ], + "name": "getFee", + "outputs": [ + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getOffRamps", + "outputs": [ + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + } + ], + "name": "getOnRamp", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], + "name": "getSupportedTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getWrappedNative", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], + "name": "isChainSupported", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "isOffRamp", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "messageId", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "sender", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Client.EVMTokenAmount[]", + "name": "destTokenAmounts", + "type": "tuple[]" + } + ], + "internalType": "struct Client.Any2EVMMessage", + "name": "message", + "type": "tuple" + }, + { + "internalType": "uint16", + "name": "gasForCallExactCheck", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "routeMessage", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "retData", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "gasUsed", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "wrappedNative", + "type": "address" + } + ], + "name": "setWrappedNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "typeAndVersion", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } + ] +} diff --git a/src/threshold-ts/tbtc/bob-testnet-artifacts/OptimismMintableUpgradableTBTC.json b/src/threshold-ts/tbtc/bob-sepolia-artifacts/OptimismMintableUpgradableTBTC.json similarity index 100% rename from src/threshold-ts/tbtc/bob-testnet-artifacts/OptimismMintableUpgradableTBTC.json rename to src/threshold-ts/tbtc/bob-sepolia-artifacts/OptimismMintableUpgradableTBTC.json diff --git a/src/threshold-ts/tbtc/bob-sepolia-artifacts/StandardBridge.json b/src/threshold-ts/tbtc/bob-sepolia-artifacts/StandardBridge.json new file mode 100644 index 000000000..d70720c12 --- /dev/null +++ b/src/threshold-ts/tbtc/bob-sepolia-artifacts/StandardBridge.json @@ -0,0 +1,628 @@ +{ + "address": "0x4200000000000000000000000000000000000010", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "DepositFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "remoteToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20BridgeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "remoteToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20BridgeInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHBridgeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHBridgeInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "WithdrawalInitiated", + "type": "event" + }, + { + "inputs": [], + "name": "MESSENGER", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OTHER_BRIDGE", + "outputs": [ + { + "internalType": "contract StandardBridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "bridgeERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "bridgeERC20To", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "bridgeETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "bridgeETHTo", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "deposits", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "finalizeBridgeERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "finalizeBridgeETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract StandardBridge", + "name": "_otherBridge", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "l1TokenBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "otherBridge", + "outputs": [ + { + "internalType": "contract StandardBridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "withdrawTo", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] +} diff --git a/src/threshold-ts/tbtc/bob-testnet-artifacts/CCIPRouter.json b/src/threshold-ts/tbtc/bob-testnet-artifacts/CCIPRouter.json deleted file mode 100644 index 37d146a1f..000000000 --- a/src/threshold-ts/tbtc/bob-testnet-artifacts/CCIPRouter.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "address": "0x1111111111111111111111111111111111111111", - "abi": [ - { - "inputs": [ - { - "internalType": "uint64", - "name": "destinationChainSelector", - "type": "uint64" - }, - { - "components": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "address", - "name": "receiver", - "type": "address" - } - ], - "internalType": "struct CCIPMessage", - "name": "message", - "type": "tuple" - } - ], - "name": "getFee", - "outputs": [ - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [ - { - "internalType": "bytes32", - "name": "messageId", - "type": "bytes32" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } - ] -} diff --git a/src/threshold-ts/tbtc/bob-testnet-artifacts/StandardBridge.json b/src/threshold-ts/tbtc/bob-testnet-artifacts/StandardBridge.json deleted file mode 100644 index 6e363b882..000000000 --- a/src/threshold-ts/tbtc/bob-testnet-artifacts/StandardBridge.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "address": "0x2222222222222222222222222222222222222222", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "_l2Token", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "uint32", - "name": "_minGasLimit", - "type": "uint32" - }, - { - "internalType": "bytes", - "name": "_extraData", - "type": "bytes" - } - ], - "name": "withdrawTo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_l1Token", - "type": "address" - }, - { - "internalType": "address", - "name": "_l2Token", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "uint32", - "name": "_minGasLimit", - "type": "uint32" - }, - { - "internalType": "bytes", - "name": "_extraData", - "type": "bytes" - } - ], - "name": "depositERC20To", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ] -} diff --git a/src/threshold-ts/tbtc/bridge.ts b/src/threshold-ts/tbtc/bridge.ts deleted file mode 100644 index 0fca9e7cd..000000000 --- a/src/threshold-ts/tbtc/bridge.ts +++ /dev/null @@ -1,1127 +0,0 @@ -import { BigNumber, Contract, providers, Signer, ethers } from "ethers" -import { TransactionResponse } from "@ethersproject/abstract-provider" -import { MaxUint256, AddressZero } from "@ethersproject/constants" -import { EthereumConfig, CrossChainConfig } from "../types" -import { IMulticall } from "../multicall" -import { getContract, getArtifact } from "../utils" -import { SupportedChainIds } from "../../networks/enums/networks" - -export interface IBridge { - // Core bridge methods - SIMPLIFIED API - withdraw( - amount: BigNumber, - opts?: BridgeOptions - ): Promise // Smart routing - handles both CCIP and Standard Bridge - depositToBob( - amount: BigNumber, - opts?: BridgeOptions - ): Promise - - // Approval helpers - approveForCcip(amount: BigNumber): Promise - approveForStandardBridge( - amount: BigNumber - ): Promise - - // Routing and quotes - pickPath(amount: BigNumber): Promise // Public for UI decisions - quoteFees(amount: BigNumber, route?: BridgeRoute): Promise - - // Utilities - getLegacyCapRemaining(): Promise - canWithdraw(amount: BigNumber): Promise<{ - canWithdraw: boolean - route?: BridgeRoute - reason?: string - }> - getCcipAllowance(): Promise - getAllowances(): Promise<{ - ccip: BigNumber - standardBridge: BigNumber - }> - getWithdrawalTime(route: BridgeRoute): number - quoteDepositFees(amount: BigNumber): Promise -} - -export type BridgeRoute = "ccip" | "standard" - -export interface BridgeOptions { - gasLimit?: BigNumber - gasPrice?: BigNumber - maxFeePerGas?: BigNumber - maxPriorityFeePerGas?: BigNumber - recipient?: string - slippage?: number - deadline?: number - useLinkForFees?: boolean // New: option to pay CCIP fees in LINK token -} - -export interface BridgeQuote { - route: BridgeRoute - fee: BigNumber - estimatedTime: number // seconds - breakdown?: { - ccipAmount?: BigNumber - standardAmount?: BigNumber - ccipFee?: BigNumber - standardFee?: BigNumber - } -} - -// Fee token addresses on BOB -const FEE_TOKENS = { - LINK: { - mainnet: "0x5aB885CDa7216b163fb6F813DEC1E1532516c833", - testnet: "0x0000000000000000000000000000000000000000", - }, - WETH: { - mainnet: "0x4200000000000000000000000000000000000006", - testnet: "0x0000000000000000000000000000000000000000", - }, -} - -export class Bridge implements IBridge { - private _ethereumConfig: EthereumConfig - private _crossChainConfig: CrossChainConfig - private _multicall: IMulticall - private _ccipRouterContract: Contract | null = null // Renamed from _ccipContract - private _standardBridgeContract: Contract | null = null - private _tokenContract: Contract | null = null - private _linkTokenContract: Contract | null = null // New: for LINK fee payments - private _burnFromMintPoolAddress: string | null = null // Reference only - private _legacyCapCache: { value: BigNumber; timestamp: number } | null = null - private readonly _cacheExpiry = 60000 // 1 minute in milliseconds - - constructor( - ethereumConfig: EthereumConfig, - crossChainConfig: CrossChainConfig, - multicall: IMulticall - ) { - this._ethereumConfig = ethereumConfig - this._crossChainConfig = crossChainConfig - this._multicall = multicall - - // Initialize contracts - this._initializeContracts() - } - - private _initializeContracts(): void { - const { - chainId, - shouldUseTestnetDevelopmentContracts, - ethereumProviderOrSigner, - account, - } = this._ethereumConfig - - // Map BOB chain ID to mainnet/testnet for artifact loading - const isBOBMainnet = chainId === 60808 - const isBOBTestnet = chainId === 808813 - - if (!isBOBMainnet && !isBOBTestnet) { - console.warn("Bridge: Not on BOB network, contracts will be null") - return - } - - // Use BOB chain ID directly for artifact loading - const bobChainId = Number(chainId) - - // Get provider - const provider = ethereumProviderOrSigner - - // Load CCIP Router (users interact with this, not pools) - const ccipRouterArtifact = getArtifact( - "CCIPRouter", - bobChainId, - shouldUseTestnetDevelopmentContracts - ) - - // Get the correct router address based on network - if (ccipRouterArtifact) { - const routerAddress = isBOBMainnet - ? ccipRouterArtifact.mainnet?.address - : ccipRouterArtifact.testnet?.address - - if ( - routerAddress && - routerAddress !== "0x0000000000000000000000000000000000000000" - ) { - this._ccipRouterContract = getContract( - routerAddress, - ccipRouterArtifact.abi, - provider, - account - ) - } else { - console.warn("CCIP Router address not configured for this network") - this._ccipRouterContract = null - } - } - - // Load BurnFromMintTokenPool address for reference (we don't interact with it directly) - const burnFromMintPoolArtifact = getArtifact( - "BurnFromMintTokenPool", - bobChainId, - shouldUseTestnetDevelopmentContracts - ) - - if (burnFromMintPoolArtifact) { - this._burnFromMintPoolAddress = isBOBMainnet - ? burnFromMintPoolArtifact.mainnet?.address - : burnFromMintPoolArtifact.testnet?.address - } - - // Load LINK token for fee payments - const linkAddress = isBOBMainnet - ? FEE_TOKENS.LINK.mainnet - : FEE_TOKENS.LINK.testnet - - if ( - linkAddress && - linkAddress !== "0x0000000000000000000000000000000000000000" - ) { - // Use standard ERC20 ABI for LINK token - const erc20Abi = [ - "function approve(address spender, uint256 amount) returns (bool)", - "function allowance(address owner, address spender) view returns (uint256)", - "function balanceOf(address owner) view returns (uint256)", - ] - this._linkTokenContract = getContract( - linkAddress, - erc20Abi, - provider, - account - ) - } - - // Load Standard Bridge - const standardBridgeArtifact = getArtifact( - "StandardBridge", - bobChainId, - shouldUseTestnetDevelopmentContracts - ) - - this._standardBridgeContract = standardBridgeArtifact - ? getContract( - standardBridgeArtifact.address, - standardBridgeArtifact.abi, - provider, - account - ) - : null - - // Load BOB tBTC Token (OptimismMintableUpgradableTBTC) - const tokenArtifact = getArtifact( - "OptimismMintableUpgradableTBTC", - bobChainId, - shouldUseTestnetDevelopmentContracts - ) - - this._tokenContract = tokenArtifact - ? getContract(tokenArtifact.address, tokenArtifact.abi, provider, account) - : null - - // Log initialization status - console.log("Bridge contracts initialized:", { - ccipRouter: !!this._ccipRouterContract, - standardBridge: !!this._standardBridgeContract, - token: !!this._tokenContract, - linkToken: !!this._linkTokenContract, - burnFromMintPool: this._burnFromMintPoolAddress, - }) - } - - // Helper method to check if contracts are initialized - private _ensureContractsInitialized(): void { - if ( - !this._ccipRouterContract || - !this._standardBridgeContract || - !this._tokenContract - ) { - throw new Error( - "Bridge contracts not initialized. Ensure you're on BOB network." - ) - } - } - - /** - * Withdraws tBTC from BOB network to Ethereum L1. - * Automatically routes through CCIP (fast, ~60 min) or Standard Bridge (slow, 7 days) - * based on the legacyCapRemaining value. - * - * @param {BigNumber} amount - Amount of tBTC to withdraw - * @param {BridgeOptions} [opts] - Optional transaction parameters - * @return {Promise} Transaction response - * @throws Error if amount exceeds legacy cap but cap > 0 - */ - async withdraw( - amount: BigNumber, - opts?: BridgeOptions - ): Promise { - // Ensure contracts are initialized - this._ensureContractsInitialized() - - // Validate amount - if (amount.lte(0)) { - throw new Error("Withdrawal amount must be greater than zero") - } - - // Determine the appropriate route - const route = await this.pickPath(amount) - - // Route to the appropriate withdrawal method - if (route === "ccip") { - return this._withdrawViaCcip(amount, opts) - } else { - return this._withdrawViaStandard(amount, opts) - } - } - - /** - * Private method to handle CCIP withdrawals - * @private - * @param {BigNumber} amount - Amount to withdraw - * @param {BridgeOptions} [opts] - Optional transaction parameters - * @return {Promise} Transaction response - */ - private async _withdrawViaCcip( - amount: BigNumber, - opts?: BridgeOptions - ): Promise { - const account = this._ethereumConfig.account - if (!account) { - throw new Error("No account connected") - } - - // Use recipient from options or default to connected account - const recipient = opts?.recipient || account - - try { - // Step 1: Check and handle token approval for Router - const approvalTx = await this.approveForCcip(amount) - if (approvalTx) { - console.log(`Waiting for CCIP Router approval tx: ${approvalTx.hash}`) - await approvalTx.wait() - } - - // Step 2: Handle LINK approval if using LINK for fees - const useLinkForFees = opts?.useLinkForFees && this._linkTokenContract - let feeToken = AddressZero // Default to native token - - if (useLinkForFees) { - feeToken = this._linkTokenContract!.address - - // Check and handle LINK approval for Router - const linkAllowance = await this._linkTokenContract!.allowance( - account, - this._ccipRouterContract!.address - ) - - // We'll calculate fees first to know how much LINK to approve - // For now, approve a reasonable amount if needed - const estimatedLinkFee = amount.mul(3).div(1000) // 0.3% estimate - - if (linkAllowance.lt(estimatedLinkFee)) { - console.log(`Approving LINK for CCIP Router fees`) - const linkApprovalTx = await this._linkTokenContract!.approve( - this._ccipRouterContract!.address, - MaxUint256 - ) - await linkApprovalTx.wait() - } - } - - // Step 3: Build EVM2AnyMessage for CCIP - const tokenAmounts = [ - { - token: this._tokenContract!.address, - amount: amount, - }, - ] - - // Determine destination chain selector based on network - const isBOBMainnet = this._ethereumConfig.chainId === 60808 - // Get Ethereum chain selector from L1 CCIP Router artifact - const ethChainId = isBOBMainnet - ? SupportedChainIds.Ethereum - : SupportedChainIds.Sepolia - const ethArtifact = getArtifact( - "L1CCIPRouter", - ethChainId, - this._ethereumConfig.shouldUseTestnetDevelopmentContracts - ) - - if (!ethArtifact || !ethArtifact.chainSelector) { - throw new Error( - "Ethereum chain selector not found in L1CCIPRouter artifact" - ) - } - - const destinationChainSelector = ethArtifact.chainSelector - - // Encode receiver address for CCIP - const encodedReceiver = ethers.utils.defaultAbiCoder.encode( - ["address"], - [recipient] - ) - - const message = { - receiver: encodedReceiver, - data: "0x", // No additional data needed for simple transfer - tokenAmounts: tokenAmounts, - feeToken: feeToken, // Use LINK or native token based on user preference - extraArgs: this._encodeExtraArgs(200000, false), // gasLimit, strict - } - - // Step 4: Calculate fees - const fees = await this._ccipRouterContract!.getFee( - destinationChainSelector, - message - ) - - // Step 5: Send CCIP message - const txParams: any = { - // Only include value if paying in native token - ...(feeToken === AddressZero && { value: fees }), - // Add gas parameters if provided - ...(opts?.gasLimit && { gasLimit: opts.gasLimit }), - ...(opts?.gasPrice && { gasPrice: opts.gasPrice }), - ...(opts?.maxFeePerGas && { maxFeePerGas: opts.maxFeePerGas }), - ...(opts?.maxPriorityFeePerGas && { - maxPriorityFeePerGas: opts.maxPriorityFeePerGas, - }), - } - - const tx = await this._ccipRouterContract!.ccipSend( - destinationChainSelector, - message, - txParams - ) - - console.log(`CCIP withdrawal initiated. Tx hash: ${tx.hash}`) - console.log(`Message ID: ${tx.value || "pending"}`) - - return tx - } catch (error: any) { - console.error("CCIP withdrawal failed:", error) - throw new Error(`CCIP withdrawal failed: ${error.message}`) - } - } - - /** - * Private method to handle Standard Bridge withdrawals - * @private - * @param {BigNumber} amount - Amount to withdraw - * @param {BridgeOptions} [opts] - Optional transaction parameters - * @return {Promise} Transaction response - */ - private async _withdrawViaStandard( - amount: BigNumber, - opts?: BridgeOptions - ): Promise { - const account = this._ethereumConfig.account - if (!account) { - throw new Error("No account connected") - } - - // Use recipient from options or default to connected account - const recipient = opts?.recipient || account - - try { - // Check and handle approval - const approvalTx = await this.approveForStandardBridge(amount) - if (approvalTx) { - console.log( - `Waiting for Standard Bridge approval tx: ${approvalTx.hash}` - ) - await approvalTx.wait() - } - - // Build transaction parameters - const txParams: any = { - // Add gas parameters if provided - ...(opts?.gasLimit && { gasLimit: opts.gasLimit }), - ...(opts?.gasPrice && { gasPrice: opts.gasPrice }), - ...(opts?.maxFeePerGas && { maxFeePerGas: opts.maxFeePerGas }), - ...(opts?.maxPriorityFeePerGas && { - maxPriorityFeePerGas: opts.maxPriorityFeePerGas, - }), - } - - // Call Standard Bridge withdraw function - // Note: Actual method name depends on Standard Bridge ABI - // Optimism bridge typically uses withdrawTo or similar - const tx = await this._standardBridgeContract!.withdrawTo( - this._tokenContract!.address, // L2 token address - recipient, - amount, - opts?.deadline || 0, // Optional deadline parameter - "0x", // Extra data (typically empty) - txParams - ) - - console.log( - `Standard Bridge withdrawal initiated (7-day delay). Tx hash: ${tx.hash}` - ) - - return tx - } catch (error: any) { - console.error("Standard Bridge withdrawal failed:", error) - throw new Error(`Standard Bridge withdrawal failed: ${error.message}`) - } - } - - async approveForCcip(amount: BigNumber): Promise { - if (!this._ccipRouterContract || !this._tokenContract) { - throw new Error("Contracts not initialized") - } - - const account = this._ethereumConfig.account - if (!account) { - throw new Error("No account connected") - } - - try { - // Check current allowance for CCIP Router (not pool!) - const currentAllowance = await this._tokenContract.allowance( - account, - this._ccipRouterContract.address - ) - - // Skip if already approved for this amount or more - if (currentAllowance.gte(amount)) { - console.log( - `CCIP Router approval not needed. Current allowance: ${currentAllowance.toString()}` - ) - return null - } - - // Use MaxUint256 for infinite approval (common pattern) - const approvalAmount = MaxUint256 - - console.log(`Approving CCIP Router for ${approvalAmount.toString()}`) - - // Send approval transaction to CCIP Router - const tx = await this._tokenContract.approve( - this._ccipRouterContract.address, - approvalAmount - ) - - return tx - } catch (error: any) { - console.error("Failed to approve for CCIP Router:", error) - throw new Error(`CCIP Router approval failed: ${error.message}`) - } - } - - async approveForStandardBridge( - amount: BigNumber - ): Promise { - if (!this._standardBridgeContract || !this._tokenContract) { - throw new Error("Contracts not initialized") - } - - const account = this._ethereumConfig.account - if (!account) { - throw new Error("No account connected") - } - - try { - // Check current allowance - const currentAllowance = await this._tokenContract.allowance( - account, - this._standardBridgeContract.address - ) - - // Skip if already approved for this amount or more - if (currentAllowance.gte(amount)) { - console.log( - `Standard Bridge approval not needed. Current allowance: ${currentAllowance.toString()}` - ) - return null - } - - // Use MaxUint256 for infinite approval (common pattern) - const approvalAmount = MaxUint256 - - console.log( - `Approving Standard Bridge contract for ${approvalAmount.toString()}` - ) - - // Send approval transaction - const tx = await this._tokenContract.approve( - this._standardBridgeContract.address, - approvalAmount - ) - - return tx - } catch (error) { - console.error("Failed to approve for Standard Bridge:", error) - throw new Error(`Standard Bridge approval failed: ${error.message}`) - } - } - - /** - * Determines the optimal withdrawal route based on the amount and legacyCapRemaining. - * This is a public method that can be called by frontend to preview which route will be used. - * - * @param {BigNumber} amount - Amount of tBTC to withdraw - * @return {Promise} "ccip" for fast path (~60 min) or "standard" for slow path (7 days) - * @throws Error if amount exceeds legacy cap but cap > 0 (blocked scenario) - * - * @example - * ```typescript - * const route = await bridge.pickPath(amount); - * if (route === "ccip") { - * console.log("Fast withdrawal available (~60 minutes)"); - * } else { - * console.log("Standard withdrawal will be used (7 days)"); - * } - * ``` - */ - async pickPath(amount: BigNumber): Promise { - // Validate input - if (amount.lte(0)) { - throw new Error("Amount must be greater than zero") - } - - // Get current legacy cap - const legacyCapRemaining = await this.getLegacyCapRemaining() - - // Case 1: Legacy cap is exhausted - CCIP available - if (legacyCapRemaining.eq(0)) { - return "ccip" - } - - // Case 2: Amount fits within legacy cap - use standard bridge - if (amount.lte(legacyCapRemaining)) { - return "standard" - } - - // Case 3: Amount exceeds legacy cap but cap > 0 - blocked - throw new Error( - `Amount ${amount.toString()} exceeds legacy cap remaining ${legacyCapRemaining.toString()}. ` + - `Please wait for legacy cap to deplete or reduce your withdrawal amount.` - ) - } - - // Helper method to check if amount can be withdrawn - async canWithdraw(amount: BigNumber): Promise<{ - canWithdraw: boolean - route?: BridgeRoute - reason?: string - }> { - try { - const route = await this.pickPath(amount) - return { canWithdraw: true, route } - } catch (error: any) { - return { - canWithdraw: false, - reason: error.message, - } - } - } - - async quoteFees( - amount: BigNumber, - route?: BridgeRoute, - useLinkForFees?: boolean - ): Promise { - // Validate amount - if (amount.lte(0)) { - throw new Error("Amount must be greater than zero") - } - - // Determine route if not provided - const actualRoute = route || (await this.pickPath(amount)) - - // Get base time estimate - const estimatedTime = this.getWithdrawalTime(actualRoute) - - try { - switch (actualRoute) { - case "ccip": - return await this._quoteCcipFees( - amount, - estimatedTime, - useLinkForFees - ) - - case "standard": - return await this._quoteStandardFees(amount, estimatedTime) - - default: - throw new Error(`Unknown route: ${actualRoute}`) - } - } catch (error: any) { - console.error("Failed to quote fees:", error) - throw new Error(`Fee quotation failed: ${error.message}`) - } - } - - // Helper method to check allowance without approving - async getCcipAllowance(): Promise { - if (!this._ccipRouterContract || !this._tokenContract) { - throw new Error("Contracts not initialized") - } - - const account = this._ethereumConfig.account - if (!account) { - throw new Error("No account connected") - } - - return await this._tokenContract.allowance( - account, - this._ccipRouterContract.address - ) - } - - // Batch approval check for both bridges - async getAllowances(): Promise<{ - ccip: BigNumber - standardBridge: BigNumber - }> { - if ( - !this._ccipRouterContract || - !this._standardBridgeContract || - !this._tokenContract - ) { - throw new Error("Contracts not initialized") - } - - const account = this._ethereumConfig.account - if (!account) { - throw new Error("No account connected") - } - - const calls = [ - { - interface: this._tokenContract.interface, - address: this._tokenContract.address, - method: "allowance", - args: [account, this._ccipRouterContract.address], - }, - { - interface: this._tokenContract.interface, - address: this._tokenContract.address, - method: "allowance", - args: [account, this._standardBridgeContract.address], - }, - ] - - const [ccip, standardBridge] = await this._multicall.aggregate(calls) - - return { ccip, standardBridge } - } - - async getLegacyCapRemaining(): Promise { - // Check cache validity - if ( - this._legacyCapCache && - Date.now() - this._legacyCapCache.timestamp < this._cacheExpiry - ) { - return this._legacyCapCache.value - } - - // Ensure contracts are initialized - if (!this._tokenContract) { - throw new Error("Token contract not initialized") - } - - try { - // Fetch from contract - const legacyCapRemaining = await this._tokenContract.legacyCapRemaining() - - // Update cache - this._legacyCapCache = { - value: legacyCapRemaining, - timestamp: Date.now(), - } - - return legacyCapRemaining - } catch (error: any) { - console.error("Failed to fetch legacyCapRemaining:", error) - - // If we have stale cache data, return it as fallback - if (this._legacyCapCache) { - console.warn("Returning stale cache data due to contract call failure") - return this._legacyCapCache.value - } - - throw new Error(`Failed to get legacy cap remaining: ${error.message}`) - } - } - - // Get withdrawal time estimates - getWithdrawalTime(route: BridgeRoute): number { - switch (route) { - case "ccip": - return 60 * 60 // ~60 minutes in seconds - case "standard": - return 7 * 24 * 60 * 60 // 7 days in seconds - default: - throw new Error(`Unknown route: ${route}`) - } - } - - // Helper method to encode CCIP extra args - private _encodeExtraArgs(gasLimit: number, strict: boolean): string { - // Encode EVMExtraArgsV1 structure - // The ABI encoding follows: ['uint256', 'bool'] - const abiCoder = new ethers.utils.AbiCoder() - return abiCoder.encode(["uint256", "bool"], [gasLimit, strict]) - } - - private async _quoteCcipFees( - amount: BigNumber, - estimatedTime: number, - useLinkForFees?: boolean - ): Promise { - if (!this._ccipRouterContract) { - throw new Error("CCIP Router not initialized") - } - - try { - // Build message for fee calculation - const tokenAmounts = [ - { - token: this._tokenContract!.address, - amount: amount, - }, - ] - - // Determine destination chain selector - const isBOBMainnet = this._ethereumConfig.chainId === 60808 - // Get Ethereum chain selector from L1 CCIP Router artifact - const ethChainId = isBOBMainnet - ? SupportedChainIds.Ethereum - : SupportedChainIds.Sepolia - const ethArtifact = getArtifact( - "L1CCIPRouter", - ethChainId, - this._ethereumConfig.shouldUseTestnetDevelopmentContracts - ) - - if (!ethArtifact || !ethArtifact.chainSelector) { - throw new Error( - "Ethereum chain selector not found in L1CCIPRouter artifact" - ) - } - - const destinationChainSelector = ethArtifact.chainSelector - - // Encode receiver address - const encodedReceiver = ethers.utils.defaultAbiCoder.encode( - ["address"], - [this._ethereumConfig.account || AddressZero] - ) - - // Use LINK token if requested and available - const feeToken = - useLinkForFees && this._linkTokenContract - ? this._linkTokenContract.address - : AddressZero - - const message = { - receiver: encodedReceiver, - data: "0x", - tokenAmounts: tokenAmounts, - feeToken: feeToken, // Calculate for specified fee token - extraArgs: this._encodeExtraArgs(200000, false), - } - - // Get fee from router - const ccipFee = await this._ccipRouterContract.getFee( - destinationChainSelector, - message - ) - - return { - route: "ccip", - fee: ccipFee, - estimatedTime: estimatedTime, - breakdown: { - ccipFee: ccipFee, - standardFee: BigNumber.from(0), - }, - } - } catch (error) { - // Fallback to estimated fee if contract call fails - // CCIP typically charges 0.1-0.5% of transfer amount - const estimatedFee = amount.mul(3).div(1000) // 0.3% - - return { - route: "ccip", - fee: estimatedFee, - estimatedTime: estimatedTime, - breakdown: { - ccipFee: estimatedFee, - standardFee: BigNumber.from(0), - }, - } - } - } - - private async _quoteStandardFees( - amount: BigNumber, - estimatedTime: number - ): Promise { - // Standard bridge typically has minimal fees - // Main cost is L2 gas for the withdrawal transaction - const gasPrice = - await this._ethereumConfig.ethereumProviderOrSigner.getGasPrice() - const estimatedGas = BigNumber.from(200000) // Typical L2 withdrawal gas - const standardFee = gasPrice.mul(estimatedGas) - - return { - route: "standard", - fee: standardFee, - estimatedTime: estimatedTime, - breakdown: { - standardFee: standardFee, - ccipFee: BigNumber.from(0), - }, - } - } - - // Special case for L1 to BOB deposit fees - async quoteDepositFees(amount: BigNumber): Promise { - try { - // L1 to L2 deposits have different fee structure - // Main costs: L1 gas + L2 execution gas - - const l1GasPrice = - await this._ethereumConfig.ethereumProviderOrSigner.getGasPrice() - const estimatedL1Gas = BigNumber.from(150000) // Typical deposit gas - const l1Fee = l1GasPrice.mul(estimatedL1Gas) - - // L2 execution cost (paid on L1) - const l2GasLimit = BigNumber.from(200000) - const l2GasPrice = BigNumber.from(1000000) // 0.001 gwei typical for L2 - const l2Fee = l2GasLimit.mul(l2GasPrice) - - const totalFee = l1Fee.add(l2Fee) - - return { - route: "standard", // Deposits use standard bridge - fee: totalFee, - estimatedTime: 15 * 60, // ~15 minutes for deposit - breakdown: { - standardFee: totalFee, - ccipFee: BigNumber.from(0), - }, - } - } catch (error: any) { - throw new Error(`Fee quotation failed: ${error.message}`) - } - } - - async depositToBob( - amount: BigNumber, - opts?: BridgeOptions - ): Promise { - // Validate we're on L1 - const chainId = Number(this._ethereumConfig.chainId) - const isMainnet = chainId === SupportedChainIds.Ethereum - const isSepolia = chainId === SupportedChainIds.Sepolia - - if (!isMainnet && !isSepolia) { - throw new Error( - "depositToBob can only be called from Ethereum L1 (mainnet or Sepolia)" - ) - } - - // Validate amount - if (amount.lte(0)) { - throw new Error("Deposit amount must be greater than zero") - } - - const account = this._ethereumConfig.account - if (!account) { - throw new Error("No account connected") - } - - // Use recipient from options or default to connected account - const recipient = opts?.recipient || account - - try { - // Get L1 CCIP Router contract - const l1CCIPRouterArtifact = getArtifact( - "L1CCIPRouter", - chainId, - this._ethereumConfig.shouldUseTestnetDevelopmentContracts - ) - - if (!l1CCIPRouterArtifact) { - throw new Error("L1 CCIP Router artifact not found") - } - - const l1CCIPRouterContract = getContract( - l1CCIPRouterArtifact.address, - l1CCIPRouterArtifact.abi, - this._ethereumConfig.ethereumProviderOrSigner, - this._ethereumConfig.account - ) - - // Get L1 tBTC token contract - const l1TokenArtifact = getArtifact( - "TBTC", - chainId, - this._ethereumConfig.shouldUseTestnetDevelopmentContracts - ) - - if (!l1TokenArtifact) { - throw new Error("L1 TBTC token artifact not found") - } - - const l1TokenContract = getContract( - l1TokenArtifact.address, - l1TokenArtifact.abi, - this._ethereumConfig.ethereumProviderOrSigner, - this._ethereumConfig.account - ) - - // Check and handle L1 token approval for CCIP Router - const currentAllowance = await l1TokenContract.allowance( - account, - l1CCIPRouterContract.address - ) - - if (currentAllowance.lt(amount)) { - console.log(`Approving L1 CCIP Router for ${amount.toString()} tBTC`) - const approvalTx = await l1TokenContract.approve( - l1CCIPRouterContract.address, - MaxUint256 - ) - console.log( - `Waiting for L1 CCIP Router approval tx: ${approvalTx.hash}` - ) - await approvalTx.wait() - } - - // Handle LINK token approval if using LINK for fees - const useLinkForFees = opts?.useLinkForFees - let feeToken = AddressZero // Default to native token - - if (useLinkForFees) { - // Get LINK token contract - const linkTokenArtifact = getArtifact( - "LinkToken", - chainId, - this._ethereumConfig.shouldUseTestnetDevelopmentContracts - ) - - if (!linkTokenArtifact) { - throw new Error("LINK token artifact not found") - } - - const linkTokenContract = getContract( - linkTokenArtifact.address, - linkTokenArtifact.abi, - this._ethereumConfig.ethereumProviderOrSigner, - this._ethereumConfig.account - ) - - feeToken = linkTokenContract.address - - // We'll approve LINK after calculating fees - } - - // Get BOB chain selector from L1 CCIP Router artifact - // This ensures we use the correct selector for the target BOB network - const bobArtifact = getArtifact( - "CCIPRouter", - isMainnet ? SupportedChainIds.Bob : SupportedChainIds.BobSepolia, - this._ethereumConfig.shouldUseTestnetDevelopmentContracts - ) - - if (!bobArtifact || !bobArtifact.chainSelector) { - throw new Error("BOB chain selector not found in CCIPRouter artifact") - } - - const bobChainSelector = bobArtifact.chainSelector - - // Build EVM2AnyMessage for CCIP - const tokenAmounts = [ - { - token: l1TokenArtifact.address, - amount: amount, - }, - ] - - // Encode receiver address for CCIP - const encodedReceiver = ethers.utils.defaultAbiCoder.encode( - ["address"], - [recipient] - ) - - const message = { - receiver: encodedReceiver, - data: "0x", // No additional data needed for simple transfer - tokenAmounts: tokenAmounts, - feeToken: feeToken, - extraArgs: this._encodeExtraArgs(200000, false), // gasLimit, strict - } - - // Calculate fees - const fees = await l1CCIPRouterContract.getFee(bobChainSelector, message) - - // Handle LINK approval if using LINK for fees - if (useLinkForFees && feeToken !== AddressZero) { - const linkTokenArtifact = getArtifact( - "LinkToken", - chainId, - this._ethereumConfig.shouldUseTestnetDevelopmentContracts - ) - - const linkTokenContract = getContract( - linkTokenArtifact!.address, - linkTokenArtifact!.abi, - this._ethereumConfig.ethereumProviderOrSigner, - this._ethereumConfig.account - ) - - const linkAllowance = await linkTokenContract.allowance( - account, - l1CCIPRouterContract.address - ) - - if (linkAllowance.lt(fees)) { - console.log(`Approving LINK for CCIP fees`) - const linkApprovalTx = await linkTokenContract.approve( - l1CCIPRouterContract.address, - MaxUint256 - ) - await linkApprovalTx.wait() - } - } - - // Build transaction parameters - const txParams: any = { - // Only include value if paying in native token - ...(feeToken === AddressZero && { value: fees }), - // Add gas parameters if provided - ...(opts?.gasLimit && { gasLimit: opts.gasLimit }), - ...(opts?.gasPrice && { gasPrice: opts.gasPrice }), - ...(opts?.maxFeePerGas && { maxFeePerGas: opts.maxFeePerGas }), - ...(opts?.maxPriorityFeePerGas && { - maxPriorityFeePerGas: opts.maxPriorityFeePerGas, - }), - } - - // Execute CCIP deposit - const tx = await l1CCIPRouterContract.ccipSend( - bobChainSelector, - message, - txParams - ) - - console.log(`L1 to BOB CCIP deposit initiated. Tx hash: ${tx.hash}`) - console.log(`Deposit will arrive on BOB in ~60 minutes`) - - return tx - } catch (error: any) { - console.error("L1 to BOB CCIP deposit failed:", error) - throw new Error(`CCIP deposit failed: ${error.message}`) - } - } -} diff --git a/src/threshold-ts/tbtc/index.ts b/src/threshold-ts/tbtc/index.ts index 7bbcc57e7..686798c57 100644 --- a/src/threshold-ts/tbtc/index.ts +++ b/src/threshold-ts/tbtc/index.ts @@ -1,12 +1,5 @@ import { BlockTag, TransactionReceipt } from "@ethersproject/abstract-provider" import { Web3Provider } from "@ethersproject/providers" -import { - Bridge, - IBridge, - BridgeOptions, - BridgeQuote, - BridgeRoute, -} from "./bridge" import { BitcoinClient, BitcoinTx, @@ -237,8 +230,6 @@ export interface ITBTC { readonly crossChainConfig: CrossChainConfig - readonly bridge: IBridge | null - /** * Initializes tbtc-v2 SDK * @param ethereumProviderOrSigner Ethers instance of Provider (if wallet is not @@ -504,7 +495,6 @@ export class TBTC implements ITBTC { private _tokenContract: Contract | null private _l1BitcoinDepositorContract: Contract | null = null private _l2TbtcToken: DestinationChainTBTCToken | null = null - private _bridge: IBridge | null = null private _multicall: IMulticall private _bitcoinClient: BitcoinClient private _ethereumConfig: EthereumConfig @@ -606,17 +596,6 @@ export class TBTC implements ITBTC { chainId: mainnetOrTestnetEthereumChainId, }) - // Initialize Bridge if on BOB network - const isBOBMainnet = chainId === 60808 - const isBOBTestnet = chainId === 808813 - if (isBOBMainnet || isBOBTestnet) { - this._bridge = new Bridge( - ethereumConfig, - crossChainConfig, - this._multicall - ) - } - if (this._crossChainConfig.isCrossChain) { const networkName = this._crossChainConfig.chainName === ChainName.Ethereum @@ -763,10 +742,6 @@ export class TBTC implements ITBTC { return this._l2TbtcToken } - get bridge() { - return this._bridge - } - private _getSdk = async (): Promise => { const sdk = await this._sdkPromise if (!sdk) throw new EmptySdkObjectError() @@ -1975,7 +1950,3 @@ export class TBTC implements ITBTC { return this._redemptionTreasuryFeeDivisor } } - -// Export Bridge types -export { Bridge } from "./bridge" -export type { IBridge, BridgeOptions, BridgeQuote, BridgeRoute } from "./bridge" diff --git a/src/threshold-ts/tbtc/l1-artifacts/CCIPRouter.json b/src/threshold-ts/tbtc/l1-artifacts/CCIPRouter.json deleted file mode 100644 index 2e76cb0b6..000000000 --- a/src/threshold-ts/tbtc/l1-artifacts/CCIPRouter.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "mainnet": { - "address": "0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D", - "chainSelector": "5009297550715157269" - }, - "sepolia": { - "address": "0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59", - "chainSelector": "16015286601757825753" - }, - "abi": [ - { - "inputs": [ - { - "internalType": "uint64", - "name": "destinationChainSelector", - "type": "uint64" - }, - { - "components": [ - { - "internalType": "bytes", - "name": "receiver", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "components": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "internalType": "struct Client.EVMTokenAmount[]", - "name": "tokenAmounts", - "type": "tuple[]" - }, - { - "internalType": "address", - "name": "feeToken", - "type": "address" - }, - { - "internalType": "bytes", - "name": "extraArgs", - "type": "bytes" - } - ], - "internalType": "struct Client.EVM2AnyMessage", - "name": "message", - "type": "tuple" - } - ], - "name": "ccipSend", - "outputs": [ - { - "internalType": "bytes32", - "name": "messageId", - "type": "bytes32" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "destinationChainSelector", - "type": "uint64" - }, - { - "components": [ - { - "internalType": "bytes", - "name": "receiver", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "components": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "internalType": "struct Client.EVMTokenAmount[]", - "name": "tokenAmounts", - "type": "tuple[]" - }, - { - "internalType": "address", - "name": "feeToken", - "type": "address" - }, - { - "internalType": "bytes", - "name": "extraArgs", - "type": "bytes" - } - ], - "internalType": "struct Client.EVM2AnyMessage", - "name": "message", - "type": "tuple" - } - ], - "name": "getFee", - "outputs": [ - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } - ] -} diff --git a/src/threshold-ts/tbtc/l1-artifacts/LinkToken.json b/src/threshold-ts/tbtc/l1-artifacts/LinkToken.json deleted file mode 100644 index da20637ce..000000000 --- a/src/threshold-ts/tbtc/l1-artifacts/LinkToken.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "mainnet": { - "address": "0x514910771AF9Ca656af840dff83E8264EcF986CA" - }, - "sepolia": { - "address": "0x779877A7B0D9E8603169DdbD7836e478b4624789" - }, - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } - ] -} diff --git a/src/threshold-ts/tbtc/mainnet-artifacts/CCIPRouter.json b/src/threshold-ts/tbtc/mainnet-artifacts/CCIPRouter.json new file mode 100644 index 000000000..4a6ee2b95 --- /dev/null +++ b/src/threshold-ts/tbtc/mainnet-artifacts/CCIPRouter.json @@ -0,0 +1,705 @@ +{ + "address": "0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "wrappedNative", + "type": "address" + }, + { + "internalType": "address", + "name": "armProxy", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "BadARMSignal", + "type": "error" + }, + { + "inputs": [], + "name": "FailedToSendValue", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientFeeTokenAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMsgValue", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "InvalidRecipientAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyOffRamp", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + } + ], + "name": "UnsupportedDestinationChain", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "messageId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "calldataHash", + "type": "bytes32" + } + ], + "name": "MessageExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "onRamp", + "type": "address" + } + ], + "name": "OnRampSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_RET_BYTES", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "onRamp", + "type": "address" + } + ], + "internalType": "struct Router.OnRamp[]", + "name": "onRampUpdates", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "offRampRemoves", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "offRampAdds", + "type": "tuple[]" + } + ], + "name": "applyRampUpdates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destinationChainSelector", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Client.EVMTokenAmount[]", + "name": "tokenAmounts", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "feeToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraArgs", + "type": "bytes" + } + ], + "internalType": "struct Client.EVM2AnyMessage", + "name": "message", + "type": "tuple" + } + ], + "name": "ccipSend", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getArmProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destinationChainSelector", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Client.EVMTokenAmount[]", + "name": "tokenAmounts", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "feeToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraArgs", + "type": "bytes" + } + ], + "internalType": "struct Client.EVM2AnyMessage", + "name": "message", + "type": "tuple" + } + ], + "name": "getFee", + "outputs": [ + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getOffRamps", + "outputs": [ + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + } + ], + "name": "getOnRamp", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], + "name": "getSupportedTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getWrappedNative", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], + "name": "isChainSupported", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "isOffRamp", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "messageId", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "sender", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Client.EVMTokenAmount[]", + "name": "destTokenAmounts", + "type": "tuple[]" + } + ], + "internalType": "struct Client.Any2EVMMessage", + "name": "message", + "type": "tuple" + }, + { + "internalType": "uint16", + "name": "gasForCallExactCheck", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "routeMessage", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "retData", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "gasUsed", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "wrappedNative", + "type": "address" + } + ], + "name": "setWrappedNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "typeAndVersion", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } + ] +} diff --git a/src/threshold-ts/tbtc/mainnet-artifacts/LockReleaseTokenPoolUpgradeable.json b/src/threshold-ts/tbtc/mainnet-artifacts/LockReleaseTokenPoolUpgradeable.json new file mode 100644 index 000000000..10d504561 --- /dev/null +++ b/src/threshold-ts/tbtc/mainnet-artifacts/LockReleaseTokenPoolUpgradeable.json @@ -0,0 +1,449 @@ +{ + "address": "0xA9fa975fccC5E42b171578140054E3422F7D0Efb", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Locked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Released", + "type": "event" + }, + { + "inputs": [], + "name": "canAcceptLiquidity", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address[]", + "name": "allowlist", + "type": "address[]" + }, + { + "internalType": "address", + "name": "rmnProxy", + "type": "address" + }, + { + "internalType": "bool", + "name": "acceptLiquidity", + "type": "bool" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "uint64", + "name": "supportedRemoteChainId", + "type": "uint64" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "isSupportedChain", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "isSupportedToken", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "originalSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraArgs", + "type": "bytes" + } + ], + "internalType": "struct Pool.LockOrBurnInV1", + "name": "lockOrBurnIn", + "type": "tuple" + } + ], + "name": "lockOrBurn", + "outputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "destTokenAddress", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "destPoolData", + "type": "bytes" + } + ], + "internalType": "struct Pool.LockOrBurnOutV1", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "originalSender", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "sourcePoolAddress", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "sourcePoolData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "offchainTokenData", + "type": "bytes" + } + ], + "internalType": "struct Pool.ReleaseOrMintInV1", + "name": "releaseOrMintIn", + "type": "tuple" + } + ], + "name": "releaseOrMint", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "destinationAmount", + "type": "uint256" + } + ], + "internalType": "struct Pool.ReleaseOrMintOutV1", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "s_acceptLiquidity", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "s_rmnProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "s_router", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "s_supportedRemoteChainId", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "s_token", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "typeAndVersion", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } + ] +} diff --git a/src/threshold-ts/tbtc/sepolia-artifacts/CCIPRouter.json b/src/threshold-ts/tbtc/sepolia-artifacts/CCIPRouter.json new file mode 100644 index 000000000..aa061ded5 --- /dev/null +++ b/src/threshold-ts/tbtc/sepolia-artifacts/CCIPRouter.json @@ -0,0 +1,705 @@ +{ + "address": "0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "wrappedNative", + "type": "address" + }, + { + "internalType": "address", + "name": "armProxy", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "BadARMSignal", + "type": "error" + }, + { + "inputs": [], + "name": "FailedToSendValue", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientFeeTokenAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMsgValue", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "InvalidRecipientAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyOffRamp", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + } + ], + "name": "UnsupportedDestinationChain", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "messageId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "calldataHash", + "type": "bytes32" + } + ], + "name": "MessageExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "OffRampRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "onRamp", + "type": "address" + } + ], + "name": "OnRampSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_RET_BYTES", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "onRamp", + "type": "address" + } + ], + "internalType": "struct Router.OnRamp[]", + "name": "onRampUpdates", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "offRampRemoves", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "offRampAdds", + "type": "tuple[]" + } + ], + "name": "applyRampUpdates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destinationChainSelector", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Client.EVMTokenAmount[]", + "name": "tokenAmounts", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "feeToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraArgs", + "type": "bytes" + } + ], + "internalType": "struct Client.EVM2AnyMessage", + "name": "message", + "type": "tuple" + } + ], + "name": "ccipSend", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getArmProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destinationChainSelector", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Client.EVMTokenAmount[]", + "name": "tokenAmounts", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "feeToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraArgs", + "type": "bytes" + } + ], + "internalType": "struct Client.EVM2AnyMessage", + "name": "message", + "type": "tuple" + } + ], + "name": "getFee", + "outputs": [ + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getOffRamps", + "outputs": [ + { + "components": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "internalType": "struct Router.OffRamp[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "destChainSelector", + "type": "uint64" + } + ], + "name": "getOnRamp", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], + "name": "getSupportedTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getWrappedNative", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], + "name": "isChainSupported", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "offRamp", + "type": "address" + } + ], + "name": "isOffRamp", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "messageId", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "sourceChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "sender", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Client.EVMTokenAmount[]", + "name": "destTokenAmounts", + "type": "tuple[]" + } + ], + "internalType": "struct Client.Any2EVMMessage", + "name": "message", + "type": "tuple" + }, + { + "internalType": "uint16", + "name": "gasForCallExactCheck", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "routeMessage", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "retData", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "gasUsed", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "wrappedNative", + "type": "address" + } + ], + "name": "setWrappedNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "typeAndVersion", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } + ] +} diff --git a/src/threshold-ts/tbtc/sepolia-artifacts/LockReleaseTokenPoolUpgradeable.json b/src/threshold-ts/tbtc/sepolia-artifacts/LockReleaseTokenPoolUpgradeable.json new file mode 100644 index 000000000..d1de3d50b --- /dev/null +++ b/src/threshold-ts/tbtc/sepolia-artifacts/LockReleaseTokenPoolUpgradeable.json @@ -0,0 +1,1674 @@ +{ + "address": "0x177b9829D779E56980c2D5897C4Ff3Be6CA5CcEB", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AllowListNotEnabled", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "CallerIsNotARampOnRouter", + "type": "error" + }, + { + "inputs": [], + "name": "CannotAcceptLiquidity", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "chainSelector", + "type": "uint64" + } + ], + "name": "ChainAlreadyExists", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "ChainNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "CursedByRMN", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientLiquidity", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "expected", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "actual", + "type": "uint8" + } + ], + "name": "InvalidDecimalArgs", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "sourcePoolData", + "type": "bytes" + } + ], + "name": "InvalidRemoteChainDecimals", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "InvalidRemotePoolForChain", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "sourcePoolAddress", + "type": "bytes" + } + ], + "name": "InvalidSourcePoolAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "InvalidToken", + "type": "error" + }, + { + "inputs": [], + "name": "MismatchedArrayLengths", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "NonExistentChain", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "remoteDecimals", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "localDecimals", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "remoteAmount", + "type": "uint256" + } + ], + "name": "OverflowDetected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "PoolAlreadyAdded", + "type": "error" + }, + { + "inputs": [], + "name": "RebalancerNotSet", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "SenderNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "AllowListAdd", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "AllowListRemove", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Burned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "remoteToken", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct RateLimiter.Config", + "name": "outboundRateLimiterConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct RateLimiter.Config", + "name": "inboundRateLimiterConfig", + "type": "tuple" + } + ], + "name": "ChainAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct RateLimiter.Config", + "name": "outboundRateLimiterConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct RateLimiter.Config", + "name": "inboundRateLimiterConfig", + "type": "tuple" + } + ], + "name": "ChainConfigured", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "ChainRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "provider", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LiquidityAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "provider", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LiquidityProvided", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "provider", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LiquidityRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LiquidityTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "rebalancer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LiquidityWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Locked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Minted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "rateLimitAdmin", + "type": "address" + } + ], + "name": "RateLimitAdminSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldRebalancer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newRebalancer", + "type": "address" + } + ], + "name": "RebalancerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Released", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "RemotePoolAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "RemotePoolRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldRouter", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newRouter", + "type": "address" + } + ], + "name": "RouterUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "addRemotePool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "removes", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "adds", + "type": "address[]" + } + ], + "name": "applyAllowListUpdates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64[]", + "name": "remoteChainSelectorsToRemove", + "type": "uint64[]" + }, + { + "components": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes[]", + "name": "remotePoolAddresses", + "type": "bytes[]" + }, + { + "internalType": "bytes", + "name": "remoteTokenAddress", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config", + "name": "outboundRateLimiterConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config", + "name": "inboundRateLimiterConfig", + "type": "tuple" + } + ], + "internalType": "struct TokenPoolMutable.ChainUpdate[]", + "name": "chainsToAdd", + "type": "tuple[]" + } + ], + "name": "applyChainUpdates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "canAcceptLiquidity", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllowList", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllowListEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "getCurrentInboundRateLimiterState", + "outputs": [ + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokens", + "type": "uint128" + }, + { + "internalType": "uint32", + "name": "lastUpdated", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.TokenBucket", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "getCurrentOutboundRateLimiterState", + "outputs": [ + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokens", + "type": "uint128" + }, + { + "internalType": "uint32", + "name": "lastUpdated", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.TokenBucket", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRateLimitAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRebalancer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "getRemotePools", + "outputs": [ + { + "internalType": "bytes[]", + "name": "", + "type": "bytes[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "getRemoteToken", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRmnProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRouter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSupportedChains", + "outputs": [ + { + "internalType": "uint64[]", + "name": "", + "type": "uint64[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getToken", + "outputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTokenDecimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint8", + "name": "_localTokenDecimals", + "type": "uint8" + }, + { + "internalType": "address[]", + "name": "_allowlist", + "type": "address[]" + }, + { + "internalType": "address", + "name": "_rmnProxy", + "type": "address" + }, + { + "internalType": "bool", + "name": "_canAcceptLiquidity", + "type": "bool" + }, + { + "internalType": "address", + "name": "_router", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "isRemotePool", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + } + ], + "name": "isSupportedChain", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + } + ], + "name": "isSupportedToken", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "originalSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraArgs", + "type": "bytes" + } + ], + "internalType": "struct Pool.LockOrBurnInV1", + "name": "lockOrBurnIn", + "type": "tuple" + } + ], + "name": "lockOrBurn", + "outputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "destTokenAddress", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "destPoolData", + "type": "bytes" + } + ], + "internalType": "struct Pool.LockOrBurnOutV1", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "provideLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "originalSender", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "internalType": "bytes", + "name": "sourcePoolAddress", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "sourcePoolData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "offchainTokenData", + "type": "bytes" + } + ], + "internalType": "struct Pool.ReleaseOrMintInV1", + "name": "releaseOrMintIn", + "type": "tuple" + } + ], + "name": "releaseOrMint", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "destinationAmount", + "type": "uint256" + } + ], + "internalType": "struct Pool.ReleaseOrMintOutV1", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "remotePoolAddress", + "type": "bytes" + } + ], + "name": "removeRemotePool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "remoteChainSelector", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config", + "name": "outboundConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config", + "name": "inboundConfig", + "type": "tuple" + } + ], + "name": "setChainRateLimiterConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64[]", + "name": "chainSelectors", + "type": "uint64[]" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config[]", + "name": "outboundConfigs", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "rate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "capacity", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isEnabled", + "type": "bool" + } + ], + "internalType": "struct RateLimiter.Config[]", + "name": "inboundConfigs", + "type": "tuple[]" + } + ], + "name": "setChainRateLimiterConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_rateLimitAdmin", + "type": "address" + } + ], + "name": "setRateLimitAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_rebalancer", + "type": "address" + } + ], + "name": "setRebalancer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newRouter", + "type": "address" + } + ], + "name": "setRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "typeAndVersion", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} diff --git a/src/threshold-ts/utils/contract.ts b/src/threshold-ts/utils/contract.ts index f09140ad4..163fc3b4d 100644 --- a/src/threshold-ts/utils/contract.ts +++ b/src/threshold-ts/utils/contract.ts @@ -26,6 +26,8 @@ import StakingArtifactMainnet from "../staking/mainnet-artifacts/TokenStaking.js import RandomBeaconArtifactMainnet from "../tbtc/mainnet-artifacts/RandomBeacon.json" import LegacyKeepStakingArtifactMainnet from "../staking/mainnet-artifacts/LegacyKeepStaking.json" import TacoArtifactMainnet from "@nucypher/nucypher-contracts/deployment/artifacts/mainnet.json" +import L1CCIPRouterMainnet from "../tbtc/mainnet-artifacts/CCIPRouter.json" +import LockReleaseTokenPoolUpgradeableArtifactMainnet from "../tbtc/mainnet-artifacts/LockReleaseTokenPoolUpgradeable.json" import ArbitrumL1BitcoinDepositorArtifactSepolia from "../tbtc/sepolia-artifacts/ArbitrumL1BitcoinDepositor.json" import BaseL1BitcoinDepositorArtifactSepolia from "../tbtc/sepolia-artifacts/BaseL1BitcoinDepositor.json" @@ -54,18 +56,18 @@ import WalletRegistryArtifactDappDevelopmentSepolia from "../tbtc/dapp-developme import StakingArtifactDappDevelopmentSepolia from "../staking/dapp-development-sepolia-artifacts/TokenStaking.json" import RandomBeaconArtifactDappDevelopmentSepolia from "../tbtc/dapp-development-sepolia-artifacts/RandomBeacon.json" import LegacyKeepStakingArtifactDappDevelopmentSepolia from "../staking/dapp-development-sepolia-artifacts/LegacyKeepStaking.json" -import TacoArtifactDappDevelopmentSepolia from "@nucypher/nucypher-contracts/deployment/artifacts/dashboard.json" +import L1CCIPRouterSepolia from "../tbtc/sepolia-artifacts/CCIPRouter.json" +import LockReleaseTokenPoolUpgradeableArtifactSepolia from "../tbtc/sepolia-artifacts/LockReleaseTokenPoolUpgradeable.json" -import CCIPRouterArtifactBOB from "../tbtc/bob-artifacts/CCIPRouter.json" -import BurnFromMintTokenPoolArtifactBOB from "../tbtc/bob-artifacts/BurnFromMintTokenPool.json" -import StandardBridgeArtifactBOB from "../tbtc/bob-artifacts/StandardBridge.json" -import OptimismMintableUpgradableTBTCArtifactBOB from "../tbtc/bob-artifacts/OptimismMintableUpgradableTBTC.json" +import CCIPRouterArtifactBob from "../tbtc/bob-artifacts/CCIPRouter.json" +import StandardBridgeArtifactBob from "../tbtc/bob-artifacts/StandardBridge.json" +import OptimismMintableUpgradableTBTCArtifactBob from "../tbtc/bob-artifacts/OptimismMintableUpgradableTBTC.json" +import BurnFromMintTokenPoolUpgradeableArtifactBob from "../tbtc/bob-artifacts/BurnFromMintTokenPoolUpgradeable.json" -import CCIPRouterArtifactBOBTestnet from "../tbtc/bob-testnet-artifacts/CCIPRouter.json" -import StandardBridgeArtifactBOBTestnet from "../tbtc/bob-testnet-artifacts/StandardBridge.json" -import OptimismMintableUpgradableTBTCArtifactBOBTestnet from "../tbtc/bob-testnet-artifacts/OptimismMintableUpgradableTBTC.json" -import L1CCIPRouterArtifact from "../tbtc/l1-artifacts/CCIPRouter.json" -import LinkTokenArtifact from "../tbtc/l1-artifacts/LinkToken.json" +import CCIPRouterArtifactBobTestnet from "../tbtc/bob-sepolia-artifacts/CCIPRouter.json" +import StandardBridgeArtifactBobTestnet from "../tbtc/bob-sepolia-artifacts/StandardBridge.json" +import OptimismMintableUpgradableTBTCArtifactBobTestnet from "../tbtc/bob-sepolia-artifacts/OptimismMintableUpgradableTBTC.json" +import BurnFromMintTokenPoolUpgradeableArtifactBobTestnet from "../tbtc/bob-sepolia-artifacts/BurnFromMintTokenPoolUpgradeable.json" export type ArtifactNameType = | "TacoRegistry" @@ -84,11 +86,9 @@ export type ArtifactNameType = | "BaseL1BitcoinDepositor" | "StarkNetBitcoinDepositor" | "CCIPRouter" - | "BurnFromMintTokenPool" | "StandardBridge" | "OptimismMintableUpgradableTBTC" - | "L1CCIPRouter" - | "LinkToken" + | "TokenPool" type ArtifactType = { address: string abi: ContractInterface @@ -118,8 +118,8 @@ const contractArtifacts: ContractArtifacts = { WalletRegistry: WalletRegistryArtifactMainnet, VendingMachineKeep: VendingMachineKeepMainnet, VendingMachineNuCypher: VendingMachineNuCypherMainnet, - L1CCIPRouter: L1CCIPRouterArtifact.mainnet, - LinkToken: LinkTokenArtifact.mainnet, + CCIPRouter: L1CCIPRouterMainnet, + TokenPool: LockReleaseTokenPoolUpgradeableArtifactMainnet, }, [SupportedChainIds.Sepolia]: { ArbitrumL1BitcoinDepositor: ArbitrumL1BitcoinDepositorArtifactSepolia, @@ -137,8 +137,8 @@ const contractArtifacts: ContractArtifacts = { WalletRegistry: WalletRegistryArtifactSepolia, VendingMachineKeep: VendingMachineKeepSepolia, VendingMachineNuCypher: VendingMachineNuCypherSepolia, - L1CCIPRouter: L1CCIPRouterArtifact.sepolia, - LinkToken: LinkTokenArtifact.sepolia, + CCIPRouter: L1CCIPRouterSepolia, + TokenPool: LockReleaseTokenPoolUpgradeableArtifactSepolia, }, [SupportedChainIds.Localhost]: { TacoRegistry: @@ -155,18 +155,18 @@ const contractArtifacts: ContractArtifacts = { VendingMachineKeep: VendingMachineKeepDappDevelopmentSepolia, VendingMachineNuCypher: VendingMachineNuCypherDappDevelopmentSepolia, }, - [SupportedChainIds.BOBMainnet]: { - CCIPRouter: CCIPRouterArtifactBOB, - BurnFromMintTokenPool: BurnFromMintTokenPoolArtifactBOB, - StandardBridge: StandardBridgeArtifactBOB, - OptimismMintableUpgradableTBTC: OptimismMintableUpgradableTBTCArtifactBOB, + [SupportedChainIds.Bob]: { + CCIPRouter: CCIPRouterArtifactBob, + StandardBridge: StandardBridgeArtifactBob, + OptimismMintableUpgradableTBTC: OptimismMintableUpgradableTBTCArtifactBob, + TokenPool: BurnFromMintTokenPoolUpgradeableArtifactBob, }, - [SupportedChainIds.BOBTestnet]: { - CCIPRouter: CCIPRouterArtifactBOBTestnet, - BurnFromMintTokenPool: BurnFromMintTokenPoolArtifactBOB, // Using same artifact for now - StandardBridge: StandardBridgeArtifactBOBTestnet, + [SupportedChainIds.BobSepolia]: { + CCIPRouter: CCIPRouterArtifactBobTestnet, + StandardBridge: StandardBridgeArtifactBobTestnet, OptimismMintableUpgradableTBTC: - OptimismMintableUpgradableTBTCArtifactBOBTestnet, + OptimismMintableUpgradableTBTCArtifactBobTestnet, + TokenPool: BurnFromMintTokenPoolUpgradeableArtifactBobTestnet, }, } diff --git a/src/types/modal.ts b/src/types/modal.ts index eda4f6317..5c6d46aee 100644 --- a/src/types/modal.ts +++ b/src/types/modal.ts @@ -47,6 +47,7 @@ import AnalyticsModal from "../components/Modal/AnalyticsModal" import { GenerateNewDepositAddress, InitiateUnminting, + InitiateBridging, NewTBTCApp, } from "../components/Modal/tBTC" import FeedbackSubmissionModal from "../components/Modal/FeedbackSubmissionModal" @@ -93,6 +94,7 @@ export const MODAL_TYPES: Record = { [ModalType.FeedbackSubmission]: FeedbackSubmissionModal, [ModalType.GenerateNewDepositAddress]: GenerateNewDepositAddress, [ModalType.InitiateUnminting]: InitiateUnminting, + [ModalType.InitiateBridging]: InitiateBridging, [ModalType.TACoCommitment]: TACoCommitmentModal, [ModalType.TACoCommitmentSuccess]: TACoCommitmentSuccessModal, } diff --git a/src/utils/tBTC.ts b/src/utils/tBTC.ts index 6e7aae583..17eb74390 100644 --- a/src/utils/tBTC.ts +++ b/src/utils/tBTC.ts @@ -163,7 +163,9 @@ export class RedemptionDetailsLinkBuilder { queryParams.set("walletPublicKeyHash", this.walletPublicKeyHash) queryParams.set("redeemerOutputScript", this.redeemerOutputScript) - return `/tBTC/unmint/redemption/${this.txHash}?${queryParams.toString()}` + return `/tBTC/deposit/unmint/redemption/${ + this.txHash + }?${queryParams.toString()}` } } diff --git a/src/web3/hooks/useTBTCv2TokenContract.ts b/src/web3/hooks/useTBTCv2TokenContract.ts index cd201feaf..e390e439d 100644 --- a/src/web3/hooks/useTBTCv2TokenContract.ts +++ b/src/web3/hooks/useTBTCv2TokenContract.ts @@ -11,9 +11,11 @@ export const TBTCV2_ADDRESSES = { [SupportedChainIds.Arbitrum]: "0x6c84a8f1c29108F47a79964b5Fe888D4f4D0dE40", [SupportedChainIds.Base]: "0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b", [SupportedChainIds.Localhost]: AddressZero, - // [SupportedChainIds.BaseSepolia]: "0xb8f31A249bcb45267d06b9E51252c4793B917Cd0", - // [SupportedChainIds.ArbitrumSepolia]: - // "0xb8f31A249bcb45267d06b9E51252c4793B917Cd0", + [SupportedChainIds.BaseSepolia]: "0xb8f31A249bcb45267d06b9E51252c4793B917Cd0", + [SupportedChainIds.ArbitrumSepolia]: + "0xb8f31A249bcb45267d06b9E51252c4793B917Cd0", + [SupportedChainIds.Bob]: "0xBBa2eF945D523C4e2608C9E1214C2Cc64D4fc2e2", + [SupportedChainIds.BobSepolia]: "0xD23F06550b0A7bC98B20eb81D4c21572a97598FA", } as Record // Switching wallet networks triggers an ethers error as the app fetches balances from an outdated