diff --git a/.changeset/wicked-beans-reply.md b/.changeset/wicked-beans-reply.md new file mode 100644 index 00000000..95e8da61 --- /dev/null +++ b/.changeset/wicked-beans-reply.md @@ -0,0 +1,6 @@ +--- +'@reservoir0x/relay-sdk': patch +'@reservoir0x/relay-kit-ui': patch +--- + +Fix bugs with bitcoin implementation diff --git a/packages/sdk/src/utils/transaction.ts b/packages/sdk/src/utils/transaction.ts index 746dc21a..42d8a09a 100644 --- a/packages/sdk/src/utils/transaction.ts +++ b/packages/sdk/src/utils/transaction.ts @@ -204,19 +204,31 @@ export async function sendTransactionSafely( } } - //If the time estimate of the tx is greater than the maximum attempts we should skip polling confirmation - const timeEstimateMs = - ((details?.timeEstimate ?? 0) + (chainId === 8253038 ? 600 : 0)) * 1000 - const maximumWaitTimeInMs = maximumAttempts * pollingInterval - - if (timeEstimateMs > maximumWaitTimeInMs) { + //If the origin chain is bitcoin, skip polling for confirmation, because the deposit will take too long + if (chainId === 8253038) { return true } //Sequence internal functions - if (step.id === 'approve') { + // We want synchronous execution in the following cases: + // - Approval Signature step required first + // - Bitcoin is the destination + // - Canonical route used + if ( + step.id === 'approve' || + details?.currencyOut?.currency?.chainId === 8253038 || + request?.data?.useExternalLiquidity + ) { await waitForTransaction().promise - await pollForConfirmation() + //In the following cases we want to skip polling for confirmation: + // - Bitcoin destination chain, we want to skip polling for confirmation as the block times are lengthy + // - Canonical route, also lengthy fill time + if ( + details?.currencyOut?.currency?.chainId !== 8253038 && + !request?.data?.useExternalLiquidity + ) { + await pollForConfirmation() + } } else { const { promise: receiptPromise, controller: receiptController } = waitForTransaction() diff --git a/packages/ui/src/components/common/TokenSelector/TokenSelector.tsx b/packages/ui/src/components/common/TokenSelector/TokenSelector.tsx index fbd43404..e157381f 100644 --- a/packages/ui/src/components/common/TokenSelector/TokenSelector.tsx +++ b/packages/ui/src/components/common/TokenSelector/TokenSelector.tsx @@ -49,7 +49,7 @@ export type TokenSelectorProps = { export type EnhancedCurrencyList = { chains: (CurrencyList[number] & { relayChain: RelayChain - balance?: DuneBalanceResponse['balances'][0] + balance?: NonNullable['balances'][0] })[] totalValueUsd?: number totalBalance?: bigint diff --git a/packages/ui/src/components/widgets/SwapWidget/index.tsx b/packages/ui/src/components/widgets/SwapWidget/index.tsx index a7b486ff..2326680a 100644 --- a/packages/ui/src/components/widgets/SwapWidget/index.tsx +++ b/packages/ui/src/components/widgets/SwapWidget/index.tsx @@ -857,7 +857,7 @@ const SwapWidget: FC = ({ hasInsufficientBalance={hasInsufficientBalance} error={error} quote={price} - currency={toToken} + currency={fromToken} isHighRelayerServiceFee={highRelayerServiceFee} isCapacityExceededError={isCapacityExceededError} isCouldNotExecuteError={isCouldNotExecuteError} diff --git a/packages/ui/src/hooks/useBitcoinBalance.ts b/packages/ui/src/hooks/useBitcoinBalance.ts index e051f195..969c0cdd 100644 --- a/packages/ui/src/hooks/useBitcoinBalance.ts +++ b/packages/ui/src/hooks/useBitcoinBalance.ts @@ -54,7 +54,8 @@ export default (address?: string, queryOptions?: Partial) => { const balanceResponse = response as BitcoinBalanceResponse const fundedTxo = balanceResponse.chain_stats.funded_txo_sum const spentTxo = balanceResponse.chain_stats.spent_txo_sum - balance = BigInt(fundedTxo) - BigInt(spentTxo) + const inflightTxo = balanceResponse.mempool_stats.spent_txo_sum + balance = BigInt(fundedTxo) - BigInt(spentTxo) - BigInt(inflightTxo) } return balance }) diff --git a/packages/ui/src/hooks/useDuneBalances.ts b/packages/ui/src/hooks/useDuneBalances.ts index 99e40210..706939c6 100644 --- a/packages/ui/src/hooks/useDuneBalances.ts +++ b/packages/ui/src/hooks/useDuneBalances.ts @@ -6,7 +6,8 @@ import { type DefaultError, type QueryKey } from '@tanstack/react-query' -import { solana, solanaAddressRegex } from '../utils/solana.js' +import { isSolanaAddress, solana } from '../utils/solana.js' +import { isBitcoinAddress } from '../utils/bitcoin.js' export type DuneBalanceResponse = { request_time: string @@ -22,7 +23,7 @@ export type DuneBalanceResponse = { price_usd?: number value_usd?: number }> -} +} | null type QueryType = typeof useQuery< DuneBalanceResponse, @@ -35,7 +36,8 @@ type QueryOptions = Parameters['0'] export default (address?: string, queryOptions?: Partial) => { const providerOptions = useContext(ProviderOptionsContext) const queryKey = ['useDuneBalances', address] - const isSvmAddress = address && solanaAddressRegex.test(address) + const isSvmAddress = isSolanaAddress(address ?? '') + const isBvmAddress = isBitcoinAddress(address ?? '') const response = (useQuery as QueryType)({ queryKey: ['useDuneBalances', address], @@ -45,6 +47,10 @@ export default (address?: string, queryOptions?: Partial) => { url = `https://api.dune.com/api/beta/balance/solana/${address}?chain_ids=all` } + if (isBvmAddress) { + return null + } + return fetch(url, { headers: { 'X-DUNE-API-KEY': providerOptions.duneApiKey @@ -54,7 +60,7 @@ export default (address?: string, queryOptions?: Partial) => { .then((response) => { if (response.balances) { const balances = - response.balances as DuneBalanceResponse['balances'] + response.balances as NonNullable['balances'] if (balances) { balances .filter((balance) => { @@ -88,17 +94,21 @@ export default (address?: string, queryOptions?: Partial) => { return response }) }, - enabled: address !== undefined && providerOptions.duneApiKey !== undefined, - ...queryOptions + ...queryOptions, + enabled: + address !== undefined && + providerOptions.duneApiKey !== undefined && + queryOptions?.enabled && + !isBvmAddress }) - response.data?.balances?.forEach((balance) => { + response?.data?.balances?.forEach((balance) => { if (!balance.chain_id && balance.chain === 'solana') { balance.chain_id = solana.id } }) - const balanceMap = response.data?.balances?.reduce( + const balanceMap = response?.data?.balances?.reduce( (balanceMap, balance) => { if (balance.address === 'native') { balance.address = @@ -114,7 +124,7 @@ export default (address?: string, queryOptions?: Partial) => { balanceMap[`${chainId}:${balance.address}`] = balance return balanceMap }, - {} as Record + {} as Record['balances'][0]> ) return { ...response, balanceMap, queryKey } as ReturnType & { diff --git a/packages/ui/src/utils/quote.ts b/packages/ui/src/utils/quote.ts index fd0d9fe3..2066dfc3 100644 --- a/packages/ui/src/utils/quote.ts +++ b/packages/ui/src/utils/quote.ts @@ -216,26 +216,15 @@ export const extractQuoteId = (steps?: Execute['steps']) => { return '' } -export const calculateTimeEstimate = (breakdown?: Execute['breakdown']) => { - const time = - breakdown?.reduce((total, breakdown) => { - return total + (breakdown.timeEstimate ?? 0) - }, 0) ?? 0 - const formattedTime = formatSeconds(time) - - return { - time, - formattedTime - } -} - export const calculatePriceTimeEstimate = ( details?: PriceResponse['details'] ) => { - const isBitcoin = details?.currencyIn?.currency?.chainId === bitcoin.id + const isBitcoin = + details?.currencyIn?.currency?.chainId === bitcoin.id || + details?.currencyOut?.currency?.chainId === bitcoin.id - //Add 10m origin because of the origin deposit time - const time = (details?.timeEstimate ?? 0) + (isBitcoin ? 600 : 0) + //If the relay is interacting with bitcoin we hardcode the time estime to 10m + const time = isBitcoin ? 600 : details?.timeEstimate ?? 0 const formattedTime = formatSeconds(time) return {