Skip to content

Commit

Permalink
chore: friday signing progress
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Nov 6, 2023
1 parent 34df5be commit d45194b
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 75 deletions.
15 changes: 12 additions & 3 deletions src/app/common/publish-subscribe.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { Transaction } from '@scure/btc-signer';
import type { StacksTransaction } from '@stacks/transactions';
import type { Psbt } from 'bitcoinjs-lib';

type PubTypeFn<E> = <Key extends string & keyof E>(
event: Key,
Expand All @@ -19,7 +21,7 @@ interface PubSubType<E> {
subscribe: SubTypeFn<E>;
unsubscribe: SubTypeFn<E>;
}
function PublishSubscribe<E>(): PubSubType<E> {
function createPublishSubscribe<E>(): PubSubType<E> {
const handlers: { [key: string]: MessageFn<any>[] } = {};

return {
Expand All @@ -42,7 +44,7 @@ function PublishSubscribe<E>(): PubSubType<E> {
}

// Global app events. Only add events if your feature isn't capable of
//communicating internally.
// communicating internally.
export interface GlobalAppEvents {
ledgerStacksTxSigned: {
unsignedTx: string;
Expand All @@ -51,6 +53,13 @@ export interface GlobalAppEvents {
ledgerStacksTxSigningCancelled: {
unsignedTx: string;
};
ledgerBitcoinTxSigned: {
unsignedPsbt: string;
signedPsbt: Transaction;
};
ledgerBitcoinTxSigningCancelled: {
unsignedPsbt: string;
};
}

export const appEvents = PublishSubscribe<GlobalAppEvents>();
export const appEvents = createPublishSubscribe<GlobalAppEvents>();
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function IncreaseStxFeeForm() {
await replaceByFee(rawTx);
},
ledger: () => {
ledgerNavigate.toConnectAndSignTransactionStep(rawTx);
ledgerNavigate.toConnectAndSignStacksTransactionStep(rawTx);
},
})();
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as btc from '@scure/btc-signer';

import { GlobalAppEvents, appEvents } from '@app/common/publish-subscribe';

export async function listenForBitcoinTxLedgerSigning(psbt: string): Promise<btc.Transaction> {
return new Promise((resolve, reject) => {
function txSignedHandler(msg: GlobalAppEvents['ledgerBitcoinTxSigned']) {
if (msg.unsignedPsbt === psbt) {
appEvents.unsubscribe('ledgerBitcoinTxSigned', txSignedHandler);
appEvents.unsubscribe('ledgerBitcoinTxSigningCancelled', signingAbortedHandler);
resolve(msg.signedPsbt);
}
}
appEvents.subscribe('ledgerBitcoinTxSigned', txSignedHandler);

function signingAbortedHandler(msg: GlobalAppEvents['ledgerBitcoinTxSigningCancelled']) {
if (msg.unsignedPsbt === psbt) {
appEvents.unsubscribe('ledgerBitcoinTxSigningCancelled', signingAbortedHandler);
appEvents.unsubscribe('ledgerBitcoinTxSigned', txSignedHandler);
reject(new Error('User cancelled the signing operation'));
}
}
appEvents.subscribe('ledgerBitcoinTxSigningCancelled', signingAbortedHandler);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { useEffect, useState } from 'react';
import toast from 'react-hot-toast';
import { Outlet, Route, useLocation, useNavigate } from 'react-router-dom';

import { bytesToHex } from '@noble/hashes/utils';
import * as btc from '@scure/btc-signer';
import { hexToBytes } from '@stacks/common';
import { Psbt } from 'bitcoinjs-lib';
import get from 'lodash.get';

import { RouteUrls } from '@shared/route-urls';

import { useScrollLock } from '@app/common/hooks/use-scroll-lock';
import { appEvents } from '@app/common/publish-subscribe';
import { delay } from '@app/common/utils';
import { BaseDrawer } from '@app/components/drawer/base-drawer';
import {
Expand Down Expand Up @@ -62,16 +65,19 @@ export function LedgerSignBitcoinTxContainer() {
useScrollLock(true);

const navigate = useNavigate();
const { broadcastTx } = useBitcoinBroadcastTransaction();
const canUserCancelAction = useActionCancellableByUser();
const [unsignedTransactionRaw, setUnsignedTransactionRaw] = useState<null | string>(null);
const [unsignedTransaction, setUnsignedTransaction] = useState<null | btc.Transaction>(null);
const signLedger = useSignLedgerTx();
const sendFormNavigate = useSendFormNavigate();

useEffect(() => {
const tx = get(location.state, 'tx');
if (tx) console.log({ tx, decoded: btc.Transaction.fromPSBT(hexToBytes(tx)) });
if (tx) setUnsignedTransaction(btc.Transaction.fromPSBT(hexToBytes(tx)));
if (tx) {
setUnsignedTransactionRaw(tx);
console.log({ tx, decoded: btc.Transaction.fromPSBT(hexToBytes(tx)) });
setUnsignedTransaction(btc.Transaction.fromPSBT(hexToBytes(tx)));
}
}, [location.state]);

useEffect(() => () => setUnsignedTransaction(null), []);
Expand All @@ -89,41 +95,24 @@ export function LedgerSignBitcoinTxContainer() {

ledgerNavigate.toDeviceBusyStep('Verifying public key on Ledger…');

// try {
ledgerNavigate.toConnectionSuccessStep('bitcoin');
await delay(1000);
if (!unsignedTransaction) throw new Error('No unsigned tx');

ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: false });

try {
const resp = await signLedger(bitcoinApp, unsignedTransaction.toPSBT());
if (!resp) throw new Error('No tx returned');
console.log(resp);
const btcTx = await signLedger(bitcoinApp, unsignedTransaction.toPSBT());
if (!btcTx || !unsignedTransactionRaw) throw new Error('No tx returned');
console.log('response from ledger', { resp: btcTx });
ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: true });
await delay(1000);

await broadcastTx({
tx: resp.hex,
onSuccess(txid) {
console.log(txid);
toast.success('Tx broadcast');
navigate('/activity', { replace: true });
},
onError(e) {
console.log(e);
},
appEvents.publish('ledgerBitcoinTxSigned', {
signedPsbt: btcTx,
unsignedPsbt: unsignedTransactionRaw,
});
navigate('/activity', { replace: true });
// sendFormNavigate.toConfirmAndSignBtcTransaction({
// fee: 1000,
// feeRowValue: '1231',
// recipient: 'anslkdjfs',
// time: 'slkdjfslkdf',
// tx: resp?.toHex(),
// });
} catch (e) {
console.log('error', e);
ledgerAnalytics.transactionSignedOnLedgerRejected();
ledgerNavigate.toOperationRejectedStep();
}
Expand Down
11 changes: 10 additions & 1 deletion src/app/features/ledger/hooks/use-ledger-navigate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom';

import { bytesToHex } from '@stacks/common';
import { ClarityValue, StacksTransaction } from '@stacks/transactions';
import { Psbt } from 'bitcoinjs-lib';

import { SupportedBlockchains } from '@shared/constants';
import { RouteUrls } from '@shared/route-urls';
Expand All @@ -22,14 +23,22 @@ export function useLedgerNavigate() {
});
},

toConnectAndSignTransactionStep(transaction: StacksTransaction) {
toConnectAndSignStacksTransactionStep(transaction: StacksTransaction) {
return navigate(RouteUrls.ConnectLedger, {
replace: true,
relative: 'path',
state: { tx: bytesToHex(transaction.serialize()) },
});
},

toConnectAndSignBitcoinTransactionStep(psbt: Psbt) {
return navigate(RouteUrls.ConnectLedger, {
replace: true,
relative: 'path',
state: { tx: psbt.toHex() },
});
},

toConnectAndSignUtf8MessageStep(message: string) {
return navigate(RouteUrls.ConnectLedger, {
replace: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useLocation, useNavigate } from 'react-router-dom';

import { hexToBytes } from '@noble/hashes/utils';
import * as btc from '@scure/btc-signer';
import { Stack } from '@stacks/ui';
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import get from 'lodash.get';
Expand Down Expand Up @@ -47,16 +49,22 @@ function useBtcSendFormConfirmationState() {
export function BtcSendFormConfirmation() {
const navigate = useNavigate();
const { tx, recipient, fee, arrivesIn, feeRowValue } = useBtcSendFormConfirmationState();
console.log({ tx });
const { refetch } = useCurrentNativeSegwitUtxos();
const analytics = useAnalytics();

const btcMarketData = useCryptoCurrencyMarketData('BTC');
const { broadcastTx, isBroadcasting } = useBitcoinBroadcastTransaction();
const psbt = decodeBitcoinTx(tx);

const transaction = btc.Transaction.fromRaw(hexToBytes(tx));

// const psbt = Psbt.fromHex(transaction.toPSBT());

const decodedTx = decodeBitcoinTx(transaction.hex);

const nav = useSendFormNavigate();

const transferAmount = satToBtc(psbt.outputs[0].amount.toString()).toString();
const transferAmount = satToBtc(decodedTx.outputs[0].amount.toString()).toString();
const txFiatValue = i18nFormatCurrency(
baseCurrencyAmountInQuote(createMoneyFromDecimal(Number(transferAmount), symbol), btcMarketData)
);
Expand All @@ -71,24 +79,25 @@ export function BtcSendFormConfirmation() {

async function initiateTransaction() {
await broadcastTx({
tx,
tx: transaction.hex,
async onSuccess(txid) {
void analytics.track('broadcast_transaction', {
symbol: 'btc',
amount: transferAmount,
fee,
inputs: psbt.inputs.length,
outputs: psbt.inputs.length,
inputs: decodedTx.inputs.length,
outputs: decodedTx.inputs.length,
});
await refetch();
navigate(RouteUrls.SentBtcTxSummary.replace(':txId', `${txid}`), {
state: formBtcTxSummaryState(txid),
});

// invalidate txs query after some time to ensure that the new tx will be shown in the list
setTimeout(() => {
void queryClient.invalidateQueries({ queryKey: ['btc-txs-by-address'] });
}, 2000);
setTimeout(
() => void queryClient.invalidateQueries({ queryKey: ['btc-txs-by-address'] }),
2000
);
},
onError(e) {
nav.toErrorPage(e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { useNavigate } from 'react-router-dom';

import { bytesToHex } from '@noble/hashes/utils';
import { Psbt } from 'bitcoinjs-lib';

import { logger } from '@shared/logger';
import { BtcFeeType } from '@shared/models/fees/bitcoin-fees.model';
import { createMoney } from '@shared/models/money.model';
import { RouteUrls } from '@shared/route-urls';

import { btcToSat } from '@app/common/money/unit-conversion';
import { formFeeRowValue } from '@app/common/send/utils';
import { useGenerateSignedNativeSegwitTx } from '@app/common/transactions/bitcoin/use-generate-bitcoin-tx';
import { useWalletType } from '@app/common/use-wallet-type';
import { OnChooseFeeArgs } from '@app/components/bitcoin-fees-list/bitcoin-fees-list';
import { useSignBitcoinTx } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks';

import { useSendBitcoinAssetContextState } from '../../family/bitcoin/components/send-bitcoin-asset-container';
import { useCalculateMaxBitcoinSpend } from '../../family/bitcoin/hooks/use-calculate-max-spend';
Expand All @@ -21,13 +20,12 @@ import { useBtcChooseFeeState } from './btc-choose-fee';
export function useBtcChooseFee() {
const { isSendingMax, txValues, utxos } = useBtcChooseFeeState();
const navigate = useNavigate();
const { whenWallet } = useWalletType();
const sendFormNavigate = useSendFormNavigate();
const generateTx = useGenerateSignedNativeSegwitTx();
const { setSelectedFeeType } = useSendBitcoinAssetContextState();
const calcMaxSpend = useCalculateMaxBitcoinSpend();
// const signLedger = useSignNativeSegwitLedgerTx();

const signTx = useSignBitcoinTx();
const amountAsMoney = createMoney(btcToSat(txValues.amount).toNumber(), 'BTC');

return {
Expand All @@ -52,31 +50,19 @@ export function useBtcChooseFee() {
const feeRowValue = formFeeRowValue(feeRate, isCustomFee);
if (!resp) return logger.error('Attempted to generate raw tx, but no tx exists');

void whenWallet({
software: async () => {
sendFormNavigate.toConfirmAndSignBtcTransaction({
tx: resp.hex,
recipient: txValues.recipient,
fee: feeValue,
feeRowValue,
time,
});
},
ledger: async () => {
console.log('opening route with', resp.hex);
// const app = await connectLedgerBitcoinApp();
navigate(RouteUrls.ConnectLedger, {
replace: true,
state: {
tx: bytesToHex(resp.psbt),
recipient: txValues.recipient,
fee: feeValue,
feeRowValue,
time,
},
});
},
})();
const signedTx = await signTx(Psbt.fromBuffer(Buffer.from(resp.psbt)));

if (!signedTx) return logger.error('Attempted to sign tx, but no tx exists');

signedTx.finalize();

sendFormNavigate.toConfirmAndSignBtcTransaction({
tx: signedTx.hex,
recipient: txValues.recipient,
fee: feeValue,
feeRowValue,
time,
});
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export function useSip10SendForm({ symbol, contractId }: UseSip10SendFormArgs) {
name: assetBalance.asset.name,
tx,
}),
ledger: () => ledgerNavigate.toConnectAndSignTransactionStep(tx),
ledger: () => ledgerNavigate.toConnectAndSignStacksTransactionStep(tx),
})();
},
};
Expand Down
21 changes: 21 additions & 0 deletions src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Psbt } from 'bitcoinjs-lib';

import { getTaprootAddress } from '@shared/crypto/bitcoin/bitcoin.utils';

import { useWalletType } from '@app/common/use-wallet-type';
import { listenForBitcoinTxLedgerSigning } from '@app/features/ledger/flows/bitcoin-tx-signing/bitcoin-tx-signing-event-listeners';
import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate';
import { useCurrentAccountIndex } from '@app/store/accounts/account';
import { useTaprootAccount } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
Expand Down Expand Up @@ -30,3 +35,19 @@ export function useZeroIndexTaprootAddress(accIndex?: number) {

return address;
}

export function useSignBitcoinTx() {
const { whenWallet } = useWalletType();
const ledgerNavigate = useLedgerNavigate();

return (tx: Psbt, inputsToSign?: [number, string]) =>
whenWallet({
async ledger(tx: Psbt) {
ledgerNavigate.toConnectAndSignBitcoinTransactionStep(tx);
return listenForBitcoinTxLedgerSigning(tx.toHex());
},
async software(tx: Psbt) {
// return signSoftwareTx(tx);
},
})(tx);
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,7 @@ export function useSignLedgerTx() {
}

// Turn back into BtcSigner lib format
const btcSignerPsbt = btc.Transaction.fromPSBT(psbt.toBuffer());

btcSignerPsbt.finalize();
return btcSignerPsbt;
// REMINDER: this tx is not finalized
return btc.Transaction.fromPSBT(psbt.toBuffer());
};
}
Loading

0 comments on commit d45194b

Please sign in to comment.