From 6b7ce6a7946441e7b03864fc3a4a3313d28d61b7 Mon Sep 17 00:00:00 2001 From: Edgar Khanzadian Date: Wed, 6 Dec 2023 10:46:20 +0400 Subject: [PATCH 01/18] fix: keep search params while doing background location redirect --- .../hooks/use-background-location-redirect.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app/routes/hooks/use-background-location-redirect.ts b/src/app/routes/hooks/use-background-location-redirect.ts index b5803ca1e0d..bca4ad2aa79 100644 --- a/src/app/routes/hooks/use-background-location-redirect.ts +++ b/src/app/routes/hooks/use-background-location-redirect.ts @@ -11,18 +11,21 @@ import { useLocationState } from '@app/common/hooks/use-location-state'; */ export function useBackgroundLocationRedirect(baseUrl = RouteUrls.Home) { - const { pathname, state } = useLocation(); + const { pathname, state, search } = useLocation(); const navigate = useNavigate(); const backgroundLocation = useLocationState('backgroundLocation'); useEffect(() => { void (async () => { if (backgroundLocation === undefined) { - return navigate(pathname, { - state: { backgroundLocation: { pathname: baseUrl }, ...state }, - }); + return navigate( + { pathname, search }, + { + state: { backgroundLocation: { pathname: baseUrl }, ...state }, + } + ); } return false; })(); - }, [backgroundLocation, baseUrl, navigate, pathname, state]); + }, [backgroundLocation, baseUrl, navigate, pathname, state, search]); } From e77a8d8db6879325c11003d36ef2e3ab8ad8af16 Mon Sep 17 00:00:00 2001 From: Edgar Khanzadian Date: Wed, 6 Dec 2023 19:52:13 +0400 Subject: [PATCH 02/18] feat: add rpc method for signing stacks messages --- .../components/clarity-value-list.tsx | 0 .../components/message-signing-disclaimer.tsx | 2 +- .../components/nested-tuple-displayer.tsx | 0 .../stacks-signature-message-content.tsx | 41 ++++++ .../components/structured-data-box.tsx | 0 .../components/structured-data-content.tsx | 43 ++++++ .../stacks-message-signing.tsx | 89 +++++++++++++ .../stacks-message-signing.utils.ts | 32 +++++ .../use-sign-stacks-message.ts | 61 +++++++++ .../rpc-sign-stacks-message.tsx | 32 +++++ .../use-rpc-sign-stacks-message.ts | 125 ++++++++++++++++++ .../sign-stacks-message-request.tsx | 31 +++++ .../use-sign-stacks-message-request.ts | 67 ++++++++++ .../stacks-signature-message-content.tsx | 34 ----- .../components/structured-data-content.tsx | 34 ----- .../stacks-message-signing-request.tsx | 43 ------ .../stacks-message-signing.utils.ts | 88 ------------ src/app/routes/request-routes.tsx | 4 +- src/app/routes/rpc-routes.tsx | 17 +++ src/app/store/signatures/requests.hooks.ts | 3 +- .../messaging/rpc-message-handler.ts | 6 + .../rpc-methods/sign-stacks-message.ts | 78 +++++++++++ src/shared/route-urls.ts | 1 + src/shared/rpc/methods/sign-stacks-message.ts | 36 +++++ src/shared/rpc/rpc-methods.ts | 4 +- test-app/src/components/signature.tsx | 38 ++++++ 26 files changed, 704 insertions(+), 205 deletions(-) rename src/app/{pages/stacks-message-signing-request => features/stacks-message-signer}/components/clarity-value-list.tsx (100%) rename src/app/{pages/stacks-message-signing-request => features/stacks-message-signer}/components/message-signing-disclaimer.tsx (95%) rename src/app/{pages/stacks-message-signing-request => features/stacks-message-signer}/components/nested-tuple-displayer.tsx (100%) create mode 100644 src/app/features/stacks-message-signer/components/stacks-signature-message-content.tsx rename src/app/{pages/stacks-message-signing-request => features/stacks-message-signer}/components/structured-data-box.tsx (100%) create mode 100644 src/app/features/stacks-message-signer/components/structured-data-content.tsx create mode 100644 src/app/features/stacks-message-signer/stacks-message-signing.tsx create mode 100644 src/app/features/stacks-message-signer/stacks-message-signing.utils.ts create mode 100644 src/app/features/stacks-message-signer/use-sign-stacks-message.ts create mode 100644 src/app/pages/rpc-sign-stacks-message/rpc-sign-stacks-message.tsx create mode 100644 src/app/pages/rpc-sign-stacks-message/use-rpc-sign-stacks-message.ts create mode 100644 src/app/pages/sign-stacks-message-request/sign-stacks-message-request.tsx create mode 100644 src/app/pages/sign-stacks-message-request/use-sign-stacks-message-request.ts delete mode 100644 src/app/pages/stacks-message-signing-request/components/stacks-signature-message-content.tsx delete mode 100644 src/app/pages/stacks-message-signing-request/components/structured-data-content.tsx delete mode 100644 src/app/pages/stacks-message-signing-request/stacks-message-signing-request.tsx delete mode 100644 src/app/pages/stacks-message-signing-request/stacks-message-signing.utils.ts create mode 100644 src/background/messaging/rpc-methods/sign-stacks-message.ts create mode 100644 src/shared/rpc/methods/sign-stacks-message.ts diff --git a/src/app/pages/stacks-message-signing-request/components/clarity-value-list.tsx b/src/app/features/stacks-message-signer/components/clarity-value-list.tsx similarity index 100% rename from src/app/pages/stacks-message-signing-request/components/clarity-value-list.tsx rename to src/app/features/stacks-message-signer/components/clarity-value-list.tsx diff --git a/src/app/pages/stacks-message-signing-request/components/message-signing-disclaimer.tsx b/src/app/features/stacks-message-signer/components/message-signing-disclaimer.tsx similarity index 95% rename from src/app/pages/stacks-message-signing-request/components/message-signing-disclaimer.tsx rename to src/app/features/stacks-message-signer/components/message-signing-disclaimer.tsx index 181f6222c67..5beb7e0a68e 100644 --- a/src/app/pages/stacks-message-signing-request/components/message-signing-disclaimer.tsx +++ b/src/app/features/stacks-message-signer/components/message-signing-disclaimer.tsx @@ -1,7 +1,7 @@ import { Disclaimer } from '@app/components/disclaimer'; interface DisclaimerProps { - appName?: string; + appName?: string | null; } export function StacksMessageSigningDisclaimer({ appName }: DisclaimerProps) { return ( diff --git a/src/app/pages/stacks-message-signing-request/components/nested-tuple-displayer.tsx b/src/app/features/stacks-message-signer/components/nested-tuple-displayer.tsx similarity index 100% rename from src/app/pages/stacks-message-signing-request/components/nested-tuple-displayer.tsx rename to src/app/features/stacks-message-signer/components/nested-tuple-displayer.tsx diff --git a/src/app/features/stacks-message-signer/components/stacks-signature-message-content.tsx b/src/app/features/stacks-message-signer/components/stacks-signature-message-content.tsx new file mode 100644 index 00000000000..7e0160ccf6c --- /dev/null +++ b/src/app/features/stacks-message-signer/components/stacks-signature-message-content.tsx @@ -0,0 +1,41 @@ +import { ChainID, bytesToHex } from '@stacks/common'; +import { hashMessage } from '@stacks/encryption'; + +import { UnsignedMessage } from '@shared/signature/signature-types'; + +import { NoFeesWarningRow } from '@app/components/no-fees-warning-row'; + +import { MessagePreviewBox } from '../../../features/message-signer/message-preview-box'; +import { SignMessageActions } from '../../../features/message-signer/stacks-sign-message-action'; +import { Utf8Payload } from '../stacks-message-signing'; +import { StacksMessageSigningDisclaimer } from './message-signing-disclaimer'; + +interface SignatureRequestMessageContentProps { + isLoading: boolean; + onSignMessage(unsignedMessage: UnsignedMessage): Promise; + onCancelMessageSigning(): void; + payload: Utf8Payload; +} +export function StacksSignatureRequestMessageContent({ + isLoading, + onSignMessage, + onCancelMessageSigning, + payload, +}: SignatureRequestMessageContentProps) { + return ( + <> + + + onSignMessage({ messageType: 'utf8', message: payload.message })} + /> +
+ + + ); +} diff --git a/src/app/pages/stacks-message-signing-request/components/structured-data-box.tsx b/src/app/features/stacks-message-signer/components/structured-data-box.tsx similarity index 100% rename from src/app/pages/stacks-message-signing-request/components/structured-data-box.tsx rename to src/app/features/stacks-message-signer/components/structured-data-box.tsx diff --git a/src/app/features/stacks-message-signer/components/structured-data-content.tsx b/src/app/features/stacks-message-signer/components/structured-data-content.tsx new file mode 100644 index 00000000000..b4043798c68 --- /dev/null +++ b/src/app/features/stacks-message-signer/components/structured-data-content.tsx @@ -0,0 +1,43 @@ +import { ChainID } from '@stacks/common'; + +import { UnsignedMessage } from '@shared/signature/signature-types'; + +import { NoFeesWarningRow } from '@app/components/no-fees-warning-row'; +import { SignMessageActions } from '@app/features/message-signer/stacks-sign-message-action'; + +import { StructuredPayload } from '../stacks-message-signing'; +import { StacksMessageSigningDisclaimer } from './message-signing-disclaimer'; +import { StructuredDataBox } from './structured-data-box'; + +interface SignatureRequestStructuredDataContentProps { + isLoading: boolean; + onSignMessage(unsignedMessage: UnsignedMessage): Promise; + onCancelMessageSigning(): void; + payload: StructuredPayload; +} +export function SignatureRequestStructuredDataContent({ + isLoading, + onSignMessage, + onCancelMessageSigning, + payload, +}: SignatureRequestStructuredDataContentProps) { + return ( + <> + + + + onSignMessage({ + messageType: 'structured', + message: payload.message, + domain: payload.domain, + }) + } + /> +
+ + + ); +} diff --git a/src/app/features/stacks-message-signer/stacks-message-signing.tsx b/src/app/features/stacks-message-signer/stacks-message-signing.tsx new file mode 100644 index 00000000000..a4193804182 --- /dev/null +++ b/src/app/features/stacks-message-signer/stacks-message-signing.tsx @@ -0,0 +1,89 @@ +import { Outlet } from 'react-router-dom'; + +import { StacksNetwork } from '@stacks/network'; +import { ClarityValue } from '@stacks/transactions/dist/esm/clarity'; + +import { + SignedMessageType, + StructuredMessageDataDomain, + UnsignedMessage, + isSignableMessageType, + isStructuredMessageType, + isUtf8MessageType, +} from '@shared/signature/signature-types'; +import { closeWindow } from '@shared/utils'; + +import { useRouteHeader } from '@app/common/hooks/use-route-header'; +import { PopupHeader } from '@app/features/current-account/popup-header'; +import { MessageSigningHeader } from '@app/features/message-signer/message-signing-header'; +import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; + +import { MessageSigningRequestLayout } from '../message-signer/message-signing-request.layout'; +import { StacksSignatureRequestMessageContent } from './components/stacks-signature-message-content'; +import { SignatureRequestStructuredDataContent } from './components/structured-data-content'; + +export interface Utf8Payload { + messageType: 'utf8'; + message: string; + network: StacksNetwork | undefined; + appName: string | undefined | null; +} + +export interface StructuredPayload { + messageType: 'structured'; + message: ClarityValue; + network: StacksNetwork | undefined; + appName: string | undefined | null; + domain: StructuredMessageDataDomain; +} + +interface StacksMessageSigningProps { + messageType: SignedMessageType; + tabId: number | null; + origin: string | null; + isLoading: boolean; + onSignMessage(unsignedMessage: UnsignedMessage): Promise; + onCancelMessageSigning(): void; + payload: Utf8Payload | StructuredPayload; +} + +export function StacksMessageSigning({ + messageType, + tabId, + origin, + isLoading, + onSignMessage, + onCancelMessageSigning, + payload, +}: StacksMessageSigningProps) { + useRouteHeader(); + useOnOriginTabClose(() => closeWindow()); + + if (!tabId) return null; + if (!isSignableMessageType(messageType)) return null; + if (!origin) return null; + + return ( + + + + {isUtf8MessageType(messageType) && payload.messageType === 'utf8' && ( + + )} + {isStructuredMessageType(messageType) && payload.messageType === 'structured' && ( + + )} + + + ); +} diff --git a/src/app/features/stacks-message-signer/stacks-message-signing.utils.ts b/src/app/features/stacks-message-signer/stacks-message-signing.utils.ts new file mode 100644 index 00000000000..f08152512cb --- /dev/null +++ b/src/app/features/stacks-message-signer/stacks-message-signing.utils.ts @@ -0,0 +1,32 @@ +import { useCallback } from 'react'; + +import { ClarityValue, TupleCV, createStacksPrivateKey } from '@stacks/transactions'; + +import { signMessage, signStructuredDataMessage } from '@shared/crypto/sign-message'; +import { isString } from '@shared/utils'; + +import { createDelay } from '@app/common/utils'; +import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; + +export const improveUxWithShortDelayAsStacksSigningIsSoFast = createDelay(1000); + +export function useMessageSignerStacksSoftwareWallet() { + const account = useCurrentStacksAccount(); + return useCallback( + ({ message, domain }: { message: string | ClarityValue; domain?: TupleCV }) => { + if (!account || account.type === 'ledger') return null; + + const privateKey = createStacksPrivateKey(account.stxPrivateKey); + + if (isString(message)) { + return signMessage(message, privateKey); + } else { + if (!domain) throw new Error('Domain is required for structured messages'); + + // returns signature in RSV format + return signStructuredDataMessage(message, domain, privateKey); + } + }, + [account] + ); +} diff --git a/src/app/features/stacks-message-signer/use-sign-stacks-message.ts b/src/app/features/stacks-message-signer/use-sign-stacks-message.ts new file mode 100644 index 00000000000..285f562f3ec --- /dev/null +++ b/src/app/features/stacks-message-signer/use-sign-stacks-message.ts @@ -0,0 +1,61 @@ +import { useState } from 'react'; + +import { SignatureData } from '@stacks/connect'; + +import { logger } from '@shared/logger'; +import { UnsignedMessage, whenSignableMessageOfType } from '@shared/signature/signature-types'; + +import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; +import { useWalletType } from '@app/common/use-wallet-type'; +import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate'; +import { + improveUxWithShortDelayAsStacksSigningIsSoFast, + useMessageSignerStacksSoftwareWallet, +} from '@app/features/stacks-message-signer/stacks-message-signing.utils'; + +interface SignStacksMessageProps { + onSignMessageCompleted(messageSignature: SignatureData): void; +} + +export function useSignStacksMessage({ onSignMessageCompleted }: SignStacksMessageProps) { + const analytics = useAnalytics(); + const signSoftwareWalletMessage = useMessageSignerStacksSoftwareWallet(); + + const { whenWallet } = useWalletType(); + const ledgerNavigate = useLedgerNavigate(); + + const [isLoading, setIsLoading] = useState(false); + + const signMessage = whenWallet({ + async software(unsignedMessage: UnsignedMessage) { + setIsLoading(true); + void analytics.track('request_signature_sign', { type: 'software' }); + + const messageSignature = signSoftwareWalletMessage(unsignedMessage); + + if (!messageSignature) { + logger.error('Cannot sign message, no account in state'); + void analytics.track('request_signature_cannot_sign_message_no_account'); + return; + } + await improveUxWithShortDelayAsStacksSigningIsSoFast(); + setIsLoading(false); + + onSignMessageCompleted(messageSignature); + }, + + async ledger(unsignedMessage: UnsignedMessage) { + void analytics.track('request_signature_sign', { type: 'ledger' }); + whenSignableMessageOfType(unsignedMessage)({ + utf8(msg) { + ledgerNavigate.toConnectAndSignUtf8MessageStep(msg); + }, + structured(domain, msg) { + ledgerNavigate.toConnectAndSignStructuredMessageStep(domain, msg); + }, + }); + }, + }); + + return { isLoading, signMessage }; +} diff --git a/src/app/pages/rpc-sign-stacks-message/rpc-sign-stacks-message.tsx b/src/app/pages/rpc-sign-stacks-message/rpc-sign-stacks-message.tsx new file mode 100644 index 00000000000..d2d28bc9f12 --- /dev/null +++ b/src/app/pages/rpc-sign-stacks-message/rpc-sign-stacks-message.tsx @@ -0,0 +1,32 @@ +import { isSignableMessageType } from '@shared/signature/signature-types'; + +import { StacksMessageSigning } from '@app/features/stacks-message-signer/stacks-message-signing'; + +import { + useRpcSignStacksMessage, + useRpcSignStacksMessageParams, + useRpcStacksMessagePayload, +} from './use-rpc-sign-stacks-message'; + +export function RpcStacksMessageSigning() { + const { requestId, messageType, tabId, origin } = useRpcSignStacksMessageParams(); + const { isLoading, signMessage, onCancelMessageSigning } = useRpcSignStacksMessage(); + const payload = useRpcStacksMessagePayload(); + + if (!requestId || !tabId) return null; + if (!isSignableMessageType(messageType)) return null; + if (!origin) return null; + if (!payload) return null; + + return ( + + ); +} diff --git a/src/app/pages/rpc-sign-stacks-message/use-rpc-sign-stacks-message.ts b/src/app/pages/rpc-sign-stacks-message/use-rpc-sign-stacks-message.ts new file mode 100644 index 00000000000..f0e6e5b42ec --- /dev/null +++ b/src/app/pages/rpc-sign-stacks-message/use-rpc-sign-stacks-message.ts @@ -0,0 +1,125 @@ +import { useMemo } from 'react'; +import { useSearchParams } from 'react-router-dom'; + +import { RpcErrorCode } from '@btckit/types'; +import { StacksNetwork } from '@stacks/network'; +import { deserializeCV } from '@stacks/transactions'; + +import { makeRpcErrorResponse, makeRpcSuccessResponse } from '@shared/rpc/rpc-methods'; +import { + isSignableMessageType, + isStructuredMessageType, + isUtf8MessageType, +} from '@shared/signature/signature-types'; +import { closeWindow } from '@shared/utils'; + +import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; +import { useDefaultRequestParams } from '@app/common/hooks/use-default-request-search-params'; +import { + StructuredPayload, + Utf8Payload, +} from '@app/features/stacks-message-signer/stacks-message-signing'; +import { useSignStacksMessage } from '@app/features/stacks-message-signer/use-sign-stacks-message'; + +function getNetwork(networkName: string | null) { + if ( + networkName === 'mainnet' || + networkName === 'testnet' || + networkName === 'devnet' || + networkName === 'mocknet' + ) { + return StacksNetwork.fromName(networkName); + } + return; +} + +export function useRpcStacksMessagePayload() { + const { messageType, message, network, appName, domain } = useRpcSignStacksMessageParams(); + + if (isUtf8MessageType(messageType)) { + return { + messageType: 'utf8' as const, + message, + network: getNetwork(network), + appName, + } satisfies Utf8Payload; + } + if (isStructuredMessageType(messageType)) { + if (!domain) return null; + + return { + messageType: 'structured' as const, + message: deserializeCV(Buffer.from(message, 'hex')), + domain: deserializeCV(Buffer.from(domain, 'hex')), + network: getNetwork(network), + appName, + } satisfies StructuredPayload; + } + return null; +} + +export function useRpcSignStacksMessageParams() { + const [searchParams] = useSearchParams(); + const { origin, tabId } = useDefaultRequestParams(); + const requestId = searchParams.get('requestId'); + const network = searchParams.get('network'); + const appName = searchParams.get('appName'); + const message = searchParams.get('message'); + const messageType = searchParams.get('messageType'); + const domain = searchParams.get('domain'); + + if (!requestId || !message || !origin || !isSignableMessageType(messageType)) + throw new Error('Invalid params'); + + return useMemo( + () => ({ + origin, + tabId: tabId ?? 0, + requestId, + network, + message, + messageType, + appName, + domain, + }), + [origin, requestId, network, message, messageType, tabId, appName, domain] + ); +} + +export function useRpcSignStacksMessage() { + const analytics = useAnalytics(); + + const { tabId, requestId } = useRpcSignStacksMessageParams(); + if (!tabId) throw new Error('Requests can only be made with corresponding tab'); + + const { isLoading, signMessage } = useSignStacksMessage({ + onSignMessageCompleted: messageSignature => { + chrome.tabs.sendMessage( + tabId, + makeRpcSuccessResponse('stx_signMessage', { + id: requestId, + result: { signature: messageSignature.signature }, + }) + ); + closeWindow(); + }, + }); + + function onCancelMessageSigning() { + if (!requestId || !tabId) return; + void analytics.track('request_signature_cancel'); + chrome.tabs.sendMessage( + tabId, + makeRpcErrorResponse('stx_signMessage', { + id: requestId, + error: { + message: 'User denied signing', + code: RpcErrorCode.USER_REJECTION, + }, + }) + ); + closeWindow(); + } + + return { isLoading, signMessage, onCancelMessageSigning }; +} diff --git a/src/app/pages/sign-stacks-message-request/sign-stacks-message-request.tsx b/src/app/pages/sign-stacks-message-request/sign-stacks-message-request.tsx new file mode 100644 index 00000000000..1214ea51515 --- /dev/null +++ b/src/app/pages/sign-stacks-message-request/sign-stacks-message-request.tsx @@ -0,0 +1,31 @@ +import { isSignableMessageType } from '@shared/signature/signature-types'; + +import { StacksMessageSigning } from '@app/features/stacks-message-signer/stacks-message-signing'; +import { + useSignStacksMessageRequest, + useStacksMessageRequestPayload, +} from '@app/pages/sign-stacks-message-request/use-sign-stacks-message-request'; +import { useSignatureRequestSearchParams } from '@app/store/signatures/requests.hooks'; + +export function SignStacksMessageRequest() { + const { requestToken, messageType, tabId, origin } = useSignatureRequestSearchParams(); + const { isLoading, signMessage, onCancelMessageSigning } = useSignStacksMessageRequest(); + const payload = useStacksMessageRequestPayload(); + + if (!requestToken || !tabId) return null; + if (!isSignableMessageType(messageType)) return null; + if (!origin) return null; + if (!payload) return null; + + return ( + + ); +} diff --git a/src/app/pages/sign-stacks-message-request/use-sign-stacks-message-request.ts b/src/app/pages/sign-stacks-message-request/use-sign-stacks-message-request.ts new file mode 100644 index 00000000000..d260811062c --- /dev/null +++ b/src/app/pages/sign-stacks-message-request/use-sign-stacks-message-request.ts @@ -0,0 +1,67 @@ +import { finalizeMessageSignature } from '@shared/actions/finalize-message-signature'; +import { isStructuredMessageType, isUtf8MessageType } from '@shared/signature/signature-types'; + +import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; +import { + getSignaturePayloadFromToken, + getStructuredDataPayloadFromToken, +} from '@app/common/signature/requests'; +import { + StructuredPayload, + Utf8Payload, +} from '@app/features/stacks-message-signer/stacks-message-signing'; +import { useSignStacksMessage } from '@app/features/stacks-message-signer/use-sign-stacks-message'; +import { useSignatureRequestSearchParams } from '@app/store/signatures/requests.hooks'; + +export function useStacksMessageRequestPayload() { + const { requestToken, messageType } = useSignatureRequestSearchParams(); + + if (!requestToken) return null; + + if (isUtf8MessageType(messageType)) { + const signatureRequest = getSignaturePayloadFromToken(requestToken); + const { message, network } = signatureRequest; + const appName = signatureRequest.appDetails?.name; + return { + messageType: 'utf8' as const, + message, + network, + appName, + } satisfies Utf8Payload; + } + if (isStructuredMessageType(messageType)) { + const signatureRequest = getStructuredDataPayloadFromToken(requestToken); + const { message, network, domain } = signatureRequest; + const appName = signatureRequest.appDetails?.name; + return { + messageType: 'structured' as const, + message, + network, + appName, + domain, + } satisfies StructuredPayload; + } + return null; +} + +export function useSignStacksMessageRequest() { + const analytics = useAnalytics(); + + const { requestToken, tabId } = useSignatureRequestSearchParams(); + if (!tabId) throw new Error('Requests can only be made with corresponding tab'); + if (!requestToken) throw new Error('Missing request token'); + + const { isLoading, signMessage } = useSignStacksMessage({ + onSignMessageCompleted: messageSignature => { + finalizeMessageSignature({ requestPayload: requestToken, tabId, data: messageSignature }); + }, + }); + + function onCancelMessageSigning() { + if (!requestToken || !tabId) return; + void analytics.track('request_signature_cancel'); + finalizeMessageSignature({ requestPayload: requestToken, tabId, data: 'cancel' }); + } + + return { isLoading, signMessage, onCancelMessageSigning }; +} diff --git a/src/app/pages/stacks-message-signing-request/components/stacks-signature-message-content.tsx b/src/app/pages/stacks-message-signing-request/components/stacks-signature-message-content.tsx deleted file mode 100644 index 5cf11adc86f..00000000000 --- a/src/app/pages/stacks-message-signing-request/components/stacks-signature-message-content.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { ChainID, bytesToHex } from '@stacks/common'; -import { hashMessage } from '@stacks/encryption'; - -import { getSignaturePayloadFromToken } from '@app/common/signature/requests'; -import { NoFeesWarningRow } from '@app/components/no-fees-warning-row'; - -import { MessagePreviewBox } from '../../../features/message-signer/message-preview-box'; -import { SignMessageActions } from '../../../features/message-signer/stacks-sign-message-action'; -import { useStacksMessageSigner } from '../stacks-message-signing.utils'; -import { StacksMessageSigningDisclaimer } from './message-signing-disclaimer'; - -interface SignatureRequestMessageContentProps { - requestToken: string; -} -export function StacksSignatureRequestMessageContent(props: SignatureRequestMessageContentProps) { - const { requestToken } = props; - const { isLoading, cancelMessageSigning, signMessage } = useStacksMessageSigner(); - const signatureRequest = getSignaturePayloadFromToken(requestToken); - const { message, network } = signatureRequest; - const appName = signatureRequest.appDetails?.name; - return ( - <> - - - signMessage({ messageType: 'utf8', message })} - /> -
- - - ); -} diff --git a/src/app/pages/stacks-message-signing-request/components/structured-data-content.tsx b/src/app/pages/stacks-message-signing-request/components/structured-data-content.tsx deleted file mode 100644 index 41e0b0a7c24..00000000000 --- a/src/app/pages/stacks-message-signing-request/components/structured-data-content.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { ChainID } from '@stacks/common'; - -import { getStructuredDataPayloadFromToken } from '@app/common/signature/requests'; -import { NoFeesWarningRow } from '@app/components/no-fees-warning-row'; -import { SignMessageActions } from '@app/features/message-signer/stacks-sign-message-action'; - -import { useStacksMessageSigner } from '../stacks-message-signing.utils'; -import { StacksMessageSigningDisclaimer } from './message-signing-disclaimer'; -import { StructuredDataBox } from './structured-data-box'; - -interface SignatureRequestStructuredDataContentProps { - requestToken: string; -} -export function SignatureRequestStructuredDataContent({ - requestToken, -}: SignatureRequestStructuredDataContentProps) { - const { isLoading, signMessage, cancelMessageSigning } = useStacksMessageSigner(); - const signatureRequest = getStructuredDataPayloadFromToken(requestToken); - const { domain, message, network } = signatureRequest; - const appName = signatureRequest.appDetails?.name; - return ( - <> - - - signMessage({ messageType: 'structured', message, domain })} - /> -
- - - ); -} diff --git a/src/app/pages/stacks-message-signing-request/stacks-message-signing-request.tsx b/src/app/pages/stacks-message-signing-request/stacks-message-signing-request.tsx deleted file mode 100644 index 18eddaf83c6..00000000000 --- a/src/app/pages/stacks-message-signing-request/stacks-message-signing-request.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Outlet } from 'react-router-dom'; - -import { - isSignableMessageType, - isStructuredMessageType, - isUtf8MessageType, -} from '@shared/signature/signature-types'; -import { closeWindow } from '@shared/utils'; - -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { PopupHeader } from '@app/features/current-account/popup-header'; -import { MessageSigningHeader } from '@app/features/message-signer/message-signing-header'; -import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; -import { useSignatureRequestSearchParams } from '@app/store/signatures/requests.hooks'; - -import { MessageSigningRequestLayout } from '../../features/message-signer/message-signing-request.layout'; -import { StacksSignatureRequestMessageContent } from './components/stacks-signature-message-content'; -import { SignatureRequestStructuredDataContent } from './components/structured-data-content'; - -export function StacksMessageSigningRequest() { - useRouteHeader(); - useOnOriginTabClose(() => closeWindow()); - - const { requestToken, messageType, tabId, origin } = useSignatureRequestSearchParams(); - - if (!requestToken || !tabId) return null; - if (!isSignableMessageType(messageType)) return null; - if (!origin) return null; - - return ( - - - - {isUtf8MessageType(messageType) && ( - - )} - {isStructuredMessageType(messageType) && ( - - )} - - - ); -} diff --git a/src/app/pages/stacks-message-signing-request/stacks-message-signing.utils.ts b/src/app/pages/stacks-message-signing-request/stacks-message-signing.utils.ts deleted file mode 100644 index cee5549c071..00000000000 --- a/src/app/pages/stacks-message-signing-request/stacks-message-signing.utils.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { useCallback, useState } from 'react'; - -import { ClarityValue, TupleCV, createStacksPrivateKey } from '@stacks/transactions'; - -import { finalizeMessageSignature } from '@shared/actions/finalize-message-signature'; -import { signMessage, signStructuredDataMessage } from '@shared/crypto/sign-message'; -import { logger } from '@shared/logger'; -import { UnsignedMessage, whenSignableMessageOfType } from '@shared/signature/signature-types'; -import { isString } from '@shared/utils'; - -import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; -import { useWalletType } from '@app/common/use-wallet-type'; -import { createDelay } from '@app/common/utils'; -import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { useSignatureRequestSearchParams } from '@app/store/signatures/requests.hooks'; - -const improveUxWithShortDelayAsSigningIsSoFast = createDelay(1000); - -function useMessageSignerStacksSoftwareWallet() { - const account = useCurrentStacksAccount(); - return useCallback( - ({ message, domain }: { message: string | ClarityValue; domain?: TupleCV }) => { - if (!account || account.type === 'ledger') return null; - const privateKey = createStacksPrivateKey(account.stxPrivateKey); - if (isString(message)) { - return signMessage(message, privateKey); - } else { - if (!domain) throw new Error('Domain is required for structured messages'); - // returns signature in RSV format - return signStructuredDataMessage(message, domain, privateKey); - } - }, - [account] - ); -} - -export function useStacksMessageSigner() { - const analytics = useAnalytics(); - const signSoftwareWalletMessage = useMessageSignerStacksSoftwareWallet(); - - const { whenWallet } = useWalletType(); - const ledgerNavigate = useLedgerNavigate(); - - const [isLoading, setIsLoading] = useState(false); - - const { requestToken, tabId } = useSignatureRequestSearchParams(); - if (!tabId) throw new Error('Requests can only be made with corresponding tab'); - if (!requestToken) throw new Error('Missing request token'); - - const signMessage = whenWallet({ - async software(unsignedMessage: UnsignedMessage) { - setIsLoading(true); - void analytics.track('request_signature_sign', { type: 'software' }); - - const messageSignature = signSoftwareWalletMessage(unsignedMessage); - - if (!messageSignature) { - logger.error('Cannot sign message, no account in state'); - void analytics.track('request_signature_cannot_sign_message_no_account'); - return; - } - await improveUxWithShortDelayAsSigningIsSoFast(); - setIsLoading(false); - finalizeMessageSignature({ requestPayload: requestToken, tabId, data: messageSignature }); - }, - - async ledger(unsignedMessage: UnsignedMessage) { - void analytics.track('request_signature_sign', { type: 'ledger' }); - whenSignableMessageOfType(unsignedMessage)({ - utf8(msg) { - ledgerNavigate.toConnectAndSignUtf8MessageStep(msg); - }, - structured(domain, msg) { - ledgerNavigate.toConnectAndSignStructuredMessageStep(domain, msg); - }, - }); - }, - }); - - function cancelMessageSigning() { - if (!requestToken || !tabId) return; - void analytics.track('request_signature_cancel'); - finalizeMessageSignature({ requestPayload: requestToken, tabId, data: 'cancel' }); - } - - return { isLoading, signMessage, cancelMessageSigning }; -} diff --git a/src/app/routes/request-routes.tsx b/src/app/routes/request-routes.tsx index 2551baaae4b..1b0e931009b 100644 --- a/src/app/routes/request-routes.tsx +++ b/src/app/routes/request-routes.tsx @@ -8,7 +8,7 @@ import { EditNonceDrawer } from '@app/features/edit-nonce-drawer/edit-nonce-draw import { ledgerStacksMessageSigningRoutes } from '@app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg.routes'; import { ledgerStacksTxSigningRoutes } from '@app/features/ledger/flows/stacks-tx-signing/ledger-sign-stacks-tx-container'; import { PsbtRequest } from '@app/pages/psbt-request/psbt-request'; -import { StacksMessageSigningRequest } from '@app/pages/stacks-message-signing-request/stacks-message-signing-request'; +import { SignStacksMessageRequest } from '@app/pages/sign-stacks-message-request/sign-stacks-message-request'; import { TransactionRequest } from '@app/pages/transaction-request/transaction-request'; import { ProfileUpdateRequest } from '@app/pages/update-profile-request/update-profile-request'; import { AccountGate } from '@app/routes/account-gate'; @@ -35,7 +35,7 @@ export const legacyRequestRoutes = ( element={ }> - + } diff --git a/src/app/routes/rpc-routes.tsx b/src/app/routes/rpc-routes.tsx index 391fa035fd0..8893db05c47 100644 --- a/src/app/routes/rpc-routes.tsx +++ b/src/app/routes/rpc-routes.tsx @@ -1,14 +1,19 @@ +import { Suspense } from 'react'; import { Route } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; import { ledgerBitcoinTxSigningRoutes } from '@app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container'; +import { ledgerStacksMessageSigningRoutes } from '@app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg.routes'; import { RpcGetAddresses } from '@app/pages/rpc-get-addresses/rpc-get-addresses'; import { rpcSendTransferRoutes } from '@app/pages/rpc-send-transfer/rpc-send-transfer.routes'; import { RpcSignPsbt } from '@app/pages/rpc-sign-psbt/rpc-sign-psbt'; import { RpcSignPsbtSummary } from '@app/pages/rpc-sign-psbt/rpc-sign-psbt-summary'; +import { RpcStacksMessageSigning } from '@app/pages/rpc-sign-stacks-message/rpc-sign-stacks-message'; import { AccountGate } from '@app/routes/account-gate'; +import { SuspenseLoadingSpinner } from './app-routes'; + export const rpcRequestRoutes = ( <> } /> + + }> + + + + } + > + {ledgerStacksMessageSigningRoutes} + ); diff --git a/src/app/store/signatures/requests.hooks.ts b/src/app/store/signatures/requests.hooks.ts index 5ec4ca0e599..793c55903ac 100644 --- a/src/app/store/signatures/requests.hooks.ts +++ b/src/app/store/signatures/requests.hooks.ts @@ -1,7 +1,6 @@ import { useMemo } from 'react'; import { SignedMessageType } from '@shared/signature/signature-types'; -import { isString } from '@shared/utils'; import { useDefaultRequestParams } from '@app/common/hooks/use-default-request-search-params'; import { initialSearchParams } from '@app/common/initial-search-params'; @@ -16,7 +15,7 @@ export function useSignatureRequestSearchParams() { const messageType = initialSearchParams.get('messageType') as SignedMessageType; return { - tabId: isString(tabId) ? parseInt(tabId, 10) : tabId, + tabId, requestToken, origin, messageType, diff --git a/src/background/messaging/rpc-message-handler.ts b/src/background/messaging/rpc-message-handler.ts index 5856cc955d8..2d2efe85208 100644 --- a/src/background/messaging/rpc-message-handler.ts +++ b/src/background/messaging/rpc-message-handler.ts @@ -10,6 +10,7 @@ import { rpcGetAddresses } from './rpc-methods/get-addresses'; import { rpcSendTransfer } from './rpc-methods/send-transfer'; import { rpcSignMessage } from './rpc-methods/sign-message'; import { rpcSignPsbt } from './rpc-methods/sign-psbt'; +import { rpcSignStacksMessage } from './rpc-methods/sign-stacks-message'; import { rpcSupportedMethods } from './rpc-methods/supported-methods'; export async function rpcMessageHandler(message: WalletRequests, port: chrome.runtime.Port) { @@ -49,6 +50,11 @@ export async function rpcMessageHandler(message: WalletRequests, port: chrome.ru break; } + case 'stx_signMessage': { + await rpcSignStacksMessage(message, port); + break; + } + default: chrome.tabs.sendMessage( getTabIdFromPort(port), diff --git a/src/background/messaging/rpc-methods/sign-stacks-message.ts b/src/background/messaging/rpc-methods/sign-stacks-message.ts new file mode 100644 index 00000000000..dc63250f709 --- /dev/null +++ b/src/background/messaging/rpc-methods/sign-stacks-message.ts @@ -0,0 +1,78 @@ +import { RpcErrorCode } from '@btckit/types'; + +import { RouteUrls } from '@shared/route-urls'; +import { + SignStacksMessageRequest, + getRpcSignStacksMessageParamErrors, + validateRpcSignStacksMessageParams, +} from '@shared/rpc/methods/sign-stacks-message'; +import { makeRpcErrorResponse } from '@shared/rpc/rpc-methods'; +import { isDefined, isUndefined } from '@shared/utils'; + +import { + RequestParams, + getTabIdFromPort, + listenForPopupClose, + makeSearchParamsWithDefaults, + triggerRequestWindowOpen, +} from '../messaging-utils'; + +export async function rpcSignStacksMessage( + message: SignStacksMessageRequest, + port: chrome.runtime.Port +) { + if (isUndefined(message.params)) { + chrome.tabs.sendMessage( + getTabIdFromPort(port), + makeRpcErrorResponse('stx_signMessage', { + id: message.id, + error: { code: RpcErrorCode.INVALID_REQUEST, message: 'Parameters undefined' }, + }) + ); + return; + } + + if (!validateRpcSignStacksMessageParams(message.params)) { + chrome.tabs.sendMessage( + getTabIdFromPort(port), + makeRpcErrorResponse('stx_signMessage', { + id: message.id, + error: { + code: RpcErrorCode.INVALID_PARAMS, + message: getRpcSignStacksMessageParamErrors(message.params), + }, + }) + ); + return; + } + + const requestParams: RequestParams = [ + ['message', message.params.message], + ['messageType', message.params.messageType], + ['requestId', message.id], + ]; + + if (isDefined(message.params.network)) { + requestParams.push(['network', message.params.network.toString()]); + } + + if (isDefined(message.params.domain)) { + requestParams.push(['domain', message.params.domain.toString()]); + } + + const { urlParams, tabId } = makeSearchParamsWithDefaults(port, requestParams); + + const { id } = await triggerRequestWindowOpen(RouteUrls.RpcStacksSignature, urlParams); + + listenForPopupClose({ + tabId, + id, + response: makeRpcErrorResponse('stx_signMessage', { + id: message.id, + error: { + code: RpcErrorCode.USER_REJECTION, + message: 'User rejected the Stacks message signing request', + }, + }), + }); +} diff --git a/src/shared/route-urls.ts b/src/shared/route-urls.ts index c2c9a64ff8f..f0b247bbea5 100644 --- a/src/shared/route-urls.ts +++ b/src/shared/route-urls.ts @@ -105,6 +105,7 @@ export enum RouteUrls { RpcSendTransferSummary = '/send-transfer/summary', RpcReceiveBitcoinContractOffer = '/bitcoin-contract-offer/:bitcoinContractOffer/:counterpartyWalletURL', RpcSignBip322Message = '/sign-bip322-message', + RpcStacksSignature = '/sign-stacks-message', // Shared legacy and rpc request routes RequestError = '/request-error', diff --git a/src/shared/rpc/methods/sign-stacks-message.ts b/src/shared/rpc/methods/sign-stacks-message.ts new file mode 100644 index 00000000000..7dab65cfb3b --- /dev/null +++ b/src/shared/rpc/methods/sign-stacks-message.ts @@ -0,0 +1,36 @@ +import { DefineRpcMethod, RpcRequest, RpcResponse } from '@btckit/types'; +import { StacksNetworks } from '@stacks/network'; +import * as yup from 'yup'; + +import { formatValidationErrors, getRpcParamErrors, validateRpcParams } from './validation.utils'; + +const SignedMessageTypeArray = ['utf8', 'structured'] as const; + +const rpcSignStacksMessageParamsSchema = yup.object().shape({ + network: yup.string().oneOf(StacksNetworks), + message: yup.string().required(), + domain: yup.string(), + messageType: yup.string().oneOf(SignedMessageTypeArray).required(), +}); + +export function validateRpcSignStacksMessageParams(obj: unknown) { + return validateRpcParams(obj, rpcSignStacksMessageParamsSchema); +} + +export function getRpcSignStacksMessageParamErrors(obj: unknown) { + return formatValidationErrors(getRpcParamErrors(obj, rpcSignStacksMessageParamsSchema)); +} + +type SignStacksMessageRequestParams = yup.InferType; + +export type SignStacksMessageRequest = RpcRequest< + 'stx_signMessage', + SignStacksMessageRequestParams +>; + +type SignStacksMessageResponse = RpcResponse<{ signature: string }>; + +export type SignStacksMessage = DefineRpcMethod< + SignStacksMessageRequest, + SignStacksMessageResponse +>; diff --git a/src/shared/rpc/rpc-methods.ts b/src/shared/rpc/rpc-methods.ts index 7384565e5c7..74460b67f7a 100644 --- a/src/shared/rpc/rpc-methods.ts +++ b/src/shared/rpc/rpc-methods.ts @@ -5,6 +5,7 @@ import { ValueOf } from '@shared/utils/type-utils'; import { AcceptBitcoinContract } from './methods/accept-bitcoin-contract'; import { SignPsbt } from './methods/sign-psbt'; +import { SignStacksMessage } from './methods/sign-stacks-message'; import { SupportedMethods } from './methods/supported-methods'; // Supports BtcKit methods, as well as custom Leather methods @@ -12,7 +13,8 @@ export type WalletMethodMap = BtcKitMethodMap & SupportedMethods & SignPsbt & AcceptBitcoinContract & - SignStacksTransaction; + SignStacksTransaction & + SignStacksMessage; export type WalletRequests = ValueOf['request']; export type WalletResponses = ValueOf['response']; diff --git a/test-app/src/components/signature.tsx b/test-app/src/components/signature.tsx index 6999a1667eb..c0a3e89816d 100644 --- a/test-app/src/components/signature.tsx +++ b/test-app/src/components/signature.tsx @@ -23,6 +23,7 @@ import { noneCV, responseErrorCV, responseOkCV, + serializeCV, someCV, standardPrincipalCV, stringAsciiCV, @@ -122,6 +123,17 @@ export const Signature = () => { }, }); }; + + const signMessageRpc = async (message: string) => { + clearState(); + setCurrentMessage(message); + + await window.btc?.request('stx_signMessage', { + message, + messageType: 'utf8', + }); + }; + const domain = tupleCV({ name: stringAsciiCV('hiro.so'), version: stringAsciiCV('1.0.0'), @@ -149,6 +161,21 @@ export const Signature = () => { }); }; + const signStructureRpc = async (message: ClarityValue, domain: TupleCV) => { + clearState(); + setCurrentStructuredData({ message, domain }); + + // ClarityValue -> Uint8Array -> Buffer -> string (hex) + const stringMessage = Buffer.from(serializeCV(message)).toString('hex'); + const stringDomain = Buffer.from(serializeCV(domain)).toString('hex'); + + await window.btc?.request('stx_signMessage', { + message: stringMessage, + messageType: 'structured', + domain: stringDomain, + }); + }; + const sip18Test = [ { message: stringAsciiCV('Hello World'), @@ -204,6 +231,17 @@ export const Signature = () => { expected hash : '1bfdab6d4158313ce34073fbb8d6b0fc32c154d439def12247a0f44bb2225259' +
+
+ RPC +
+ signMessageRpc(signatureMessage)}> + Signature RPC (Testnet) + +
+ signStructureRpc(structuredData, domain)}> + Signature Structure RPC (Testnet) + ); }; From 5bef424e0e457e850cea1e08a1befe9274c34b72 Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Wed, 6 Dec 2023 13:56:04 -0600 Subject: [PATCH 03/18] fix: playwright error --- package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 4e8a06b21f3..e0f6e2d509a 100644 --- a/package.json +++ b/package.json @@ -235,7 +235,7 @@ "@leather-wallet/tokens": "0.0.2", "@ls-lint/ls-lint": "2.1.0", "@pandacss/dev": "0.18.3", - "@playwright/test": "1.38.1", + "@playwright/test": "1.40.1", "@pmmmwh/react-refresh-webpack-plugin": "0.5.11", "@redux-devtools/cli": "3.0.2", "@redux-devtools/remote": "0.8.1", diff --git a/yarn.lock b/yarn.lock index 091155a8428..8a47bf102a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2140,12 +2140,12 @@ picocolors "^1.0.0" tslib "^2.6.0" -"@playwright/test@1.38.1": - version "1.38.1" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.38.1.tgz#8ef4263e355cd1d8ad7905d471d268e8acb82ed6" - integrity sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ== +"@playwright/test@1.40.1": + version "1.40.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.40.1.tgz#9e66322d97b1d74b9f8718bacab15080f24cde65" + integrity sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw== dependencies: - playwright "1.38.1" + playwright "1.40.1" "@pmmmwh/react-refresh-webpack-plugin@0.5.11": version "0.5.11" @@ -14469,17 +14469,17 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright-core@1.38.1: - version "1.38.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.38.1.tgz#75a3c470aa9576b7d7c4e274de3d79977448ba08" - integrity sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg== +playwright-core@1.40.1: + version "1.40.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.40.1.tgz#442d15e86866a87d90d07af528e0afabe4c75c05" + integrity sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ== -playwright@1.38.1: - version "1.38.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.38.1.tgz#82ecd9bc4f4f64dbeee8a11c31793748e2528130" - integrity sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow== +playwright@1.40.1: + version "1.40.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.40.1.tgz#a11bf8dca15be5a194851dbbf3df235b9f53d7ae" + integrity sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw== dependencies: - playwright-core "1.38.1" + playwright-core "1.40.1" optionalDependencies: fsevents "2.3.2" From d275d8c9bb6a1914ffd3b94e7e41e956c7fb2035 Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Wed, 6 Dec 2023 17:09:55 -0600 Subject: [PATCH 04/18] fix: selecting testnet in tests --- src/app/features/theme-drawer/theme-list.tsx | 2 - .../components/network-list-item.layout.tsx | 47 +++++++++---------- tests/page-object-models/home.page.ts | 3 +- tests/specs/rpc-sign-psbt/sign-psbt.spec.ts | 1 + 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/app/features/theme-drawer/theme-list.tsx b/src/app/features/theme-drawer/theme-list.tsx index 7c8a4ccdf00..4cdd87616e7 100644 --- a/src/app/features/theme-drawer/theme-list.tsx +++ b/src/app/features/theme-drawer/theme-list.tsx @@ -1,6 +1,5 @@ import { useCallback } from 'react'; -import { SettingsSelectors } from '@tests/selectors/settings.selectors'; import { Flex, FlexProps } from 'leather-styles/jsx'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; @@ -30,7 +29,6 @@ export function ThemeList(props: FlexProps) { {themes.map(theme => ( - - + + + {network.name} @@ -76,7 +71,7 @@ export function NetworkListItemLayout({ )} - - + + ); } diff --git a/tests/page-object-models/home.page.ts b/tests/page-object-models/home.page.ts index b7d22ea65c0..a613ed74d40 100644 --- a/tests/page-object-models/home.page.ts +++ b/tests/page-object-models/home.page.ts @@ -99,9 +99,10 @@ export class HomePage { async enableTestMode() { await this.page.getByTestId(SettingsSelectors.SettingsMenuBtn).click(); await this.page.getByTestId(SettingsSelectors.ChangeNetworkAction).click(); + await this.page.waitForTimeout(1000); await ( await this.page.waitForSelector(this.testNetworkSelector, { timeout: 30000 }) - ).isEnabled(); + ).isVisible(); await this.page.getByTestId(WalletDefaultNetworkConfigurationIds.testnet).click(); } diff --git a/tests/specs/rpc-sign-psbt/sign-psbt.spec.ts b/tests/specs/rpc-sign-psbt/sign-psbt.spec.ts index 8f0bca29968..c5acef3aed0 100644 --- a/tests/specs/rpc-sign-psbt/sign-psbt.spec.ts +++ b/tests/specs/rpc-sign-psbt/sign-psbt.spec.ts @@ -26,6 +26,7 @@ test.describe('Sign PSBT', () => { function clickActionButton(context: BrowserContext) { return async (buttonToPress: 'Cancel' | 'Confirm') => { const popup = await context.waitForEvent('page'); + await popup.waitForTimeout(1000); const btn = popup.locator(`text="${buttonToPress}"`); await btn.click(); }; From 6539075521f8250dcea08ff487b35267b7f94ac7 Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Mon, 20 Nov 2023 15:46:39 -0600 Subject: [PATCH 05/18] chore: enable ledger swaps to test --- src/app/pages/home/components/account-actions.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/app/pages/home/components/account-actions.tsx b/src/app/pages/home/components/account-actions.tsx index 62a7cf50f57..42a00009303 100644 --- a/src/app/pages/home/components/account-actions.tsx +++ b/src/app/pages/home/components/account-actions.tsx @@ -5,7 +5,6 @@ import { Flex, FlexProps } from 'leather-styles/jsx'; import { RouteUrls } from '@shared/route-urls'; -import { useWalletType } from '@app/common/use-wallet-type'; import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { ArrowDownIcon } from '@app/ui/components/icons/arrow-down-icon'; @@ -19,7 +18,6 @@ export function AccountActions(props: FlexProps) { const navigate = useNavigate(); const location = useLocation(); const isBitcoinEnabled = useConfigBitcoinEnabled(); - const { whenWallet } = useWalletType(); const stacksAccount = useCurrentStacksAccount(); const receivePath = isBitcoinEnabled @@ -45,12 +43,7 @@ export function AccountActions(props: FlexProps) { /> )} - {whenWallet({ - software: ( - } label="Swap" onClick={() => navigate(RouteUrls.Swap)} /> - ), - ledger: null, - })} + } label="Swap" onClick={() => navigate(RouteUrls.Swap)} /> ); } From 886309b318dda5fa1c19f30b5a2772408e21acb6 Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Fri, 1 Dec 2023 09:48:17 -0600 Subject: [PATCH 06/18] fix: broadcast ledger swap --- .../ledger/utils/stacks-ledger-utils.ts | 6 +++--- src/app/pages/swap/alex-swap-container.tsx | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/app/features/ledger/utils/stacks-ledger-utils.ts b/src/app/features/ledger/utils/stacks-ledger-utils.ts index b4b50cef1be..ca6ea389db9 100644 --- a/src/app/features/ledger/utils/stacks-ledger-utils.ts +++ b/src/app/features/ledger/utils/stacks-ledger-utils.ts @@ -70,11 +70,11 @@ export function signLedgerStacksStructuredMessage(app: StacksApp) { } export function signStacksTransactionWithSignature(transaction: string, signatureVRS: Buffer) { - const deserialzedTx = deserializeTransaction(transaction); + const deserializedTx = deserializeTransaction(transaction); const spendingCondition = createMessageSignature(signatureVRS.toString('hex')); - (deserialzedTx.auth.spendingCondition as SingleSigSpendingCondition).signature = + (deserializedTx.auth.spendingCondition as SingleSigSpendingCondition).signature = spendingCondition; - return deserialzedTx; + return deserializedTx; } export function useActionCancellableByUser() { diff --git a/src/app/pages/swap/alex-swap-container.tsx b/src/app/pages/swap/alex-swap-container.tsx index aa1595e7c81..9d2abaddc0a 100644 --- a/src/app/pages/swap/alex-swap-container.tsx +++ b/src/app/pages/swap/alex-swap-container.tsx @@ -16,6 +16,7 @@ import { RouteUrls } from '@shared/route-urls'; import { isDefined, isUndefined } from '@shared/utils'; import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; +import { useWalletType } from '@app/common/use-wallet-type'; import { NonceSetter } from '@app/components/nonce-setter'; import { defaultFeesMinValues } from '@app/query/stacks/fees/fees.hooks'; import { useStacksPendingTransactions } from '@app/query/stacks/mempool/mempool.hooks'; @@ -43,8 +44,12 @@ function AlexSwapContainer() { const generateUnsignedTx = useGenerateStacksContractCallUnsignedTx(); const signTx = useSignStacksTransaction(); const { transactions: pendingTransactions } = useStacksPendingTransactions(); + const { whenWallet } = useWalletType(); - const isSponsoredByAlex = !pendingTransactions.length; + const isSponsoredByAlex = whenWallet({ + ledger: false, + software: !pendingTransactions.length, + }); const { alexSDK, @@ -172,10 +177,12 @@ function AlexSwapContainer() { return logger.error('Attempted to generate raw tx, but signed tx is undefined'); const txRaw = bytesToHex(signedTx.serialize()); - if (isSponsoredByAlex) { - return await broadcastAlexSwap(txRaw); - } - return await broadcastStacksSwap(unsignedTx); + return whenWallet({ + ledger: await broadcastStacksSwap(signedTx), + software: isSponsoredByAlex + ? await broadcastAlexSwap(txRaw) + : await broadcastStacksSwap(signedTx), + }); } catch (error) {} } From bd6dc1a6d946727859f5b4f1b89b7d7b0b199f99 Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Wed, 6 Dec 2023 12:27:10 -0600 Subject: [PATCH 07/18] refactor: add swap tests --- .../pages/home/components/account-actions.tsx | 7 ++- .../select-asset-trigger-button.tsx | 9 +++- .../swap/components/swap-amount-field.tsx | 2 + .../swap-asset-item.layout.tsx | 9 +++- .../swap/components/swap-content.layout.tsx | 4 +- .../swap-details/swap-detail.layout.tsx | 10 +++- .../components/swap-details/swap-details.tsx | 9 +++- .../components/swap-asset-list.tsx | 2 + .../swap-choose-asset/swap-choose-asset.tsx | 3 +- .../pages/swap/swap-review/swap-review.tsx | 10 +++- src/app/pages/swap/swap.tsx | 2 + tests/page-object-models/swap.page.ts | 35 +++++++++++-- tests/selectors/swap.selectors.ts | 11 +++- tests/specs/swap/swap.spec.ts | 50 +++++++++++++++++-- 14 files changed, 141 insertions(+), 22 deletions(-) diff --git a/src/app/pages/home/components/account-actions.tsx b/src/app/pages/home/components/account-actions.tsx index 42a00009303..84608e7746e 100644 --- a/src/app/pages/home/components/account-actions.tsx +++ b/src/app/pages/home/components/account-actions.tsx @@ -43,7 +43,12 @@ export function AccountActions(props: FlexProps) { /> )} - } label="Swap" onClick={() => navigate(RouteUrls.Swap)} /> + } + label="Swap" + onClick={() => navigate(RouteUrls.Swap)} + /> ); } diff --git a/src/app/pages/swap/components/select-asset-trigger-button.tsx b/src/app/pages/swap/components/select-asset-trigger-button.tsx index c46a7fdb3eb..9f6a2c7897b 100644 --- a/src/app/pages/swap/components/select-asset-trigger-button.tsx +++ b/src/app/pages/swap/components/select-asset-trigger-button.tsx @@ -1,3 +1,4 @@ +import { SwapSelectors } from '@tests/selectors/swap.selectors'; import { useField } from 'formik'; import { HStack, styled } from 'leather-styles/jsx'; @@ -19,7 +20,13 @@ export function SelectAssetTriggerButton({ const [field] = useField(name); return ( - + {icon && } {symbol} diff --git a/src/app/pages/swap/components/swap-amount-field.tsx b/src/app/pages/swap/components/swap-amount-field.tsx index b5183d815ae..e20758958c1 100644 --- a/src/app/pages/swap/components/swap-amount-field.tsx +++ b/src/app/pages/swap/components/swap-amount-field.tsx @@ -1,5 +1,6 @@ import { ChangeEvent } from 'react'; +import { SwapSelectors } from '@tests/selectors/swap.selectors'; import BigNumber from 'bignumber.js'; import { useField, useFormikContext } from 'formik'; import { Stack, styled } from 'leather-styles/jsx'; @@ -60,6 +61,7 @@ export function SwapAmountField({ amountAsFiat, isDisabled, name }: SwapAmountFi bg="accent.background-primary" border="none" color={showError ? 'error.label' : 'accent.text-primary'} + data-testid={SwapSelectors.SwapAmountInput} display="block" disabled={isDisabled || isFetchingExchangeRate} id={name} diff --git a/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx b/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx index 19fe903475b..b82408b71c7 100644 --- a/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx +++ b/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx @@ -1,3 +1,4 @@ +import { SwapSelectors } from '@tests/selectors/swap.selectors'; import { HStack, styled } from 'leather-styles/jsx'; import { Flag } from '@app/components/layout/flag'; @@ -20,8 +21,12 @@ export function SwapAssetItemLayout({ caption, icon, symbol, value }: SwapAssetI {caption} - {symbol} - {value} + + {symbol} + + + {value} + ); diff --git a/src/app/pages/swap/components/swap-content.layout.tsx b/src/app/pages/swap/components/swap-content.layout.tsx index d6e94e7d75d..0d3d2147691 100644 --- a/src/app/pages/swap/components/swap-content.layout.tsx +++ b/src/app/pages/swap/components/swap-content.layout.tsx @@ -1,4 +1,4 @@ -import { SwapCryptoAssetSelectors } from '@tests/selectors/swap.selectors'; +import { SwapSelectors } from '@tests/selectors/swap.selectors'; import { Flex } from 'leather-styles/jsx'; import { HasChildren } from '@app/common/has-children'; @@ -7,7 +7,7 @@ export function SwapContentLayout({ children }: HasChildren) { return ( @@ -25,7 +31,7 @@ export function SwapDetailLayout({ title, tooltipLabel, value }: SwapDetailLayou ) : null} - + {value} diff --git a/src/app/pages/swap/components/swap-details/swap-details.tsx b/src/app/pages/swap/components/swap-details/swap-details.tsx index 1cc0e8d1d64..81cd10f386c 100644 --- a/src/app/pages/swap/components/swap-details/swap-details.tsx +++ b/src/app/pages/swap/components/swap-details/swap-details.tsx @@ -1,3 +1,4 @@ +import { SwapSelectors } from '@tests/selectors/swap.selectors'; import BigNumber from 'bignumber.js'; import { HStack, styled } from 'leather-styles/jsx'; @@ -21,7 +22,7 @@ function RouteNames(props: { swapSubmissionData: SwapSubmissionData }) { return ( {route.name} - {insertIcon && } + {insertIcon && } ); }); @@ -49,7 +50,11 @@ export function SwapDetails() { return ( - + {selectableAssets.map(asset => ( onChooseAsset(asset)} textAlign="left" diff --git a/src/app/pages/swap/swap-choose-asset/swap-choose-asset.tsx b/src/app/pages/swap/swap-choose-asset/swap-choose-asset.tsx index b997b5c4df5..d095c762849 100644 --- a/src/app/pages/swap/swap-choose-asset/swap-choose-asset.tsx +++ b/src/app/pages/swap/swap-choose-asset/swap-choose-asset.tsx @@ -1,5 +1,6 @@ import { useLocation, useNavigate } from 'react-router-dom'; +import { SwapSelectors } from '@tests/selectors/swap.selectors'; import { Box, styled } from 'leather-styles/jsx'; import get from 'lodash.get'; @@ -37,7 +38,7 @@ export function SwapChooseAsset() { return ( navigate(-1)}> - + {title} diff --git a/src/app/pages/swap/swap-review/swap-review.tsx b/src/app/pages/swap/swap-review/swap-review.tsx index f4da62947bb..54306d04e0f 100644 --- a/src/app/pages/swap/swap-review/swap-review.tsx +++ b/src/app/pages/swap/swap-review/swap-review.tsx @@ -1,5 +1,7 @@ import { Outlet } from 'react-router-dom'; +import { SwapSelectors } from '@tests/selectors/swap.selectors'; + import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { ModalHeader } from '@app/components/modal-header'; @@ -26,7 +28,13 @@ export function SwapReview() { - + Swap diff --git a/src/app/pages/swap/swap.tsx b/src/app/pages/swap/swap.tsx index f876fae4b0e..81c86719649 100644 --- a/src/app/pages/swap/swap.tsx +++ b/src/app/pages/swap/swap.tsx @@ -1,6 +1,7 @@ import { useAsync } from 'react-async-hook'; import { Outlet } from 'react-router-dom'; +import { SwapSelectors } from '@tests/selectors/swap.selectors'; import { useFormikContext } from 'formik'; import { isUndefined } from '@shared/utils'; @@ -37,6 +38,7 @@ export function Swap() { { +const alexSdkPostRoute = 'https://gql.alexlab.co/v1/graphql'; + +test.describe('Swaps', () => { test.beforeEach(async ({ extensionId, globalPage, homePage, onboardingPage, swapPage }) => { await globalPage.setupAndUseApiCalls(extensionId); await onboardingPage.signInWithTestAccount(extensionId); await homePage.enableTestMode(); await homePage.swapButton.click(); - await swapPage.waitForSendPageReady(); + await swapPage.waitForSwapPageReady(); + }); + + test('that it defaults to swapping STX', async ({ swapPage }) => { + test.expect(swapPage.page.getByText('STX')).toBeTruthy(); }); - // Skip tests until feature is live - test.skip('that it shows swap page', async ({ swapPage }) => { - await test.expect(swapPage.page.getByText('Swap')).toBeVisible(); + test('that it shows correct swap review details', async ({ swapPage }) => { + const swapAmountInputs = await swapPage.swapAmountInput.all(); + await swapAmountInputs[0].fill('1'); + await swapPage.selectAssetToReceive(); + + const swapProtocol = await swapPage.swapDetailsProtocol.innerText(); + test.expect(swapProtocol).toEqual('ALEX'); + + const swapAssets = await swapPage.swapDetailsSymbol.all(); + const swapAssetFrom = await swapAssets[0].innerText(); + const swapAssetTo = await swapAssets[1].innerText(); + test.expect(swapAssetFrom).toEqual('STX'); + test.expect(swapAssetTo).toEqual('ALEX'); + + const swapAmounts = await swapPage.swapDetailsAmount.all(); + const swapAmountFrom = await swapAmounts[0].innerText(); + test.expect(swapAmountFrom).toEqual('1'); + + test.expect(swapPage.page.getByText('Sponsored')).toBeTruthy(); + }); + + test('that the swap is broadcast', async ({ swapPage }) => { + const requestPromise = swapPage.page.waitForRequest(alexSdkPostRoute); + + await swapPage.page.route(alexSdkPostRoute, async route => { + await route.abort(); + }); + + const swapAmountInputs = await swapPage.swapAmountInput.all(); + await swapAmountInputs[0].fill('1'); + await swapPage.selectAssetToReceive(); + + await swapPage.swapBtn.click(); + + const request = await requestPromise; + const requestBody = request.postDataBuffer(); + test.expect(requestBody).toBeDefined(); }); }); From 029952550cf4ba46176c06a860a0f5d7fbb0c027 Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Fri, 8 Dec 2023 13:07:46 +0100 Subject: [PATCH 08/18] test: publish reports --- .github/workflows/build-extension.yml | 10 ++++- .github/workflows/gh-pages.yml | 2 +- .github/workflows/playwright.yml | 56 ++++++++++++++++++++++++--- playwright.config.ts | 5 ++- 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index c57fe668daa..f6576cdbbd7 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -29,7 +29,8 @@ jobs: - pre-run strategy: matrix: - target: [chromium, firefox] + # Firefox removed while off store + target: [chromium] steps: - uses: actions/checkout@v4 with: @@ -65,7 +66,12 @@ jobs: - pre-run - build steps: + - name: Extract branch name + shell: bash + run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + id: extract_branch + - uses: kyranjamie/pull-request-fixed-header@v1.0.1 with: - header: '> Try out this version of Leather — [download extension builds](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}).' + header: '> Try out this version of Leather — [Extension build](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}), [Test report](https://leather-wallet.github.io/playwright-reports/${{ steps.extract_branch.outputs.branch }})' GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index fef3c68d177..073f6a4e26c 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,4 +1,4 @@ -name: GH pages +name: Publish unit test coverage on: push: diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 76d896b5e3f..973f77cde8e 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,4 +1,4 @@ -name: Playwright Tests +name: Integration Tests env: CI: true @@ -73,9 +73,55 @@ jobs: run: xvfb-run yarn playwright test tests/specs --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=1 env: TEST_ACCOUNT_SECRET_KEY: ${{ secrets.TEST_ACCOUNT_SECRET_KEY }} - - uses: actions/upload-artifact@v3 + + - name: Upload blob report to GitHub Actions Artifacts if: always() + uses: actions/upload-artifact@v3 + with: + name: all-blob-reports + path: blob-report + retention-days: 1 + if-no-files-found: error + + merge-reports: + name: Merge reports + if: always() + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + + - name: Install dependencies + run: yarn --frozen-lockfile + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v3 + with: + name: all-blob-reports + path: all-blob-reports + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./all-blob-reports + + - name: Upload HTML report + uses: actions/upload-artifact@v3 + with: + name: html-report--attempt-${{ github.run_attempt }} + path: playwright-report + retention-days: 14 + + - name: Extract branch name + shell: bash + run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + id: extract_branch + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: github.event_name == 'pull_request' with: - name: playwright-report - path: playwright-report/ - retention-days: 10 + personal_token: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} + external_repository: leather-wallet/playwright-reports + publish_branch: main + publish_dir: ./playwright-report + destination_dir: ${{ steps.extract_branch.outputs.branch }} diff --git a/playwright.config.ts b/playwright.config.ts index 4aa52b26aa9..a70909af8bd 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -12,7 +12,10 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, - reporter: [[process.env.CI ? 'github' : 'list'], ['html', { open: 'never' }]], + reporter: [ + [process.env.CI ? 'github' : 'list'], + [process.env.CI ? 'blob' : 'html', { open: 'never' }], + ], use: { trace: 'on-first-retry', }, From d2edb187e80bef233606a9a38d8b83dc83f4d91f Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Tue, 5 Dec 2023 15:10:53 +0100 Subject: [PATCH 09/18] refactor(signing): support non-index zero input signing, closes #4620, #4628 --- .../ledger-bitcoin-sign-tx-container.tsx | 15 +- .../ledger/hooks/use-ledger-navigate.ts | 6 +- .../psbt-signer/hooks/use-psbt-signer.tsx | 9 +- .../pages/psbt-request/use-psbt-request.tsx | 8 +- .../pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx | 8 +- .../hooks/use-generate-ordinal-tx.ts | 28 +- .../hooks/use-send-inscription-form.tsx | 2 +- .../blockchain/bitcoin/bitcoin.hooks.ts | 68 ++- src/shared/crypto/bitcoin/bitcoin.utils.ts | 10 +- .../crypto/bitcoin/signer-config.spec.ts | 87 ++++ src/shared/crypto/bitcoin/signer-config.ts | 54 +++ tests/mocks/constants.ts | 4 +- tests/specs/rpc-sign-psbt/sign-psbt.spec.ts | 14 +- yarn.lock | 428 ++++++++++++++++-- 14 files changed, 634 insertions(+), 107 deletions(-) create mode 100644 src/shared/crypto/bitcoin/signer-config.spec.ts create mode 100644 src/shared/crypto/bitcoin/signer-config.ts diff --git a/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx b/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx index 7c49cbfcfee..287a4764f18 100644 --- a/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx +++ b/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx @@ -5,6 +5,7 @@ import * as btc from '@scure/btc-signer'; import { hexToBytes } from '@stacks/common'; import get from 'lodash.get'; +import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config'; import { logger } from '@shared/logger'; import { RouteUrls } from '@shared/route-urls'; @@ -45,7 +46,7 @@ function LedgerSignBitcoinTxContainer() { const [unsignedTransaction, setUnsignedTransaction] = useState(null); const signLedger = useSignLedgerBitcoinTx(); - const inputsToSign = useLocationStateWithCache('inputsToSign'); + const inputsToSign = useLocationStateWithCache('inputsToSign'); const allowUserToGoBack = useLocationState('goBack'); useEffect(() => { @@ -68,12 +69,12 @@ function LedgerSignBitcoinTxContainer() { try { const versionInfo = await getBitcoinAppVersion(bitcoinApp); - ledgerAnalytics.trackDeviceVersionInfo(versionInfo); setAwaitingDeviceConnection(false); - setLatestDeviceResponse(versionInfo as any); - } catch (e) {} + } catch (e) { + logger.error('Unable to get Ledger app version info', e); + } ledgerNavigate.toDeviceBusyStep('Verifying public key on Ledger…'); @@ -84,7 +85,11 @@ function LedgerSignBitcoinTxContainer() { ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: false }); try { - const btcTx = await signLedger(bitcoinApp, unsignedTransaction.toPSBT(), inputsToSign); + const btcTx = await signLedger( + bitcoinApp, + unsignedTransaction.toPSBT(), + inputsToSign?.map(x => x.index) + ); if (!btcTx || !unsignedTransactionRaw) throw new Error('No tx returned'); ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: true }); diff --git a/src/app/features/ledger/hooks/use-ledger-navigate.ts b/src/app/features/ledger/hooks/use-ledger-navigate.ts index 605e27f6ff8..2fe2844406c 100644 --- a/src/app/features/ledger/hooks/use-ledger-navigate.ts +++ b/src/app/features/ledger/hooks/use-ledger-navigate.ts @@ -5,6 +5,7 @@ import { bytesToHex } from '@stacks/common'; import { ClarityValue, StacksTransaction } from '@stacks/transactions'; import { SupportedBlockchains } from '@shared/constants'; +import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config'; import { RouteUrls } from '@shared/route-urls'; import { immediatelyAttemptLedgerConnection } from './use-when-reattempt-ledger-connection'; @@ -30,7 +31,10 @@ export function useLedgerNavigate() { }); }, - toConnectAndSignBitcoinTransactionStep(psbt: Uint8Array, inputsToSign?: number[]) { + toConnectAndSignBitcoinTransactionStep( + psbt: Uint8Array, + inputsToSign?: BitcoinInputSigningConfig[] + ) { return navigate(RouteUrls.ConnectLedger, { replace: true, relative: 'route', diff --git a/src/app/features/psbt-signer/hooks/use-psbt-signer.tsx b/src/app/features/psbt-signer/hooks/use-psbt-signer.tsx index edf27070016..493f13aacc9 100644 --- a/src/app/features/psbt-signer/hooks/use-psbt-signer.tsx +++ b/src/app/features/psbt-signer/hooks/use-psbt-signer.tsx @@ -3,6 +3,7 @@ import { useMemo } from 'react'; import { hexToBytes } from '@noble/hashes/utils'; import * as btc from '@scure/btc-signer'; +import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config'; import { logger } from '@shared/logger'; import { isString } from '@shared/utils'; @@ -14,7 +15,7 @@ import { export type RawPsbt = ReturnType; interface SignPsbtArgs { - indexesToSign?: number[]; + signingConfig: BitcoinInputSigningConfig[]; tx: btc.Transaction; } export function usePsbtSigner() { @@ -23,9 +24,9 @@ export function usePsbtSigner() { return useMemo( () => ({ - async signPsbt({ indexesToSign, tx }: SignPsbtArgs) { - addMissingTapInteralKeys(tx, indexesToSign); - return signBitcoinTx(tx.toPSBT(), indexesToSign); + async signPsbt({ signingConfig, tx }: SignPsbtArgs) { + addMissingTapInteralKeys(tx, signingConfig); + return signBitcoinTx(tx.toPSBT(), signingConfig); }, getPsbtAsTransaction(psbt: string | Uint8Array) { const bytes = isString(psbt) ? hexToBytes(psbt) : psbt; diff --git a/src/app/pages/psbt-request/use-psbt-request.tsx b/src/app/pages/psbt-request/use-psbt-request.tsx index ec091af8b5c..144e46adf37 100644 --- a/src/app/pages/psbt-request/use-psbt-request.tsx +++ b/src/app/pages/psbt-request/use-psbt-request.tsx @@ -1,7 +1,7 @@ import { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { bytesToHex } from '@noble/hashes/utils'; +import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; import { finalizePsbt } from '@shared/actions/finalize-psbt'; import { RouteUrls } from '@shared/route-urls'; @@ -9,6 +9,7 @@ import { RouteUrls } from '@shared/route-urls'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { usePsbtRequestSearchParams } from '@app/common/psbt/use-psbt-request-params'; import { usePsbtSigner } from '@app/features/psbt-signer/hooks/use-psbt-signer'; +import { useGetAssumedZeroIndexSigningConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; export function usePsbtRequest() { const [isLoading, setIsLoading] = useState(false); @@ -17,6 +18,7 @@ export function usePsbtRequest() { const { appName, origin, payload, requestToken, signAtIndex, tabId } = usePsbtRequestSearchParams(); const { signPsbt, getRawPsbt, getPsbtAsTransaction } = usePsbtSigner(); + const getDefaultSigningConfig = useGetAssumedZeroIndexSigningConfig(); return useMemo(() => { return { @@ -41,7 +43,8 @@ export function usePsbtRequest() { const tx = getPsbtAsTransaction(payload.hex); try { - const signedTx = await signPsbt({ indexesToSign: signAtIndex, tx }); + const signingConfig = getDefaultSigningConfig(hexToBytes(payload.hex)); + const signedTx = await signPsbt({ tx, signingConfig }); const signedPsbt = signedTx.toPSBT(); @@ -71,6 +74,7 @@ export function usePsbtRequest() { tabId, getPsbtAsTransaction, signPsbt, + getDefaultSigningConfig, navigate, ]); } diff --git a/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx b/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx index 248d8c1c8f9..4c43efa4b8e 100644 --- a/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx +++ b/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx @@ -1,6 +1,7 @@ import { useNavigate } from 'react-router-dom'; import { RpcErrorCode } from '@btckit/types'; +import { hexToBytes } from '@noble/hashes/utils'; import { bytesToHex } from '@stacks/common'; import { Money } from '@shared/models/money.model'; @@ -20,6 +21,7 @@ import { useCalculateBitcoinFiatValue, useCryptoCurrencyMarketData, } from '@app/query/common/market-data/market-data.hooks'; +import { useGetAssumedZeroIndexSigningConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; interface BroadcastSignedPsbtTxArgs { addressNativeSegwitTotal: Money; @@ -36,6 +38,7 @@ export function useRpcSignPsbt() { const { refetch } = useCurrentNativeSegwitUtxos(); const btcMarketData = useCryptoCurrencyMarketData('BTC'); const calculateBitcoinFiatValue = useCalculateBitcoinFiatValue(); + const getDefaultSigningConfig = useGetAssumedZeroIndexSigningConfig(); if (!requestId || !psbtHex || !origin) throw new Error('Invalid params in useRpcSignPsbt'); @@ -90,7 +93,10 @@ export function useRpcSignPsbt() { const tx = getPsbtAsTransaction(psbtHex); try { - const signedTx = await signPsbt({ tx, indexesToSign: signAtIndex }); + const signedTx = await signPsbt({ + tx, + signingConfig: getDefaultSigningConfig(hexToBytes(psbtHex), signAtIndex), + }); const psbt = signedTx.toPSBT(); diff --git a/src/app/pages/send/ordinal-inscription/hooks/use-generate-ordinal-tx.ts b/src/app/pages/send/ordinal-inscription/hooks/use-generate-ordinal-tx.ts index d7eff2cbbeb..fa00bfd4490 100644 --- a/src/app/pages/send/ordinal-inscription/hooks/use-generate-ordinal-tx.ts +++ b/src/app/pages/send/ordinal-inscription/hooks/use-generate-ordinal-tx.ts @@ -1,10 +1,12 @@ import * as btc from '@scure/btc-signer'; import { AddressType, getAddressInfo } from 'bitcoin-address-validation'; +import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config'; import { logger } from '@shared/logger'; import { OrdinalSendFormValues } from '@shared/models/form.model'; import { determineUtxosForSpend } from '@app/common/transactions/bitcoin/coinselect/local-coin-selection'; +import { createCounter } from '@app/common/utils/counter'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; import { TaprootUtxo } from '@app/query/bitcoin/bitcoin-client'; import { useBitcoinScureLibNetworkConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin-keychain'; @@ -29,6 +31,7 @@ export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) { function formTaprootOrdinalTx(values: OrdinalSendFormValues) { const inscriptionInput = taprootInput; + const taprootSigner = createTaprootSigner?.(inscriptionInput.addressIndex); const nativeSegwitSigner = createNativeSegwitSigner?.(0); @@ -42,6 +45,10 @@ export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) { feeRate: values.feeRate, }); + const psbtInputCounter = createCounter(); + + const signingConfig: BitcoinInputSigningConfig[] = []; + if (!result.success) return null; const { inputs, outputs } = result; @@ -60,9 +67,14 @@ export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) { amount: BigInt(taprootInput.value), }, }); + signingConfig.push({ + derivationPath: taprootSigner.derivationPath, + index: psbtInputCounter.getValue(), + }); + psbtInputCounter.increment(); // Fee-covering Native Segwit inputs - inputs.forEach(input => + inputs.forEach(input => { tx.addInput({ txid: input.txid, index: input.vout, @@ -71,15 +83,20 @@ export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) { amount: BigInt(input.value), script: nativeSegwitSigner.payment.script, }, - }) - ); + }); + signingConfig.push({ + derivationPath: nativeSegwitSigner.derivationPath, + index: psbtInputCounter.getValue(), + }); + psbtInputCounter.increment(); + }); // Recipient and change outputs outputs.forEach(output => tx.addOutputAddress(output.address, output.value, networkMode)); tx.toPSBT(); - return { hex: tx.hex, psbt: tx.toPSBT() }; + return { psbt: tx.toPSBT(), signingConfig }; } catch (e) { logger.error('Unable to sign transaction'); return null; @@ -88,6 +105,7 @@ export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) { function formNativeSegwitOrdinalTx(values: OrdinalSendFormValues) { const nativeSegwitSigner = createNativeSegwitSigner?.(0); + const { feeRate, recipient } = values; if (!nativeSegwitSigner || !nativeSegwitUtxos || !values.feeRate) return; @@ -130,7 +148,7 @@ export function useGenerateUnsignedOrdinalTx(taprootInput: TaprootUtxo) { tx.addOutputAddress(values.recipient, BigInt(output.value), networkMode); }); - return { hex: tx.hex, psbt: tx.toPSBT() }; + return { psbt: tx.toPSBT(), signingConfig: undefined }; } catch (e) { logger.error('Unable to sign transaction'); return null; diff --git a/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx b/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx index 596b7c853ac..614dd19e5a9 100644 --- a/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx +++ b/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx @@ -101,7 +101,7 @@ export function useSendInscriptionForm() { return; } - const signedTx = await sign(resp.psbt); + const signedTx = await sign(resp.psbt, resp.signingConfig); if (!signedTx) { logger.error('No signed transaction returned'); diff --git a/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts b/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts index 138396fd4e8..4c51298b7dc 100644 --- a/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts +++ b/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts @@ -5,13 +5,20 @@ import { Psbt } from 'bitcoinjs-lib'; import AppClient from 'ledger-bitcoin'; import { getBitcoinJsLibNetworkConfigByMode } from '@shared/crypto/bitcoin/bitcoin.network'; -import { getTaprootAddress } from '@shared/crypto/bitcoin/bitcoin.utils'; +import { + extractAddressIndexFromPath, + getTaprootAddress, +} from '@shared/crypto/bitcoin/bitcoin.utils'; import { getInputPaymentType } from '@shared/crypto/bitcoin/bitcoin.utils'; import { getTaprootAccountDerivationPath } from '@shared/crypto/bitcoin/p2tr-address-gen'; import { getNativeSegwitAccountDerivationPath } from '@shared/crypto/bitcoin/p2wpkh-address-gen'; +import { + BitcoinInputSigningConfig, + getAssumedZeroIndexSigningConfig, +} from '@shared/crypto/bitcoin/signer-config'; import { logger } from '@shared/logger'; import { allSighashTypes } from '@shared/rpc/methods/sign-psbt'; -import { makeNumberRange } from '@shared/utils'; +import { isNumber, makeNumberRange } from '@shared/utils'; import { useWalletType } from '@app/common/use-wallet-type'; import { listenForBitcoinTxLedgerSigning } from '@app/features/ledger/flows/bitcoin-tx-signing/bitcoin-tx-signing-event-listeners'; @@ -62,23 +69,22 @@ export function useZeroIndexTaprootAddress(accIndex?: number) { return address; } -// This implementation assumes address re-use of the 0 index. Funds spread -// across multiple address indexes does not work here. function useSignBitcoinSoftwareTx() { const createNativeSegwitSigner = useCurrentAccountNativeSegwitSigner(); const createTaprootSigner = useCurrentAccountTaprootSigner(); const network = useCurrentNetwork(); - return async (psbt: Uint8Array, inputsToSign?: number[]) => { - const nativeSegwitSigner = createNativeSegwitSigner?.(0); - const taprootSigner = createTaprootSigner?.(0); - - if (!nativeSegwitSigner || !taprootSigner) throw new Error('Signers not available'); + return async (psbt: Uint8Array, inputSigningConfig: BitcoinInputSigningConfig[]) => { const tx = btc.Transaction.fromPSBT(psbt); - const inputIndexes = inputsToSign ?? makeNumberRange(tx.inputsLength); + inputSigningConfig.forEach(({ index, derivationPath }) => { + const nativeSegwitSigner = createNativeSegwitSigner?.( + extractAddressIndexFromPath(derivationPath) + ); + const taprootSigner = createTaprootSigner?.(extractAddressIndexFromPath(derivationPath)); + + if (!nativeSegwitSigner || !taprootSigner) throw new Error('Signers not available'); - inputIndexes.forEach(index => { const input = tx.getInput(index); const addressType = getInputPaymentType(index, input, network.chain.bitcoin.bitcoinNetwork); @@ -199,11 +205,10 @@ export function useSignLedgerBitcoinTx() { export function useAddTapInternalKeysIfMissing() { const createTaprootSigner = useCurrentAccountTaprootSigner(); - return (tx: btc.Transaction, inputIndexes?: number[]) => { - const taprootSigner = createTaprootSigner?.(0); - if (!taprootSigner) throw new Error('Taproot signer not found'); - - (inputIndexes ?? makeNumberRange(tx.inputsLength)).forEach(index => { + return (tx: btc.Transaction, inputIndexes: BitcoinInputSigningConfig[]) => { + inputIndexes.forEach(({ index, derivationPath }) => { + const taprootSigner = createTaprootSigner?.(extractAddressIndexFromPath(derivationPath)); + if (!taprootSigner) throw new Error('Taproot signer not found'); const input = tx.getInput(index); const witnessOutputScript = input.witnessUtxo?.script && btc.OutScript.decode(input.witnessUtxo.script); @@ -214,28 +219,47 @@ export function useAddTapInternalKeysIfMissing() { }; } +export function useGetAssumedZeroIndexSigningConfig() { + const network = useCurrentNetwork(); + const accountIndex = useCurrentAccountIndex(); + + return (psbt: Uint8Array, indexesToSign?: number[]) => + getAssumedZeroIndexSigningConfig({ + psbt, + network: network.chain.bitcoin.bitcoinNetwork, + indexesToSign, + }).forAccountIndex(accountIndex); +} + export function useSignBitcoinTx() { const { whenWallet } = useWalletType(); const ledgerNavigate = useLedgerNavigate(); const signSoftwareTx = useSignBitcoinSoftwareTx(); + const getDefaultSigningConfig = useGetAssumedZeroIndexSigningConfig(); /** * Don't forget to finalize the tx once it's returned. You can broadcast with - * the hex value from `tx.hex` TODO: add support for signing specific inputs + * the hex value from `tx.hex`. */ - return (psbt: Uint8Array, inputsToSign?: number[]) => - whenWallet({ + return (psbt: Uint8Array, inputsToSign?: BitcoinInputSigningConfig[] | number[]) => { + function getSigningConfig(inputsToSign?: BitcoinInputSigningConfig[] | number[]) { + if (!inputsToSign) return getDefaultSigningConfig(psbt); + if (inputsToSign.every(isNumber)) return getDefaultSigningConfig(psbt, inputsToSign); + return inputsToSign; + } + + return whenWallet({ async ledger() { // Because Ledger signing is a multi-step process that takes place over // many routes, in order to achieve a consistent API between // Ledger/software, we subscribe to the event that occurs when the // unsigned tx is signed - ledgerNavigate.toConnectAndSignBitcoinTransactionStep(psbt, inputsToSign); - // ledgerNavigate.toConnectAndSignBitcoinTransactionStep(psbt, inputsToSign); + ledgerNavigate.toConnectAndSignBitcoinTransactionStep(psbt, getSigningConfig(inputsToSign)); return listenForBitcoinTxLedgerSigning(bytesToHex(psbt)); }, async software() { - return signSoftwareTx(psbt, inputsToSign); + return signSoftwareTx(psbt, getSigningConfig(inputsToSign)); }, })(); + }; } diff --git a/src/shared/crypto/bitcoin/bitcoin.utils.ts b/src/shared/crypto/bitcoin/bitcoin.utils.ts index beacbeaf40b..0197364e3cd 100644 --- a/src/shared/crypto/bitcoin/bitcoin.utils.ts +++ b/src/shared/crypto/bitcoin/bitcoin.utils.ts @@ -249,20 +249,14 @@ export function getTaprootAddress({ index, keychain, network }: GetTaprootAddres export function getPsbtTxInputs(psbtTx: btc.Transaction) { const inputsLength = psbtTx.inputsLength; const inputs: btc.TransactionInput[] = []; - if (inputsLength === 0) return inputs; - for (let i = 0; i < inputsLength; i++) { - inputs.push(psbtTx.getInput(i)); - } + for (let i = 0; i < inputsLength; i++) inputs.push(psbtTx.getInput(i)); return inputs; } export function getPsbtTxOutputs(psbtTx: btc.Transaction) { const outputsLength = psbtTx.outputsLength; const outputs: btc.TransactionOutput[] = []; - if (outputsLength === 0) return outputs; - for (let i = 0; i < outputsLength; i++) { - outputs.push(psbtTx.getOutput(i)); - } + for (let i = 0; i < outputsLength; i++) outputs.push(psbtTx.getOutput(i)); return outputs; } diff --git a/src/shared/crypto/bitcoin/signer-config.spec.ts b/src/shared/crypto/bitcoin/signer-config.spec.ts new file mode 100644 index 00000000000..512cadc05c0 --- /dev/null +++ b/src/shared/crypto/bitcoin/signer-config.spec.ts @@ -0,0 +1,87 @@ +import { HDKey } from '@scure/bip32'; +import { mnemonicToSeedSync } from '@scure/bip39'; +import * as btc from '@scure/btc-signer'; +import { STANDARD_BIP_FAKE_MNEMONIC } from '@tests/mocks/constants'; + +import { makeNumberRange } from '@shared/utils'; + +import { deriveAddressIndexKeychainFromAccount, ecdsaPublicKeyToSchnorr } from './bitcoin.utils'; +import { getTaprootAccountDerivationPath } from './p2tr-address-gen'; +import { getNativeSegwitAccountDerivationPath } from './p2wpkh-address-gen'; +import { getAssumedZeroIndexSigningConfig } from './signer-config'; + +describe(getAssumedZeroIndexSigningConfig.name, () => { + const seed = mnemonicToSeedSync(STANDARD_BIP_FAKE_MNEMONIC); + const keychain = HDKey.fromMasterSeed(seed); + + const mockTxid = '7fc33a83ba9627b8eeb0eebef90552f73518f27a45dbdc41bd6bd4342d098bf3'; + + test('for a given transaction with p2wpkh', () => { + const nativeSegwitAccountKeychain = keychain.derive( + getNativeSegwitAccountDerivationPath('mainnet', 0) + ); + const nativeSegwitKeychain = deriveAddressIndexKeychainFromAccount(nativeSegwitAccountKeychain)( + 0 + ); + const nativeSegwitPayment = btc.p2wpkh(nativeSegwitKeychain.publicKey!); + + const testTx = new btc.Transaction(); + testTx.addInput({ + index: 5, + txid: mockTxid, + witnessUtxo: { + amount: 1000n, + script: nativeSegwitPayment.script, + }, + }); + const result = getAssumedZeroIndexSigningConfig({ + psbt: testTx.toPSBT(), + network: 'mainnet', + }).forAccountIndex(0); + + expect(result).toEqual([{ derivationPath: "m/84'/0'/0'/0/0", index: 0 }]); + }); + + const taprootAccountKeychain = keychain.derive(getTaprootAccountDerivationPath('mainnet', 0)); + const taprootKeychain = deriveAddressIndexKeychainFromAccount(taprootAccountKeychain)(0); + const taprootPayment = btc.p2tr(ecdsaPublicKeyToSchnorr(taprootKeychain.publicKey!)); + + test('for a given transaction with p2tr', () => { + const testTx = new btc.Transaction(); + testTx.addInput({ + index: 0, + txid: mockTxid, + witnessUtxo: { amount: 1000n, script: taprootPayment.script }, + }); + + const result = getAssumedZeroIndexSigningConfig({ + psbt: testTx.toPSBT(), + network: 'testnet', + }).forAccountIndex(0); + + expect(result).toEqual([{ derivationPath: "m/86'/1'/0'/0/0", index: 0 }]); + }); + + test('it only returns config for inputs given, if passed', () => { + const testTx = new btc.Transaction(); + makeNumberRange(5).forEach(index => + testTx.addInput({ + // Index of the UTXO being spend on the txid, not its order in new tx + index: index * 2, + txid: mockTxid, + witnessUtxo: { amount: 1000n, script: taprootPayment.script }, + }) + ); + const result = getAssumedZeroIndexSigningConfig({ + psbt: testTx.toPSBT(), + network: 'mainnet', + indexesToSign: [0, 2, 4], + }).forAccountIndex(99); + + expect(result).toEqual([ + { derivationPath: "m/86'/0'/99'/0/0", index: 0 }, + { derivationPath: "m/86'/0'/99'/0/0", index: 2 }, + { derivationPath: "m/86'/0'/99'/0/0", index: 4 }, + ]); + }); +}); diff --git a/src/shared/crypto/bitcoin/signer-config.ts b/src/shared/crypto/bitcoin/signer-config.ts new file mode 100644 index 00000000000..886a310ba00 --- /dev/null +++ b/src/shared/crypto/bitcoin/signer-config.ts @@ -0,0 +1,54 @@ +import * as btc from '@scure/btc-signer'; + +import { BitcoinNetworkModes } from '@shared/constants'; +import { logger } from '@shared/logger'; +import { makeNumberRange } from '@shared/utils'; + +import { getInputPaymentType } from './bitcoin.utils'; +import { getTaprootAddressIndexDerivationPath } from './p2tr-address-gen'; +import { getNativeSegwitAddressIndexDerivationPath } from './p2wpkh-address-gen'; + +// Used to pass to a signing function, with info needed for determine which key +// to use to sign a given input +export interface BitcoinInputSigningConfig { + derivationPath: string; + index: number; +} + +interface GetAssumedZeroIndexSigningConfigArgs { + psbt: Uint8Array; + network: BitcoinNetworkModes; + indexesToSign?: number[]; +} +export function getAssumedZeroIndexSigningConfig({ + psbt, + network, + indexesToSign, +}: GetAssumedZeroIndexSigningConfigArgs) { + const tx = btc.Transaction.fromPSBT(psbt); + const indexes = indexesToSign ?? makeNumberRange(tx.inputsLength); + return { + forAccountIndex(accountIndex: number): BitcoinInputSigningConfig[] { + return indexes.map(inputIndex => { + const input = tx.getInput(inputIndex); + + const paymentType = getInputPaymentType(inputIndex, input, 'mainnet'); + switch (paymentType) { + case 'p2wpkh': + return { + index: inputIndex, + derivationPath: getNativeSegwitAddressIndexDerivationPath(network, accountIndex, 0), + }; + case 'p2tr': + return { + index: inputIndex, + derivationPath: getTaprootAddressIndexDerivationPath(network, accountIndex, 0), + }; + default: + logger.error('Cannot assume zero index for non-segwit input types'); + return { index: inputIndex, derivationPath: '' }; + } + }); + }, + }; +} diff --git a/tests/mocks/constants.ts b/tests/mocks/constants.ts index fe617c032f1..af9b99da5a2 100644 --- a/tests/mocks/constants.ts +++ b/tests/mocks/constants.ts @@ -1,4 +1,6 @@ -// Bitcoin test addresses +export const STANDARD_BIP_FAKE_MNEMONIC = + 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon cactus'; + export const TEST_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS = 'bc1q530dz4h80kwlzywlhx2qn0k6vdtftd93c499yq'; export const TEST_ACCOUNT_1_TAPROOT_ADDRESS = 'bc1putuzj9lyfcm8fef9jpy85nmh33cxuq9u6wyuk536t9kemdk37yjqmkc0pg'; diff --git a/tests/specs/rpc-sign-psbt/sign-psbt.spec.ts b/tests/specs/rpc-sign-psbt/sign-psbt.spec.ts index c5acef3aed0..05855904865 100644 --- a/tests/specs/rpc-sign-psbt/sign-psbt.spec.ts +++ b/tests/specs/rpc-sign-psbt/sign-psbt.spec.ts @@ -11,8 +11,10 @@ import { test } from '../../fixtures/fixtures'; // https://github.com/microsoft/playwright/issues/17075 const unsignedPsbtHex = '70736274ff01007b02000000020c9199d8079e6fe8a6c78ac9c4e0311c97c9fcdc8b5586c56d191b6d98c0035e0000000000ffffffff087168f5b929b37a27704d338aa9d0d3508a819f879c244ba12128f04a5b37ef0000000000ffffffff01c800000000000000160014a8113965cee4d5ffa2d9996a204866a58200131d000000000001011f6400000000000000160014a8113965cee4d5ffa2d9996a204866a58200131d0001011f6400000000000000160014a8113965cee4d5ffa2d9996a204866a58200131d0000'; + const signedAllPsbt = '70736274ff01007b02000000020c9199d8079e6fe8a6c78ac9c4e0311c97c9fcdc8b5586c56d191b6d98c0035e0000000000ffffffff087168f5b929b37a27704d338aa9d0d3508a819f879c244ba12128f04a5b37ef0000000000ffffffff01c800000000000000160014a8113965cee4d5ffa2d9996a204866a58200131d000000000001011f6400000000000000160014a8113965cee4d5ffa2d9996a204866a58200131d220203fe21e3444109e30ff7d19da0f530c344cad2e35fbee89afb2413858e4a9d7aa5483045022100ea4c2a68f1032102ad2c73504096f5dbd63d242ccce8000aa9db1a0ce4c4c59402204269fdd3536697329ed9bffcf67e3584d2d3426f84bb004fe467286abe7b02d8010001011f6400000000000000160014a8113965cee4d5ffa2d9996a204866a58200131d220203fe21e3444109e30ff7d19da0f530c344cad2e35fbee89afb2413858e4a9d7aa54730440220014950184114126c0cfeef37c87fff342d297c33190fcbd3fb9bf7c960d2bbe3022057554115f480ae984b12d919a505b1f52cfa89e49cd25f25e877db03bc153a77010000'; + const signedOnlyIndexZeroPsbt = '70736274ff01007b02000000020c9199d8079e6fe8a6c78ac9c4e0311c97c9fcdc8b5586c56d191b6d98c0035e0000000000ffffffff087168f5b929b37a27704d338aa9d0d3508a819f879c244ba12128f04a5b37ef0000000000ffffffff01c800000000000000160014a8113965cee4d5ffa2d9996a204866a58200131d000000000001011f6400000000000000160014a8113965cee4d5ffa2d9996a204866a58200131d220203fe21e3444109e30ff7d19da0f530c344cad2e35fbee89afb2413858e4a9d7aa5483045022100ea4c2a68f1032102ad2c73504096f5dbd63d242ccce8000aa9db1a0ce4c4c59402204269fdd3536697329ed9bffcf67e3584d2d3426f84bb004fe467286abe7b02d8010001011f6400000000000000160014a8113965cee4d5ffa2d9996a204866a58200131d0000'; @@ -20,7 +22,7 @@ test.describe('Sign PSBT', () => { test.beforeEach(async ({ extensionId, globalPage, onboardingPage, page }) => { await globalPage.setupAndUseApiCalls(extensionId); await onboardingPage.signInWithTestAccount(extensionId); - await page.goto('https://leather.io'); + await page.goto('localhost:3000'); }); function clickActionButton(context: BrowserContext) { @@ -35,9 +37,7 @@ test.describe('Sign PSBT', () => { async function interceptBroadcastRequest(context: BrowserContext) { const popup = await context.waitForEvent('page'); const requestPromise = popup.waitForRequest('**/*/tx'); - await popup.route('**/*/tx', async route => { - await route.abort(); - }); + await popup.route('**/*/tx', async route => await route.abort()); return requestPromise; } @@ -56,9 +56,9 @@ test.describe('Sign PSBT', () => { return async (params: SignPsbtRequestParams & { broadcast?: boolean }) => page.evaluate( async params => - (window as any).LeatherProvider.request('signPsbt', { - ...params, - }).catch((e: unknown) => e), + (window as any).LeatherProvider.request('signPsbt', { ...params }).catch( + (e: unknown) => e + ), { ...params } ); } diff --git a/yarn.lock b/yarn.lock index 8a47bf102a3..7bd96429763 100644 --- a/yarn.lock +++ b/yarn.lock @@ -262,7 +262,7 @@ dependencies: undici "^5.22.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.8.3": version "7.22.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== @@ -270,6 +270,14 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" +"@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + "@babel/code-frame@^8.0.0-alpha.4": version "8.0.0-alpha.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-8.0.0-alpha.4.tgz#d1deb7be4b01c6a79f7a0c0da99faff66fbeb81d" @@ -279,11 +287,11 @@ chalk "^5.3.0" "@babel/compat-data@^7.22.9": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.3.tgz#3febd552541e62b5e883a25eb3effd7c7379db11" - integrity sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ== + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== -"@babel/core@>=7.0.0-0 <8.0.0", "@babel/core@^7.12.3", "@babel/core@^7.22.5": +"@babel/core@>=7.0.0-0 <8.0.0", "@babel/core@^7.22.5": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.3.tgz#5ec09c8803b91f51cc887dedc2654a35852849c9" integrity sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew== @@ -304,6 +312,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.12.3": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.5.tgz#6e23f2acbcb77ad283c5ed141f824fd9f70101c7" + integrity sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.5" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.5" + "@babel/parser" "^7.23.5" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.5" + "@babel/types" "^7.23.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@7.17.7": version "7.17.7" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" @@ -313,7 +342,7 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.22.5", "@babel/generator@^7.23.0", "@babel/generator@^7.23.3": +"@babel/generator@^7.22.5", "@babel/generator@^7.23.0": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.3.tgz#86e6e83d95903fbe7613f448613b8b319f330a8e" integrity sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg== @@ -323,6 +352,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.23.3", "@babel/generator@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.5.tgz#17d0a1ea6b62f351d281350a5f80b87a810c4755" + integrity sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA== + dependencies: + "@babel/types" "^7.23.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" @@ -398,10 +437,10 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== +"@babel/helper-string-parser@^7.22.5", "@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== "@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" @@ -414,23 +453,23 @@ integrity sha512-nbcDbkNVXaLNg5Q4fWLMlXrOmwIGkeED/lpqghIE2CAEH1A9MaC4pO5iY0nnhGW9csEeWLJYvMuenvHHuTcosQ== "@babel/helper-validator-option@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" - integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== -"@babel/helpers@^7.23.2": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.2.tgz#2832549a6e37d484286e15ba36a5330483cac767" - integrity sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ== +"@babel/helpers@^7.23.2", "@babel/helpers@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.5.tgz#52f522840df8f1a848d06ea6a79b79eefa72401e" + integrity sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg== dependencies: "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.2" - "@babel/types" "^7.23.0" + "@babel/traverse" "^7.23.5" + "@babel/types" "^7.23.5" -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== +"@babel/highlight@^7.22.13", "@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== dependencies: "@babel/helper-validator-identifier" "^7.22.20" chalk "^2.4.2" @@ -445,11 +484,16 @@ chalk "^5.3.0" js-tokens "^8.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.5", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.5", "@babel/parser@^7.23.0", "@babel/parser@^7.23.3": +"@babel/parser@^7.1.0", "@babel/parser@^7.20.5", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5", "@babel/parser@^7.23.0": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.3.tgz#0ce0be31a4ca4f1884b5786057cadcb6c3be58f9" integrity sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw== +"@babel/parser@^7.14.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.3", "@babel/parser@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.5.tgz#37dee97c4752af148e1d38c34b856b2507660563" + integrity sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ== + "@babel/plugin-syntax-jsx@^7.22.5": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473" @@ -475,13 +519,20 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.11", "@babel/runtime@^7.23.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.11", "@babel/runtime@^7.23.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.9.2": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.5.tgz#11edb98f8aeec529b82b211028177679144242db" + integrity sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -507,7 +558,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.22.5", "@babel/traverse@^7.23.2", "@babel/traverse@^7.23.3", "@babel/traverse@^7.4.5": +"@babel/traverse@^7.22.5", "@babel/traverse@^7.4.5": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.3.tgz#26ee5f252e725aa7aca3474aa5b324eaf7908b5b" integrity sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ== @@ -523,6 +574,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.23.3", "@babel/traverse@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.5.tgz#f546bf9aba9ef2b042c0e00d245990c15508e7ec" + integrity sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.5" + "@babel/types" "^7.23.5" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@7.17.0": version "7.17.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" @@ -531,7 +598,7 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.3": +"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.20.7": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.3.tgz#d5ea892c07f2ec371ac704420f4dcdb07b5f9598" integrity sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw== @@ -540,6 +607,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.3", "@babel/types@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.5.tgz#48d730a00c95109fa4393352705954d74fb5b602" + integrity sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@bitcoinerlab/descriptors@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@bitcoinerlab/descriptors/-/descriptors-1.0.2.tgz#684e5755afcc7171b73a263612e00dc7dc407dcc" @@ -1102,6 +1178,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.7.tgz#646156aea43e8e6723de6e94a4ac07c5aed41be1" integrity sha512-YEDcw5IT7hW3sFKZBkCAQaOCJQLONVcD4bOyTXMZz5fr66pTHnAet46XAtbXAkJRfIn2YVhdC6R9g4xa27jQ1w== +"@esbuild/android-arm64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz#fb7130103835b6d43ea499c3f30cfb2b2ed58456" + integrity sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA== + "@esbuild/android-arm@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d" @@ -1122,6 +1203,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.7.tgz#0827b49aed813c33ea18ee257c1728cdc4a01030" integrity sha512-YGSPnndkcLo4PmVl2tKatEn+0mlVMr3yEpOOT0BeMria87PhvoJb5dg5f5Ft9fbCVgtAz4pWMzZVgSEGpDAlww== +"@esbuild/android-arm@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.8.tgz#b46e4d9e984e6d6db6c4224d72c86b7757e35bcb" + integrity sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA== + "@esbuild/android-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1" @@ -1142,6 +1228,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.7.tgz#fa294ed5214d88219d519e0ab1bbb0253a89b864" integrity sha512-jhINx8DEjz68cChFvM72YzrqfwJuFbfvSxZAk4bebpngGfNNRm+zRl4rtT9oAX6N9b6gBcFaJHFew5Blf6CvUw== +"@esbuild/android-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.8.tgz#a13db9441b5a4f4e4fec4a6f8ffacfea07888db7" + integrity sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A== + "@esbuild/darwin-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276" @@ -1162,6 +1253,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.7.tgz#e24d2ed545749ff251eabe8bce11fefa688892d3" integrity sha512-dr81gbmWN//3ZnBIm6YNCl4p3pjnabg1/ZVOgz2fJoUO1a3mq9WQ/1iuEluMs7mCL+Zwv7AY5e3g1hjXqQZ9Iw== +"@esbuild/darwin-arm64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz#49f5718d36541f40dd62bfdf84da9c65168a0fc2" + integrity sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw== + "@esbuild/darwin-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb" @@ -1182,6 +1278,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.7.tgz#02d1f8a572874c90d8f55dde8a859e5145bd06f6" integrity sha512-Lc0q5HouGlzQEwLkgEKnWcSazqr9l9OdV2HhVasWJzLKeOt0PLhHaUHuzb8s/UIya38DJDoUm74GToZ6Wc7NGQ== +"@esbuild/darwin-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz#75c5c88371eea4bfc1f9ecfd0e75104c74a481ac" + integrity sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q== + "@esbuild/freebsd-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2" @@ -1202,6 +1303,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.7.tgz#bc6a69b9a7915da278f0a5ebaec069c813982c22" integrity sha512-+y2YsUr0CxDFF7GWiegWjGtTUF6gac2zFasfFkRJPkMAuMy9O7+2EH550VlqVdpEEchWMynkdhC9ZjtnMiHImQ== +"@esbuild/freebsd-arm64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz#9d7259fea4fd2b5f7437b52b542816e89d7c8575" + integrity sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw== + "@esbuild/freebsd-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4" @@ -1222,6 +1328,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.7.tgz#ec3708488625d70e565968ceea1355e7c8613865" integrity sha512-CdXOxIbIzPJmJhrpmJTLx+o35NoiKBIgOvmvT+jeSadYiWJn0vFKsl+0bSG/5lwjNHoIDEyMYc/GAPR9jxusTA== +"@esbuild/freebsd-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz#abac03e1c4c7c75ee8add6d76ec592f46dbb39e3" + integrity sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg== + "@esbuild/linux-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb" @@ -1242,6 +1353,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.7.tgz#8e04b66c306858f92d4f90f8222775270755e88a" integrity sha512-inHqdOVCkUhHNvuQPT1oCB7cWz9qQ/Cz46xmVe0b7UXcuIJU3166aqSunsqkgSGMtUCWOZw3+KMwI6otINuC9g== +"@esbuild/linux-arm64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz#c577932cf4feeaa43cb9cec27b89cbe0df7d9098" + integrity sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ== + "@esbuild/linux-arm@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a" @@ -1262,6 +1378,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.7.tgz#12d5b65e089029ee1fe4c591b60969c9b1a85355" integrity sha512-Y+SCmWxsJOdQtjcBxoacn/pGW9HDZpwsoof0ttL+2vGcHokFlfqV666JpfLCSP2xLxFpF1lj7T3Ox3sr95YXww== +"@esbuild/linux-arm@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz#d6014d8b98b5cbc96b95dad3d14d75bb364fdc0f" + integrity sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ== + "@esbuild/linux-ia32@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a" @@ -1282,6 +1403,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.7.tgz#01eabc2a3ad9039e115db650268e4f48f910dbe2" integrity sha512-2BbiL7nLS5ZO96bxTQkdO0euGZIUQEUXMTrqLxKUmk/Y5pmrWU84f+CMJpM8+EHaBPfFSPnomEaQiG/+Gmh61g== +"@esbuild/linux-ia32@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz#2379a0554307d19ac4a6cdc15b08f0ea28e7a40d" + integrity sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ== + "@esbuild/linux-loong64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72" @@ -1302,6 +1428,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.7.tgz#70681113632970e6a5766607bbdb98aa18cf4d5f" integrity sha512-BVFQla72KXv3yyTFCQXF7MORvpTo4uTA8FVFgmwVrqbB/4DsBFWilUm1i2Oq6zN36DOZKSVUTb16jbjedhfSHw== +"@esbuild/linux-loong64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz#e2a5bbffe15748b49356a6cd7b2d5bf60c5a7123" + integrity sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ== + "@esbuild/linux-mips64el@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289" @@ -1322,6 +1453,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.7.tgz#f63c022a71a3d70c482d1943a27cb8997021e230" integrity sha512-DzAYckIaK+pS31Q/rGpvUKu7M+5/t+jI+cdleDgUwbU7KdG2eC3SUbZHlo6Q4P1CfVKZ1lUERRFP8+q0ob9i2w== +"@esbuild/linux-mips64el@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz#1359331e6f6214f26f4b08db9b9df661c57cfa24" + integrity sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q== + "@esbuild/linux-ppc64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7" @@ -1342,6 +1478,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.7.tgz#614eafd08b0c50212f287b948b3c08d6e60f221f" integrity sha512-JQ1p0SmUteNdUaaiRtyS59GkkfTW0Edo+e0O2sihnY4FoZLz5glpWUQEKMSzMhA430ctkylkS7+vn8ziuhUugQ== +"@esbuild/linux-ppc64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz#9ba436addc1646dc89dae48c62d3e951ffe70951" + integrity sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg== + "@esbuild/linux-riscv64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09" @@ -1362,6 +1503,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.7.tgz#31d3b63f92f65968268a8e61ba59872538e80e88" integrity sha512-xGwVJ7eGhkprY/nB7L7MXysHduqjpzUl40+XoYDGC4UPLbnG+gsyS1wQPJ9lFPcxYAaDXbdRXd1ACs9AE9lxuw== +"@esbuild/linux-riscv64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz#fbcf0c3a0b20f40b5fc31c3b7695f0769f9de66b" + integrity sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg== + "@esbuild/linux-s390x@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829" @@ -1382,6 +1528,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.7.tgz#be94974e0caa0783ae05f9477fd7170b9ac29cb0" integrity sha512-U8Rhki5PVU0L0nvk+E8FjkV8r4Lh4hVEb9duR6Zl21eIEYEwXz8RScj4LZWA2i3V70V4UHVgiqMpszXvG0Yqhg== +"@esbuild/linux-s390x@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz#989e8a05f7792d139d5564ffa7ff898ac6f20a4a" + integrity sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg== + "@esbuild/linux-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4" @@ -1402,6 +1553,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.7.tgz#84e8018a913dd4ecee954623e395984aef3d0007" integrity sha512-ZYZopyLhm4mcoZXjFt25itRlocKlcazDVkB4AhioiL9hOWhDldU9n38g62fhOI4Pth6vp+Mrd5rFKxD0/S+7aQ== +"@esbuild/linux-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz#b187295393a59323397fe5ff51e769ec4e72212b" + integrity sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg== + "@esbuild/netbsd-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462" @@ -1422,6 +1578,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.7.tgz#98898ba8800374c9df9bb182ca4f69fcecaf4411" integrity sha512-/yfjlsYmT1O3cum3J6cmGG16Fd5tqKMcg5D+sBYLaOQExheAJhqr8xOAEIuLo8JYkevmjM5zFD9rVs3VBcsjtQ== +"@esbuild/netbsd-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz#c1ec0e24ea82313cb1c7bae176bd5acd5bde7137" + integrity sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw== + "@esbuild/openbsd-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691" @@ -1442,6 +1603,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.7.tgz#46dc4eda2adb51f16361b1ad10e9b3f4938c4573" integrity sha512-MYDFyV0EW1cTP46IgUJ38OnEY5TaXxjoDmwiTXPjezahQgZd+j3T55Ht8/Q9YXBM0+T9HJygrSRGV5QNF/YVDQ== +"@esbuild/openbsd-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz#0c5b696ac66c6d70cf9ee17073a581a28af9e18d" + integrity sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ== + "@esbuild/sunos-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273" @@ -1462,6 +1628,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.7.tgz#1650d40dd88412ecc11490119cd23cbaf661a591" integrity sha512-JcPvgzf2NN/y6X3UUSqP6jSS06V0DZAV/8q0PjsZyGSXsIGcG110XsdmuWiHM+pno7/mJF6fjH5/vhUz/vA9fw== +"@esbuild/sunos-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz#2a697e1f77926ff09fcc457d8f29916d6cd48fb1" + integrity sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w== + "@esbuild/win32-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f" @@ -1482,6 +1653,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.7.tgz#e61de6c4eb204d83fd912f3ae6812cc8c7d32d25" integrity sha512-ZA0KSYti5w5toax5FpmfcAgu3ZNJxYSRm0AW/Dao5up0YV1hDVof1NvwLomjEN+3/GMtaWDI+CIyJOMTRSTdMw== +"@esbuild/win32-arm64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz#ec029e62a2fca8c071842ecb1bc5c2dd20b066f1" + integrity sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg== + "@esbuild/win32-ia32@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03" @@ -1502,6 +1678,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.7.tgz#3d9c159d42c67e37a433e44ef8217c661cb6f6d0" integrity sha512-CTOnijBKc5Jpk6/W9hQMMvJnsSYRYgveN6O75DTACCY18RA2nqka8dTZR+x/JqXCRiKk84+5+bRKXUSbbwsS0A== +"@esbuild/win32-ia32@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz#cbb9a3146bde64dc15543e48afe418c7a3214851" + integrity sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw== + "@esbuild/win32-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061" @@ -1522,6 +1703,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.7.tgz#02c4446f802706098d8e6ee70cf2b7aba96ded0b" integrity sha512-gRaP2sk6hc98N734luX4VpF318l3w+ofrtTu9j5L8EQXF+FzQKV6alCOHMVoJJHvVK/mGbwBXfOL1HETQu9IGQ== +"@esbuild/win32-x64@0.19.8": + version "0.19.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz#c8285183dbdb17008578dbacb6e22748709b4822" + integrity sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA== + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -3170,6 +3356,66 @@ prop-types "^15.7.2" react-is "16.9.0" +"@rollup/rollup-android-arm-eabi@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz#0ea289f68ff248b50fea5716ca9f65f7d4dba3ae" + integrity sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA== + +"@rollup/rollup-android-arm64@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz#27c8c67fc5de574874085a1b480ac65b3e18378e" + integrity sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA== + +"@rollup/rollup-darwin-arm64@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz#c5735c042980c85495411af7183dd20294763bd8" + integrity sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw== + +"@rollup/rollup-darwin-x64@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz#af844bd54abb73ca3c9cf89a31eec17861d1375d" + integrity sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg== + +"@rollup/rollup-linux-arm-gnueabihf@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz#5e972f63c441eaf859551039b3f18db9b035977d" + integrity sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ== + +"@rollup/rollup-linux-arm64-gnu@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz#f4cfbc71e3b6fdb395b28b1472414e181515c72d" + integrity sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw== + +"@rollup/rollup-linux-arm64-musl@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz#6a94c691830dc29bf708de7c640f494996130893" + integrity sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw== + +"@rollup/rollup-linux-x64-gnu@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz#f07bae3f7dc532d9ea5ab36c9071db329f9a1efb" + integrity sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA== + +"@rollup/rollup-linux-x64-musl@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz#357a34fdbf410af88ce48bd802bea6462bb9a8bc" + integrity sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ== + +"@rollup/rollup-win32-arm64-msvc@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz#b6e97fd38281667e35297033393cd1101f4a31be" + integrity sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ== + +"@rollup/rollup-win32-ia32-msvc@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz#a95db026c640c8128bfd38546d85342f2329beaf" + integrity sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw== + +"@rollup/rollup-win32-x64-msvc@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz#45785b5caf83200a34a9867ba50d69560880c120" + integrity sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A== + "@schemastore/web-manifest@0.0.6": version "0.0.6" resolved "https://registry.yarnpkg.com/@schemastore/web-manifest/-/web-manifest-0.0.6.tgz#cb1c82977690e9004e0872b77d4cc517f2bcf805" @@ -4741,9 +4987,9 @@ "@types/chai" "*" "@types/chai@*", "@types/chai@^4.3.5": - version "4.3.10" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.10.tgz#2ad2959d1767edee5b0e4efb1a0cd2b500747317" - integrity sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg== + version "4.3.11" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.11.tgz#e95050bf79a932cb7305dd130254ccdf9bde671c" + integrity sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ== "@types/chroma-js@2.4.1": version "2.4.1" @@ -5315,9 +5561,9 @@ "@types/node" "*" "@types/node@*": - version "20.9.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298" - integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw== + version "20.10.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.3.tgz#4900adcc7fc189d5af5bb41da8f543cea6962030" + integrity sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg== dependencies: undici-types "~5.26.4" @@ -7203,7 +7449,7 @@ browserify-sign@^4.0.0: readable-stream "^3.6.2" safe-buffer "^5.2.1" -browserslist@4.22.1, browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.21.9: +browserslist@4.22.1, browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.21.4: version "4.22.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== @@ -7213,6 +7459,16 @@ browserslist@4.22.1, browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4. node-releases "^2.0.13" update-browserslist-db "^1.0.13" +browserslist@^4.21.9: + version "4.22.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== + dependencies: + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + bs58@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -7454,11 +7710,16 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001520, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001520, caniuse-lite@^1.0.30001538: version "1.0.30001562" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz#9d16c5fd7e9c592c4cd5e304bc0f75b0008b2759" integrity sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng== +caniuse-lite@^1.0.30001541, caniuse-lite@^1.0.30001565: + version "1.0.30001566" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz#61a8e17caf3752e3e426d4239c549ebbb37fef0d" + integrity sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -9163,10 +9424,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.535: - version "1.4.583" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.583.tgz#7b0ac4f36388da4b5485788adb92cd7dd0abffc4" - integrity sha512-93y1gcONABZ7uqYe/JWDVQP/Pj/sQSunF0HVAPdlg/pfBnOyBMLlQUxWvkqcljJg1+W6cjvPuYD+r1Th9Tn8mA== +electron-to-chromium@^1.4.535, electron-to-chromium@^1.4.601: + version "1.4.603" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.603.tgz#446907c21d333b55d0beaba1cb5b48430775a8a7" + integrity sha512-Dvo5OGjnl7AZTU632dFJtWj0uJK835eeOVQIuRcmBmsFsTNn3cL05FqOyHAfGQDIoHfLhyJ1Tya3PJ0ceMz54g== electron@^26.1.0: version "26.5.0" @@ -9546,6 +9807,34 @@ esbuild@^0.19.0: "@esbuild/win32-ia32" "0.19.5" "@esbuild/win32-x64" "0.19.5" +esbuild@^0.19.3: + version "0.19.8" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.8.tgz#ad05b72281d84483fa6b5345bd246c27a207b8f1" + integrity sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w== + optionalDependencies: + "@esbuild/android-arm" "0.19.8" + "@esbuild/android-arm64" "0.19.8" + "@esbuild/android-x64" "0.19.8" + "@esbuild/darwin-arm64" "0.19.8" + "@esbuild/darwin-x64" "0.19.8" + "@esbuild/freebsd-arm64" "0.19.8" + "@esbuild/freebsd-x64" "0.19.8" + "@esbuild/linux-arm" "0.19.8" + "@esbuild/linux-arm64" "0.19.8" + "@esbuild/linux-ia32" "0.19.8" + "@esbuild/linux-loong64" "0.19.8" + "@esbuild/linux-mips64el" "0.19.8" + "@esbuild/linux-ppc64" "0.19.8" + "@esbuild/linux-riscv64" "0.19.8" + "@esbuild/linux-s390x" "0.19.8" + "@esbuild/linux-x64" "0.19.8" + "@esbuild/netbsd-x64" "0.19.8" + "@esbuild/openbsd-x64" "0.19.8" + "@esbuild/sunos-x64" "0.19.8" + "@esbuild/win32-arm64" "0.19.8" + "@esbuild/win32-ia32" "0.19.8" + "@esbuild/win32-x64" "0.19.8" + escalade@3.1.1, escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -10303,7 +10592,7 @@ fsevents@2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== -fsevents@~2.3.2: +fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -13595,7 +13884,7 @@ nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== -nanoid@3.3.4, nanoid@^3.1.23, nanoid@^3.3.6, nanoid@^4.0.2: +nanoid@3.3.4, nanoid@^3.1.23, nanoid@^3.3.6, nanoid@^3.3.7, nanoid@^4.0.2: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== @@ -13725,10 +14014,10 @@ node-notifier@10.0.1: uuid "^8.3.2" which "^2.0.2" -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +node-releases@^2.0.13, node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== nopt@^5.0.0: version "5.0.0" @@ -14831,7 +15120,7 @@ postcss@8.4.29: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@8.4.31, postcss@^8.4.21, postcss@^8.4.27, postcss@^8.4.31: +postcss@8.4.31, postcss@^8.4.21, postcss@^8.4.31: version "8.4.31" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== @@ -14840,6 +15129,15 @@ postcss@8.4.31, postcss@^8.4.21, postcss@^8.4.27, postcss@^8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.27, postcss@^8.4.32: + version "8.4.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" + integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + preferred-pm@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/preferred-pm/-/preferred-pm-3.1.2.tgz#aedb70550734a574dffcbf2ce82642bd1753bdd6" @@ -15912,6 +16210,25 @@ rollup@^3.27.1: optionalDependencies: fsevents "~2.3.2" +rollup@^4.2.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.6.1.tgz#351501c86b5b4f976dde8c5837516452b59921f8" + integrity sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ== + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.6.1" + "@rollup/rollup-android-arm64" "4.6.1" + "@rollup/rollup-darwin-arm64" "4.6.1" + "@rollup/rollup-darwin-x64" "4.6.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.6.1" + "@rollup/rollup-linux-arm64-gnu" "4.6.1" + "@rollup/rollup-linux-arm64-musl" "4.6.1" + "@rollup/rollup-linux-x64-gnu" "4.6.1" + "@rollup/rollup-linux-x64-musl" "4.6.1" + "@rollup/rollup-win32-arm64-msvc" "4.6.1" + "@rollup/rollup-win32-ia32-msvc" "4.6.1" + "@rollup/rollup-win32-x64-msvc" "4.6.1" + fsevents "~2.3.2" + rrweb-cssom@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" @@ -16665,9 +16982,9 @@ statuses@2.0.1: integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== std-env@^3.3.3: - version "3.5.0" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.5.0.tgz#83010c9e29bd99bf6f605df87c19012d82d63b97" - integrity sha512-JGUEaALvL0Mf6JCfYnJOTcobY+Nc7sG/TemDRBqCA0wEr4DER7zDchaaixTlmOxAjG1uRJmX82EQcxwTQTkqVA== + version "3.6.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.6.0.tgz#94807562bddc68fa90f2e02c5fd5b6865bb4e98e" + integrity sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg== stdin-discarder@^0.1.0: version "0.1.0" @@ -17951,7 +18268,18 @@ vite@4.4.11: optionalDependencies: fsevents "~2.3.2" -"vite@^3.0.0 || ^4.0.0 || ^5.0.0-0", "vite@^3.1.0 || ^4.0.0 || ^5.0.0-0", vite@^4.4.6: +"vite@^3.0.0 || ^4.0.0 || ^5.0.0-0", "vite@^3.1.0 || ^4.0.0 || ^5.0.0-0": + version "5.0.5" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.5.tgz#3eebe3698e3b32cea36350f58879258fec858a3c" + integrity sha512-OekeWqR9Ls56f3zd4CaxzbbS11gqYkEiBtnWFFgYR2WV8oPJRRKq0mpskYy/XaoCL3L7VINDhqqOMNDiYdGvGg== + dependencies: + esbuild "^0.19.3" + postcss "^8.4.32" + rollup "^4.2.0" + optionalDependencies: + fsevents "~2.3.3" + +vite@^4.4.6: version "4.5.0" resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.0.tgz#ec406295b4167ac3bc23e26f9c8ff559287cff26" integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw== From 8b1be500fa099bd09b1e9617d77c98be7843bab2 Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Fri, 8 Dec 2023 16:11:56 +0100 Subject: [PATCH 10/18] fix: revert signing logic to try both keys, closes #4645 --- .vscode/settings.json | 3 +- .../blockchain/bitcoin/bitcoin.hooks.ts | 38 +++++++++---------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f26241ce518..ff9962c5109 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "prettier.documentSelectors": ["src/**/*.{ts,tsx}", "*.{js,json}"], "vitest.include": ["src/**/*.spec.ts"], - "githubPullRequests.ignoredPullRequestBranches": ["dev"] + "githubPullRequests.ignoredPullRequestBranches": ["dev"], + "typescript.preferences.preferTypeOnlyAutoImports": true } diff --git a/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts b/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts index 4c51298b7dc..69cb4eb8c38 100644 --- a/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts +++ b/src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts @@ -16,10 +16,10 @@ import { BitcoinInputSigningConfig, getAssumedZeroIndexSigningConfig, } from '@shared/crypto/bitcoin/signer-config'; -import { logger } from '@shared/logger'; import { allSighashTypes } from '@shared/rpc/methods/sign-psbt'; import { isNumber, makeNumberRange } from '@shared/utils'; +import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useWalletType } from '@app/common/use-wallet-type'; import { listenForBitcoinTxLedgerSigning } from '@app/features/ledger/flows/bitcoin-tx-signing/bitcoin-tx-signing-event-listeners'; import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate'; @@ -72,7 +72,6 @@ export function useZeroIndexTaprootAddress(accIndex?: number) { function useSignBitcoinSoftwareTx() { const createNativeSegwitSigner = useCurrentAccountNativeSegwitSigner(); const createTaprootSigner = useCurrentAccountTaprootSigner(); - const network = useCurrentNetwork(); return async (psbt: Uint8Array, inputSigningConfig: BitcoinInputSigningConfig[]) => { const tx = btc.Transaction.fromPSBT(psbt); @@ -85,20 +84,16 @@ function useSignBitcoinSoftwareTx() { if (!nativeSegwitSigner || !taprootSigner) throw new Error('Signers not available'); - const input = tx.getInput(index); - const addressType = getInputPaymentType(index, input, network.chain.bitcoin.bitcoinNetwork); - - switch (addressType) { - case 'p2tr': { + // See #4628. + // Our API doesn't support users specifying which key they want to sign + // with. Until we support this, we sign with both, as in some cases, e.g. + // Asigna, the Native Segwit key is used to sign a multisig taproot input + try { + nativeSegwitSigner.signIndex(tx, index, allSighashTypes); + } catch (e) { + try { taprootSigner.signIndex(tx, index, allSighashTypes); - break; - } - case 'p2wpkh': { - nativeSegwitSigner.signIndex(tx, index, allSighashTypes); - break; - } - default: - logger.warn('Cannot sign input of address type ' + addressType); + } catch (er) {} } }); @@ -205,18 +200,21 @@ export function useSignLedgerBitcoinTx() { export function useAddTapInternalKeysIfMissing() { const createTaprootSigner = useCurrentAccountTaprootSigner(); - return (tx: btc.Transaction, inputIndexes: BitcoinInputSigningConfig[]) => { + const analytics = useAnalytics(); + return (tx: btc.Transaction, inputIndexes: BitcoinInputSigningConfig[]) => inputIndexes.forEach(({ index, derivationPath }) => { const taprootSigner = createTaprootSigner?.(extractAddressIndexFromPath(derivationPath)); if (!taprootSigner) throw new Error('Taproot signer not found'); const input = tx.getInput(index); + const witnessOutputScript = input.witnessUtxo?.script && btc.OutScript.decode(input.witnessUtxo.script); - if (taprootSigner && witnessOutputScript?.type === 'tr' && !input.tapInternalKey) + if (taprootSigner && witnessOutputScript?.type === 'tr' && !input.tapInternalKey) { + void analytics.track('psbt_sign_request_p2tr_missing_taproot_internal_key'); tx.updateInput(index, { ...input, tapInternalKey: taprootSigner.payment.tapInternalKey }); + } }); - }; } export function useGetAssumedZeroIndexSigningConfig() { @@ -238,8 +236,8 @@ export function useSignBitcoinTx() { const getDefaultSigningConfig = useGetAssumedZeroIndexSigningConfig(); /** - * Don't forget to finalize the tx once it's returned. You can broadcast with - * the hex value from `tx.hex`. + * Bitcoin signing function. Don't forget to finalize the tx once it's + * returned. You can broadcast with the hex value from `tx.hex`. */ return (psbt: Uint8Array, inputsToSign?: BitcoinInputSigningConfig[] | number[]) => { function getSigningConfig(inputsToSign?: BitcoinInputSigningConfig[] | number[]) { From 5aa7c3c3d6d5c383c78f6457ac6aa9dd4a73e8c5 Mon Sep 17 00:00:00 2001 From: Pete Watters <2938440+pete-watters@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:12:08 +0000 Subject: [PATCH 11/18] fix: fix brc-20 sendby signing transaction before finalising then broadcasting, closes #4635 --- .../form/brc-20/brc-20-choose-fee.tsx | 9 +++++++-- .../send-crypto-asset-form.routes.tsx | 2 +- .../{brc20-sent-symmary.tsx => brc20-sent-summary.tsx} | 0 3 files changed, 8 insertions(+), 3 deletions(-) rename src/app/pages/send/sent-summary/{brc20-sent-symmary.tsx => brc20-sent-summary.tsx} (100%) diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx index 4b56ffd75a3..e116664d4be 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx @@ -24,6 +24,7 @@ import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choos import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend'; import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client'; import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/use-brc-20'; +import { useSignBitcoinTx } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; import { useSendBitcoinAssetContextState } from '../../family/bitcoin/components/send-bitcoin-asset-container'; @@ -41,6 +42,7 @@ export function BrcChooseFee() { const navigate = useNavigate(); const { amount, recipient, tick, utxos } = useBrc20ChooseFeeState(); const generateTx = useGenerateUnsignedNativeSegwitSingleRecipientTx(); + const signTx = useSignBitcoinTx(); const { selectedFeeType, setSelectedFeeType } = useSendBitcoinAssetContextState(); const { initiateTransfer } = useBrc20Transfers(); const { feesList, isLoading } = useBitcoinFeesList({ @@ -80,12 +82,15 @@ export function BrcChooseFee() { ); if (!resp) return logger.error('Attempted to generate raw tx, but no tx exists'); + const signedTx = await signTx(resp.psbt); + + if (!signedTx) return logger.error('Attempted to sign tx, but no tx exists'); + signedTx.finalize(); - const { hex } = resp; const feeRowValue = formFeeRowValue(feeRate, isCustomFee); navigate(RouteUrls.SendBrc20Confirmation.replace(':ticker', tick), { state: { - tx: hex, + tx: signedTx.hex, orderId: id, fee: feeValue, serviceFee: charge.amount, diff --git a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx index 7bd95d10a71..23bdf75383d 100644 --- a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx +++ b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx @@ -14,7 +14,7 @@ import { AccountGate } from '@app/routes/account-gate'; import { BroadcastError } from '../broadcast-error/broadcast-error'; import { ChooseCryptoAsset } from '../choose-crypto-asset/choose-crypto-asset'; import { SendContainer } from '../send-container'; -import { Brc20SentSummary } from '../sent-summary/brc20-sent-symmary'; +import { Brc20SentSummary } from '../sent-summary/brc20-sent-summary'; import { BtcSentSummary } from '../sent-summary/btc-sent-summary'; import { StxSentSummary } from '../sent-summary/stx-sent-summary'; import { RecipientAccountsDrawer } from './components/recipient-accounts-drawer/recipient-accounts-drawer'; diff --git a/src/app/pages/send/sent-summary/brc20-sent-symmary.tsx b/src/app/pages/send/sent-summary/brc20-sent-summary.tsx similarity index 100% rename from src/app/pages/send/sent-summary/brc20-sent-symmary.tsx rename to src/app/pages/send/sent-summary/brc20-sent-summary.tsx From 42ee98101aacd61eacdde381b53209b738522518 Mon Sep 17 00:00:00 2001 From: Pete Watters <2938440+pete-watters@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:41:28 +0000 Subject: [PATCH 12/18] chore: add ledger signing routes to BRC-20 send --- .../form/brc-20/brc-20-choose-fee.tsx | 46 ++++++++++--------- .../send-crypto-asset-form.routes.tsx | 4 +- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx index e116664d4be..016faad82a3 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/brc-20/brc-20-choose-fee.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { toast } from 'react-hot-toast'; +import { Outlet } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom'; import { Stack } from 'leather-styles/jsx'; @@ -130,26 +131,29 @@ export function BrcChooseFee() { ) : ( - setSelectedFeeType(value)} - onValidateBitcoinSpend={onValidateBitcoinFeeSpend} - selectedFeeType={selectedFeeType} - /> - } - isLoading={isLoading} - isSendingMax={false} - onChooseFee={previewTransaction} - onSetSelectedFeeType={(value: BtcFeeType | null) => setSelectedFeeType(value)} - onValidateBitcoinSpend={onValidateBitcoinFeeSpend} - recommendedFeeRate={recommendedFeeRate} - recipient={recipient} - showError={showInsufficientBalanceError} - maxRecommendedFeeRate={feesList[0]?.feeRate} - /> + <> + setSelectedFeeType(value)} + onValidateBitcoinSpend={onValidateBitcoinFeeSpend} + selectedFeeType={selectedFeeType} + /> + } + isLoading={isLoading} + isSendingMax={false} + onChooseFee={previewTransaction} + onSetSelectedFeeType={(value: BtcFeeType | null) => setSelectedFeeType(value)} + onValidateBitcoinSpend={onValidateBitcoinFeeSpend} + recommendedFeeRate={recommendedFeeRate} + recipient={recipient} + showError={showInsufficientBalanceError} + maxRecommendedFeeRate={feesList[0]?.feeRate} + /> + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx index 23bdf75383d..02080a77214 100644 --- a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx +++ b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx @@ -71,7 +71,9 @@ export const sendCryptoAssetFormRoutes = ( } /> } /> - } /> + }> + {ledgerBitcoinTxSigningRoutes} + } /> } /> From 4b55f10103a21841c90c08bcb9f1e1d1a0180eb4 Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Fri, 8 Dec 2023 17:49:24 +0100 Subject: [PATCH 13/18] test: run on localhost, improve reliability --- tests/specs/message-signing/bip322-message-signing.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/specs/message-signing/bip322-message-signing.spec.ts b/tests/specs/message-signing/bip322-message-signing.spec.ts index 62a57088f1d..02a4a60a8a4 100644 --- a/tests/specs/message-signing/bip322-message-signing.spec.ts +++ b/tests/specs/message-signing/bip322-message-signing.spec.ts @@ -7,7 +7,7 @@ test.describe('Message signing', () => test.beforeEach(async ({ extensionId, globalPage, onboardingPage, page }) => { await globalPage.setupAndUseApiCalls(extensionId); await onboardingPage.signInWithTestAccount(extensionId); - await page.goto('https://leather.io'); + await page.goto('localhost:3000', { waitUntil: 'networkidle' }); }); function clickActionButton(context: BrowserContext) { From 7ec6bd1eab0a40260fd9e9a3f1abd56c8c16dba8 Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Fri, 8 Dec 2023 18:06:41 +0100 Subject: [PATCH 14/18] test: remove more leather.io page views --- .../bitcoin-contract-offer-accept.spec.ts | 3 +-- tests/specs/rpc-stacks-transaction/transaction-signing.spec.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/specs/bitcoin-contract-offer-accept/bitcoin-contract-offer-accept.spec.ts b/tests/specs/bitcoin-contract-offer-accept/bitcoin-contract-offer-accept.spec.ts index d54959b9550..0d6ef50f8e5 100644 --- a/tests/specs/bitcoin-contract-offer-accept/bitcoin-contract-offer-accept.spec.ts +++ b/tests/specs/bitcoin-contract-offer-accept/bitcoin-contract-offer-accept.spec.ts @@ -22,8 +22,7 @@ test.describe('Bitcoin Contract Request Test', () => { await homePage.clickSettingsButton(); await page.getByTestId(SettingsSelectors.ChangeNetworkAction).click(); await page.locator(`text="Testnet"`).click(); - await page.goto('https://leather.io'); - await page.waitForLoadState('networkidle'); + await page.goto('localhost:3000', { waitUntil: 'networkidle' }); }); function initiateOfferRequest(page: Page) { diff --git a/tests/specs/rpc-stacks-transaction/transaction-signing.spec.ts b/tests/specs/rpc-stacks-transaction/transaction-signing.spec.ts index 89eb41bdd55..bde52856f5f 100644 --- a/tests/specs/rpc-stacks-transaction/transaction-signing.spec.ts +++ b/tests/specs/rpc-stacks-transaction/transaction-signing.spec.ts @@ -14,7 +14,7 @@ test.describe('Transaction signing', () => { test.beforeEach(async ({ extensionId, globalPage, onboardingPage, page }) => { await globalPage.setupAndUseApiCalls(extensionId); await onboardingPage.signInWithTestAccount(extensionId); - await page.goto('https://leather.io'); + await page.goto('localhost:3000', { waitUntil: 'networkidle' }); }); function checkVisibleContent(context: BrowserContext) { From 7483b9e52e175431bbd02778e128f6de603da352 Mon Sep 17 00:00:00 2001 From: Pete Watters <2938440+pete-watters@users.noreply.github.com> Date: Mon, 11 Dec 2023 10:46:21 +0000 Subject: [PATCH 15/18] fix: add background location to brc-20 modal and fix typo --- src/app/features/collectibles/collectibles.tsx | 8 +++++++- src/app/routes/app-routes.tsx | 2 +- src/shared/route-urls.ts | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/app/features/collectibles/collectibles.tsx b/src/app/features/collectibles/collectibles.tsx index d68749e0168..7f440757a5b 100644 --- a/src/app/features/collectibles/collectibles.tsx +++ b/src/app/features/collectibles/collectibles.tsx @@ -32,7 +32,13 @@ export function Collectibles() { subHeader={whenWallet({ software: ( navigate(RouteUrls.RetriveTaprootFunds)} + onSelectRetrieveBalance={() => + navigate(RouteUrls.RetrieveTaprootFunds, { + state: { + backgroundLocation: { pathname: RouteUrls.Home }, + }, + }) + } /> ), ledger: null, diff --git a/src/app/routes/app-routes.tsx b/src/app/routes/app-routes.tsx index 4f1668dc512..7ec17bcc076 100644 --- a/src/app/routes/app-routes.tsx +++ b/src/app/routes/app-routes.tsx @@ -89,7 +89,7 @@ function useAppRoutes() { {homePageModalRoutes} - } /> + } /> }> {ledgerStacksTxSigningRoutes} diff --git a/src/shared/route-urls.ts b/src/shared/route-urls.ts index f0b247bbea5..b8cd76a91ef 100644 --- a/src/shared/route-urls.ts +++ b/src/shared/route-urls.ts @@ -55,7 +55,7 @@ export enum RouteUrls { EditNonce = 'edit-nonce', SelectNetwork = 'choose-network', SignOutConfirm = 'sign-out', - RetriveTaprootFunds = 'retrive-taproot-funds', + RetrieveTaprootFunds = 'retrieve-taproot-funds', // Send crypto asset routes SendCryptoAsset = '/send', From 24d36773adfb2ab830fa0ab8d4063182c7656829 Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Fri, 8 Dec 2023 13:54:18 -0600 Subject: [PATCH 16/18] fix: swap test route path --- tests/specs/swap/swap.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/specs/swap/swap.spec.ts b/tests/specs/swap/swap.spec.ts index 2d497f66c27..f5923a5db76 100644 --- a/tests/specs/swap/swap.spec.ts +++ b/tests/specs/swap/swap.spec.ts @@ -1,6 +1,6 @@ import { test } from '../../fixtures/fixtures'; -const alexSdkPostRoute = 'https://gql.alexlab.co/v1/graphql'; +const alexSdkPostRoute = '*/**/v1/graphql'; test.describe('Swaps', () => { test.beforeEach(async ({ extensionId, globalPage, homePage, onboardingPage, swapPage }) => { From 61f19408b312d4625aa2ed5427ef869871ad274e Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Mon, 11 Dec 2023 11:57:14 +0100 Subject: [PATCH 17/18] test: include origin in glob --- tests/specs/swap/swap.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/specs/swap/swap.spec.ts b/tests/specs/swap/swap.spec.ts index f5923a5db76..66e4c44747b 100644 --- a/tests/specs/swap/swap.spec.ts +++ b/tests/specs/swap/swap.spec.ts @@ -1,6 +1,6 @@ import { test } from '../../fixtures/fixtures'; -const alexSdkPostRoute = '*/**/v1/graphql'; +const alexSdkPostRoute = 'https://*.alexlab.co/v1/graphql'; test.describe('Swaps', () => { test.beforeEach(async ({ extensionId, globalPage, homePage, onboardingPage, swapPage }) => { From 3d5ca6d6e740952b520e3784c61fc9be0d8298b8 Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Mon, 11 Dec 2023 11:10:13 +0100 Subject: [PATCH 18/18] test: trace on dev --- .github/workflows/playwright.yml | 2 +- playwright.config.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 973f77cde8e..089cede9d9a 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -72,6 +72,7 @@ jobs: - name: Run Playwright tests run: xvfb-run yarn playwright test tests/specs --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=1 env: + GITHUB_HEAD_REF: ${{ github.head_ref }} TEST_ACCOUNT_SECRET_KEY: ${{ secrets.TEST_ACCOUNT_SECRET_KEY }} - name: Upload blob report to GitHub Actions Artifacts @@ -118,7 +119,6 @@ jobs: - name: Deploy uses: peaceiris/actions-gh-pages@v3 - if: github.event_name == 'pull_request' with: personal_token: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} external_repository: leather-wallet/playwright-reports diff --git a/playwright.config.ts b/playwright.config.ts index a70909af8bd..dd15fadaec6 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,5 +1,7 @@ import { defineConfig, devices } from '@playwright/test'; +console.log('Branch: ', process.env.GITHUB_HEAD_REF); + /** * See https://playwright.dev/docs/test-configuration */ @@ -17,7 +19,9 @@ export default defineConfig({ [process.env.CI ? 'blob' : 'html', { open: 'never' }], ], use: { - trace: 'on-first-retry', + // Traces are heavy so we want to use them sparingly, but having full trace + // to reference of the latest dev build is useful to inspect. + trace: process.env.GITHUB_HEAD_REF === 'dev' ? 'on' : 'on-first-retry', }, projects: [ {