diff --git a/account-kit/react/src/hooks/useExportAccount.ts b/account-kit/react/src/hooks/useExportAccount.ts index 3046ef867b..6adf95564e 100644 --- a/account-kit/react/src/hooks/useExportAccount.ts +++ b/account-kit/react/src/hooks/useExportAccount.ts @@ -1,7 +1,11 @@ "use client"; import { DEFAULT_IFRAME_CONTAINER_ID } from "@account-kit/core"; -import type { ExportWalletParams as ExportAccountParams } from "@account-kit/signer"; +import type { + AlchemyWebSigner, + ExportWalletParams as ExportAccountParams, + ExportWalletOutput, +} from "@account-kit/signer"; import { useMutation, type UseMutateFunction } from "@tanstack/react-query"; import { createElement, useCallback, type CSSProperties } from "react"; import { useAlchemyAccountContext } from "./useAlchemyAccountContext.js"; @@ -10,7 +14,7 @@ import { useSigner } from "./useSigner.js"; export type UseExportAccountMutationArgs = { params?: ExportAccountParams; -} & BaseHookMutationArgs; +} & BaseHookMutationArgs; /** * Props for the `ExportAccountComponent` component. This component is @@ -30,7 +34,7 @@ export type ExportAccountComponentProps = { }; export type UseExportAccountResult = { - exportAccount: UseMutateFunction; + exportAccount: UseMutateFunction; isExported: boolean; isExporting: boolean; error: Error | null; @@ -66,7 +70,7 @@ export function useExportAccount( ): UseExportAccountResult { const { params, ...mutationArgs } = args ?? {}; const { queryClient } = useAlchemyAccountContext(); - const signer = useSigner(); + const signer = useSigner(); const { iframeContainerId } = params ?? { iframeContainerId: DEFAULT_IFRAME_CONTAINER_ID, }; diff --git a/account-kit/rn-signer/package.json b/account-kit/rn-signer/package.json index cf30cec75c..f444433703 100644 --- a/account-kit/rn-signer/package.json +++ b/account-kit/rn-signer/package.json @@ -144,6 +144,7 @@ "dependencies": { "@aa-sdk/core": "^4.60.1", "@account-kit/signer": "^4.60.1", + "@turnkey/crypto": "^2.5.0", "@turnkey/react-native-passkey-stamper": "^1.0.14", "uuid": "^11.1.0", "viem": "^2.29.2", diff --git a/account-kit/rn-signer/src/client.ts b/account-kit/rn-signer/src/client.ts index b4574c9fd8..bd6c66b388 100644 --- a/account-kit/rn-signer/src/client.ts +++ b/account-kit/rn-signer/src/client.ts @@ -2,8 +2,6 @@ import "react-native-get-random-values"; import "./utils/buffer-polyfill"; import "./utils/mmkv-localstorage-polyfill"; - -/* eslint-disable import/extensions */ import { type ConnectionConfig } from "@aa-sdk/core"; import { createPasskey, @@ -37,6 +35,8 @@ import { } from "@account-kit/signer"; import { InAppBrowser } from "react-native-inappbrowser-reborn"; import { z } from "zod"; +import { generateP256KeyPair, hpkeDecrypt } from "@turnkey/crypto"; +import { toHex } from "viem"; import { InAppBrowserUnavailableError } from "./errors"; import NativeTEKStamper from "./NativeTEKStamper"; import { parseSearchParams } from "./utils/parseUrlParams"; @@ -54,8 +54,17 @@ export const RNSignerClientParamsSchema = z.object({ export type RNSignerClientParams = z.input; +export type ExportWalletParams = { + exportAs?: "PRIVATE_KEY" | "SEED_PHRASE"; +}; + +export type ExportWalletResult = string; + // TODO: need to emit events -export class RNSignerClient extends BaseSignerClient { +export class RNSignerClient extends BaseSignerClient< + ExportWalletParams, + string +> { private stamper = NativeTEKStamper; oauthCallbackUrl: string; rpId: string | undefined; @@ -273,8 +282,132 @@ export class RNSignerClient extends BaseSignerClient { this.stamper.clear(); await this.stamper.init(); } - override exportWallet(_params: unknown): Promise { - throw new Error("Method not implemented."); + + /** + * Exports the wallet and returns the decrypted private key or seed phrase. + * + * @param {ExportWalletParams} params - Export parameters + * @returns {Promise} The decrypted private key or seed phrase + * @throws {Error} If the user is not authenticated or export fails + */ + async exportWallet(params?: ExportWalletParams): Promise { + if (!this.user) { + throw new Error("User must be authenticated to export wallet"); + } + + const exportAs = params?.exportAs || "PRIVATE_KEY"; + + // Step 1: Generate a P256 key pair for encryption + const embeddedKey = generateP256KeyPair(); + + try { + let exportBundle: string; + + if (exportAs === "PRIVATE_KEY") { + // Step 2a: Export as private key + const { activity } = await this.turnkeyClient.exportWalletAccount({ + organizationId: this.user.orgId, + type: "ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT", + timestampMs: Date.now().toString(), + parameters: { + address: this.user.address, + targetPublicKey: embeddedKey.publicKeyUncompressed, + }, + }); + + const result = await this.pollActivityCompletion( + activity, + this.user.orgId, + "exportWalletAccountResult", + ); + + if (!result.exportBundle) { + throw new Error("Failed to export wallet: no export bundle returned"); + } + + exportBundle = result.exportBundle; + } else { + // Step 2b: Export as seed phrase (need to find the wallet first) + const { wallets } = await this.turnkeyClient.getWallets({ + organizationId: this.user.orgId, + }); + + const walletAccountResponses = await Promise.all( + wallets.map(({ walletId }) => + this.turnkeyClient.getWalletAccounts({ + organizationId: this.user!.orgId, + walletId, + }), + ), + ); + const walletAccounts = walletAccountResponses.flatMap( + (x) => x.accounts, + ); + + const walletAccount = walletAccounts.find( + (x) => x.address.toLowerCase() === this.user!.address.toLowerCase(), + ); + + if (!walletAccount) { + throw new Error("Could not find wallet account"); + } + + const { activity } = await this.turnkeyClient.exportWallet({ + organizationId: this.user.orgId, + type: "ACTIVITY_TYPE_EXPORT_WALLET", + timestampMs: Date.now().toString(), + parameters: { + walletId: walletAccount.walletId, + targetPublicKey: embeddedKey.publicKeyUncompressed, + }, + }); + + const result = await this.pollActivityCompletion( + activity, + this.user.orgId, + "exportWalletResult", + ); + + if (!result.exportBundle) { + throw new Error("Failed to export wallet: no export bundle returned"); + } + + exportBundle = result.exportBundle; + } + + // Step 3: Parse the export bundle and decrypt using HPKE + // The export bundle is a JSON string containing version, data, etc. + const bundleJson = JSON.parse(exportBundle); + + // The data field contains another JSON string that's hex-encoded + const innerDataHex = bundleJson.data; + const innerDataJson = JSON.parse( + Buffer.from(innerDataHex, "hex").toString(), + ); + + // Extract the encapped public key and ciphertext from the inner data + const encappedPublicKeyHex = innerDataJson.encappedPublic; + const ciphertextHex = innerDataJson.ciphertext; + + const encappedKeyBuf = Buffer.from(encappedPublicKeyHex, "hex"); + const ciphertextBuf = Buffer.from(ciphertextHex, "hex"); + + // Decrypt the data using HPKE + const decryptedData = hpkeDecrypt({ + ciphertextBuf: ciphertextBuf, + encappedKeyBuf: encappedKeyBuf, + receiverPriv: embeddedKey.privateKey, + }); + + // Step 4: Process the decrypted data based on export type + if (exportAs === "PRIVATE_KEY") { + return toHex(decryptedData); + } else { + return new TextDecoder().decode(decryptedData); + } + } finally { + // No cleanup needed - key is only in memory + } } override targetPublicKey(): Promise { diff --git a/account-kit/rn-signer/src/index.tsx b/account-kit/rn-signer/src/index.tsx index b24716b86e..2e5a5cb749 100644 --- a/account-kit/rn-signer/src/index.tsx +++ b/account-kit/rn-signer/src/index.tsx @@ -2,3 +2,4 @@ export type * from "./signer"; export { RNAlchemySigner } from "./signer"; export { RNSignerClient } from "./client"; +export type { ExportWalletParams, ExportWalletResult } from "./client"; diff --git a/account-kit/signer/src/base.ts b/account-kit/signer/src/base.ts index fdb263130c..aebb3e76f2 100644 --- a/account-kit/signer/src/base.ts +++ b/account-kit/signer/src/base.ts @@ -960,11 +960,9 @@ export abstract class BaseAlchemySigner * ``` * * @param {unknown} params export wallet parameters - * @returns {boolean} true if the wallet was exported successfully + * @returns {Promise} the result of the wallet export operation */ - exportWallet: ( - params: Parameters<(typeof this.inner)["exportWallet"]>[0], - ) => Promise = async (params) => { + exportWallet: TClient["exportWallet"] = async (params) => { return this.inner.exportWallet(params); }; diff --git a/account-kit/signer/src/client/base.ts b/account-kit/signer/src/client/base.ts index 835f78b954..d95538f4e5 100644 --- a/account-kit/signer/src/client/base.ts +++ b/account-kit/signer/src/client/base.ts @@ -15,7 +15,6 @@ import { getDefaultProviderCustomization } from "../oauth.js"; import type { OauthMode } from "../signer.js"; import { base64UrlEncode } from "../utils/base64UrlEncode.js"; import { resolveRelativeUrl } from "../utils/resolveRelativeUrl.js"; -import { assertNever } from "../utils/typeAssertions.js"; import type { AlchemySignerClientEvent, AlchemySignerClientEvents, @@ -81,7 +80,10 @@ const withHexPrefix = (hex: string) => `0x${hex}` as const; /** * Base class for all Alchemy Signer clients */ -export abstract class BaseSignerClient { +export abstract class BaseSignerClient< + TExportWalletParams = unknown, + TExportWalletOutput = unknown, +> { private _user: User | undefined; private connectionConfig: ConnectionConfig; protected turnkeyClient: TurnkeyClient; @@ -137,29 +139,6 @@ export abstract class BaseSignerClient { this.turnkeyClient.stamper = stamper; } - /** - * Exports wallet credentials based on the specified type, either as a SEED_PHRASE or PRIVATE_KEY. - * - * @param {object} params The parameters for exporting the wallet - * @param {ExportWalletStamper} params.exportStamper The stamper used for exporting the wallet - * @param {"SEED_PHRASE" | "PRIVATE_KEY"} params.exportAs Specifies the format for exporting the wallet, either as a SEED_PHRASE or PRIVATE_KEY - * @returns {Promise} A promise that resolves to true if the export is successful - */ - protected exportWalletInner(params: { - exportStamper: ExportWalletStamper; - exportAs: "SEED_PHRASE" | "PRIVATE_KEY"; - }): Promise { - const { exportAs } = params; - switch (exportAs) { - case "PRIVATE_KEY": - return this.exportAsPrivateKey(params.exportStamper); - case "SEED_PHRASE": - return this.exportAsSeedPhrase(params.exportStamper); - default: - assertNever(exportAs, `Unknown export mode: ${exportAs}`); - } - } - /** * Authenticates the user by either email or passkey account creation flow. Emits events during the process. * @@ -265,7 +244,9 @@ export abstract class BaseSignerClient { public abstract disconnect(): Promise; - public abstract exportWallet(params: TExportWalletParams): Promise; + public abstract exportWallet( + params: TExportWalletParams, + ): Promise; public abstract targetPublicKey(): Promise; @@ -1141,94 +1122,6 @@ export abstract class BaseSignerClient { // #endregion // #region PRIVATE METHODS - private exportAsSeedPhrase = async (stamper: ExportWalletStamper) => { - if (!this.user) { - throw new NotAuthenticatedError(); - } - - const { wallets } = await this.turnkeyClient.getWallets({ - organizationId: this.user.orgId, - }); - - const walletAccounts = await Promise.all( - wallets.map(({ walletId }) => - this.turnkeyClient.getWalletAccounts({ - organizationId: this.user!.orgId, - walletId, - }), - ), - ).then((x) => x.flatMap((x) => x.accounts)); - - const walletAccount = walletAccounts.find( - (x) => x.address === this.user!.address, - ); - - if (!walletAccount) { - throw new Error( - `Could not find wallet associated with ${this.user.address}`, - ); - } - - const { activity } = await this.turnkeyClient.exportWallet({ - organizationId: this.user.orgId, - type: "ACTIVITY_TYPE_EXPORT_WALLET", - timestampMs: Date.now().toString(), - parameters: { - walletId: walletAccount!.walletId, - targetPublicKey: stamper.publicKey()!, - }, - }); - - const { exportBundle } = await this.pollActivityCompletion( - activity, - this.user.orgId, - "exportWalletResult", - ); - - const result = await stamper.injectWalletExportBundle( - exportBundle, - this.user.orgId, - ); - - if (!result) { - throw new Error("Failed to inject wallet export bundle"); - } - - return result; - }; - - private exportAsPrivateKey = async (stamper: ExportWalletStamper) => { - if (!this.user) { - throw new NotAuthenticatedError(); - } - - const { activity } = await this.turnkeyClient.exportWalletAccount({ - organizationId: this.user.orgId, - type: "ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT", - timestampMs: Date.now().toString(), - parameters: { - address: this.user.address, - targetPublicKey: stamper.publicKey()!, - }, - }); - - const { exportBundle } = await this.pollActivityCompletion( - activity, - this.user.orgId, - "exportWalletAccountResult", - ); - - const result = await stamper.injectKeyExportBundle( - exportBundle, - this.user.orgId, - ); - - if (!result) { - throw new Error("Failed to inject wallet export bundle"); - } - - return result; - }; /** * Returns the authentication url for the selected OAuth Proivder diff --git a/account-kit/signer/src/client/index.ts b/account-kit/signer/src/client/index.ts index 1ed0368245..4020e29f11 100644 --- a/account-kit/signer/src/client/index.ts +++ b/account-kit/signer/src/client/index.ts @@ -5,7 +5,9 @@ import { WebauthnStamper } from "@turnkey/webauthn-stamper"; import { z } from "zod"; import type { AuthParams } from "../signer.js"; import { generateRandomBuffer } from "../utils/generateRandomBuffer.js"; -import { BaseSignerClient } from "./base.js"; +import { assertNever } from "../utils/typeAssertions.js"; +import { BaseSignerClient, type ExportWalletStamper } from "./base.js"; +import { NotAuthenticatedError } from "../errors.js"; import type { AlchemySignerClientEvents, AuthenticatingEventMetadata, @@ -22,6 +24,7 @@ import type { GetWebAuthnAttestationResult, IdTokenOnly, SmsAuthParams, + ExportWalletOutput, } from "./types.js"; import { MfaRequiredError } from "../errors.js"; import { parseMfaError } from "../utils/parseMfaError.js"; @@ -54,7 +57,10 @@ export type AlchemySignerClientParams = z.input< * A lower level client used by the AlchemySigner used to communicate with * Alchemy's signer service. */ -export class AlchemySignerWebClient extends BaseSignerClient { +export class AlchemySignerWebClient extends BaseSignerClient< + ExportWalletParams, + ExportWalletOutput +> { private iframeStamper: IframeStamper; private webauthnStamper: WebauthnStamper; oauthCallbackUrl: string; @@ -389,7 +395,7 @@ export class AlchemySignerWebClient extends BaseSignerClient public override exportWallet = async ({ iframeContainerId, iframeElementId = "turnkey-export-iframe", - }: ExportWalletParams) => { + }: ExportWalletParams): Promise => { const exportWalletIframeStamper = new IframeStamper({ iframeContainer: document.getElementById(iframeContainerId), iframeElementId: iframeElementId, @@ -410,6 +416,29 @@ export class AlchemySignerWebClient extends BaseSignerClient }); }; + /** + * Exports wallet credentials based on the specified type, either as a SEED_PHRASE or PRIVATE_KEY. + * + * @param {object} params The parameters for exporting the wallet + * @param {ExportWalletStamper} params.exportStamper The stamper used for exporting the wallet + * @param {"SEED_PHRASE" | "PRIVATE_KEY"} params.exportAs Specifies the format for exporting the wallet, either as a SEED_PHRASE or PRIVATE_KEY + * @returns {Promise} A promise that resolves to true if the export is successful + */ + protected exportWalletInner(params: { + exportStamper: ExportWalletStamper; + exportAs: "SEED_PHRASE" | "PRIVATE_KEY"; + }): Promise { + const { exportAs } = params; + switch (exportAs) { + case "PRIVATE_KEY": + return this.exportAsPrivateKey(params.exportStamper); + case "SEED_PHRASE": + return this.exportAsSeedPhrase(params.exportStamper); + default: + assertNever(exportAs, `Unknown export mode: ${exportAs}`); + } + } + /** * Asynchronous function that clears the user and resets the iframe stamper. * @@ -748,6 +777,96 @@ export class AlchemySignerWebClient extends BaseSignerClient ]; } } + + private exportAsSeedPhrase = async (stamper: ExportWalletStamper) => { + if (!this.user) { + throw new NotAuthenticatedError(); + } + + const { wallets } = await this.turnkeyClient.getWallets({ + organizationId: this.user.orgId, + }); + + const walletAccountResponses = await Promise.all( + wallets.map(({ walletId }) => + this.turnkeyClient.getWalletAccounts({ + organizationId: this.user!.orgId, + walletId, + }), + ), + ); + const walletAccounts = walletAccountResponses.flatMap((x) => x.accounts); + + const walletAccount = walletAccounts.find( + (x) => x.address.toLowerCase() === this.user!.address.toLowerCase(), + ); + + if (!walletAccount) { + throw new Error( + `Could not find wallet associated with ${this.user.address}`, + ); + } + + const { activity } = await this.turnkeyClient.exportWallet({ + organizationId: this.user.orgId, + type: "ACTIVITY_TYPE_EXPORT_WALLET", + timestampMs: Date.now().toString(), + parameters: { + walletId: walletAccount!.walletId, + targetPublicKey: stamper.publicKey()!, + }, + }); + + const { exportBundle } = await this.pollActivityCompletion( + activity, + this.user.orgId, + "exportWalletResult", + ); + + const result = await stamper.injectWalletExportBundle( + exportBundle, + this.user.orgId, + ); + + if (!result) { + throw new Error("Failed to inject wallet export bundle"); + } + + return result; + }; + + private exportAsPrivateKey = async (stamper: ExportWalletStamper) => { + if (!this.user) { + throw new NotAuthenticatedError(); + } + + const { activity } = await this.turnkeyClient.exportWalletAccount({ + organizationId: this.user.orgId, + type: "ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT", + timestampMs: Date.now().toString(), + parameters: { + address: this.user.address, + targetPublicKey: stamper.publicKey()!, + }, + }); + + const { exportBundle } = await this.pollActivityCompletion( + activity, + this.user.orgId, + "exportWalletAccountResult", + ); + + const result = await stamper.injectKeyExportBundle( + exportBundle, + this.user.orgId, + ); + + if (!result) { + throw new Error("Failed to inject wallet export bundle"); + } + + return result; + }; } /** diff --git a/account-kit/signer/src/client/types.ts b/account-kit/signer/src/client/types.ts index 33734ecc88..d02fd6d6a6 100644 --- a/account-kit/signer/src/client/types.ts +++ b/account-kit/signer/src/client/types.ts @@ -30,6 +30,8 @@ export type ExportWalletParams = { iframeElementId?: string; }; +export type ExportWalletOutput = boolean; + export type CreateAccountParams = | { type: "email"; diff --git a/docs/pages/reference/account-kit/signer/classes/BaseAlchemySigner/exportWallet.mdx b/docs/pages/reference/account-kit/signer/classes/BaseAlchemySigner/exportWallet.mdx index bb750bee9a..ddd86faa45 100644 --- a/docs/pages/reference/account-kit/signer/classes/BaseAlchemySigner/exportWallet.mdx +++ b/docs/pages/reference/account-kit/signer/classes/BaseAlchemySigner/exportWallet.mdx @@ -44,5 +44,5 @@ export wallet parameters ## Returns -`boolean` -true if the wallet was exported successfully +`Promise` +the result of the wallet export operation diff --git a/yarn.lock b/yarn.lock index daa03211cb..2b94698763 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1227,20 +1227,7 @@ "@babel/parser" "^7.27.2" "@babel/types" "^7.27.1" -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" - integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.1" - "@babel/parser" "^7.27.1" - "@babel/template" "^7.27.1" - "@babel/types" "^7.27.1" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/traverse@^7.18.9", "@babel/traverse@^7.20.0", "@babel/traverse@^7.25.3", "@babel/traverse@^7.27.1": +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.18.9", "@babel/traverse@^7.20.0", "@babel/traverse@^7.25.3", "@babel/traverse@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== @@ -8237,6 +8224,18 @@ react-native-quick-base64 "2.1.2" typescript "5.0.4" +"@turnkey/crypto@^2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@turnkey/crypto/-/crypto-2.5.0.tgz#1498d7c131078c9e506d24ac2a13e7d44a621743" + integrity sha512-aeYPO9rPFlM6eG+hjDiE6BKi9O6xcSDSIoq3mlw6KaaDgg6T2wFVapquIhAvwdTn+SMemDhcw2XaK5jsrQvsdQ== + dependencies: + "@noble/ciphers" "1.3.0" + "@noble/curves" "1.9.0" + "@noble/hashes" "1.8.0" + "@turnkey/encoding" "0.5.0" + bs58 "6.0.0" + bs58check "4.0.0" + "@turnkey/encoding@0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@turnkey/encoding/-/encoding-0.2.1.tgz#ccde2afe97d0ab39911fe005ce473faa5a681e92" @@ -11528,7 +11527,7 @@ bs58check@3.0.1, bs58check@^3.0.1: "@noble/hashes" "^1.2.0" bs58 "^5.0.0" -bs58check@^4.0.0: +bs58check@4.0.0, bs58check@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-4.0.0.tgz#46cda52a5713b7542dcb78ec2efdf78f5bf1d23c" integrity sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g== @@ -23182,16 +23181,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -23317,7 +23307,7 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -23331,13 +23321,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -25156,7 +25139,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -25174,15 +25157,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"