diff --git a/packages/adapters/bitcoin/src/connectors/TrezorConnector/index.ts b/packages/adapters/bitcoin/src/connectors/TrezorConnector/index.ts index bda2db6827..400fe701a0 100644 --- a/packages/adapters/bitcoin/src/connectors/TrezorConnector/index.ts +++ b/packages/adapters/bitcoin/src/connectors/TrezorConnector/index.ts @@ -591,15 +591,27 @@ export class TrezorConnector extends ProviderEventEmitter implements BitcoinConn return } - await getTrezorConnect().init({ - manifest: { - email: 'support@reown.com', - appUrl: typeof window === 'undefined' ? 'https://reown.com' : window.location.origin, - appName: 'Reown AppKit' - }, - lazyLoad: true, - popup: true - }) + try { + await getTrezorConnect().init({ + manifest: { + email: 'support@reown.com', + appUrl: typeof window === 'undefined' ? 'https://reown.com' : window.location.origin, + appName: 'Reown AppKit' + }, + lazyLoad: true, + popup: true + }) + } catch (error) { + /* + * Trezor Connect is a page-global singleton: the host app (or another + * connector) may have initialized it first, which init() reports as an + * error even though the instance is fully usable. + */ + const message = error instanceof Error ? error.message : String(error) + if (!message.toLowerCase().includes('already initialized')) { + throw error + } + } this.initialized = true diff --git a/packages/adapters/ethers5/package.json b/packages/adapters/ethers5/package.json index 5df7057c3f..09fd4dbd53 100644 --- a/packages/adapters/ethers5/package.json +++ b/packages/adapters/ethers5/package.json @@ -26,6 +26,7 @@ "@reown/appkit-scaffold-ui": "workspace:*", "@reown/appkit-utils": "workspace:*", "@reown/appkit-wallet": "workspace:*", + "@trezor/connect-web": "9.7.3", "@walletconnect/universal-provider": "2.23.7", "valtio": "2.1.7" }, diff --git a/packages/adapters/ethers5/src/client.ts b/packages/adapters/ethers5/src/client.ts index 0c6886450f..087bcb9902 100644 --- a/packages/adapters/ethers5/src/client.ts +++ b/packages/adapters/ethers5/src/client.ts @@ -15,6 +15,7 @@ import { ConstantsUtil, PresetsUtil } from '@reown/appkit-common' import { AdapterBlueprint, AssetController, + type ChainAdapterConnector, ChainController, type CombinedProvider, type Connector, @@ -42,6 +43,7 @@ import { } from '@reown/appkit-utils/ethers' import type { W3mFrameProvider } from '@reown/appkit-wallet' +import { TrezorConnector } from './connectors/TrezorConnector/index.js' import { Ethers5Methods } from './utils/Ethers5Methods.js' export interface EIP6963ProviderDetail { @@ -273,6 +275,15 @@ export class Ethers5Adapter extends AdapterBlueprint { }) } }) + + const trezorConnector = TrezorConnector.getWallet({ + requestedChains: this.getCaipNetworks(), + requestedCaipNetworkId: ChainController.getActiveCaipNetwork(CommonConstantsUtil.CHAIN.EVM) + ?.caipNetworkId + }) + if (trezorConnector) { + this.addConnector(trezorConnector as unknown as ChainAdapterConnector) + } } private async disconnectAll() { diff --git a/packages/adapters/ethers5/src/connectors/TrezorConnector/index.ts b/packages/adapters/ethers5/src/connectors/TrezorConnector/index.ts new file mode 100644 index 0000000000..20e544d30c --- /dev/null +++ b/packages/adapters/ethers5/src/connectors/TrezorConnector/index.ts @@ -0,0 +1,459 @@ +import * as TrezorConnectWeb from '@trezor/connect-web' +import { utils } from 'ethers' + +import type { CaipNetwork, CaipNetworkId } from '@reown/appkit-common' +import { CoreHelperUtil, type Provider, type RequestArguments } from '@reown/appkit-controllers' + +/** + * Standard Ethereum derivation path, first account. Matches the address + * MetaMask and Ledger Live derive for the same seed, so funds appear where + * users expect them. + * + * Trezor firmware may reject signing for chains with their own registered + * SLIP-44 coin type (e.g. Rootstock: 137 mainnet / 37310 testnet) from this + * path with "Forbidden key path" unless the device's Safety Checks setting + * is set to "Prompt". + */ +const ETH_DERIVATION_PATH = "m/44'/60'/0'/0/0" + +interface TrezorResponse

{ + success: boolean + payload: P & { error?: string; code?: string } +} + +interface TrezorConnectApi { + init(settings: { + manifest: { email: string; appUrl: string; appName?: string } + lazyLoad?: boolean + popup?: boolean + }): Promise + ethereumGetAddress(params: { + path: string + showOnTrezor?: boolean + }): Promise> + ethereumSignTransaction(params: { + path: string + transaction: { + to: string + value: string + gasPrice: string + gasLimit: string + nonce: string + data: string + chainId: number + } + }): Promise> + ethereumSignMessage(params: { + path: string + message: string + hex?: boolean + }): Promise> + ethereumSignTypedData(params: { + path: string + data: Record + metamask_v4_compat: boolean + }): Promise> +} + +let cachedTrezorConnect: TrezorConnectApi | undefined = undefined + +/** + * `@trezor/connect-web` is CommonJS and exposes its API as `exports.default`, + * alongside `export *` named exports. + * + * Bundlers that honour the `__esModule` marker (webpack, Rollup, and Vite's + * source transform) hand that object back as the default import. Node's ESM/CJS + * interop does not: there, and under esbuild's node-mode interop — which Vite + * uses when it prebundles this package as a dependency — the default import is + * the whole `module.exports`, so the API sits one level deeper and + * `TrezorConnect.init` is undefined. + * + * Probing for `init` covers every interop shape instead of assuming one. It is + * deliberately lazy so that a bad resolution surfaces when the connector is + * used rather than breaking the adapter's module import. + */ +function getTrezorConnect(): TrezorConnectApi { + if (cachedTrezorConnect) { + return cachedTrezorConnect + } + + const namespace = TrezorConnectWeb as unknown as { + default?: { default?: unknown } & Record + } + + const candidates = [namespace.default?.default, namespace.default, namespace] + const resolved = candidates.find( + candidate => typeof (candidate as TrezorConnectApi | undefined)?.init === 'function' + ) + + if (!resolved) { + throw new Error( + '@trezor/connect-web did not expose an init() method. This usually means the module was ' + + 'loaded through an interop path that hides its default export.' + ) + } + + cachedTrezorConnect = resolved as TrezorConnectApi + + return cachedTrezorConnect +} + +function rpcError(message: string, code: number): Error { + return Object.assign(new Error(message), { code }) +} + +function isUserRejection(message: string): boolean { + const lower = message.toLowerCase() + + return lower.includes('cancel') || lower.includes('closed') || lower.includes('denied') +} + +interface EthTransaction { + from?: string + to?: string + value?: string + data?: string + gas?: string + gasLimit?: string + gasPrice?: string + nonce?: string +} + +type Listener = (data: unknown) => void + +export namespace TrezorConnectorTypes { + export interface ConstructorParams { + /** Networks the host app configured; only eip155 ones are used. */ + requestedChains: CaipNetwork[] + /** The network to start on; falls back to the first requested chain. */ + requestedCaipNetworkId?: CaipNetworkId + } + + export type GetWalletParams = ConstructorParams +} + +/** + * EIP-1193 provider backed by Trezor Connect, registered by the adapter like + * an announced browser wallet. Read-only JSON-RPC calls are forwarded to the + * active network's RPC endpoint; account and signing requests go to the + * device. Transactions are signed on the device and broadcast through the + * same RPC endpoint as legacy gas-price transactions, which every EVM chain + * accepts — including those without EIP-1559 support, such as Rootstock. + */ +export class TrezorConnector implements Provider { + public readonly id = 'trezor' + public readonly name = 'Trezor' + public readonly chain = 'eip155' + public readonly type = 'ANNOUNCED' + public readonly chains: CaipNetwork[] = [] + public readonly imageUrl = + 'https://pbs.twimg.com/profile_images/1876994745022529536/5FD_cxXO_400x400.jpg' + + public readonly provider = this + + private readonly listeners = new Map>() + private readonly rpcUrls = new Map() + private accounts: string[] = [] + private initialized = false + private chainIdHex = '0x1' + + constructor({ requestedChains, requestedCaipNetworkId }: TrezorConnectorTypes.ConstructorParams) { + const evmChains = requestedChains.filter(chain => chain.chainNamespace === 'eip155') + this.chains = evmChains + + for (const network of evmChains) { + const id = Number(network.id) + const rpcUrl = network.rpcUrls?.default?.http?.[0] + if (Number.isFinite(id) && rpcUrl) { + this.rpcUrls.set(`0x${id.toString(16)}`, rpcUrl) + } + } + + const requested = evmChains.find(chain => chain.caipNetworkId === requestedCaipNetworkId) + const initialChain = requested ?? evmChains[0] + if (initialChain) { + this.chainIdHex = `0x${Number(initialChain.id).toString(16)}` + } + } + + public static getWallet( + params: TrezorConnectorTypes.GetWalletParams + ): TrezorConnector | undefined { + if (!CoreHelperUtil.isClient()) { + return undefined + } + + // Trezor uses a popup flow, so it's always available in browser environments + return new TrezorConnector(params) + } + + // -- Provider event emitter --------------------------------- // + + public on(event: string, listener: Listener): void { + if (!this.listeners.has(event)) { + this.listeners.set(event, new Set()) + } + this.listeners.get(event)?.add(listener) + } + + public removeListener(event: string, listener: (data: T) => void): void { + this.listeners.get(event)?.delete(listener as Listener) + } + + public emit(event: string, data?: unknown): void { + this.listeners.get(event)?.forEach(listener => listener(data)) + } + + // -- Provider interface ------------------------------------- // + + public async connect(): Promise { + const [address] = await this.connectAccounts() + + return address ?? '' + } + + public async disconnect(): Promise { + this.accounts = [] + this.emit('accountsChanged', []) + this.emit('disconnect') + + return Promise.resolve() + } + + public async request(args: RequestArguments): Promise { + const method = args.method + const params = args.params as unknown[] | undefined + + switch (method) { + case 'eth_requestAccounts': + return (await this.connectAccounts()) as T + case 'eth_accounts': + return this.accounts as T + case 'eth_chainId': + return this.chainIdHex as T + case 'net_version': + return String(parseInt(this.chainIdHex, 16)) as T + case 'wallet_switchEthereumChain': + return this.switchChain(params) as T + case 'wallet_addEthereumChain': + return null as T + case 'wallet_getPermissions': + case 'wallet_requestPermissions': + return [] as T + case 'wallet_revokePermissions': + this.accounts = [] + this.emit('accountsChanged', []) + + return null as T + case 'eth_sendTransaction': + return (await this.sendTransaction((params?.[0] ?? {}) as EthTransaction)) as T + case 'personal_sign': + return (await this.signMessage(String(params?.[0] ?? ''))) as T + case 'eth_sign': + return (await this.signMessage(String(params?.[1] ?? ''))) as T + case 'eth_signTypedData_v4': + case 'eth_signTypedData': + return (await this.signTypedData(params?.[1])) as T + default: + return (await this.rpcRequest(method, params)) as T + } + } + + // -- Private methods ----------------------------------------- // + + private async initTrezor(): Promise { + const trezor = getTrezorConnect() + + if (this.initialized) { + return trezor + } + + try { + await trezor.init({ + manifest: { + email: 'support@reown.com', + appUrl: typeof window === 'undefined' ? 'https://reown.com' : window.location.origin, + appName: 'Reown AppKit' + }, + lazyLoad: true, + popup: true + }) + } catch (error) { + /* + * Trezor Connect is a page-global singleton: the host app (or another + * connector, e.g. the Bitcoin adapter's) may have initialized it first, + * which init() reports as an error even though the instance is usable. + */ + const message = error instanceof Error ? error.message : String(error) + if (!message.toLowerCase().includes('already initialized')) { + throw error + } + } + + this.initialized = true + + return trezor + } + + private async connectAccounts(): Promise { + if (this.accounts.length > 0) { + return this.accounts + } + + const trezor = await this.initTrezor() + const result = await trezor.ethereumGetAddress({ + path: ETH_DERIVATION_PATH, + showOnTrezor: false + }) + if (!result.success) { + const message = result.payload.error ?? 'Trezor: ethereumGetAddress failed' + throw rpcError(message, isUserRejection(message) ? 4001 : -32603) + } + + this.accounts = [result.payload.address] + this.emit('accountsChanged', this.accounts) + + return this.accounts + } + + private switchChain(params?: unknown[]): null { + const requested = (params?.[0] as { chainId?: string } | undefined)?.chainId?.toLowerCase() + if (!requested || !this.rpcUrls.has(requested)) { + throw rpcError(`Unrecognized chain ID ${requested ?? ''}`, 4902) + } + + if (requested !== this.chainIdHex) { + this.chainIdHex = requested + this.emit('chainChanged', this.chainIdHex) + } + + return null + } + + private async sendTransaction(tx: EthTransaction): Promise { + const [from] = await this.connectAccounts() + const chainId = parseInt(this.chainIdHex, 16) + if (!tx.to) { + throw rpcError('eth_sendTransaction requires a `to` address', -32602) + } + + const [nonce, gasPrice, gasLimit] = (await Promise.all([ + tx.nonce ?? this.rpcRequest('eth_getTransactionCount', [from, 'pending']), + tx.gasPrice ?? this.rpcRequest('eth_gasPrice', []), + tx.gas ?? + tx.gasLimit ?? + this.rpcRequest('eth_estimateGas', [ + { + from, + to: tx.to, + value: tx.value ?? '0x0', + data: tx.data ?? '0x' + } + ]) + ])) as [string, string, string] + + const trezor = await this.initTrezor() + const result = await trezor.ethereumSignTransaction({ + path: ETH_DERIVATION_PATH, + transaction: { + to: tx.to, + value: tx.value ?? '0x0', + gasPrice, + gasLimit, + nonce, + data: tx.data ?? '0x', + chainId + } + }) + if (!result.success) { + const message = result.payload.error ?? 'Trezor: ethereumSignTransaction failed' + throw rpcError(message, isUserRejection(message) ? 4001 : -32603) + } + + const raw = utils.serializeTransaction( + { + to: tx.to, + value: tx.value ?? '0x0', + gasPrice, + gasLimit, + nonce: parseInt(nonce, 16), + data: tx.data ?? '0x', + chainId + }, + { + r: result.payload.r, + s: result.payload.s, + v: parseInt(result.payload.v, 16) + } + ) + + return (await this.rpcRequest('eth_sendRawTransaction', [raw])) as string + } + + private async signMessage(message: string): Promise { + const trezor = await this.initTrezor() + await this.connectAccounts() + const hex = message.startsWith('0x') + ? message.slice(2) + : Array.from(new TextEncoder().encode(message), byte => + byte.toString(16).padStart(2, '0') + ).join('') + const result = await trezor.ethereumSignMessage({ + path: ETH_DERIVATION_PATH, + message: hex, + hex: true + }) + if (!result.success) { + const errorMessage = result.payload.error ?? 'Trezor: ethereumSignMessage failed' + throw rpcError(errorMessage, isUserRejection(errorMessage) ? 4001 : -32603) + } + const signature = result.payload.signature + + return signature.startsWith('0x') ? signature : `0x${signature}` + } + + private async signTypedData(payload: unknown): Promise { + const trezor = await this.initTrezor() + await this.connectAccounts() + const data = + typeof payload === 'string' + ? (JSON.parse(payload) as Record) + : (payload as Record) + const result = await trezor.ethereumSignTypedData({ + path: ETH_DERIVATION_PATH, + data, + metamask_v4_compat: true + }) + if (!result.success) { + const errorMessage = result.payload.error ?? 'Trezor: ethereumSignTypedData failed' + throw rpcError(errorMessage, isUserRejection(errorMessage) ? 4001 : -32603) + } + const signature = result.payload.signature + + return signature.startsWith('0x') ? signature : `0x${signature}` + } + + private async rpcRequest(method: string, params?: unknown[]): Promise { + const rpcUrl = this.rpcUrls.get(this.chainIdHex) + if (!rpcUrl) { + throw rpcError(`No RPC endpoint for chain ${this.chainIdHex}`, 4901) + } + + const response = await fetch(rpcUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', id: Date.now(), method, params: params ?? [] }) + }) + if (!response.ok) { + throw rpcError(`RPC request failed with status ${response.status}`, -32603) + } + + const body: { result?: unknown; error?: { message: string; code: number } } = + await response.json() + if (body.error) { + throw rpcError(body.error.message, body.error.code) + } + + return body.result + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfef6c8d2d..c1210acca9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2461,9 +2461,12 @@ importers: '@reown/appkit-wallet': specifier: workspace:* version: link:../../wallet + '@trezor/connect-web': + specifier: 9.7.3 + version: 9.7.3(@solana/sysvars@5.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.2))(bufferutil@4.1.0)(fastestsmallesttextencoderdecoder@1.0.22)(tslib@2.8.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@walletconnect/universal-provider': specifier: 2.23.7 - version: 2.23.7(aws4fetch@1.0.20)(bufferutil@4.1.0)(db0@0.3.4)(ioredis@5.9.1)(typescript@5.8.3)(utf-8-validate@5.0.10) + version: 2.23.7(aws4fetch@1.0.20)(bufferutil@4.1.0)(db0@0.3.4)(ioredis@5.9.1)(typescript@5.9.2)(utf-8-validate@5.0.10) ethers: specifier: '>=4.1 <6.0.0' version: 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) @@ -24258,7 +24261,7 @@ snapshots: scule: 1.3.0 semver: 7.7.3 tinyglobby: 0.2.15 - ufo: 1.6.2 + ufo: 1.6.4 unctx: 2.5.0 untyped: 2.0.0 transitivePeerDependencies: @@ -24284,7 +24287,7 @@ snapshots: scule: 1.3.0 semver: 7.7.3 tinyglobby: 0.2.15 - ufo: 1.6.2 + ufo: 1.6.4 unctx: 2.5.0 untyped: 2.0.0 transitivePeerDependencies: @@ -38804,8 +38807,8 @@ snapshots: '@typescript-eslint/parser': 6.18.1(eslint@8.56.0)(typescript@5.9.2) eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0))(eslint@8.56.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.56.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.56.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.9.2))(eslint@8.56.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.56.0) eslint-plugin-react: 7.37.5(eslint@8.56.0) eslint-plugin-react-hooks: 5.2.0(eslint@8.56.0) @@ -38878,6 +38881,21 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.56.0): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 8.56.0 + get-tsconfig: 4.13.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.9.2))(eslint@8.56.0) + transitivePeerDependencies: + - supports-color + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -38904,6 +38922,17 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.56.0))(eslint@8.56.0): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 6.18.1(eslint@8.56.0)(typescript@5.9.2) + eslint: 8.56.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.56.0) + transitivePeerDependencies: + - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@6.18.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 3.2.7 @@ -38955,6 +38984,35 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.9.2))(eslint@8.56.0): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.56.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.18.1(eslint@8.56.0)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.56.0))(eslint@8.56.0) + hasown: 2.0.2 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 6.18.1(eslint@8.56.0)(typescript@5.9.2) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.18.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 @@ -41143,7 +41201,7 @@ snapshots: node-forge: 1.3.3 pathe: 1.1.2 std-env: 3.10.0 - ufo: 1.6.2 + ufo: 1.6.4 untun: 0.1.3 uqr: 0.1.2 @@ -41320,7 +41378,7 @@ snapshots: mlly: 1.8.0 regexp-tree: 0.1.27 type-level-regexp: 0.1.17 - ufo: 1.6.2 + ufo: 1.6.4 unplugin: 2.3.11 magic-string-ast@1.0.3: @@ -45842,7 +45900,7 @@ snapshots: defu: 6.1.4 ohash: 1.1.6 pathe: 1.1.2 - ufo: 1.6.2 + ufo: 1.6.4 unenv@2.0.0-rc.17: dependencies: @@ -47164,7 +47222,7 @@ snapshots: dependencies: '@babel/core': 7.28.6 '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/types': 7.29.7 babylon: 7.0.0-beta.47 webassembly-interpreter: 0.0.30 transitivePeerDependencies: