diff --git a/src/app/pages/swap/bitflow-swap-container.tsx b/src/app/pages/swap/bitflow-swap-container.tsx
index a017a988867..baf8ec3ad9a 100644
--- a/src/app/pages/swap/bitflow-swap-container.tsx
+++ b/src/app/pages/swap/bitflow-swap-container.tsx
@@ -1,6 +1,7 @@
import { useCallback, useState } from 'react';
import { Outlet, useNavigate } from 'react-router-dom';
+import type { P2Ret } from '@scure/btc-signer/payment';
import { bytesToHex } from '@stacks/common';
import { type ContractCallPayload, TransactionTypes } from '@stacks/connect';
import {
@@ -19,11 +20,13 @@ import { bitflow } from '@shared/utils/bitflow-sdk';
import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading';
import { Content, Page } from '@app/components/layout';
+import { BitcoinNativeSegwitAccountLoader } from '@app/components/loaders/bitcoin-account-loader';
import { PageHeader } from '@app/features/container/headers/page.header';
import type {
SbtcSponsorshipEligibility,
TransactionBase,
} from '@app/query/sbtc/sponsored-transactions.query';
+import type { Signer } from '@app/store/accounts/blockchain/bitcoin/bitcoin-signer';
import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { useGenerateStacksContractCallUnsignedTx } from '@app/store/transactions/contract-call.hooks';
import { useSignStacksTransaction } from '@app/store/transactions/transaction.hooks';
@@ -38,9 +41,17 @@ import { useStacksBroadcastSwap } from './hooks/use-stacks-broadcast-swap';
import { useSwapNavigate } from './hooks/use-swap-navigate';
import { SwapContext, SwapProvider } from './swap.context';
-export const bitflowSwapRoutes = generateSwapRoutes();
+// TODO: Refactor coupled Bitflow and Bitcoin swap containers, they should be separate
+export const bitflowSwapRoutes = generateSwapRoutes(
+ }>
+ {signer => }
+
+);
-function BitflowSwapContainer() {
+interface BitflowSwapContainerProps {
+ btcSigner?: Signer;
+}
+function BitflowSwapContainer({ btcSigner }: BitflowSwapContainerProps) {
const [unsignedTx, setUnsignedTx] = useState();
const [isSendingMax, setIsSendingMax] = useState(false);
const [isPreparingSwapReview, setIsPreparingSwapReview] = useState(false);
@@ -51,7 +62,7 @@ function BitflowSwapContainer() {
const generateUnsignedTx = useGenerateStacksContractCallUnsignedTx();
const signTx = useSignStacksTransaction();
const broadcastStacksSwap = useStacksBroadcastSwap();
- const { onDepositSbtc, onReviewDepositSbtc } = useSbtcDepositTransaction();
+ const { onDepositSbtc, onReviewDepositSbtc } = useSbtcDepositTransaction(btcSigner);
const [sponsorshipEligibility, setSponsorshipEligibility] = useState<
SbtcSponsorshipEligibility | undefined
@@ -72,7 +83,7 @@ function BitflowSwapContainer() {
swappableAssetsBase,
swappableAssetsQuote,
swapSubmissionData,
- } = useBitflowSwap();
+ } = useBitflowSwap(btcSigner);
const onSubmitSwapForReview = useCallback(
async (values: SwapFormValues) => {
diff --git a/src/app/pages/swap/generate-swap-routes.tsx b/src/app/pages/swap/generate-swap-routes.tsx
index 18798311d38..b8084376a95 100644
--- a/src/app/pages/swap/generate-swap-routes.tsx
+++ b/src/app/pages/swap/generate-swap-routes.tsx
@@ -2,6 +2,7 @@ import { Route } from 'react-router-dom';
import { RouteUrls } from '@shared/route-urls';
+import { ledgerBitcoinTxSigningRoutes } from '@app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container';
import { ledgerStacksTxSigningRoutes } from '@app/features/ledger/flows/stacks-tx-signing/ledger-sign-stacks-tx-container';
import { AccountGate } from '@app/routes/account-gate';
@@ -20,6 +21,7 @@ export function generateSwapRoutes(container: React.ReactNode) {
} />
}>
+ {ledgerBitcoinTxSigningRoutes}
{ledgerStacksTxSigningRoutes}
diff --git a/src/app/pages/swap/hooks/use-bitflow-swap.tsx b/src/app/pages/swap/hooks/use-bitflow-swap.tsx
index 41643f96e09..1b5f01d3000 100644
--- a/src/app/pages/swap/hooks/use-bitflow-swap.tsx
+++ b/src/app/pages/swap/hooks/use-bitflow-swap.tsx
@@ -1,5 +1,6 @@
import { useCallback, useMemo, useState } from 'react';
+import type { P2Ret } from '@scure/btc-signer/payment';
import type { RouteQuote } from 'bitflow-sdk';
import { type SwapAsset } from '@leather.io/query';
@@ -9,7 +10,9 @@ import { logger } from '@shared/logger';
import { bitflow } from '@shared/utils/bitflow-sdk';
import { useConfigSbtc } from '@app/query/common/remote-config/remote-config.query';
+import type { Signer } from '@app/store/accounts/blockchain/bitcoin/bitcoin-signer';
import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
+import { useHasLedgerKeys } from '@app/store/ledger/ledger.selectors';
import { SwapSubmissionData } from '../swap.context';
import { useBitflowSwappableAssets } from './use-bitflow-swappable-assets';
@@ -26,7 +29,7 @@ function getBitflowSwappableAssetsWithSbtcAtTop(assets: SwapAsset[]) {
].filter(isDefined);
}
-export function useBitflowSwap() {
+export function useBitflowSwap(btcSigner?: Signer) {
const [isCrossChainSwap, setIsCrossChainSwap] = useState(false);
const [swapSubmissionData, setSwapSubmissionData] = useState();
const [slippage, _setSlippage] = useState(0.04);
@@ -34,14 +37,16 @@ export function useBitflowSwap() {
const address = useCurrentStacksAccountAddress();
const { data: bitflowSwapAssets = [] } = useBitflowSwappableAssets(address);
const { isSbtcEnabled } = useConfigSbtc();
+ const isLedger = useHasLedgerKeys();
- const createBtcAsset = useBtcSwapAsset();
+ const createBtcAsset = useBtcSwapAsset(btcSigner);
const btcAsset = createBtcAsset();
const swappableAssetsBase = useMemo(() => {
- if (!isSbtcEnabled) return migratePositiveAssetBalancesToTop(bitflowSwapAssets);
+ if (!isSbtcEnabled || !btcSigner || isLedger)
+ return migratePositiveAssetBalancesToTop(bitflowSwapAssets);
return [btcAsset, ...getBitflowSwappableAssetsWithSbtcAtTop(bitflowSwapAssets)];
- }, [bitflowSwapAssets, btcAsset, isSbtcEnabled]);
+ }, [bitflowSwapAssets, btcAsset, btcSigner, isLedger, isSbtcEnabled]);
const swappableAssetsQuote = useMemo(() => {
if (!isSbtcEnabled) return bitflowSwapAssets;
diff --git a/src/app/pages/swap/hooks/use-btc-bridge-asset.tsx b/src/app/pages/swap/hooks/use-btc-bridge-asset.tsx
index 3a10b147b55..ea578dd5a5e 100644
--- a/src/app/pages/swap/hooks/use-btc-bridge-asset.tsx
+++ b/src/app/pages/swap/hooks/use-btc-bridge-asset.tsx
@@ -1,15 +1,15 @@
import { useCallback } from 'react';
import BtcAvatarIconSrc from '@assets/avatars/btc-avatar-icon.png';
+import type { P2Ret } from '@scure/btc-signer/payment';
import { type SwapAsset, useCryptoCurrencyMarketDataMeanAverage } from '@leather.io/query';
import { useBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
-import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
+import type { Signer } from '@app/store/accounts/blockchain/bitcoin/bitcoin-signer';
-export function useBtcSwapAsset() {
- const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
- const currentBitcoinAddress = nativeSegwitSigner.address;
+export function useBtcSwapAsset(btcSigner?: Signer) {
+ const currentBitcoinAddress = btcSigner?.address ?? '';
const { balance } = useBtcCryptoAssetBalanceNativeSegwit(currentBitcoinAddress);
const bitcoinMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC');
diff --git a/src/app/pages/swap/hooks/use-sbtc-deposit-transaction.tsx b/src/app/pages/swap/hooks/use-sbtc-deposit-transaction.tsx
index 24cb0e8a4cf..c85c274d51c 100644
--- a/src/app/pages/swap/hooks/use-sbtc-deposit-transaction.tsx
+++ b/src/app/pages/swap/hooks/use-sbtc-deposit-transaction.tsx
@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
import { bytesToHex } from '@noble/hashes/utils';
import * as btc from '@scure/btc-signer';
-import type { P2TROut } from '@scure/btc-signer/payment';
+import type { P2Ret, P2TROut } from '@scure/btc-signer/payment';
import {
MAINNET,
REGTEST,
@@ -29,7 +29,7 @@ import { useToast } from '@app/features/toasts/use-toast';
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
import { useBreakOnNonCompliantEntity } from '@app/query/common/compliance-checker/compliance-checker.query';
import { useBitcoinScureLibNetworkConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin-keychain';
-import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
+import type { Signer } from '@app/store/accounts/blockchain/bitcoin/bitcoin-signer';
import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
@@ -61,16 +61,17 @@ function getSbtcNetworkConfig(network: BitcoinNetworkModes) {
const clientMainnet = new SbtcApiClientMainnet();
const clientTestnet = new SbtcApiClientTestnet();
-export function useSbtcDepositTransaction() {
+export function useSbtcDepositTransaction(btcSigner?: Signer) {
const toast = useToast();
const { setIsIdle } = useLoading(LoadingKeys.SUBMIT_SWAP_TRANSACTION);
const stacksAccount = useCurrentStacksAccount();
const { data: utxos } = useCurrentNativeSegwitUtxos();
const { data: feeRates } = useAverageBitcoinFeeRates();
- const signer = useCurrentAccountNativeSegwitIndexZeroSigner();
const networkMode = useBitcoinScureLibNetworkConfig();
const navigate = useNavigate();
const network = useCurrentNetwork();
+ // TODO: Use with Ledger integration
+ // const sign = useSignBitcoinTx();
const client = useMemo(
() => (network.chain.bitcoin.mode === 'mainnet' ? clientMainnet : clientTestnet),
@@ -82,7 +83,7 @@ export function useSbtcDepositTransaction() {
return {
async onReviewDepositSbtc(swapData: SwapSubmissionData, isSendingMax: boolean) {
- if (!stacksAccount || !utxos) return;
+ if (!stacksAccount || !utxos || !btcSigner) return;
try {
const deposit: SbtcDeposit = buildSbtcDepositTx({
@@ -92,7 +93,7 @@ export function useSbtcDepositTransaction() {
signersPublicKey: await client.fetchSignersPublicKey(),
maxSignerFee,
reclaimLockTime,
- reclaimPublicKey: bytesToHex(signer.publicKey).slice(2),
+ reclaimPublicKey: bytesToHex(btcSigner.publicKey).slice(2),
});
const determineUtxosArgs = {
@@ -110,7 +111,7 @@ export function useSbtcDepositTransaction() {
? determineUtxosForSpendAll(determineUtxosArgs)
: determineUtxosForSpend(determineUtxosArgs);
- const p2wpkh = btc.p2wpkh(signer.publicKey, networkMode);
+ const p2wpkh = btc.p2wpkh(btcSigner.publicKey, networkMode);
for (const input of inputs) {
deposit.transaction.addInput({
@@ -128,7 +129,11 @@ export function useSbtcDepositTransaction() {
outputs.forEach(output => {
// Add change output
if (!output.address) {
- deposit.transaction.addOutputAddress(signer.address, BigInt(output.value), networkMode);
+ deposit.transaction.addOutputAddress(
+ btcSigner.address,
+ BigInt(output.value),
+ networkMode
+ );
return;
}
});
@@ -140,11 +145,11 @@ export function useSbtcDepositTransaction() {
}
},
async onDepositSbtc(swapSubmissionData: SwapSubmissionData) {
- if (!stacksAccount) return;
+ if (!stacksAccount || !btcSigner) return;
const sBtcDeposit = swapSubmissionData.txData?.deposit as SbtcDeposit;
try {
- signer.sign(sBtcDeposit.transaction);
+ btcSigner.sign(sBtcDeposit.transaction);
sBtcDeposit.transaction.finalize();
logger.info('Deposit', { deposit: sBtcDeposit });
diff --git a/src/app/query/bitcoin/address/utxos-by-address.hooks.ts b/src/app/query/bitcoin/address/utxos-by-address.hooks.ts
index 7aaf3a6fd0f..9d1891d1c9b 100644
--- a/src/app/query/bitcoin/address/utxos-by-address.hooks.ts
+++ b/src/app/query/bitcoin/address/utxos-by-address.hooks.ts
@@ -1,6 +1,6 @@
import { useNativeSegwitUtxosByAddress } from '@leather.io/query';
-import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
+import { useCurrentAccountNativeSegwitIndexZeroSignerNullable } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
const defaultArgs = {
filterInscriptionUtxos: true,
@@ -15,8 +15,8 @@ const defaultArgs = {
export function useCurrentNativeSegwitUtxos(args = defaultArgs) {
const { filterInscriptionUtxos, filterPendingTxsUtxos, filterRunesUtxos } = args;
- const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
- const address = nativeSegwitSigner.address;
+ const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSignerNullable();
+ const address = nativeSegwitSigner?.address ?? '';
return useNativeSegwitUtxosByAddress({
address,