Skip to content

Commit

Permalink
fix: ledger no request keys context
Browse files Browse the repository at this point in the history
  • Loading branch information
edgarkhanzadian committed Feb 6, 2024
1 parent 04e5d0e commit 5242a52
Show file tree
Hide file tree
Showing 15 changed files with 354 additions and 248 deletions.
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
import { useEffect, useState } from 'react';
import toast from 'react-hot-toast';
import { Outlet, Route, useLocation } from 'react-router-dom';
import { Route, useLocation } from 'react-router-dom';

import * as btc from '@scure/btc-signer';
import { hexToBytes } from '@stacks/common';
import BitcoinApp from 'ledger-bitcoin';
import get from 'lodash.get';

import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config';
import { logger } from '@shared/logger';
import { RouteUrls } from '@shared/route-urls';
import { isError } from '@shared/utils';

import { useLocationState, useLocationStateWithCache } from '@app/common/hooks/use-location-state';
import { useLocationStateWithCache } from '@app/common/hooks/use-location-state';
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 { ApproveSignLedgerBitcoinTx } from '@app/features/ledger/flows/bitcoin-tx-signing/steps/approve-bitcoin-sign-ledger-tx';
import { ledgerSignTxRoutes } from '@app/features/ledger/generic-flows/tx-signing/ledger-sign-tx-route-generator';
import { LedgerTxSigningContext } from '@app/features/ledger/generic-flows/tx-signing/ledger-sign-tx.context';
import { TxSigningFlow } from '@app/features/ledger/generic-flows/tx-signing/tx-signing-flow';
import { useLedgerSignTx } from '@app/features/ledger/generic-flows/tx-signing/use-ledger-sign-tx';
import { useLedgerAnalytics } from '@app/features/ledger/hooks/use-ledger-analytics.hook';
import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate';
import {
LedgerTxSigningContext,
LedgerTxSigningProvider,
} from '@app/features/ledger/generic-flows/tx-signing/ledger-sign-tx.context';
import { useActionCancellableByUser } from '@app/features/ledger/utils/stacks-ledger-utils';
connectLedgerBitcoinApp,
getBitcoinAppVersion,
isBitcoinAppOpen,
} from '@app/features/ledger/utils/bitcoin-ledger-utils';
import { useSignLedgerBitcoinTx } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';

import { ledgerSignTxRoutes } from '../../generic-flows/tx-signing/ledger-sign-tx-route-generator';
import { useLedgerAnalytics } from '../../hooks/use-ledger-analytics.hook';
import { useLedgerNavigate } from '../../hooks/use-ledger-navigate';
import { connectLedgerBitcoinApp, getBitcoinAppVersion } from '../../utils/bitcoin-ledger-utils';
import { checkLockedDeviceError, useLedgerResponseState } from '../../utils/generic-ledger-utils';
import { ApproveSignLedgerBitcoinTx } from './steps/approve-bitcoin-sign-ledger-tx';

export const ledgerBitcoinTxSigningRoutes = ledgerSignTxRoutes({
component: <LedgerSignBitcoinTxContainer />,
customRoutes: (
Expand All @@ -44,16 +43,13 @@ function LedgerSignBitcoinTxContainer() {
const ledgerAnalytics = useLedgerAnalytics();
useScrollLock(true);

const canUserCancelAction = useActionCancellableByUser();
const [unsignedTransactionRaw, setUnsignedTransactionRaw] = useState<null | string>(null);
const [unsignedTransaction, setUnsignedTransaction] = useState<null | btc.Transaction>(null);
const signLedger = useSignLedgerBitcoinTx();
const network = useCurrentNetwork();

const inputsToSign = useLocationStateWithCache<BitcoinInputSigningConfig[]>('inputsToSign');

const allowUserToGoBack = useLocationState<boolean>('goBack');

useEffect(() => {
const tx = get(location.state, 'tx');
if (tx) {
Expand All @@ -64,85 +60,62 @@ function LedgerSignBitcoinTxContainer() {

useEffect(() => () => setUnsignedTransaction(null), []);

const [latestDeviceResponse, setLatestDeviceResponse] = useLedgerResponseState();

const [awaitingDeviceConnection, setAwaitingDeviceConnection] = useState(false);

if (!inputsToSign) {
ledgerNavigate.cancelLedgerAction();
toast.error('No input signing config defined');
return null;
}

const signTransaction = async () => {
setAwaitingDeviceConnection(true);

try {
const bitcoinApp = await connectLedgerBitcoinApp(network.chain.bitcoin.bitcoinNetwork)();
const chain = 'bitcoin' as const;

try {
const versionInfo = await getBitcoinAppVersion(bitcoinApp);
ledgerAnalytics.trackDeviceVersionInfo(versionInfo);
setAwaitingDeviceConnection(false);
setLatestDeviceResponse(versionInfo as any);
} catch (e) {
setLatestDeviceResponse(e as any);
logger.error('Unable to get Ledger app version info', e);
}
const { signTransaction, latestDeviceResponse, awaitingDeviceConnection } =
useLedgerSignTx<BitcoinApp>({
chain,
isAppOpen: isBitcoinAppOpen({ network: network.chain.bitcoin.bitcoinNetwork }),
getAppVersion: getBitcoinAppVersion,
connectApp: connectLedgerBitcoinApp(network.chain.bitcoin.bitcoinNetwork),
async signTransactionWithDevice(bitcoinApp) {
if (!inputsToSign) {
ledgerNavigate.cancelLedgerAction();
toast.error('No input signing config defined');
return;
}

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

ledgerNavigate.toConnectionSuccessStep('bitcoin');
await delay(1200);
if (!unsignedTransaction) throw new Error('No unsigned tx');

ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: false });

try {
const btcTx = await signLedger(bitcoinApp, unsignedTransaction.toPSBT(), inputsToSign);

if (!btcTx || !unsignedTransactionRaw) throw new Error('No tx returned');
ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: true });
ledgerNavigate.toConnectionSuccessStep('bitcoin');
await delay(1200);
appEvents.publish('ledgerBitcoinTxSigned', {
signedPsbt: btcTx,
unsignedPsbt: unsignedTransactionRaw,
});
} catch (e) {
logger.error('Unable to sign tx with ledger', e);
ledgerAnalytics.transactionSignedOnLedgerRejected();
ledgerNavigate.toOperationRejectedStep();
} finally {
void bitcoinApp.transport.close();
}
} catch (e) {
if (isError(e) && checkLockedDeviceError(e)) {
setLatestDeviceResponse({ deviceLocked: true } as any);
return;
}
}
};
if (!unsignedTransaction) throw new Error('No unsigned tx');

ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: false });

try {
const btcTx = await signLedger(bitcoinApp, unsignedTransaction.toPSBT(), inputsToSign);

if (!btcTx || !unsignedTransactionRaw) throw new Error('No tx returned');
ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: true });
await delay(1200);
appEvents.publish('ledgerBitcoinTxSigned', {
signedPsbt: btcTx,
unsignedPsbt: unsignedTransactionRaw,
});
} catch (e) {
logger.error('Unable to sign tx with ledger', e);
ledgerAnalytics.transactionSignedOnLedgerRejected();
ledgerNavigate.toOperationRejectedStep();
} finally {
void bitcoinApp.transport.close();
}
},
});

const ledgerContextValue: LedgerTxSigningContext = {
chain: 'bitcoin',
chain,
transaction: unsignedTransaction,
signTransaction,
latestDeviceResponse,
awaitingDeviceConnection,
};

return (
<LedgerTxSigningProvider value={ledgerContextValue}>
<BaseDrawer
enableGoBack={allowUserToGoBack}
isShowing
isWaitingOnPerformedAction={awaitingDeviceConnection || canUserCancelAction}
onClose={ledgerNavigate.cancelLedgerAction}
pauseOnClickOutside
waitingOnPerformedActionMessage="Ledger device in use"
>
<Outlet />
</BaseDrawer>
</LedgerTxSigningProvider>
<TxSigningFlow
context={ledgerContextValue}
awaitingDeviceConnection={awaitingDeviceConnection}
closeAction={ledgerNavigate.cancelLedgerAction}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export function LedgerSignJwtContainer() {
const [jwtPayloadHash, setJwtPayloadHash] = useState<null | string>(null);
const { origin, tabId } = useDefaultRequestParams();

const chain = 'stacks';

const signJwtPayload = async () => {
if (!origin) throw new Error('Cannot sign payload for unknown origin');

Expand Down Expand Up @@ -97,7 +99,7 @@ export function LedgerSignJwtContainer() {
setLatestDeviceResponse({ deviceLocked: true } as any);
return;
}
ledgerNavigate.toErrorStep();
ledgerNavigate.toErrorStep(chain);
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';

import BitcoinApp from 'ledger-bitcoin';

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

import { pullBitcoinKeysFromLedgerDevice } from '@app/features/ledger/flows/request-bitcoin-keys/request-bitcoin-keys.utils';
import { ledgerRequestKeysRoutes } from '@app/features/ledger/generic-flows/request-keys/ledger-request-keys-route-generator';
import { LedgerRequestKeysContext } from '@app/features/ledger/generic-flows/request-keys/ledger-request-keys.context';
import { RequestKeysFlow } from '@app/features/ledger/generic-flows/request-keys/request-keys-flow';
import { useRequestLedgerKeys } from '@app/features/ledger/generic-flows/request-keys/use-request-ledger-keys';
import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate';
import {
connectLedgerBitcoinApp,
getBitcoinAppVersion,
isBitcoinAppOpen,
} from '@app/features/ledger/utils/bitcoin-ledger-utils';
import { useActionCancellableByUser } from '@app/features/ledger/utils/stacks-ledger-utils';
import { bitcoinKeysSlice } from '@app/store/ledger/bitcoin/bitcoin-key.slice';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';

import { ledgerRequestKeysRoutes } from '../../generic-flows/request-keys/ledger-request-keys-route-generator';
import { RequestKeysFlow } from '../../generic-flows/request-keys/request-keys-flow';
import { useRequestLedgerKeys } from '../../generic-flows/request-keys/use-request-ledger-keys';
import { connectLedgerBitcoinApp, getBitcoinAppVersion } from '../../utils/bitcoin-ledger-utils';
import { pullBitcoinKeysFromLedgerDevice } from './request-bitcoin-keys.utils';

function LedgerRequestBitcoinKeys() {
const navigate = useNavigate();
const dispatch = useDispatch();
Expand All @@ -23,32 +28,33 @@ function LedgerRequestBitcoinKeys() {
const ledgerNavigate = useLedgerNavigate();
const network = useCurrentNetwork();

const { requestKeys, latestDeviceResponse, awaitingDeviceConnection } = useRequestLedgerKeys({
chain: 'bitcoin',
connectApp: connectLedgerBitcoinApp(network.chain.bitcoin.bitcoinNetwork),
getAppVersion: getBitcoinAppVersion,
isAppOpen({ name }: { name: string }) {
return name === 'Bitcoin' || name === 'Bitcoin Test';
},
onSuccess() {
navigate('/', { replace: true });
},
async pullKeysFromDevice(app) {
const { keys } = await pullBitcoinKeysFromLedgerDevice(app)({
network: bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.bitcoinNetwork),
onRequestKey(index) {
if (index <= 4) {
ledgerNavigate.toDeviceBusyStep(
`Requesting Bitcoin Native Segwit address (${index + 1}…5)`
);
return;
}
ledgerNavigate.toDeviceBusyStep(`Requesting Bitcoin Taproot address (${index - 4}…5)`);
},
});
dispatch(bitcoinKeysSlice.actions.addKeys(keys));
},
});
const chain = 'bitcoin' as const;

const { requestKeys, latestDeviceResponse, awaitingDeviceConnection } =
useRequestLedgerKeys<BitcoinApp>({
chain,
connectApp: connectLedgerBitcoinApp(network.chain.bitcoin.bitcoinNetwork),
getAppVersion: getBitcoinAppVersion,
isAppOpen: isBitcoinAppOpen({ network: network.chain.bitcoin.bitcoinNetwork }),
onSuccess() {
navigate('/', { replace: true });
},
async pullKeysFromDevice(app) {
const { keys } = await pullBitcoinKeysFromLedgerDevice(app)({
network: bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.bitcoinNetwork),
onRequestKey(index) {
if (index <= 4) {
ledgerNavigate.toDeviceBusyStep(
`Requesting Bitcoin Native Segwit address (${index + 1}…5)`
);
return;
}
ledgerNavigate.toDeviceBusyStep(`Requesting Bitcoin Taproot address (${index - 4}…5)`);
},
});
dispatch(bitcoinKeysSlice.actions.addKeys(keys));
},
});

const ledgerContextValue: LedgerRequestKeysContext = {
chain: 'bitcoin',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,39 @@ import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';

import StacksApp from '@zondax/ledger-stacks';
import { pullStacksKeysFromLedgerDevice } from 'app/features/ledger/flows/request-stacks-keys/request-stacks-keys.utils';

import { defaultWalletKeyId } from '@shared/utils';

import { ledgerRequestKeysRoutes } from '@app/features/ledger/generic-flows/request-keys/ledger-request-keys-route-generator';
import { LedgerRequestKeysContext } from '@app/features/ledger/generic-flows/request-keys/ledger-request-keys.context';
import { RequestKeysFlow } from '@app/features/ledger/generic-flows/request-keys/request-keys-flow';
import { useRequestLedgerKeys } from '@app/features/ledger/generic-flows/request-keys/use-request-ledger-keys';
import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate';
import {
connectLedgerStacksApp,
getStacksAppVersion,
isStacksLedgerAppClosed,
isStacksAppOpen,
useActionCancellableByUser,
} from '@app/features/ledger/utils/stacks-ledger-utils';
import { stacksKeysSlice } from '@app/store/ledger/stacks/stacks-key.slice';

import { ledgerRequestKeysRoutes } from '../../generic-flows/request-keys/ledger-request-keys-route-generator';
import { RequestKeysFlow } from '../../generic-flows/request-keys/request-keys-flow';
import { useRequestLedgerKeys } from '../../generic-flows/request-keys/use-request-ledger-keys';
import { pullStacksKeysFromLedgerDevice } from './request-stacks-keys.utils';

function LedgerRequestStacksKeys() {
const navigate = useNavigate();
const ledgerNavigate = useLedgerNavigate();
const canUserCancelAction = useActionCancellableByUser();

const dispatch = useDispatch();

const chain = 'stacks';

const { requestKeys, latestDeviceResponse, awaitingDeviceConnection, outdatedAppVersionWarning } =
useRequestLedgerKeys({
chain: 'stacks',
useRequestLedgerKeys<StacksApp>({
chain,
connectApp: connectLedgerStacksApp,
getAppVersion: getStacksAppVersion,
isAppOpen(resp) {
return !isStacksLedgerAppClosed(resp);
},
isAppOpen: isStacksAppOpen,
onSuccess() {
navigate('/', { replace: true });
},
Expand All @@ -45,7 +46,7 @@ function LedgerRequestStacksKeys() {
});
if (resp.status === 'failure') {
toast.error(resp.errorMessage);
ledgerNavigate.toErrorStep(resp.errorMessage);
ledgerNavigate.toErrorStep(chain, resp.errorMessage);
return;
}
ledgerNavigate.toDeviceBusyStep();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ function LedgerSignStacksMsg({ account, unsignedMessage }: LedgerSignMsgProps) {

const [awaitingDeviceConnection, setAwaitingDeviceConnection] = useState(false);

const chain = 'stacks';

async function signMessage() {
const stacksApp = await prepareLedgerDeviceStacksAppConnection({
setLoadingState: setAwaitingDeviceConnection,
Expand All @@ -72,7 +74,7 @@ function LedgerSignStacksMsg({ account, unsignedMessage }: LedgerSignMsgProps) {
setLatestDeviceResponse({ deviceLocked: true } as any);
return;
}
ledgerNavigate.toErrorStep();
ledgerNavigate.toErrorStep(chain);
},
});

Expand Down
Loading

0 comments on commit 5242a52

Please sign in to comment.