From 28bb4ca7c555561ca02733217c1f79844a052e74 Mon Sep 17 00:00:00 2001 From: fbwoolf Date: Mon, 13 Mar 2023 12:03:11 -0500 Subject: [PATCH] fix: test-app taproot psbts and update pkgs --- package.json | 6 +- src/app/components/disclaimer.tsx | 2 +- .../pages/psbt-request/use-psbt-request.tsx | 64 ++++++++++----- .../use-next-fresh-taproot-address.query.ts | 2 +- src/app/query/bitcoin/ordinals/utils.ts | 2 +- .../bitcoin/taproot-account.hooks.ts | 2 +- test-app/src/components/bitcoin.tsx | 44 +++++----- .../integration/settings.selectors.ts | 1 - .../integration/settings/settings.spec.ts | 80 +++++++++++++++++++ tests-legacy/page-objects/wallet.page.ts | 1 - yarn.lock | 28 +++---- 11 files changed, 162 insertions(+), 70 deletions(-) create mode 100644 tests-legacy/integration/settings/settings.spec.ts diff --git a/package.json b/package.json index ccd4afab..a2e7d000 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "@stacks/auth": "6.1.1", "@stacks/blockchain-api-client": "6.3.4", "@stacks/common": "6.0.0", - "@stacks/connect": "5.0.6-alpha.9d9606f.0", + "@stacks/connect": "7.2.1", "@stacks/connect-ui": "6.0.0", "@stacks/encryption": "6.1.1", "@stacks/network": "6.1.1", @@ -226,7 +226,7 @@ "@redux-devtools/remote": "0.8.0", "@schemastore/web-manifest": "0.0.5", "@sentry/webpack-plugin": "1.20.0", - "@stacks/connect-react": "5.0.6-alpha.9d9606f.0", + "@stacks/connect-react": "22.0.1", "@stacks/eslint-config": "1.2.0", "@stacks/prettier-config": "0.0.10", "@stacks/stacks-blockchain-api-types": "6.3.4", @@ -317,8 +317,6 @@ "webpack-hot-middleware": "2.25.3" }, "resolutions": { - "@stacks/connect": "5.0.6-alpha.9d9606f.0", - "@stacks/connect-react": "5.0.6-alpha.9d9606f.0", "@types/react": "17.0.2", "@types/react-dom": "17.0.2", "socket.io-parser": "4.2.2" diff --git a/src/app/components/disclaimer.tsx b/src/app/components/disclaimer.tsx index ec8c54af..758f8abd 100644 --- a/src/app/components/disclaimer.tsx +++ b/src/app/components/disclaimer.tsx @@ -19,7 +19,7 @@ export function DisclaimerLayout({ disclaimerText, learnMoreUrl }: DisclaimerPro Learn more ) : null} - . + {learnMoreUrl ? '.' : null} ); diff --git a/src/app/pages/psbt-request/use-psbt-request.tsx b/src/app/pages/psbt-request/use-psbt-request.tsx index bef1cd7c..bde1a87e 100644 --- a/src/app/pages/psbt-request/use-psbt-request.tsx +++ b/src/app/pages/psbt-request/use-psbt-request.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; import * as btc from '@scure/btc-signer'; @@ -6,7 +6,7 @@ import { PsbtPayload } from '@stacks/connect'; import { finalizePsbt } from '@shared/actions/finalize-psbt'; import { logger } from '@shared/logger'; -import { isUndefined } from '@shared/utils'; +import { isNumber, isUndefined } from '@shared/utils'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useOnMount } from '@app/common/hooks/use-on-mount'; @@ -42,7 +42,7 @@ export function usePsbtRequest() { setTx(tx); }); - const getPsbtDetails = () => { + const getPsbtDetails = useCallback(() => { if (!psbtPayload || !tx) return; try { return btc.RawPSBTV0.decode(hexToBytes(psbtPayload.hex)); @@ -54,34 +54,46 @@ export function usePsbtRequest() { } } return; - }; + }, [psbtPayload, tx]); - const onCancel = () => { + const onCancel = useCallback(() => { void analytics.track('request_psbt_cancel'); finalizePsbt({ requestPayload: requestToken ?? '', tabId, data: 'cancel' }); - }; + }, [analytics, requestToken, tabId]); - const onSignPsbt = async () => { + const signPsbtAtIndex = useCallback( + (allowedSighash, idx, tx) => { + try { + signNativeSegwitTxAtIndex({ allowedSighash, idx, tx }); + } catch (e1) { + try { + signTaprootTxAtIndex({ allowedSighash, idx, tx }); + } catch (e2) { + logger.error('Error signing tx at provided index', e1, e2); + } + } + }, + [signNativeSegwitTxAtIndex, signTaprootTxAtIndex] + ); + + const onSignPsbt = useCallback(() => { setIsLoading(true); void analytics.track('request_sign_psbt_submit'); if (!tx) return logger.error('No psbt to sign'); - const indexes = psbtPayload?.signAtIndex; + const indexOrIndexes = psbtPayload?.signAtIndex; const allowedSighash = psbtPayload?.allowedSighash; - if (!isUndefined(indexes) && indexes.length) { - indexes.map(idx => { - try { - signNativeSegwitTxAtIndex({ allowedSighash, idx, tx }); - } catch (e1) { - try { - signTaprootTxAtIndex({ allowedSighash, idx, tx }); - } catch (e2) { - logger.error('Error signing tx at provided index', e1, e2); - } - } - }); + if (!isUndefined(indexOrIndexes)) { + if (Array.isArray(indexOrIndexes) && indexOrIndexes.length) { + indexOrIndexes.map(idx => { + signPsbtAtIndex(allowedSighash, idx, tx); + }); + } + if (isNumber(indexOrIndexes)) { + signPsbtAtIndex(allowedSighash, indexOrIndexes, tx); + } } else { try { signNativeSegwitTx(tx); @@ -105,7 +117,17 @@ export function usePsbtRequest() { tabId, data: { hex: bytesToHex(psbt) }, }); - }; + }, [ + analytics, + psbtPayload?.allowedSighash, + psbtPayload?.signAtIndex, + requestToken, + signNativeSegwitTx, + signPsbtAtIndex, + signTaprootTx, + tabId, + tx, + ]); const appName = psbtPayload?.appDetails?.name; const psbtDetails = getPsbtDetails(); diff --git a/src/app/query/bitcoin/ordinals/use-next-fresh-taproot-address.query.ts b/src/app/query/bitcoin/ordinals/use-next-fresh-taproot-address.query.ts index 0c451aa6..5347a58c 100644 --- a/src/app/query/bitcoin/ordinals/use-next-fresh-taproot-address.query.ts +++ b/src/app/query/bitcoin/ordinals/use-next-fresh-taproot-address.query.ts @@ -20,7 +20,7 @@ export function useNextFreshTaprootAddressQuery() { return useQuery( ['next-taproot-address', bytesToHex(keychain?.pubKeyHash!), network.id] as const, async () => { - if (!keychain) throw new Error('Expected keychain to be provided.'); + if (!keychain) throw new Error('Expected keychain to be provided'); async function taprootAddressIndexActivity(index: number) { const address = getTaprootAddress({ diff --git a/src/app/query/bitcoin/ordinals/utils.ts b/src/app/query/bitcoin/ordinals/utils.ts index 0d4a56e2..f03c008b 100644 --- a/src/app/query/bitcoin/ordinals/utils.ts +++ b/src/app/query/bitcoin/ordinals/utils.ts @@ -15,7 +15,7 @@ interface GetTaprootAddressArgs { network: NetworkModes; } export function getTaprootAddress({ index, keychain, network }: GetTaprootAddressArgs) { - if (!keychain) throw new Error('Expected keychain to be provided.'); + if (!keychain) throw new Error('Expected keychain to be provided'); if (keychain.depth !== DerivationPathDepth.Account) throw new Error('Expects keychain to be on the account index'); diff --git a/src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts b/src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts index 97361300..17caec9c 100644 --- a/src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts +++ b/src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts @@ -108,7 +108,7 @@ export function useAllBitcoinTaprootNetworksByAccount() { export function useCurrentAccountTaprootSigner() { const network = useCurrentNetwork(); const accountKeychain = useCurrentTaprootAccountKeychain(); - if (!accountKeychain) return; + if (!accountKeychain) return; // TODO: Revisit this return early const addressIndexKeychainFn = deriveAddressIndexKeychainFromAccount(accountKeychain); return (addressIndex: number) => { diff --git a/test-app/src/components/bitcoin.tsx b/test-app/src/components/bitcoin.tsx index 8bf95bb9..318dce46 100644 --- a/test-app/src/components/bitcoin.tsx +++ b/test-app/src/components/bitcoin.tsx @@ -89,26 +89,26 @@ function buildTestNativeSegwitPsbtRequestWithIndexes(pubKey: Uint8Array): PsbtRe } function buildTestTaprootPsbtRequest(pubKey: Uint8Array): PsbtRequestOptions { - const p2wpkh = btc.p2wpkh(pubKey, bitcoinTestnet); + const payment = getTaprootPayment(pubKey); const tx = new btc.Transaction(); tx.addInput({ index: 0, - tapInternalKey: getTaprootPayment(pubKey).tapInternalKey, + tapInternalKey: payment.tapInternalKey, txid: '15f34b3bd2aab555a003cd1c6959ac09b36239c6af1cb16ff8198cef64f8db9c', witnessUtxo: { amount: BigInt(1000), - script: p2wpkh.script, + script: payment.script, }, }); tx.addInput({ index: 1, - tapInternalKey: getTaprootPayment(pubKey).tapInternalKey, + tapInternalKey: payment.tapInternalKey, txid: 'dca5179afaa63eae112d8a97794de2d30dd823315bcabe6d8b8a6b98e3567705', witnessUtxo: { amount: BigInt(2000), - script: p2wpkh.script, + script: payment.script, }, }); @@ -117,39 +117,31 @@ function buildTestTaprootPsbtRequest(pubKey: Uint8Array): PsbtRequestOptions { return { hex: bytesToHex(psbt) }; } -function buildTestTaprootPsbtRequestWithIndexes(pubKey: Uint8Array): PsbtRequestOptions { - const p2wpkh = btc.p2wpkh(pubKey, bitcoinTestnet); +function buildTestTaprootPsbtRequestWithIndex(pubKey: Uint8Array): PsbtRequestOptions { + const payment = getTaprootPayment(pubKey); const tx = new btc.Transaction(); tx.addInput({ index: 0, - tapInternalKey: getTaprootPayment(pubKey).tapInternalKey, + tapInternalKey: payment.tapInternalKey, txid: '15f34b3bd2aab555a003cd1c6959ac09b36239c6af1cb16ff8198cef64f8db9c', witnessUtxo: { amount: BigInt(1000), - script: p2wpkh.script, - }, - }); - tx.addInput({ - index: 1, - tapInternalKey: getTaprootPayment(pubKey).tapInternalKey, - txid: 'dca5179afaa63eae112d8a97794de2d30dd823315bcabe6d8b8a6b98e3567705', - witnessUtxo: { - amount: BigInt(2000), - script: p2wpkh.script, + script: payment.script, }, }); const psbt = tx.toPSBT(); - return { signAtIndex: [0, 1], hex: bytesToHex(psbt) }; + return { signAtIndex: 0, hex: bytesToHex(psbt) }; } export const Bitcoin = () => { const { userData } = useContext(AppContext); const { signPsbt } = useConnect(); - const pubKey = hexToBytes(userData?.profile.btcPublicKey.p2wpkh); + const segwitPubKey = hexToBytes(userData?.profile.btcPublicKey.p2wpkh); + const taprootPubKey = hexToBytes(userData?.profile.btcPublicKey.p2tr); console.log('userData', userData); @@ -178,7 +170,7 @@ export const Bitcoin = () => { @@ -186,7 +178,7 @@ export const Bitcoin = () => { ml={3} mt={3} onClick={() => - signTx(buildTestNativeSegwitPsbtRequestWithIndexes(pubKey), stacksTestnetNetwork) + signTx(buildTestNativeSegwitPsbtRequestWithIndexes(segwitPubKey), stacksTestnetNetwork) } > Sign PSBT at indexes (SegWit) @@ -194,16 +186,18 @@ export const Bitcoin = () => { ); diff --git a/tests-legacy/integration/settings.selectors.ts b/tests-legacy/integration/settings.selectors.ts index 0735b503..4a7836f8 100644 --- a/tests-legacy/integration/settings.selectors.ts +++ b/tests-legacy/integration/settings.selectors.ts @@ -3,7 +3,6 @@ export enum SettingsSelectors { NetworkListItem = 'network-list-item', ChangeNetworkAction = 'settings-change-network', CurrentNetwork = 'current-network', - CreateAccountBtn = 'create-account-button', CurrentAccountDisplayName = 'current-account-display-name', ViewSecretKeyListItem = 'settings-view-secret-key', CopyKeyToClipboardBtn = 'copy-key-to-clipboard-btn', diff --git a/tests-legacy/integration/settings/settings.spec.ts b/tests-legacy/integration/settings/settings.spec.ts new file mode 100644 index 00000000..ad7b85bc --- /dev/null +++ b/tests-legacy/integration/settings/settings.spec.ts @@ -0,0 +1,80 @@ +import { RouteUrls } from '@shared/route-urls'; + +import { delay } from '@app/common/utils'; + +import { WalletPage } from '../../page-objects/wallet.page'; +import { SettingsSelectors } from '../settings.selectors'; +import { BrowserDriver, createTestSelector, randomString, setupBrowser } from '../utils'; + +jest.setTimeout(60_000); +jest.retryTimes(process.env.CI ? 2 : 0); + +describe(`Settings integration tests`, () => { + let secretKey = ''; + let browser: BrowserDriver; + let wallet: WalletPage; + + beforeAll(async () => { + browser = await setupBrowser(); + wallet = await WalletPage.init(browser, RouteUrls.Onboarding); + await wallet.signUp(); + await wallet.waitForHideOnboardingsStepsButton(); + await wallet.clickHideSteps(); + await delay(500); + }); + + afterAll(async () => { + try { + await browser.context.close(); + } catch (error) {} + }); + + it('should be able to view and save secret key to clipboard', async () => { + await wallet.goToSecretKey(); + await wallet.waitForEnterPasswordInput(); + await wallet.enterPasswordAndUnlockWallet(); + secretKey = await wallet.getSecretKey(); + await wallet.page.click(createTestSelector(SettingsSelectors.CopyKeyToClipboardBtn)); + const copySuccessMessage = await wallet.page.textContent( + createTestSelector(SettingsSelectors.CopyKeyToClipboardBtn) + ); + expect(copySuccessMessage).toContain('Copied!'); + }); + + it('should be able to sign out, lock and unlock the extension', async () => { + await wallet.waitForSettingsButton(); + await wallet.clickSettingsButton(); + await wallet.page.click(createTestSelector(SettingsSelectors.SignOutListItem)); + await wallet.page.click(wallet.$signOutConfirmHasBackupCheckbox); + await wallet.page.click(wallet.$signOutConfirmPasswordDisable); + await wallet.page.click(wallet.$signOutDeleteWalletBtn); + await wallet.clickDenyAnalytics(); + await wallet.clickSignIn(); + await wallet.enterSecretKey(secretKey); + const password = randomString(15); + await wallet.enterNewPassword(password); + await wallet.waitForSettingsButton(); + await wallet.clickSettingsButton(); + await wallet.page.click(createTestSelector(SettingsSelectors.LockListItem)); + await wallet.enterPasswordAndUnlockWallet(password); + const displayName = await wallet.page.textContent( + createTestSelector(SettingsSelectors.CurrentAccountDisplayName) + ); + expect(displayName).toEqual('Account 1'); + }); + + it('should be able to change network', async () => { + await wallet.waitForSettingsButton(); + await wallet.clickSettingsButton(); + const currentNetwork = await wallet.page.textContent( + createTestSelector(SettingsSelectors.CurrentNetwork) + ); + expect(currentNetwork).toContain('mainnet'); + await wallet.page.click(createTestSelector(SettingsSelectors.ChangeNetworkAction)); + await wallet.page.waitForTimeout(850); + const networkListItems = await wallet.page.$$( + createTestSelector(SettingsSelectors.NetworkListItem) + ); + expect(networkListItems).toHaveLength(3); + }); +}); diff --git a/tests-legacy/page-objects/wallet.page.ts b/tests-legacy/page-objects/wallet.page.ts index 6e4e6e5b..d204fafa 100644 --- a/tests-legacy/page-objects/wallet.page.ts +++ b/tests-legacy/page-objects/wallet.page.ts @@ -38,7 +38,6 @@ export class WalletPage { $contractCallButton = createTestSelector('btn-contract-call'); $settingsViewSecretKey = createTestSelector(SettingsSelectors.ViewSecretKeyListItem); $homePageBalancesList = createTestSelector(HomePageSelectorsLegacy.BalancesList); - $createAccountButton = createTestSelector(SettingsSelectors.CreateAccountBtn); $statusMessage = createTestSelector(WalletPageSelectors.StatusMessage); $hiroWalletLogo = createTestSelector(OnboardingSelectors.HiroWalletLogoRouteToHome); $signOutConfirmHasBackupCheckbox = createTestSelector( diff --git a/yarn.lock b/yarn.lock index 58d5e740..08ec88c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3525,12 +3525,12 @@ "@types/node" "^18.0.4" buffer "^6.0.3" -"@stacks/connect-react@5.0.6-alpha.9d9606f.0": - version "5.0.6-alpha.9d9606f.0" - resolved "https://registry.yarnpkg.com/@stacks/connect-react/-/connect-react-5.0.6-alpha.9d9606f.0.tgz#3647f71f69708a50dff3da46c4a483fc3dc46dd2" - integrity sha512-dweidsbJoIV5qJYF1jB1W1mxguDs8+PEbKneCzXhhLoOQaLENqAti1GHFZckUeUaMomUnbps7OevjYr/Wbc/pg== +"@stacks/connect-react@22.0.1": + version "22.0.1" + resolved "https://registry.yarnpkg.com/@stacks/connect-react/-/connect-react-22.0.1.tgz#65541f5cca07c178284ec873b301682fec63b9a6" + integrity sha512-g74wV4hjUxSK8hxj6NeXrichkTxTg72o6jCoDC54frXpiDQa3ur01Jk4IRuJYY2MClPQd/7JpzWwJuZb6UQydA== dependencies: - "@stacks/connect" "^5.0.6-alpha.9d9606f.0" + "@stacks/connect" "7.2.1" jsontokens "^4.0.1" "@stacks/connect-ui@6.0.0": @@ -3540,20 +3540,20 @@ dependencies: "@stencil/core" "^2.17.1" -"@stacks/connect-ui@^5.0.6-alpha.9d9606f.0": - version "5.5.4" - resolved "https://registry.yarnpkg.com/@stacks/connect-ui/-/connect-ui-5.5.4.tgz#f9612e2b05cbc0f013656ce16d07cdcb389bd0ca" - integrity sha512-xstC05PoxpPeA2HWytkJp/NJBtDEPDUoHuUyyvpuqS7f95rsHyx/tLFWv9ElxWiIbeMPPDEQipoy6S1qnZb8jw== +"@stacks/connect-ui@6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@stacks/connect-ui/-/connect-ui-6.0.1.tgz#1604f15df9b49b15c974ce65a786995ed5a2aa74" + integrity sha512-DOB2UdwLJAznHfsOmloTzK7JDIfxwUq+GqEH6z0snxA3Gsu2aernlLhwUW1QLFXQtPw/fUp1ty+re71qHUc6tg== dependencies: "@stencil/core" "^2.17.1" -"@stacks/connect@5.0.6-alpha.9d9606f.0", "@stacks/connect@^5.0.6-alpha.9d9606f.0": - version "5.0.6-alpha.9d9606f.0" - resolved "https://registry.yarnpkg.com/@stacks/connect/-/connect-5.0.6-alpha.9d9606f.0.tgz#39c2a16bf981c7d1c84d101b49ea9184723f053c" - integrity sha512-Ec0txcTn4HaR7BF28NX+l+prfAPIkOP0hGFu+CbbJMqrh8qg9tzBGY/DD6VEw1Ft7WnMhrmJavSYkLfYO/88MQ== +"@stacks/connect@7.2.1": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@stacks/connect/-/connect-7.2.1.tgz#72450c221389dad8e248ac9def7335845df09629" + integrity sha512-AWFvyVWH4z9pbPA+iFoFpVCABcGjJ8ZsqZNroNQOyAq6DVSM+GBSMnNXqnugUnKUE9+IYM7TNKfvKkzN1temiQ== dependencies: "@stacks/auth" "^6.1.1" - "@stacks/connect-ui" "^5.0.6-alpha.9d9606f.0" + "@stacks/connect-ui" "6.0.1" "@stacks/network" "^6.1.1" "@stacks/profile" "^6.1.1" "@stacks/transactions" "^6.1.1"