Skip to content

Commit

Permalink
refactor: initial signing work for ledger
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Nov 9, 2023
1 parent 831989d commit 3215076
Show file tree
Hide file tree
Showing 89 changed files with 1,185 additions and 583 deletions.
3 changes: 1 addition & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"prettier.documentSelectors": ["src/**/*.{ts,tsx}", "*.{js,json}"],
"vitest.include": ["src/**/*.spec.ts"],
"githubPullRequests.ignoredPullRequestBranches": ["dev"],
"editor.folding": false
"githubPullRequests.ignoredPullRequestBranches": ["dev"]
}
2 changes: 1 addition & 1 deletion src/app/common/authentication/use-finish-auth-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function useFinishAuthRequest() {
p2tr: bytesToHex(taprootAccount.mainnet.keychain.publicKey!),
p2wpkh: bytesToHex(nativeSegwitAccount.mainnet.keychain.publicKey!),
},
walletProvider: 'hiro-wallet',
walletProvider: 'leather',
},
});
keyActions.switchAccount(accountIndex);
Expand Down
6 changes: 3 additions & 3 deletions src/app/common/hooks/use-explorer-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import { openInNewTab } from '../utils/open-in-new-tab';
export interface HandleOpenTxLinkArgs {
blockchain: Blockchains;
suffix?: string;
txId: string;
txid: string;
}
export function useExplorerLink() {
const { mode } = useCurrentNetworkState();
const handleOpenTxLink = useCallback(
({ blockchain, suffix, txId }: HandleOpenTxLinkArgs) =>
openInNewTab(makeTxExplorerLink({ blockchain, mode, suffix, txId })),
({ blockchain, suffix, txid }: HandleOpenTxLinkArgs) =>
openInNewTab(makeTxExplorerLink({ blockchain, mode, suffix, txid })),
[mode]
);

Expand Down
1 change: 1 addition & 0 deletions src/app/common/hooks/use-submit-stx-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function useSubmitTransactionCallback({ loadingKey }: UseSubmitTransactio
txId: safelyFormatHexTxid(response.txid),
});
toast.success('Transaction submitted!');

void analytics.track('broadcast_transaction', { symbol: 'stx' });
onSuccess(safelyFormatHexTxid(response.txid));
setIsIdle();
Expand Down
27 changes: 11 additions & 16 deletions src/app/common/psbt/use-psbt-request-params.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';

import { ensureArray, undefinedIfLengthZero } from '@shared/utils';

import { useRejectIfLedgerWallet } from '@app/common/rpc-helpers';

import { useDefaultRequestParams } from '../hooks/use-default-request-search-params';
import { initialSearchParams } from '../initial-search-params';
import { getPsbtPayloadFromToken } from './requests';

export function usePsbtRequestSearchParams() {
const [searchParams] = useSearchParams();
const { origin, tabId } = useDefaultRequestParams();
const requestToken = searchParams.get('request');
const requestToken = initialSearchParams.get('request');

if (!requestToken) throw new Error('Cannot decode psbt without request token');

Expand All @@ -33,23 +30,21 @@ export function usePsbtRequestSearchParams() {
}

export function useRpcSignPsbtParams() {
useRejectIfLedgerWallet('signPsbt');

const [searchParams] = useSearchParams();
const { origin, tabId } = useDefaultRequestParams();
const broadcast = searchParams.get('broadcast');
const psbtHex = searchParams.get('hex');
const requestId = searchParams.get('requestId');
const signAtIndex = searchParams.getAll('signAtIndex');
const broadcast = initialSearchParams.get('broadcast');
const psbtHex = initialSearchParams.get('hex');
const requestId = initialSearchParams.get('requestId');
const signAtIndex = initialSearchParams.getAll('signAtIndex');

return useMemo(() => {
return {
return useMemo(
() => ({
broadcast: broadcast === 'true',
origin,
psbtHex,
requestId,
signAtIndex: undefinedIfLengthZero(ensureArray(signAtIndex).map(h => Number(h))),
tabId: tabId ?? 0,
};
}, [broadcast, origin, psbtHex, requestId, signAtIndex, tabId]);
}),
[broadcast, origin, psbtHex, requestId, signAtIndex, tabId]
);
}
14 changes: 11 additions & 3 deletions src/app/common/publish-subscribe.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Transaction } from '@scure/btc-signer';
import type { StacksTransaction } from '@stacks/transactions';

type PubTypeFn<E> = <Key extends string & keyof E>(
Expand All @@ -19,7 +20,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 +43,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 +52,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>();
22 changes: 11 additions & 11 deletions src/app/common/transactions/bitcoin/use-generate-bitcoin-tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
import { useBitcoinScureLibNetworkConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin-keychain';
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';

interface GenerateNativeSegwitTxValues {
interface GenerateNativeSegwitSingleRecipientTxValues {
amount: Money;
recipient: string;
}
export function useGenerateSignedNativeSegwitTx() {
export function useGenerateUnsignedNativeSegwitSingleRecipientTx() {
const signer = useCurrentAccountNativeSegwitIndexZeroSigner();

const networkMode = useBitcoinScureLibNetworkConfig();

return useCallback(
(
values: GenerateNativeSegwitTxValues,
async (
values: GenerateNativeSegwitSingleRecipientTxValues,
feeRate: number,
utxos: UtxoResponseItem[],
isSendingMax?: boolean
Expand Down Expand Up @@ -56,8 +56,9 @@ export function useGenerateSignedNativeSegwitTx() {
if (outputs.length > 2)
throw new Error('Address reuse mode: wallet should have max 2 outputs');

inputs.forEach(input => {
const p2wpkh = btc.p2wpkh(signer.publicKey, networkMode);
const p2wpkh = btc.p2wpkh(signer.publicKey, networkMode);

for (const input of inputs) {
tx.addInput({
txid: input.txid,
index: input.vout,
Expand All @@ -68,7 +69,8 @@ export function useGenerateSignedNativeSegwitTx() {
amount: BigInt(input.value),
},
});
});
}

outputs.forEach(output => {
// When coin selection returns output with no address we assume it is
// a change output
Expand All @@ -78,16 +80,14 @@ export function useGenerateSignedNativeSegwitTx() {
}
tx.addOutputAddress(values.recipient, BigInt(output.value), networkMode);
});
signer.sign(tx);
tx.finalize();

return { hex: tx.hex, fee };
return { hex: tx.hex, fee, psbt: tx.toPSBT(), inputs };
} catch (e) {
// eslint-disable-next-line no-console
console.log('Error signing bitcoin transaction', e);
return null;
}
},
[networkMode, signer]
[networkMode, signer.address, signer.publicKey]
);
}
8 changes: 4 additions & 4 deletions src/app/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,19 @@ interface MakeTxExplorerLinkArgs {
blockchain: Blockchains;
mode: BitcoinNetworkModes;
suffix?: string;
txId: string;
txid: string;
}
export function makeTxExplorerLink({
blockchain,
mode,
suffix = '',
txId,
txid,
}: MakeTxExplorerLinkArgs) {
switch (blockchain) {
case 'bitcoin':
return `https://mempool.space/${mode !== 'mainnet' ? mode + '/' : ''}tx/${txId}`;
return `https://mempool.space/${mode !== 'mainnet' ? mode + '/' : ''}tx/${txid}`;
case 'stacks':
return `https://explorer.hiro.so/txid/${txId}?chain=${mode}${suffix}`;
return `https://explorer.hiro.so/txid/${txid}?chain=${mode}${suffix}`;
default:
return '';
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/account/account-list-item-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useViewportMinWidth } from '@app/common/hooks/use-media-query';
import { CaptionDotSeparator } from '../caption-dot-separator';
import { CheckmarkIcon } from '../icons/checkmark-icon';
import { Flag } from '../layout/flag';
import { StacksAccountLoader } from '../stacks-account-loader';
import { StacksAccountLoader } from '../loaders/stacks-account-loader';
import { BitcoinNativeSegwitAccountLoader } from './bitcoin-account-loader';

interface AccountListItemLayoutProps extends StackProps {
Expand Down
12 changes: 0 additions & 12 deletions src/app/components/account/account-name.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
import { memo } from 'react';

import { BoxProps } from '@stacks/ui';
import { styled } from 'leather-styles/jsx';

import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names';
import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models';

interface AccountNameLayoutProps {
children: React.ReactNode;
}
export const AccountNameLayout = memo(({ children }: AccountNameLayoutProps) => (
<styled.p textStyle="label.01">{children}</styled.p>
));

interface AccountNameProps extends BoxProps {
account: StacksAccount;
}
export const AccountName = memo(({ account }: AccountNameProps) => {
const name = useAccountDisplayName(account);
return <AccountNameLayout>{name}</AccountNameLayout>;
});
32 changes: 32 additions & 0 deletions src/app/components/animated-tick-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { styled } from 'leather-styles/jsx';

const defaultSize = '20px';

interface AnimatedTickProps {
size?: string;
}
export function AnimatedTick({ size = defaultSize }: AnimatedTickProps) {
return (
<styled.svg
preserveAspectRatio="xMidYMid meet"
width={size}
height={size}
viewBox="0 0 400 400"
>
<styled.polyline
fill="none"
stroke="currentColor"
strokeWidth="24"
points="88,214 173,284 304,138"
strokeDasharray="350"
strokeDashoffset="350"
transformOrigin="center"
transform="translateY(-10px)"
scale={1.5}
animation="animatedTick 750ms ease-out"
animationDelay="200ms"
animationFillMode="forwards"
/>
</styled.svg>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,7 @@ export function BitcoinTransactionItem({ transaction, ...rest }: BitcoinTransact
openInNewTab(createInscriptionInfoUrl(inscriptionData.id));
return;
}
handleOpenTxLink({
blockchain: 'bitcoin',
txId: transaction?.txid || '',
});
handleOpenTxLink({ blockchain: 'bitcoin', txid: transaction?.txid || '' });
};

const isOriginator = !isBitcoinTxInbound(bitcoinAddress, transaction);
Expand Down
14 changes: 14 additions & 0 deletions src/app/components/loaders/bitcoin-account-loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BitcoinAccount } from '@shared/crypto/bitcoin/bitcoin.utils';

import { useCurrentNativeSegwitAccount } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useCurrentTaprootAccount } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';

interface CurrentBitcoinAccountLoaderProps {
children: (data: { nativeSegwit: BitcoinAccount; taproot: BitcoinAccount }) => React.ReactNode;
}
export function CurrentBitcoinAccountLoader({ children }: CurrentBitcoinAccountLoaderProps) {
const nativeSegwit = useCurrentNativeSegwitAccount();
const taproot = useCurrentTaprootAccount();
if (!taproot || !nativeSegwit) return null;
return children({ nativeSegwit, taproot });
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-acco
interface CurrentStacksAccountLoaderProps {
children(data: StacksAccount): React.ReactNode;
}

export function CurrentStacksAccountLoader({ children }: CurrentStacksAccountLoaderProps) {
const currentAccount = useCurrentStacksAccount();
if (!currentAccount) return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function StacksTransactionItem({
void analytics.track('view_transaction');
handleOpenTxLink({
blockchain: 'stacks',
txId: transaction?.tx_id || transferDetails?.link || '',
txid: transaction?.tx_id || transferDetails?.link || '',
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function SubmittedTransactionItem(props: SubmittedTransactionItemProps) {
handleOpenTxLink({
blockchain: 'stacks',
suffix: `&submitted=true`,
txId,
txid: txId,
})
}
position="relative"
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/asset-list/asset-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { BitcoinContractEntryPoint } from '@app/components/bitcoin-contract-entr
import { Brc20TokensLoader } from '@app/components/brc20-tokens-loader';
import { CryptoCurrencyAssetItem } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item';
import { BtcIcon } from '@app/components/icons/btc-icon';
import { CurrentStacksAccountLoader } from '@app/components/stacks-account-loader';
import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader';
import { useHasBitcoinLedgerKeychain } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger';
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
Expand Down
Loading

0 comments on commit 3215076

Please sign in to comment.