Skip to content

Commit

Permalink
feat: return btc address during auth, closes #2909, #3092
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Feb 22, 2023
1 parent 5942a54 commit 0073207
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 50 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
"@stacks/ui-core": "7.3.0",
"@stacks/ui-theme": "7.5.0",
"@stacks/ui-utils": "7.5.0",
"@stacks/wallet-sdk": "6.1.1",
"@stacks/wallet-sdk": "6.1.2-pr.4e0feae.0",
"@styled-system/theme-get": "5.1.2",
"@tanstack/query-sync-storage-persister": "4.24.4",
"@tanstack/react-query": "4.24.4",
Expand Down
21 changes: 15 additions & 6 deletions src/app/common/authentication/use-finish-auth-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useAuthRequestParams } from '@app/common/hooks/auth/use-auth-request-pa
import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state';
import { useKeyActions } from '@app/common/hooks/use-key-actions';
import { useWalletType } from '@app/common/use-wallet-type';
import { useAllBitcoinNativeSegWitNetworksByAccount } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { useStacksWallet } from '@app/store/accounts/blockchain/stacks/stacks-keychain';

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

const deriveNativeSegWitAccountAtIndex = useAllBitcoinNativeSegWitNetworksByAccount();

return useCallback(
async (accountIndex: number) => {
const account = accounts?.[accountIndex];
Expand Down Expand Up @@ -76,6 +79,11 @@ export function useFinishAuthRequest() {
transitPublicKey: decodedAuthRequest.public_keys[0],
scopes: decodedAuthRequest.scopes,
account: legacyAccount,
additionalData: {
btcAddress: {
p2wpkh: deriveNativeSegWitAccountAtIndex(accountIndex),
},
},
});
keyActions.switchAccount(accountIndex);
finalizeAuthResponse({
Expand All @@ -89,15 +97,16 @@ export function useFinishAuthRequest() {
},
[
accounts,
appIcon,
appName,
authRequest,
wallet,
decodedAuthRequest,
keyActions,
authRequest,
origin,
wallet,
walletType,
tabId,
walletType,
appIcon,
appName,
deriveNativeSegWitAccountAtIndex,
keyActions,
]
);
}
5 changes: 5 additions & 0 deletions src/app/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ export function whenStxChainId(chainId: ChainID) {
return <T>(chainIdMap: WhenChainIdMap<T>): T => chainIdMap[chainId];
}

type NetworkMap<T> = Record<NetworkModes, T>;
export function whenNetwork(mode: NetworkModes) {
return <T>(networkMap: NetworkMap<T>): T => networkMap[mode];
}

export function sumNumbers(nums: number[]) {
return nums.reduce((acc, num) => acc.plus(num), new BigNumber(0));
}
Expand Down
52 changes: 33 additions & 19 deletions src/app/store/accounts/blockchain/bitcoin/bitcoin-keychain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,49 @@ import { mnemonicToRootNode } from '@app/common/keychain/keychain';
import { selectInMemoryKey } from '@app/store/in-memory-key/in-memory-key.selectors';
import { selectCurrentKey } from '@app/store/keys/key.selectors';
import { defaultKeyId } from '@app/store/keys/key.slice';
import { selectCurrentNetwork } from '@app/store/networks/networks.selectors';

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

if (!inMemKey.keys[defaultKeyId]) throw new Error('No in-memory key found');

return keychainFn(mnemonicToRootNode(inMemKey.keys.default), network);
});
}

export function getNativeSegwitAddressFromMnemonic(secretKey: string) {
return (index: number) => {
export function getNativeSegwitMainnetAddressFromMnemonic(secretKey: string) {
return (accountIndex: number) => {
const rootNode = mnemonicToRootNode(secretKey);
const account = deriveNativeSegWitAccountKeychain(rootNode, 'mainnet')(index);
const account = deriveNativeSegWitAccountKeychain(rootNode, 'mainnet')(accountIndex);
return getNativeSegWitAddressIndex(account, 'mainnet');
};
}

export const selectSoftwareBitcoinNativeSegWitKeychain = bitcoinKeychainSelectorFactory(
deriveNativeSegWitAccountKeychain
export const selectMainnetNativeSegWitKeychain = bitcoinKeychainSelectorFactory(
deriveNativeSegWitAccountKeychain,
'mainnet'
);

export const selectTestnetNativeSegWitKeychain = bitcoinKeychainSelectorFactory(
deriveNativeSegWitAccountKeychain,
'testnet'
);

export const selectMainnetTaprootKeychain = bitcoinKeychainSelectorFactory(
deriveTaprootAccountFromRootKeychain,
'mainnet'
);

export const selectSoftwareBitcoinTaprootKeychain = bitcoinKeychainSelectorFactory(
deriveTaprootAccountFromRootKeychain
export const selectTestnetTaprootKeychain = bitcoinKeychainSelectorFactory(
deriveTaprootAccountFromRootKeychain,
'testnet'
);
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,54 @@ import { deriveAddressIndexZeroFromAccount } from '@shared/crypto/bitcoin/bitcoi
import { deriveNativeSegWitReceiveAddressIndex } from '@shared/crypto/bitcoin/p2wpkh-address-gen';
import { isUndefined } from '@shared/utils';

import { whenNetwork } from '@app/common/utils';
import {
formatBitcoinAccount,
tempHardwareAccountForTesting,
} from '@app/store/accounts/blockchain/bitcoin/bitcoin-account.models';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';

import { useCurrentAccountIndex } from '../../account';
import { selectSoftwareBitcoinNativeSegWitKeychain } from './bitcoin-keychain';
import {
selectMainnetNativeSegWitKeychain,
selectTestnetNativeSegWitKeychain,
} from './bitcoin-keychain';

function useNativeSegWitCurrentNetworkAccountKeychain() {
const network = useCurrentNetwork();
return useSelector(
whenNetwork(network.chain.bitcoin.network)({
mainnet: selectMainnetNativeSegWitKeychain,
testnet: selectTestnetNativeSegWitKeychain,
})
);
}

export function useAllBitcoinNativeSegWitNetworksByAccount() {
const mainnetKeychainAtAccount = useSelector(selectMainnetNativeSegWitKeychain);
const testnetKeychainAtAccount = useSelector(selectTestnetNativeSegWitKeychain);

return useCallback(
(accountIndex: number) => {
if (!mainnetKeychainAtAccount || !testnetKeychainAtAccount)
throw new Error('Cannot derive addresses in non-software mode');
return {
mainnet: deriveNativeSegWitReceiveAddressIndex({
xpub: mainnetKeychainAtAccount(accountIndex).publicExtendedKey,
network: 'mainnet',
})?.address,
testnet: deriveNativeSegWitReceiveAddressIndex({
xpub: testnetKeychainAtAccount(accountIndex).publicExtendedKey,
network: 'testnet',
})?.address,
};
},
[mainnetKeychainAtAccount, testnetKeychainAtAccount]
);
}

function useBitcoinNativeSegwitAccount(index: number) {
const keychain = useSelector(selectSoftwareBitcoinNativeSegWitKeychain);
const keychain = useNativeSegWitCurrentNetworkAccountKeychain();
return useMemo(() => {
// TODO: Remove with bitcoin Ledger integration
if (isUndefined(keychain)) return tempHardwareAccountForTesting;
Expand Down Expand Up @@ -69,7 +106,7 @@ export function useCurrentBitcoinNativeSegwitAddressIndexKeychain() {

export function useSignBitcoinNativeSegwitTx() {
const index = useCurrentAccountIndex();
const keychain = useSelector(selectSoftwareBitcoinNativeSegWitKeychain)?.(index);
const keychain = useNativeSegWitCurrentNetworkAccountKeychain()?.(index);
return useCallback(
(tx: btc.Transaction) => {
if (isUndefined(keychain)) return;
Expand Down
19 changes: 13 additions & 6 deletions src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@ import { useSelector } from 'react-redux';
import { deriveTaprootReceiveAddressIndex } from '@shared/crypto/bitcoin/p2tr-address-gen';
import { isUndefined } from '@shared/utils';

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

import { useCurrentAccountIndex } from '../../account';
import { formatBitcoinAccount, tempHardwareAccountForTesting } from './bitcoin-account.models';
import { selectSoftwareBitcoinTaprootKeychain } from './bitcoin-keychain';
import { selectMainnetTaprootKeychain, selectTestnetTaprootKeychain } from './bitcoin-keychain';

function useTaprootKeychainByAccount() {
const network = useCurrentNetwork();
return useSelector(
whenNetwork(network.chain.bitcoin.network)({
mainnet: selectMainnetTaprootKeychain,
testnet: selectTestnetTaprootKeychain,
})
);
}

function useBitcoinTaprootAccount(index: number) {
const keychain = useSelector(selectSoftwareBitcoinTaprootKeychain);
const keychain = useSelector(selectMainnetTaprootKeychain);
return useMemo(() => {
// TODO: Remove with bitcoin Ledger integration
if (isUndefined(keychain)) return tempHardwareAccountForTesting;
Expand All @@ -37,10 +48,6 @@ function useDeriveTaprootAccountIndexAddressIndexZero(xpub: string) {
);
}

function useTaprootKeychainByAccount() {
return useSelector(selectSoftwareBitcoinTaprootKeychain);
}

export function useCurrentTaprootAccountKeychain() {
const currentAccountIndex = useCurrentAccountIndex();
const accountKeychain = useTaprootKeychainByAccount();
Expand Down
4 changes: 2 additions & 2 deletions src/app/store/keys/key.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { BitcoinClient } from '@app/query/bitcoin/bitcoin-client';
import { StacksClient } from '@app/query/stacks/stacks-client';
import { AppThunk } from '@app/store';

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

const btcAddress = getNativeSegwitAddressFromMnemonic(secretKey)(index);
const btcAddress = getNativeSegwitMainnetAddressFromMnemonic(secretKey)(index);
const hasBtcBalance = await doesBitcoinAddressHaveBalance(btcAddress.address!);
// TODO: add inscription check here also?
return hasStxBalance || hasNames || hasBtcBalance;
Expand Down
2 changes: 1 addition & 1 deletion src/app/store/networks/networks.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const selectAppRequestedNetworkId = createSelector(selectNetworks, networ
return findMatchingNetworkKey({ coreApiUrl, networkChainId, networks });
});

export const selectCurrentNetwork = createSelector(
const selectCurrentNetwork = createSelector(
selectNetworks,
selectCurrentNetworkId,
selectAppRequestedNetworkId,
Expand Down
1 change: 1 addition & 0 deletions test-app/src/common/use-auth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useCallback, useEffect, useMemo } from 'react';

import { AppState, defaultState } from '@common/context';
import { AppConfig, UserSession } from '@stacks/auth';
import { AuthOptions } from '@stacks/connect';
Expand Down
Loading

0 comments on commit 0073207

Please sign in to comment.