Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/hooks/tbtc/useDepositTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BitcoinNetwork } from "../../threshold-ts/types"
import { verifyDepositAddress } from "../../utils/verifyDepositAddress"
import { useCaptureMessage } from "../sentry"
import { ApiUrl, endpointUrl } from "../../enums"
import { isLocalhost } from "../../utils/isLocalhost"

export const useDepositTelemetry = (network: BitcoinNetwork) => {
const captureMessage = useCaptureMessage()
Expand Down Expand Up @@ -43,6 +44,14 @@ export const useDepositTelemetry = (network: BitcoinNetwork) => {
}
)

// Skip telemetry submission in localhost to avoid CORS issues
if (isLocalhost()) {
console.info(
"Skipping deposit telemetry submission in localhost environment"
)
return
}

const requestBody = {
depositAddress,
depositor: depositor.identifierHex,
Expand All @@ -61,8 +70,19 @@ export const useDepositTelemetry = (network: BitcoinNetwork) => {
requestBody,
{ timeout: 10000 }
)
} catch (error) {
throw new Error("Failed to submit deposit telemetry", { cause: error })
} catch (error: any) {
// Log the error but don't throw it to prevent blocking the deposit flow
console.warn("Failed to submit deposit telemetry:", error.message)

// In production, throw only for non-CORS errors
if (
!isLocalhost() &&
(error.response || error.code !== "ERR_NETWORK")
) {
throw new Error("Failed to submit deposit telemetry", {
cause: error,
})
}
}
},
[verifyDepositAddress, network, captureMessage]
Expand Down
6 changes: 4 additions & 2 deletions src/pages/tBTC/Deposit/Minting/InitiateMinting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,17 @@ const InitiateMintingComponent: FC<{
withSymbol
/>{" "}
and will receive{" "}
<Skeleton isLoaded={!!tBTCMintAmount} maxW="105px" as="span">
{tBTCMintAmount ? (
<InlineTokenBalance
tokenAmount={tBTCMintAmount}
tokenSymbol="tBTC"
precision={6}
higherPrecision={8}
withSymbol
/>
</Skeleton>
) : (
<span style={{ display: "inline-block", width: "105px" }}>--</span>
)}
</H5>
<BodyLg>{chainInfo.mintingProcessDescription}</BodyLg>
</InfoBox>
Expand Down
30 changes: 22 additions & 8 deletions src/pages/tBTC/Deposit/Minting/MakeDeposit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,19 +108,33 @@ const BTCAddressSection: FC<{
maxW="205px"
borderRadius="8px"
>
<QRCode
size={256}
style={{ height: "auto", maxWidth: "100%", width: "100%" }}
value={btcDepositAddress}
viewBox={`0 0 256 256`}
/>
{btcDepositAddress ? (
<QRCode
size={256}
style={{ height: "auto", maxWidth: "100%", width: "100%" }}
value={btcDepositAddress}
viewBox={`0 0 256 256`}
/>
) : (
<Box
width="100%"
height="185px"
display="flex"
alignItems="center"
justifyContent="center"
borderRadius="8px"
backgroundColor="gray.50"
>
<BodyMd color="gray.500">Loading address...</BodyMd>
</Box>
)}
</Box>
</BTCAddressCard>
<CopyToClipboard textToCopy={btcDepositAddress}>
<CopyToClipboard textToCopy={btcDepositAddress || ""}>
<HStack mt="2.5">
<BTCAddressCard minW="0" p="2">
<BodyMd color={btcAddressColor} textStyle="chain-identifier">
{btcDepositAddress}
{btcDepositAddress || "..."}
</BodyMd>
</BTCAddressCard>
<BTCAddressCard
Expand Down
25 changes: 25 additions & 0 deletions src/threshold-ts/tbtc/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Centralized API endpoints for tBTC services
*/

export const TBTC_API_ENDPOINTS = {
MAINNET: {
BASE_URL: "https://api.tbtcscan.com",
GASLESS_REVEAL: "https://api.tbtcscan.com/api/gasless-reveal",
RELAYER_REVEAL: "http://relayer.tbtcscan.com/api/reveal",
},
TESTNET: {
BASE_URL: "http://localhost:3000",
GASLESS_REVEAL: "http://localhost:3000/api/gasless-reveal",
RELAYER_REVEAL: "http://localhost:3000/api/reveal",
},
} as const

/**
* Get the appropriate API endpoints based on network
* @param {boolean} isTestnet Whether the network is testnet or mainnet
* @return {object} API endpoints for the specified network
*/
export const getApiEndpoints = (isTestnet: boolean) => {
return isTestnet ? TBTC_API_ENDPOINTS.TESTNET : TBTC_API_ENDPOINTS.MAINNET
}
144 changes: 144 additions & 0 deletions src/threshold-ts/tbtc/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BlockTag, TransactionReceipt } from "@ethersproject/abstract-provider"
import { Web3Provider, JsonRpcProvider } from "@ethersproject/providers"
import axios from "axios"
import {
BitcoinClient,
BitcoinTx,
Expand All @@ -9,12 +10,16 @@ import {
CrossChainDepositor,
Deposit,
DepositRequest,
DepositReceipt,
BitcoinRawTxVectors,
ElectrumClient,
ethereumAddressFromSigner,
EthereumBridge,
chainIdFromSigner,
Hex,
loadEthereumCoreContracts,
packRevealDepositParameters,
extractBitcoinRawTxVectors,
TBTC as SDK,
Chains,
DestinationChainName,
Expand Down Expand Up @@ -66,6 +71,7 @@ import { SupportedChainIds } from "../../networks/enums/networks"
import { getThresholdLibProvider } from "../../utils/getThresholdLib"
import { getEthereumDefaultProviderChainId } from "../../utils/getEnvVariable"
import { getCrossChainRpcUrl } from "../../networks/utils/getCrossChainRpcUrl"
import { getApiEndpoints } from "./constants"

export enum BridgeActivityStatus {
PENDING = "PENDING",
Expand Down Expand Up @@ -352,6 +358,22 @@ export interface ITBTC {
*/
revealDeposit(utxo: BitcoinUtxo): Promise<string | TransactionReceipt>

/**
* Reveals the given deposit using gasless reveal through a relayer service.
* This method calls an external API to trigger the deposit transaction via a relayer.
* @param depositTx Bitcoin raw transaction vectors
* @param depositOutputIndex Index of the deposit output
* @param deposit Deposit receipt containing the deposit parameters
* @param vault Optional vault address
* @returns Transaction receipt from the gasless reveal
*/
gaslessRevealDeposit(
depositTx: BitcoinRawTxVectors,
depositOutputIndex: number,
deposit: DepositReceipt,
vault?: ChainIdentifier
): Promise<TransactionReceipt>

/**
* Gets a revealed deposit from the bridge.
* @param utxo Deposit UTXO of the revealed deposit
Expand Down Expand Up @@ -1107,6 +1129,46 @@ export class TBTC implements ITBTC {
const { value, ...transactionOutpoint } = utxo
if (!this._deposit) throw new EmptyDepositObjectError()

// Check if we should use gasless reveal for L1 networks
const isL1 =
!this._crossChainConfig.isCrossChain && this._ethereumConfig.chainId
if (isL1) {
// Use gasless reveal for L1 networks
const depositReceipt = this._deposit.getReceipt()

// Get the raw bitcoin transaction
const rawTx = await this._bitcoinClient.getRawTransaction(
utxo.transactionHash
)

// Extract transaction vectors
const depositTx = extractBitcoinRawTxVectors(rawTx)

// Get vault if available
const vaultAddress = this._tbtcVaultContract?.address
let vault: ChainIdentifier | undefined
if (vaultAddress) {
// Create a proper ChainIdentifier object
vault = {
identifierHex: vaultAddress.slice(2).toLowerCase(),
equals: function (other: ChainIdentifier): boolean {
return this.identifierHex === other.identifierHex
},
}
}

const receipt = await this.gaslessRevealDeposit(
depositTx,
utxo.outputIndex,
depositReceipt,
vault
)

this.removeDepositData()
return receipt
}

// Use regular reveal for L2/cross-chain networks
const result = await this._deposit.initiateMinting(transactionOutpoint)
this.removeDepositData()

Expand All @@ -1125,6 +1187,88 @@ export class TBTC implements ITBTC {
throw new Error("Unexpected result type from initiateMinting")
}

/**
* Reveals the given deposit using gasless reveal through a relayer service.
* This method calls an external API to trigger the deposit transaction via a relayer.
* @param {BitcoinRawTxVectors} depositTx Bitcoin raw transaction vectors
* @param {number} depositOutputIndex Index of the deposit output
* @param {DepositReceipt} deposit Deposit receipt containing the deposit parameters
* @param {ChainIdentifier} vault Optional vault address
* @return {Promise<TransactionReceipt>} Transaction receipt from the gasless reveal
*/
gaslessRevealDeposit = async (
depositTx: BitcoinRawTxVectors,
depositOutputIndex: number,
deposit: DepositReceipt,
vault?: ChainIdentifier
): Promise<TransactionReceipt> => {
const { fundingTx, reveal } = packRevealDepositParameters(
depositTx,
depositOutputIndex,
deposit,
vault
)

// For gasless reveals, we need the deposit owner address
const depositOwner = deposit.depositor

// Determine the API endpoint based on network
const isTestnet = this.bitcoinNetwork === BitcoinNetwork.Testnet
const apiEndpoints = getApiEndpoints(isTestnet)
const apiUrl = apiEndpoints.GASLESS_REVEAL

try {
const response = await axios.post(apiUrl, {
fundingTx,
reveal,
destinationChainDepositOwner: `0x${depositOwner.identifierHex}`,
})

const { data } = response
if (!data || data.status !== "success") {
throw new Error(
`Unexpected response from /api/gasless-reveal: ${JSON.stringify(
data
)}`
)
}

// Convert the response to match TransactionReceipt format
const receipt: TransactionReceipt = {
to: data.data.contractAddress || "",
from: "",
contractAddress: data.data.contractAddress || "",
transactionIndex: 0,
root: undefined,
gasUsed: BigNumber.from(0),
logsBloom: "",
blockHash: "",
transactionHash: data.data.transactionHash,
logs: [],
blockNumber: data.data.blockNumber,
confirmations: 0,
cumulativeGasUsed: BigNumber.from(0),
effectiveGasPrice: BigNumber.from(0),
byzantium: true,
type: 0,
status: data.data.status,
}

return receipt
} catch (error: any) {
if (error.response) {
console.error("Gasless reveal API error response:", error.response.data)
throw new Error(
error.response.data?.message ||
error.response.data?.error ||
"Failed to perform gasless reveal"
)
}
console.error("Error calling /api/gasless-reveal endpoint:", error)
throw error
}
}

getRevealedDeposit = async (utxo: BitcoinUtxo): Promise<DepositRequest> => {
const sdk = await this._getSdk()
const deposit = await sdk.tbtcContracts.bridge.deposits(
Expand Down
17 changes: 17 additions & 0 deletions src/utils/isLocalhost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Check if the current environment is localhost/development
* @return {boolean} True if running on localhost
*/
export const isLocalhost = (): boolean => {
if (typeof window === "undefined") return false

const hostname = window.location.hostname
return (
hostname === "localhost" ||
hostname === "127.0.0.1" ||
hostname === "0.0.0.0" ||
hostname.startsWith("192.168.") ||
hostname.startsWith("10.") ||
hostname.endsWith(".local")
)
}