diff --git a/package.json b/package.json index ba2593bb5f6..d1ab8b6d5e6 100644 --- a/package.json +++ b/package.json @@ -212,7 +212,7 @@ "react-intersection-observer": "9.5.2", "react-lottie": "1.2.3", "react-redux": "8.1.3", - "react-router-dom": "6.16.0", + "react-router-dom": "6.20.1", "react-virtuoso": "4.6.0", "redux-persist": "6.0.0", "rxjs": "7.8.1", diff --git a/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx b/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx index 287a4764f18..36b89785a5b 100644 --- a/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx +++ b/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import toast from 'react-hot-toast'; import { Outlet, Route, useLocation } from 'react-router-dom'; import * as btc from '@scure/btc-signer'; @@ -47,6 +48,7 @@ function LedgerSignBitcoinTxContainer() { const signLedger = useSignLedgerBitcoinTx(); const inputsToSign = useLocationStateWithCache('inputsToSign'); + const allowUserToGoBack = useLocationState('goBack'); useEffect(() => { @@ -63,6 +65,12 @@ function LedgerSignBitcoinTxContainer() { const [awaitingDeviceConnection, setAwaitingDeviceConnection] = useState(false); + if (!inputsToSign) { + ledgerNavigate.cancelLedgerAction(); + toast.error('No input signing config defined'); + return null; + } + const signTransaction = async () => { setAwaitingDeviceConnection(true); const bitcoinApp = await connectLedgerBitcoinApp(); @@ -73,6 +81,7 @@ function LedgerSignBitcoinTxContainer() { setAwaitingDeviceConnection(false); setLatestDeviceResponse(versionInfo as any); } catch (e) { + setLatestDeviceResponse(e as any); logger.error('Unable to get Ledger app version info', e); } @@ -85,11 +94,7 @@ function LedgerSignBitcoinTxContainer() { ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: false }); try { - const btcTx = await signLedger( - bitcoinApp, - unsignedTransaction.toPSBT(), - inputsToSign?.map(x => x.index) - ); + const btcTx = await signLedger(bitcoinApp, unsignedTransaction.toPSBT(), inputsToSign); if (!btcTx || !unsignedTransactionRaw) throw new Error('No tx returned'); ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: true }); diff --git a/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx b/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx index 614dd19e5a9..5aa4859b6b8 100644 --- a/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx +++ b/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx @@ -81,7 +81,6 @@ export function useSendInscriptionForm() { inscription, recipient: values.recipient, utxo, - backgroundLocation: { pathname: RouteUrls.Home }, }, } ); @@ -122,7 +121,6 @@ export function useSendInscriptionForm() { time, feeRowValue, signedTx: signedTx.extract(), - backgroundLocation: { pathname: RouteUrls.Home }, }, }); }, diff --git a/src/app/routes/app-routes.tsx b/src/app/routes/app-routes.tsx index abefedcce67..d53359792b8 100644 --- a/src/app/routes/app-routes.tsx +++ b/src/app/routes/app-routes.tsx @@ -66,13 +66,13 @@ export function AppRoutes() { export const homePageModalRoutes = ( <> - {sendOrdinalRoutes} {settingsRoutes} {receiveRoutes} {ledgerStacksTxSigningRoutes} {ledgerBitcoinTxSigningRoutes} {requestBitcoinKeysRoutes} {requestStacksKeysRoutes} + {sendOrdinalRoutes} ); diff --git a/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts b/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts index 69cb4eb8c38..1076321a929 100644 --- a/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts +++ b/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts @@ -17,7 +17,7 @@ import { getAssumedZeroIndexSigningConfig, } from '@shared/crypto/bitcoin/signer-config'; import { allSighashTypes } from '@shared/rpc/methods/sign-psbt'; -import { isNumber, makeNumberRange } from '@shared/utils'; +import { isNumber } from '@shared/utils'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useWalletType } from '@app/common/use-wallet-type'; @@ -113,7 +113,11 @@ export function useSignLedgerBitcoinTx() { const updateTaprootLedgerInputs = useUpdateLedgerSpecificTaprootInputPropsForAdddressIndexZero(); - return async (app: AppClient, rawPsbt: Uint8Array, inputsToSign?: number[]) => { + return async ( + app: AppClient, + rawPsbt: Uint8Array, + signingConfig: BitcoinInputSigningConfig[] + ) => { const fingerprint = await app.getMasterFingerprint(); // BtcSigner not compatible with Ledger. Encoded format returns more terse @@ -124,16 +128,14 @@ export function useSignLedgerBitcoinTx() { const btcSignerPsbtClone = btc.Transaction.fromPSBT(psbt.toBuffer()); - const inputByPaymentType = ( - inputsToSign ?? makeNumberRange(btcSignerPsbtClone.inputsLength) - ).map(index => [ - index, + const inputByPaymentType = signingConfig.map(config => [ + config, getInputPaymentType( - index, - btcSignerPsbtClone.getInput(index), + config.index, + btcSignerPsbtClone.getInput(config.index), network.chain.bitcoin.bitcoinNetwork ), - ]) as readonly [number, PaymentTypes][]; + ]) as readonly [BitcoinInputSigningConfig, PaymentTypes][]; // // Taproot diff --git a/src/app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks.ts b/src/app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks.ts index aa4f5616ca6..32095620775 100644 --- a/src/app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks.ts +++ b/src/app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks.ts @@ -6,6 +6,7 @@ import { Psbt } from 'bitcoinjs-lib'; import { deriveAddressIndexZeroFromAccount, + extractAddressIndexFromPath, lookUpLedgerKeysByPath, } from '@shared/crypto/bitcoin/bitcoin.utils'; import { @@ -13,7 +14,8 @@ import { getNativeSegWitPaymentFromAddressIndex, getNativeSegwitAccountDerivationPath, } from '@shared/crypto/bitcoin/p2wpkh-address-gen'; -import { makeNumberRange, reverseBytes } from '@shared/utils'; +import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config'; +import { reverseBytes } from '@shared/utils'; import { mnemonicToRootNode } from '@app/common/keychain/keychain'; import { useBitcoinClient } from '@app/store/common/api-clients.hooks'; @@ -126,7 +128,7 @@ export function getNativeSegwitMainnetAddressFromMnemonic(secretKey: string) { export function useUpdateLedgerSpecificNativeSegwitUtxoHexForAdddressIndexZero() { const bitcoinClient = useBitcoinClient(); - return async (tx: Psbt, inputsToUpdate: number[] = []) => { + return async (tx: Psbt, inputSigningConfig: BitcoinInputSigningConfig[]) => { const inputsTxHex = await Promise.all( tx.txInputs.map(input => bitcoinClient.transactionsApi.getBitcoinTransactionHex( @@ -135,31 +137,32 @@ export function useUpdateLedgerSpecificNativeSegwitUtxoHexForAdddressIndexZero() ) ) ); - - const inputsToSign = - inputsToUpdate.length > 0 ? inputsToUpdate : makeNumberRange(tx.inputCount); - - inputsToSign.forEach(inputIndex => { - tx.updateInput(inputIndex, { - nonWitnessUtxo: Buffer.from(inputsTxHex[inputIndex], 'hex'), + inputSigningConfig.forEach(({ index }) => { + tx.updateInput(index, { + nonWitnessUtxo: Buffer.from(inputsTxHex[index], 'hex'), }); }); }; } + export function useUpdateLedgerSpecificNativeSegwitBip32DerivationForAdddressIndexZero() { - const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner(); + const createNativeSegwitSigner = useCurrentAccountNativeSegwitSigner(); + + return async (tx: Psbt, fingerprint: string, inputSigningConfig: BitcoinInputSigningConfig[]) => { + inputSigningConfig.forEach(({ index, derivationPath }) => { + const nativeSegwitSigner = createNativeSegwitSigner?.( + extractAddressIndexFromPath(derivationPath) + ); - return async (tx: Psbt, fingerprint: string, inputsToUpdate: number[] = []) => { - const inputsToSign = - inputsToUpdate.length > 0 ? inputsToUpdate : makeNumberRange(tx.inputCount); + if (!nativeSegwitSigner) + throw new Error(`Unable to update input for path ${derivationPath}}`); - inputsToSign.forEach(inputIndex => { - tx.updateInput(inputIndex, { + tx.updateInput(index, { bip32Derivation: [ { masterFingerprint: Buffer.from(fingerprint, 'hex'), pubkey: Buffer.from(nativeSegwitSigner.publicKey), - path: nativeSegwitSigner.derivationPath, + path: derivationPath, }, ], }); diff --git a/src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts b/src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts index 9a6aec363bf..6e61ad63583 100644 --- a/src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts +++ b/src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts @@ -7,6 +7,7 @@ import { Psbt } from 'bitcoinjs-lib'; import { BitcoinNetworkModes } from '@shared/constants'; import { ecdsaPublicKeyToSchnorr, + extractAddressIndexFromPath, lookUpLedgerKeysByPath, } from '@shared/crypto/bitcoin/bitcoin.utils'; import { @@ -14,7 +15,7 @@ import { getTaprootAccountDerivationPath, getTaprootPaymentFromAddressIndex, } from '@shared/crypto/bitcoin/p2tr-address-gen'; -import { makeNumberRange } from '@shared/utils'; +import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config'; import { selectCurrentNetwork, useCurrentNetwork } from '@app/store/networks/networks.selectors'; import { selectCurrentAccountIndex } from '@app/store/software-keys/software-key.selectors'; @@ -99,19 +100,27 @@ export function useCurrentAccountTaprootSigner() { } export function useUpdateLedgerSpecificTaprootInputPropsForAdddressIndexZero() { - const taprootSigner = useCurrentAccountTaprootIndexZeroSigner(); - - return async (tx: Psbt, fingerprint: string, inputsToUpdate: number[] = []) => { - const inputsToSign = - inputsToUpdate.length > 0 ? inputsToUpdate : makeNumberRange(tx.inputCount); - - inputsToSign.forEach(inputIndex => { - tx.updateInput(inputIndex, { + const createTaprootSigner = useCurrentAccountTaprootSigner(); + + return async ( + tx: Psbt, + fingerprint: string, + inputsToUpdate: BitcoinInputSigningConfig[] = [] + ) => { + inputsToUpdate.forEach(({ index, derivationPath }) => { + const taprootAddressIndexSigner = createTaprootSigner?.( + extractAddressIndexFromPath(derivationPath) + ); + + if (!taprootAddressIndexSigner) + throw new Error(`Unable to update taproot input for path ${derivationPath}}`); + + tx.updateInput(index, { tapBip32Derivation: [ { masterFingerprint: Buffer.from(fingerprint, 'hex'), - pubkey: Buffer.from(ecdsaPublicKeyToSchnorr(taprootSigner.publicKey)), - path: taprootSigner.derivationPath, + pubkey: Buffer.from(ecdsaPublicKeyToSchnorr(taprootAddressIndexSigner.publicKey)), + path: derivationPath, leafHashes: [], }, ], diff --git a/yarn.lock b/yarn.lock index 89d15fa3cee..056bc2f8d7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3335,10 +3335,10 @@ redux-thunk "^2.4.2" reselect "^4.1.8" -"@remix-run/router@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.9.0.tgz#9033238b41c4cbe1e961eccb3f79e2c588328cf6" - integrity sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA== +"@remix-run/router@1.13.1": + version "1.13.1" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.13.1.tgz#07e2a8006f23a3bc898b3f317e0a58cc8076b86e" + integrity sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q== "@rjsf/core@^4.2.0": version "4.2.3" @@ -15677,20 +15677,20 @@ react-remove-scroll@2.5.5: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" -react-router-dom@6.16.0: - version "6.16.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.16.0.tgz#86f24658da35eb66727e75ecbb1a029e33ee39d9" - integrity sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg== +react-router-dom@6.20.1: + version "6.20.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.20.1.tgz#e34f8075b9304221420de3609e072bb349824984" + integrity sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw== dependencies: - "@remix-run/router" "1.9.0" - react-router "6.16.0" + "@remix-run/router" "1.13.1" + react-router "6.20.1" -react-router@6.16.0: - version "6.16.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.16.0.tgz#abbf3d5bdc9c108c9b822a18be10ee004096fb81" - integrity sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA== +react-router@6.20.1: + version "6.20.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.20.1.tgz#e8cc326031d235aaeec405bb234af77cf0fe75ef" + integrity sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA== dependencies: - "@remix-run/router" "1.9.0" + "@remix-run/router" "1.13.1" react-select@^5.3.2: version "5.8.0"