Skip to content

Commit 0073207

Browse files
committed
feat: return btc address during auth, closes #2909, #3092
1 parent 5942a54 commit 0073207

File tree

10 files changed

+202
-50
lines changed

10 files changed

+202
-50
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@
153153
"@stacks/ui-core": "7.3.0",
154154
"@stacks/ui-theme": "7.5.0",
155155
"@stacks/ui-utils": "7.5.0",
156-
"@stacks/wallet-sdk": "6.1.1",
156+
"@stacks/wallet-sdk": "6.1.2-pr.4e0feae.0",
157157
"@styled-system/theme-get": "5.1.2",
158158
"@tanstack/query-sync-storage-persister": "4.24.4",
159159
"@tanstack/react-query": "4.24.4",

src/app/common/authentication/use-finish-auth-request.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { useAuthRequestParams } from '@app/common/hooks/auth/use-auth-request-pa
1515
import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state';
1616
import { useKeyActions } from '@app/common/hooks/use-key-actions';
1717
import { useWalletType } from '@app/common/use-wallet-type';
18+
import { useAllBitcoinNativeSegWitNetworksByAccount } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
1819
import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
1920
import { useStacksWallet } from '@app/store/accounts/blockchain/stacks/stacks-keychain';
2021

@@ -26,6 +27,8 @@ export function useFinishAuthRequest() {
2627
const accounts = useStacksAccounts();
2728
const { origin, tabId } = useAuthRequestParams();
2829

30+
const deriveNativeSegWitAccountAtIndex = useAllBitcoinNativeSegWitNetworksByAccount();
31+
2932
return useCallback(
3033
async (accountIndex: number) => {
3134
const account = accounts?.[accountIndex];
@@ -76,6 +79,11 @@ export function useFinishAuthRequest() {
7679
transitPublicKey: decodedAuthRequest.public_keys[0],
7780
scopes: decodedAuthRequest.scopes,
7881
account: legacyAccount,
82+
additionalData: {
83+
btcAddress: {
84+
p2wpkh: deriveNativeSegWitAccountAtIndex(accountIndex),
85+
},
86+
},
7987
});
8088
keyActions.switchAccount(accountIndex);
8189
finalizeAuthResponse({
@@ -89,15 +97,16 @@ export function useFinishAuthRequest() {
8997
},
9098
[
9199
accounts,
92-
appIcon,
93-
appName,
94-
authRequest,
100+
wallet,
95101
decodedAuthRequest,
96-
keyActions,
102+
authRequest,
97103
origin,
98-
wallet,
99-
walletType,
100104
tabId,
105+
walletType,
106+
appIcon,
107+
appName,
108+
deriveNativeSegWitAccountAtIndex,
109+
keyActions,
101110
]
102111
);
103112
}

src/app/common/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,11 @@ export function whenStxChainId(chainId: ChainID) {
312312
return <T>(chainIdMap: WhenChainIdMap<T>): T => chainIdMap[chainId];
313313
}
314314

315+
type NetworkMap<T> = Record<NetworkModes, T>;
316+
export function whenNetwork(mode: NetworkModes) {
317+
return <T>(networkMap: NetworkMap<T>): T => networkMap[mode];
318+
}
319+
315320
export function sumNumbers(nums: number[]) {
316321
return nums.reduce((acc, num) => acc.plus(num), new BigNumber(0));
317322
}

src/app/store/accounts/blockchain/bitcoin/bitcoin-keychain.ts

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,49 @@ import { mnemonicToRootNode } from '@app/common/keychain/keychain';
1212
import { selectInMemoryKey } from '@app/store/in-memory-key/in-memory-key.selectors';
1313
import { selectCurrentKey } from '@app/store/keys/key.selectors';
1414
import { defaultKeyId } from '@app/store/keys/key.slice';
15-
import { selectCurrentNetwork } from '@app/store/networks/networks.selectors';
1615

16+
// This factory selector extends from the wallet root keychain to derive child
17+
// keychains. It accepts a curried fn that takes a keychain and returns a fn
18+
// accepting an account index, to further derive a nested layer of derivation.
19+
// We use this approach to reuse code between both native segwit and taproot
20+
// keychains.
1721
function bitcoinKeychainSelectorFactory(
18-
keychainFn: (hdkey: HDKey, network: NetworkModes) => (index: number) => HDKey
22+
keychainFn: (hdkey: HDKey, network: NetworkModes) => (index: number) => HDKey,
23+
network: NetworkModes
1924
) {
20-
return createSelector(
21-
selectCurrentKey,
22-
selectInMemoryKey,
23-
selectCurrentNetwork,
24-
(currentKey, inMemKey, network) => {
25-
if (currentKey?.type !== 'software') return;
26-
if (!inMemKey.keys[defaultKeyId]) throw new Error('No in-memory key found');
27-
return keychainFn(mnemonicToRootNode(inMemKey.keys.default), network.chain.bitcoin.network);
28-
}
29-
);
25+
return createSelector(selectCurrentKey, selectInMemoryKey, (currentKey, inMemKey) => {
26+
if (currentKey?.type !== 'software') return;
27+
28+
if (!inMemKey.keys[defaultKeyId]) throw new Error('No in-memory key found');
29+
30+
return keychainFn(mnemonicToRootNode(inMemKey.keys.default), network);
31+
});
3032
}
3133

32-
export function getNativeSegwitAddressFromMnemonic(secretKey: string) {
33-
return (index: number) => {
34+
export function getNativeSegwitMainnetAddressFromMnemonic(secretKey: string) {
35+
return (accountIndex: number) => {
3436
const rootNode = mnemonicToRootNode(secretKey);
35-
const account = deriveNativeSegWitAccountKeychain(rootNode, 'mainnet')(index);
37+
const account = deriveNativeSegWitAccountKeychain(rootNode, 'mainnet')(accountIndex);
3638
return getNativeSegWitAddressIndex(account, 'mainnet');
3739
};
3840
}
3941

40-
export const selectSoftwareBitcoinNativeSegWitKeychain = bitcoinKeychainSelectorFactory(
41-
deriveNativeSegWitAccountKeychain
42+
export const selectMainnetNativeSegWitKeychain = bitcoinKeychainSelectorFactory(
43+
deriveNativeSegWitAccountKeychain,
44+
'mainnet'
45+
);
46+
47+
export const selectTestnetNativeSegWitKeychain = bitcoinKeychainSelectorFactory(
48+
deriveNativeSegWitAccountKeychain,
49+
'testnet'
50+
);
51+
52+
export const selectMainnetTaprootKeychain = bitcoinKeychainSelectorFactory(
53+
deriveTaprootAccountFromRootKeychain,
54+
'mainnet'
4255
);
4356

44-
export const selectSoftwareBitcoinTaprootKeychain = bitcoinKeychainSelectorFactory(
45-
deriveTaprootAccountFromRootKeychain
57+
export const selectTestnetTaprootKeychain = bitcoinKeychainSelectorFactory(
58+
deriveTaprootAccountFromRootKeychain,
59+
'testnet'
4660
);

src/app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,54 @@ import { deriveAddressIndexZeroFromAccount } from '@shared/crypto/bitcoin/bitcoi
88
import { deriveNativeSegWitReceiveAddressIndex } from '@shared/crypto/bitcoin/p2wpkh-address-gen';
99
import { isUndefined } from '@shared/utils';
1010

11+
import { whenNetwork } from '@app/common/utils';
1112
import {
1213
formatBitcoinAccount,
1314
tempHardwareAccountForTesting,
1415
} from '@app/store/accounts/blockchain/bitcoin/bitcoin-account.models';
1516
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
1617

1718
import { useCurrentAccountIndex } from '../../account';
18-
import { selectSoftwareBitcoinNativeSegWitKeychain } from './bitcoin-keychain';
19+
import {
20+
selectMainnetNativeSegWitKeychain,
21+
selectTestnetNativeSegWitKeychain,
22+
} from './bitcoin-keychain';
23+
24+
function useNativeSegWitCurrentNetworkAccountKeychain() {
25+
const network = useCurrentNetwork();
26+
return useSelector(
27+
whenNetwork(network.chain.bitcoin.network)({
28+
mainnet: selectMainnetNativeSegWitKeychain,
29+
testnet: selectTestnetNativeSegWitKeychain,
30+
})
31+
);
32+
}
33+
34+
export function useAllBitcoinNativeSegWitNetworksByAccount() {
35+
const mainnetKeychainAtAccount = useSelector(selectMainnetNativeSegWitKeychain);
36+
const testnetKeychainAtAccount = useSelector(selectTestnetNativeSegWitKeychain);
37+
38+
return useCallback(
39+
(accountIndex: number) => {
40+
if (!mainnetKeychainAtAccount || !testnetKeychainAtAccount)
41+
throw new Error('Cannot derive addresses in non-software mode');
42+
return {
43+
mainnet: deriveNativeSegWitReceiveAddressIndex({
44+
xpub: mainnetKeychainAtAccount(accountIndex).publicExtendedKey,
45+
network: 'mainnet',
46+
})?.address,
47+
testnet: deriveNativeSegWitReceiveAddressIndex({
48+
xpub: testnetKeychainAtAccount(accountIndex).publicExtendedKey,
49+
network: 'testnet',
50+
})?.address,
51+
};
52+
},
53+
[mainnetKeychainAtAccount, testnetKeychainAtAccount]
54+
);
55+
}
1956

2057
function useBitcoinNativeSegwitAccount(index: number) {
21-
const keychain = useSelector(selectSoftwareBitcoinNativeSegWitKeychain);
58+
const keychain = useNativeSegWitCurrentNetworkAccountKeychain();
2259
return useMemo(() => {
2360
// TODO: Remove with bitcoin Ledger integration
2461
if (isUndefined(keychain)) return tempHardwareAccountForTesting;
@@ -69,7 +106,7 @@ export function useCurrentBitcoinNativeSegwitAddressIndexKeychain() {
69106

70107
export function useSignBitcoinNativeSegwitTx() {
71108
const index = useCurrentAccountIndex();
72-
const keychain = useSelector(selectSoftwareBitcoinNativeSegWitKeychain)?.(index);
109+
const keychain = useNativeSegWitCurrentNetworkAccountKeychain()?.(index);
73110
return useCallback(
74111
(tx: btc.Transaction) => {
75112
if (isUndefined(keychain)) return;

src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,25 @@ import { useSelector } from 'react-redux';
44
import { deriveTaprootReceiveAddressIndex } from '@shared/crypto/bitcoin/p2tr-address-gen';
55
import { isUndefined } from '@shared/utils';
66

7+
import { whenNetwork } from '@app/common/utils';
78
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
89

910
import { useCurrentAccountIndex } from '../../account';
1011
import { formatBitcoinAccount, tempHardwareAccountForTesting } from './bitcoin-account.models';
11-
import { selectSoftwareBitcoinTaprootKeychain } from './bitcoin-keychain';
12+
import { selectMainnetTaprootKeychain, selectTestnetTaprootKeychain } from './bitcoin-keychain';
13+
14+
function useTaprootKeychainByAccount() {
15+
const network = useCurrentNetwork();
16+
return useSelector(
17+
whenNetwork(network.chain.bitcoin.network)({
18+
mainnet: selectMainnetTaprootKeychain,
19+
testnet: selectTestnetTaprootKeychain,
20+
})
21+
);
22+
}
1223

1324
function useBitcoinTaprootAccount(index: number) {
14-
const keychain = useSelector(selectSoftwareBitcoinTaprootKeychain);
25+
const keychain = useSelector(selectMainnetTaprootKeychain);
1526
return useMemo(() => {
1627
// TODO: Remove with bitcoin Ledger integration
1728
if (isUndefined(keychain)) return tempHardwareAccountForTesting;
@@ -37,10 +48,6 @@ function useDeriveTaprootAccountIndexAddressIndexZero(xpub: string) {
3748
);
3849
}
3950

40-
function useTaprootKeychainByAccount() {
41-
return useSelector(selectSoftwareBitcoinTaprootKeychain);
42-
}
43-
4451
export function useCurrentTaprootAccountKeychain() {
4552
const currentAccountIndex = useCurrentAccountIndex();
4653
const accountKeychain = useTaprootKeychainByAccount();

src/app/store/keys/key.actions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { BitcoinClient } from '@app/query/bitcoin/bitcoin-client';
1111
import { StacksClient } from '@app/query/stacks/stacks-client';
1212
import { AppThunk } from '@app/store';
1313

14-
import { getNativeSegwitAddressFromMnemonic } from '../accounts/blockchain/bitcoin/bitcoin-keychain';
14+
import { getNativeSegwitMainnetAddressFromMnemonic } from '../accounts/blockchain/bitcoin/bitcoin-keychain';
1515
import { getStacksAddressByIndex } from '../accounts/blockchain/stacks/stacks-keychain';
1616
import { stxChainSlice } from '../chains/stx-chain.slice';
1717
import { selectDefaultWalletKey } from '../in-memory-key/in-memory-key.selectors';
@@ -67,7 +67,7 @@ function setWalletEncryptionPassword(args: {
6767
const hasStxBalance = await doesStacksAddressHaveBalance(stxAddress);
6868
const hasNames = await doesStacksAddressHaveBnsName(stxAddress);
6969

70-
const btcAddress = getNativeSegwitAddressFromMnemonic(secretKey)(index);
70+
const btcAddress = getNativeSegwitMainnetAddressFromMnemonic(secretKey)(index);
7171
const hasBtcBalance = await doesBitcoinAddressHaveBalance(btcAddress.address!);
7272
// TODO: add inscription check here also?
7373
return hasStxBalance || hasNames || hasBtcBalance;

src/app/store/networks/networks.selectors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const selectAppRequestedNetworkId = createSelector(selectNetworks, networ
4242
return findMatchingNetworkKey({ coreApiUrl, networkChainId, networks });
4343
});
4444

45-
export const selectCurrentNetwork = createSelector(
45+
const selectCurrentNetwork = createSelector(
4646
selectNetworks,
4747
selectCurrentNetworkId,
4848
selectAppRequestedNetworkId,

test-app/src/common/use-auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useCallback, useEffect, useMemo } from 'react';
2+
23
import { AppState, defaultState } from '@common/context';
34
import { AppConfig, UserSession } from '@stacks/auth';
45
import { AuthOptions } from '@stacks/connect';

0 commit comments

Comments
 (0)