diff --git a/packages/keyring-api/src/api/account-options.test.ts b/packages/keyring-api/src/api/account-options.test.ts index 1dd556860..b6c8f1284 100644 --- a/packages/keyring-api/src/api/account-options.test.ts +++ b/packages/keyring-api/src/api/account-options.test.ts @@ -99,4 +99,27 @@ describe('api', () => { `At path: entropy.type -- Expected the literal \`"mnemonic"\`, but received: "${options.entropy.type}"`, ); }); + + it('throws if options.entropy.type is Hardware (not implemented)', () => { + const options = { + entropy: { type: KeyringAccountEntropyTypeOption.Hardware }, + }; + + expect(() => assert(options, KeyringAccountOptionsStruct)).toThrow( + `At path: entropy.type -- Expected the literal \`"mnemonic"\`, but received: "${KeyringAccountEntropyTypeOption.Hardware}"`, + ); + }); + + it('throws if options.entropy.type is Hardware with additional fields', () => { + const options = { + entropy: { + type: KeyringAccountEntropyTypeOption.Hardware, + deviceId: 'some-device-id', + }, + }; + + expect(() => assert(options, KeyringAccountOptionsStruct)).toThrow( + `At path: entropy.type -- Expected the literal \`"mnemonic"\`, but received: "${KeyringAccountEntropyTypeOption.Hardware}"`, + ); + }); }); diff --git a/packages/keyring-api/src/api/account-options.ts b/packages/keyring-api/src/api/account-options.ts index a8fbbeb26..cc9c0270f 100644 --- a/packages/keyring-api/src/api/account-options.ts +++ b/packages/keyring-api/src/api/account-options.ts @@ -25,6 +25,11 @@ export enum KeyringAccountEntropyTypeOption { */ PrivateKey = 'private-key', + /** + * Indicates that the account was created from a hardware wallet. + */ + Hardware = 'hardware', + /** * Indicates that the account was created with custom, keyring-specific entropy. * This is an opaque type where the entropy source is managed internally by the keyring. diff --git a/packages/keyring-eth-ledger-bridge/CHANGELOG.md b/packages/keyring-eth-ledger-bridge/CHANGELOG.md index dd7ac3749..2fbc541ac 100644 --- a/packages/keyring-eth-ledger-bridge/CHANGELOG.md +++ b/packages/keyring-eth-ledger-bridge/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Implement `getAppConfiguration` in `LedgerMobileDMKBridge` to retrieve actual app configuration from the device ([#TODO](https://github.com/MetaMask/accounts/pull/TODO)) + - Parse device flags to determine blind signing support (`arbitraryDataEnabled`). + - Return actual app version from the device instead of hardcoded value. +- Configure `LedgerMobileDMKBridge` to use the React Native BLE `mobile` transport and expose DMK device discovery helpers ([#TODO](https://github.com/MetaMask/accounts/pull/TODO)) + - Register `@ledgerhq/device-transport-kit-react-native-ble` with the DMK builder. + - Add DMK discovery and connect passthroughs on the transport middleware and bridge. + ## [11.3.0] ### Changed diff --git a/packages/keyring-eth-ledger-bridge/jest.config.js b/packages/keyring-eth-ledger-bridge/jest.config.js index 48b8bcb0b..fd7cc3fdf 100644 --- a/packages/keyring-eth-ledger-bridge/jest.config.js +++ b/packages/keyring-eth-ledger-bridge/jest.config.js @@ -26,10 +26,10 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 93.36, - functions: 98.29, - lines: 97.86, - statements: 97.88, + branches: 93.95, + functions: 98.63, + lines: 98.18, + statements: 98.19, }, }, }); diff --git a/packages/keyring-eth-ledger-bridge/package.json b/packages/keyring-eth-ledger-bridge/package.json index 2ddb7310b..5e1f497d8 100644 --- a/packages/keyring-eth-ledger-bridge/package.json +++ b/packages/keyring-eth-ledger-bridge/package.json @@ -49,12 +49,17 @@ "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/tx": "^5.4.0", "@ethereumjs/util": "^9.1.0", + "@ledgerhq/context-module": "^1.14.1", + "@ledgerhq/device-management-kit": "^1.1.0", + "@ledgerhq/device-signer-kit-ethereum": "^1.11.1", + "@ledgerhq/device-transport-kit-react-native-ble": "^1.3.2", "@ledgerhq/hw-app-eth": "^6.42.0", "@metamask/eth-sig-util": "^8.2.0", "@metamask/hw-wallet-sdk": "workspace:^", "@metamask/keyring-api": "workspace:^", "@metamask/keyring-utils": "workspace:^", - "hdkey": "^2.1.0" + "hdkey": "^2.1.0", + "rxjs": "^7.8.2" }, "devDependencies": { "@ethereumjs/common": "^4.4.0", @@ -72,8 +77,8 @@ "@types/ethereumjs-tx": "^1.0.1", "@types/hdkey": "^2.0.1", "@types/jest": "^29.5.12", - "@types/node": "^20.12.12", "@types/web": "^0.0.69", + "@types/ws": "^8.18.1", "deepmerge": "^4.2.2", "depcheck": "^1.4.7", "ethereumjs-tx": "^1.3.7", diff --git a/packages/keyring-eth-ledger-bridge/src/index.ts b/packages/keyring-eth-ledger-bridge/src/index.ts index 46951e098..28a115999 100644 --- a/packages/keyring-eth-ledger-bridge/src/index.ts +++ b/packages/keyring-eth-ledger-bridge/src/index.ts @@ -2,6 +2,8 @@ export * from './ledger-keyring'; export * from './ledger-keyring-v2'; export * from './ledger-iframe-bridge'; export * from './ledger-mobile-bridge'; +export * from './ledger-dmk-bridge'; +export * from './ledger-dmk-transport-middleware'; export type * from './ledger-bridge'; export * from './ledger-transport-middleware'; export type * from './type'; diff --git a/packages/keyring-eth-ledger-bridge/src/ledger-dmk-bridge.test.ts b/packages/keyring-eth-ledger-bridge/src/ledger-dmk-bridge.test.ts new file mode 100644 index 000000000..9e727bb6f --- /dev/null +++ b/packages/keyring-eth-ledger-bridge/src/ledger-dmk-bridge.test.ts @@ -0,0 +1,589 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +import { + CommandResultStatus, + DeviceActionStatus, + DeviceManagementKit, + DeviceManagementKitBuilder, +} from '@ledgerhq/device-management-kit'; +import { RNBleTransportFactory } from '@ledgerhq/device-transport-kit-react-native-ble'; +import { EIP712Message } from '@ledgerhq/types-live'; +import { of } from 'rxjs'; + +import { LedgerMobileDMKBridge } from './ledger-dmk-bridge'; +import { LedgerMobileDMKTransportMiddleware } from './ledger-dmk-transport-middleware'; + +jest.mock('@ledgerhq/device-transport-kit-react-native-ble', () => ({ + RNBleTransportFactory: jest.fn(), +})); + +// Mock the transport middleware +jest.mock('./ledger-dmk-transport-middleware'); + +describe('LedgerMobileDMKBridge', () => { + let bridge: LedgerMobileDMKBridge; + let addTransportSpy: jest.SpyInstance; + let buildSpy: jest.SpyInstance; + let mockTransportMiddleware: jest.Mocked; + let mockSDK: { + connect: jest.Mock; + sendCommand: jest.Mock; + startDiscovering: jest.Mock; + }; + let mockEthSigner: { + getAddress: jest.Mock; + signMessage: jest.Mock; + signTransaction: jest.Mock; + signTypedData: jest.Mock; + }; + + const mockSendCommandResult = { + name: 'Ethereum', + version: '2.4.0', + flags: 1, // Blind signing enabled by default + }; + + beforeEach(() => { + mockSDK = { + connect: jest.fn().mockResolvedValue('test-session-id'), + sendCommand: jest.fn().mockResolvedValue({ + status: CommandResultStatus.Success, + data: mockSendCommandResult, + }), + startDiscovering: jest.fn().mockReturnValue(of({ id: 'device-id' })), + }; + + mockEthSigner = { + getAddress: jest.fn().mockReturnValue({ + observable: of({ + status: DeviceActionStatus.Completed, + output: { + publicKey: '0xpublicKey', + address: '0xaddress', + chainCode: '0xchainCode', + }, + }), + }), + signTransaction: jest.fn().mockReturnValue({ + observable: of({ + status: DeviceActionStatus.Completed, + output: { + v: 27, + r: '0xr-value', + s: '0xs-value', + }, + }), + }), + signMessage: jest.fn().mockReturnValue({ + observable: of({ + status: DeviceActionStatus.Completed, + output: { + v: 27, + r: '0xr-value', + s: '0xs-value', + }, + }), + }), + signTypedData: jest.fn().mockReturnValue({ + observable: of({ + status: DeviceActionStatus.Completed, + output: { + v: 27, + r: '0xr-value', + s: '0xs-value', + }, + }), + }), + }; + + addTransportSpy = jest + .spyOn(DeviceManagementKitBuilder.prototype, 'addTransport') + .mockImplementation(function mockAddTransport( + this: DeviceManagementKitBuilder, + ) { + // eslint-disable-next-line no-invalid-this + return this; + }); + + buildSpy = jest + .spyOn(DeviceManagementKitBuilder.prototype, 'build') + .mockReturnValue(mockSDK as unknown as DeviceManagementKit); + + // Create mock transport middleware + mockTransportMiddleware = { + connect: jest.fn().mockResolvedValue('test-session-id'), + setSessionId: jest.fn(), + getSessionId: jest.fn().mockReturnValue('test-session-id'), + getSDK: jest + .fn() + .mockReturnValue(mockSDK as unknown as DeviceManagementKit), + dispose: jest.fn().mockResolvedValue(undefined), + getEthSigner: jest.fn().mockReturnValue(mockEthSigner), + startDiscovering: jest.fn().mockReturnValue(of({ id: 'device-id' })), + } as unknown as jest.Mocked; + + // Mock the constructor to return our mock + ( + LedgerMobileDMKTransportMiddleware as unknown as jest.Mock + ).mockImplementation(() => mockTransportMiddleware); + + bridge = new LedgerMobileDMKBridge(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('constructor', () => { + it('initializes with default options', () => { + expect(bridge).toBeDefined(); + expect(bridge.isDeviceConnected).toBe(false); + }); + + it('initializes DMK with DeviceManagementKitBuilder', () => { + expect(buildSpy).toHaveBeenCalledTimes(1); + }); + + it('registers the mobile BLE transport', () => { + expect(addTransportSpy).toHaveBeenCalledWith(RNBleTransportFactory); + }); + + it('creates transport middleware with SDK', () => { + expect(LedgerMobileDMKTransportMiddleware).toHaveBeenCalledTimes(1); + }); + }); + + describe('init', () => { + it('does not throw error during init', async () => { + let result: Error | null = null; + try { + await bridge.init(); + } catch (error) { + result = error as Error; + } + expect(result).toBeNull(); + }); + }); + + describe('destroy', () => { + it('triggers middleware dispose', async () => { + await bridge.updateSessionId('test-session-id'); + expect(bridge.isDeviceConnected).toBe(true); + await bridge.destroy(); + expect(mockTransportMiddleware.dispose.mock.calls).toHaveLength(1); + expect(bridge.isDeviceConnected).toBe(false); + }); + + it('does not throw error when dispose fails', async () => { + const error = new Error('dispose error'); + mockTransportMiddleware.dispose.mockRejectedValueOnce(error); + const consoleSpy = jest + .spyOn(console, 'error') + .mockImplementation(() => undefined); + await bridge.destroy(); + expect(consoleSpy).toHaveBeenCalledWith( + 'Failed to dispose DMK transport middleware:', + error, + ); + expect(bridge.isDeviceConnected).toBe(false); + }); + }); + + describe('getOptions', () => { + it('returns the bridge instance options', async () => { + const result = await bridge.getOptions(); + expect(result).toStrictEqual({}); + }); + }); + + describe('setOptions', () => { + it('sets options', async () => { + const opts = { key: 'value' }; + await bridge.setOptions(opts); + expect(await bridge.getOptions()).toStrictEqual(opts); + }); + }); + + describe('updateSessionId', () => { + it('sets session ID in transport middleware and sets isDeviceConnected to true', async () => { + const sessionId = 'test-session-id'; + const result = await bridge.updateSessionId(sessionId); + expect(mockTransportMiddleware.setSessionId.mock.calls).toStrictEqual([ + [sessionId], + ]); + expect(bridge.isDeviceConnected).toBe(true); + expect(result).toBe(true); + }); + }); + + describe('updateTransportMethod', () => { + it('accepts the mobile transport type', async () => { + expect(await bridge.updateTransportMethod('mobile')).toBe(true); + }); + + it('remains compatible with legacy transport names', async () => { + expect(await bridge.updateTransportMethod('webhid')).toBe(true); + }); + }); + + describe('startDiscovering', () => { + it('delegates device discovery to the transport middleware', () => { + const discovery = bridge.startDiscovering({}); + + expect(mockTransportMiddleware.startDiscovering.mock.calls).toStrictEqual( + [[{}]], + ); + expect(discovery).toBeDefined(); + }); + }); + + describe('connect', () => { + it('connects through the transport middleware and marks the device as connected', async () => { + const params = { + device: { id: 'device-id' }, + } as unknown as Parameters< + LedgerMobileDMKTransportMiddleware['connect'] + >[0]; + const result = await bridge.connect(params); + + expect(mockTransportMiddleware.connect.mock.calls).toStrictEqual([ + [params], + ]); + expect(result).toBe('test-session-id'); + expect(bridge.isDeviceConnected).toBe(true); + }); + }); + + describe('attemptMakeApp', () => { + it('returns true', async () => { + const result = await bridge.attemptMakeApp(); + expect(result).toBe(true); + }); + }); + + describe('getPublicKey', () => { + it('calls the DMK signer getAddress with hdPath', async () => { + const hdPath = "m/44'/60'/0'/0/0"; + const result = await bridge.getPublicKey({ hdPath }); + + expect(mockTransportMiddleware.getEthSigner.mock.calls).toHaveLength(1); + expect(mockEthSigner.getAddress.mock.calls).toStrictEqual([ + [ + hdPath, + { + checkOnDevice: false, + returnChainCode: true, + }, + ], + ]); + expect(result).toStrictEqual({ + publicKey: '0xpublicKey', + address: '0xaddress', + chainCode: '0xchainCode', + }); + }); + + it('throws signer errors emitted by the device action observable', async () => { + const error = new Error('device action failed'); + mockEthSigner.getAddress.mockReturnValueOnce({ + observable: of({ + status: DeviceActionStatus.Error, + error, + }), + }); + + await expect( + bridge.getPublicKey({ hdPath: "m/44'/60'/0'/0/0" }), + ).rejects.toThrow(error); + }); + + it('throws when a device action completes without a terminal status', async () => { + jest.resetModules(); + let isolatedTest: Promise | undefined; + + jest.isolateModules(() => { + jest.doMock('rxjs/operators', () => ({ + filter: + () => + (source: unknown): unknown => + source, + })); + jest.doMock('@ledgerhq/device-management-kit', () => { + const actual = jest.requireActual('@ledgerhq/device-management-kit'); + + return { + ...actual, + DeviceManagementKitBuilder: jest.fn().mockImplementation(() => ({ + addTransport: jest.fn().mockReturnThis(), + build: jest.fn().mockReturnValue({}), + })), + }; + }); + jest.doMock('@ledgerhq/device-transport-kit-react-native-ble', () => ({ + RNBleTransportFactory: jest.fn(), + })); + jest.doMock('./ledger-dmk-transport-middleware', () => ({ + LedgerMobileDMKTransportMiddleware: jest + .fn() + .mockImplementation(() => ({ + dispose: jest.fn(), + getEthSigner: jest.fn().mockReturnValue({ + getAddress: jest.fn().mockReturnValue({ + observable: of({ status: 'pending' }), + }), + }), + getSessionId: jest.fn().mockReturnValue('test-session-id'), + setSessionId: jest.fn(), + })), + LedgerDMKTransportMiddleware: jest.fn().mockImplementation(() => ({ + dispose: jest.fn(), + getEthSigner: jest.fn().mockReturnValue({ + getAddress: jest.fn().mockReturnValue({ + observable: of({ status: 'pending' }), + }), + }), + getSessionId: jest.fn().mockReturnValue('test-session-id'), + setSessionId: jest.fn(), + })), + })); + + isolatedTest = (async (): Promise => { + // eslint-disable-next-line @typescript-eslint/no-require-imports, n/global-require + const Bridge = require('./ledger-dmk-bridge').LedgerMobileDMKBridge; + + await expect( + new Bridge().getPublicKey({ + hdPath: "m/44'/60'/0'/0/0", + }), + ).rejects.toThrow('Ledger device action ended without completion.'); + })(); + }); + + await isolatedTest; + + jest.resetModules(); + }); + }); + + afterAll(() => { + jest.resetModules(); + }); + + describe('deviceSignTransaction', () => { + it('calls the DMK signer signTransaction with hdPath and tx', async () => { + const hdPath = "m/44'/60'/0'/0/0"; + const tx = 'f86d8202b38477359400825208'; + const result = await bridge.deviceSignTransaction({ hdPath, tx }); + + expect(mockTransportMiddleware.getEthSigner.mock.calls).toHaveLength(1); + expect(mockEthSigner.signTransaction.mock.calls).toStrictEqual([ + [hdPath, Uint8Array.from(Buffer.from(tx, 'hex'))], + ]); + expect(result).toStrictEqual({ + v: '1b', + r: 'r-value', + s: 's-value', + }); + }); + + it('normalizes hex string v values', async () => { + mockEthSigner.signTransaction.mockReturnValueOnce({ + observable: of({ + status: DeviceActionStatus.Completed, + output: { + v: '0x1c', + r: '0xr-value', + s: '0xs-value', + }, + }), + }); + + const result = await bridge.deviceSignTransaction({ + hdPath: "m/44'/60'/0'/0/0", + tx: 'f86d8202b38477359400825208', + }); + + expect(result.v).toBe('1c'); + }); + }); + + describe('deviceSignMessage', () => { + it('calls the DMK signer signMessage with hdPath and message', async () => { + const hdPath = "m/44'/60'/0'/0/0"; + const message = 'test message'; + const result = await bridge.deviceSignMessage({ hdPath, message }); + + expect(mockTransportMiddleware.getEthSigner.mock.calls).toHaveLength(1); + expect(mockEthSigner.signMessage.mock.calls).toStrictEqual([ + [hdPath, message], + ]); + expect(result).toStrictEqual({ + v: 27, + r: 'r-value', + s: 's-value', + }); + }); + + it('preserves signature parts that are already missing a hex prefix', async () => { + mockEthSigner.signMessage.mockReturnValueOnce({ + observable: of({ + status: DeviceActionStatus.Completed, + output: { + v: 27, + r: 'r-value', + s: 's-value', + }, + }), + }); + + const result = await bridge.deviceSignMessage({ + hdPath: "m/44'/60'/0'/0/0", + message: 'test message', + }); + + expect(result).toStrictEqual({ + v: 27, + r: 'r-value', + s: 's-value', + }); + }); + }); + + describe('deviceSignTypedData', () => { + it('calls the DMK signer signTypedData with hdPath and message', async () => { + const hdPath = "m/44'/60'/0'/0/0"; + const message: EIP712Message = { + domain: { + chainId: 1, + verifyingContract: '0xdsf', + }, + primaryType: 'string', + types: { + EIP712Domain: [], + string: [], + }, + message: { test: 'test' }, + }; + const result = await bridge.deviceSignTypedData({ hdPath, message }); + + expect(mockTransportMiddleware.getEthSigner.mock.calls).toHaveLength(1); + expect(mockEthSigner.signTypedData.mock.calls).toStrictEqual([ + [hdPath, message], + ]); + expect(result).toStrictEqual({ + v: 27, + r: 'r-value', + s: 's-value', + }); + }); + }); + + describe('getAppNameAndVersion', () => { + it('calls sdk sendCommand and returns app name and version', async () => { + const result = await bridge.getAppNameAndVersion(); + + expect(result).toStrictEqual({ + appName: 'Ethereum', + version: '2.4.0', + }); + }); + + it('rethrows SDK command errors when available', async () => { + const error = new Error('command failed'); + mockSDK.sendCommand.mockResolvedValueOnce({ + status: 'error', + error, + }); + + await expect(bridge.getAppNameAndVersion()).rejects.toThrow(error); + }); + }); + + describe('getAppConfiguration', () => { + it('returns app configuration with version from SDK', async () => { + const result = await bridge.getAppConfiguration(); + + expect(result).toStrictEqual({ + arbitraryDataEnabled: 1, + erc20ProvisioningNecessary: 0, + starkEnabled: 0, + starkv2Supported: 0, + version: '2.4.0', + }); + }); + + it('parses flags to determine arbitraryDataEnabled', async () => { + mockSDK.sendCommand.mockResolvedValueOnce({ + status: CommandResultStatus.Success, + data: { + name: 'Ethereum', + version: '2.4.0', + flags: 0, + }, + }); + + const result = await bridge.getAppConfiguration(); + + expect(result.arbitraryDataEnabled).toBe(0); + expect(result.version).toBe('2.4.0'); + }); + + it('returns arbitraryDataEnabled when flags indicate blind signing support', async () => { + mockSDK.sendCommand.mockResolvedValueOnce({ + status: CommandResultStatus.Success, + data: { + name: 'Ethereum', + version: '2.4.0', + flags: 1, + }, + }); + + const result = await bridge.getAppConfiguration(); + + expect(result.arbitraryDataEnabled).toBe(1); + expect(result.version).toBe('2.4.0'); + }); + + it('defaults arbitraryDataEnabled when flags are not numeric', async () => { + mockSDK.sendCommand.mockResolvedValueOnce({ + status: CommandResultStatus.Success, + data: { + name: 'Ethereum', + version: '2.4.0', + flags: 'unexpected', + }, + }); + + const result = await bridge.getAppConfiguration(); + + expect(result.arbitraryDataEnabled).toBe(1); + expect(result.version).toBe('2.4.0'); + }); + + it('defaults missing flags to disabled blind signing', async () => { + mockSDK.sendCommand.mockResolvedValueOnce({ + status: CommandResultStatus.Success, + data: { + name: 'Ethereum', + version: '2.4.0', + }, + }); + + const result = await bridge.getAppConfiguration(); + + expect(result.arbitraryDataEnabled).toBe(0); + expect(result.version).toBe('2.4.0'); + }); + + it('falls back to a generic error for unknown SDK command failures', async () => { + mockSDK.sendCommand.mockResolvedValueOnce({ + status: 'error', + error: 'unknown failure', + }); + + await expect(bridge.getAppConfiguration()).rejects.toThrow( + 'Ledger command failed.', + ); + }); + }); +}); diff --git a/packages/keyring-eth-ledger-bridge/src/ledger-dmk-bridge.ts b/packages/keyring-eth-ledger-bridge/src/ledger-dmk-bridge.ts new file mode 100644 index 000000000..0585c8f8d --- /dev/null +++ b/packages/keyring-eth-ledger-bridge/src/ledger-dmk-bridge.ts @@ -0,0 +1,380 @@ +import { + DeviceActionStatus, + type DeviceActionState, + type DeviceManagementKit, + DeviceManagementKitBuilder, + GetAppAndVersionCommand, + isSuccessCommandResult, +} from '@ledgerhq/device-management-kit'; +import type { + Signature, + TypedData, +} from '@ledgerhq/device-signer-kit-ethereum'; +import { RNBleTransportFactory } from '@ledgerhq/device-transport-kit-react-native-ble'; +import type Transport from '@ledgerhq/hw-transport'; +import type { Observable } from 'rxjs'; +import { firstValueFrom } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +import { + AppConfigurationResponse, + GetAppNameAndVersionResponse, + GetPublicKeyParams, + GetPublicKeyResponse, + LedgerBridge, + LedgerSignMessageParams, + LedgerSignMessageResponse, + LedgerSignTransactionParams, + LedgerSignTransactionResponse, + LedgerSignTypedDataParams, + LedgerSignTypedDataResponse, +} from './ledger-bridge'; +import { LedgerMobileDMKTransportMiddleware } from './ledger-dmk-transport-middleware'; + +export type LedgerMobileDMKBridgeOptions = Record; + +type PublicKeyOutput = Pick< + GetPublicKeyResponse, + 'address' | 'chainCode' | 'publicKey' +>; + +/** + * LedgerMobileDMKBridge is a bridge between the LedgerKeyring and the + * LedgerMobileDMKTransportMiddleware. + * It initializes and manages the DeviceManagementKit internally. + */ +export class LedgerMobileDMKBridge + implements LedgerBridge +{ + readonly #transportMiddleware: LedgerMobileDMKTransportMiddleware; + + readonly #sdk: DeviceManagementKit; + + #opts: LedgerMobileDMKBridgeOptions; + + isDeviceConnected = false; + + constructor(opts: LedgerMobileDMKBridgeOptions = {}) { + this.#opts = opts; + this.#sdk = new DeviceManagementKitBuilder() + .addTransport(RNBleTransportFactory) + .build(); + this.#transportMiddleware = new LedgerMobileDMKTransportMiddleware( + this.#sdk, + ); + } + + /** + * Compatibility hook for the shared LedgerBridge interface. + * DMK session setup happens externally via updateSessionId. + * + * @returns A promise that resolves immediately. + */ + async init(): Promise { + return undefined; + } + + /** + * Destroys the bridge and cleans up resources. + * + * @returns A promise that resolves when cleanup is complete. + */ + async destroy(): Promise { + try { + await this.#transportMiddleware.dispose(); + } catch (error) { + console.error('Failed to dispose DMK transport middleware:', error); + } + this.isDeviceConnected = false; + } + + /** + * Gets bridge options. + * + * @returns A promise that resolves with the current bridge options. + */ + async getOptions(): Promise { + return this.#opts; + } + + /** + * Sets bridge options. + * + * @param opts - The options to set for the bridge. + * @returns A promise that resolves when options are set. + */ + async setOptions(opts: LedgerMobileDMKBridgeOptions): Promise { + this.#opts = opts; + } + + /** + * Updates the session ID used for communication. + * + * @param sessionId - The session ID from DMK. + * @returns A promise that resolves with true if the session was updated successfully. + */ + async updateSessionId(sessionId: string): Promise { + this.#transportMiddleware.setSessionId(sessionId); + this.isDeviceConnected = true; + return true; + } + + /** + * Starts BLE device discovery for the mobile DMK transport. + * + * @param args - Optional DMK discovery options. + * @returns An observable that emits discovered devices. + */ + startDiscovering( + ...args: Parameters + ): ReturnType { + return this.#transportMiddleware.startDiscovering(...args); + } + + /** + * Connects to a discovered device using the configured mobile BLE transport. + * + * @param args - The DMK connection arguments. + * @returns The created session ID. + */ + async connect( + ...args: Parameters + ): ReturnType { + const sessionId = await this.#transportMiddleware.connect(...args); + this.isDeviceConnected = true; + + return sessionId; + } + + /** + * Compatibility method for the shared LedgerBridge interface. + * DMK always uses the React Native BLE-based `mobile` transport. + * The provided transport argument is ignored to preserve backwards compatibility + * with existing callers that still pass legacy transport identifiers. + * + * @param _transportType - The requested transport type. + * @returns `true`. + */ + async updateTransportMethod( + _transportType: string | Transport, + ): Promise { + return true; + } + + /** + * Compatibility method for app initialization. + * + * @returns A promise that resolves with true indicating the app is ready. + */ + async attemptMakeApp(): Promise { + return Promise.resolve(true); // SignerEth handles app check + } + + /** + * Retrieves public key/address for a given HD path. + * + * @param options0 - The parameters object. + * @param options0.hdPath - The HD path to derive the public key from. + * @returns A promise that resolves with the public key response. + */ + async getPublicKey({ + hdPath, + }: GetPublicKeyParams): Promise { + const { observable } = this.#transportMiddleware + .getEthSigner() + .getAddress(hdPath, { + checkOnDevice: false, + returnChainCode: true, + }); + const response = await this.#waitForDeviceAction( + observable as Observable< + DeviceActionState + >, + ); + + return { + publicKey: response.publicKey, + address: response.address, + chainCode: response.chainCode, + }; + } + + /** + * Signs an Ethereum transaction. + * + * @param options0 - The parameters object. + * @param options0.tx - The transaction hex string to sign. + * @param options0.hdPath - The HD path for signing. + * @returns A promise that resolves with the transaction signature. + */ + async deviceSignTransaction({ + tx, + hdPath, + }: LedgerSignTransactionParams): Promise { + const { observable } = this.#transportMiddleware + .getEthSigner() + .signTransaction(hdPath, Uint8Array.from(Buffer.from(tx, 'hex'))); + const signature = await this.#waitForDeviceAction( + observable as Observable>, + ); + + return { + v: this.#toHexString(signature.v), + r: this.#stripHexPrefix(signature.r), + s: this.#stripHexPrefix(signature.s), + }; + } + + /** + * Signs a personal message. + * + * @param options0 - The parameters object. + * @param options0.hdPath - The HD path for signing. + * @param options0.message - The message to sign. + * @returns A promise that resolves with the message signature. + */ + async deviceSignMessage({ + hdPath, + message, + }: LedgerSignMessageParams): Promise { + const { observable } = this.#transportMiddleware + .getEthSigner() + .signMessage(hdPath, message); + const signature = await this.#waitForDeviceAction( + observable as Observable>, + ); + + return { + v: signature.v, + r: this.#stripHexPrefix(signature.r), + s: this.#stripHexPrefix(signature.s), + }; + } + + /** + * Signs EIP-712 typed data. + * + * @param options0 - The parameters object. + * @param options0.hdPath - The HD path for signing. + * @param options0.message - The typed data message to sign. + * @returns A promise that resolves with the typed data signature. + */ + async deviceSignTypedData({ + hdPath, + message, + }: LedgerSignTypedDataParams): Promise { + const { observable } = this.#transportMiddleware + .getEthSigner() + .signTypedData(hdPath, message as TypedData); + const signature = await this.#waitForDeviceAction( + observable as Observable>, + ); + + return { + v: signature.v, + r: this.#stripHexPrefix(signature.r), + s: this.#stripHexPrefix(signature.s), + }; + } + + /** + * Retrieves the current app name and version. + * + * @returns A promise that resolves with the app name and version. + */ + async getAppNameAndVersion(): Promise { + const sessionId = this.#transportMiddleware.getSessionId(); + const command = new GetAppAndVersionCommand(); + const result = await this.#sdk.sendCommand({ + sessionId, + command, + }); + + if (!isSuccessCommandResult(result)) { + throw this.#toError(result.error); + } + + return { + appName: result.data.name, + version: result.data.version, + }; + } + + /** + * Retrieves the Ethereum app configuration. + * + * @returns A promise that resolves with the app configuration. + */ + async getAppConfiguration(): Promise { + const sessionId = this.#transportMiddleware.getSessionId(); + const command = new GetAppAndVersionCommand(); + const result = await this.#sdk.sendCommand({ + sessionId, + command, + }); + + if (!isSuccessCommandResult(result)) { + throw this.#toError(result.error); + } + + const { data } = result; + + // Parse flags if available to determine app capabilities + // Flags are bit-encoded: bit 0 = arbitrary data (blind signing) support + const flags = data.flags ?? 0; + // eslint-disable-next-line no-bitwise + const arbitraryDataEnabled = typeof flags === 'number' ? flags & 0x01 : 1; + + return { + arbitraryDataEnabled, + erc20ProvisioningNecessary: 0, + starkEnabled: 0, + starkv2Supported: 0, + version: data.version, + }; + } + + async #waitForDeviceAction( + observable: Observable>, + ): Promise { + const state = await firstValueFrom( + observable.pipe( + filter( + ({ status }) => + status === DeviceActionStatus.Completed || + status === DeviceActionStatus.Error, + ), + ), + ); + + if (state.status === DeviceActionStatus.Completed) { + return state.output; + } + + if (state.status === DeviceActionStatus.Error) { + throw state.error; + } + + throw new Error('Ledger device action ended without completion.'); + } + + #stripHexPrefix(value: string): string { + return value.startsWith('0x') ? value.slice(2) : value; + } + + #toError(error: unknown): Error { + if (error instanceof Error) { + return error; + } + + return new Error('Ledger command failed.'); + } + + #toHexString(value: bigint | number | string): string { + if (typeof value === 'string') { + return this.#stripHexPrefix(value); + } + + return value.toString(16); + } +} diff --git a/packages/keyring-eth-ledger-bridge/src/ledger-dmk-transport-middleware.test.ts b/packages/keyring-eth-ledger-bridge/src/ledger-dmk-transport-middleware.test.ts new file mode 100644 index 000000000..6e4c11f06 --- /dev/null +++ b/packages/keyring-eth-ledger-bridge/src/ledger-dmk-transport-middleware.test.ts @@ -0,0 +1,94 @@ +import type { DeviceManagementKit } from '@ledgerhq/device-management-kit'; +import { SignerEthBuilder } from '@ledgerhq/device-signer-kit-ethereum'; +import { of } from 'rxjs'; + +import { LedgerMobileDMKTransportMiddleware } from './ledger-dmk-transport-middleware'; + +describe('LedgerMobileDMKTransportMiddleware', () => { + const mockDiscovery = of({ id: 'device-id' }); + const mockSigner = { signer: true }; + + let middleware: LedgerMobileDMKTransportMiddleware; + let buildSpy: jest.SpyInstance; + let mockSDK: { + connect: jest.Mock; + disconnect: jest.Mock; + startDiscovering: jest.Mock; + }; + + beforeEach(() => { + buildSpy = jest + .spyOn(SignerEthBuilder.prototype, 'build') + .mockReturnValue( + mockSigner as unknown as ReturnType, + ); + mockSDK = { + connect: jest.fn().mockResolvedValue('test-session-id'), + disconnect: jest.fn().mockResolvedValue(undefined), + startDiscovering: jest.fn().mockReturnValue(mockDiscovery), + }; + + middleware = new LedgerMobileDMKTransportMiddleware( + mockSDK as unknown as DeviceManagementKit, + ); + }); + + describe('getSDK', () => { + it('returns the wrapped SDK instance', () => { + expect(middleware.getSDK()).toBe(mockSDK); + }); + }); + + describe('startDiscovering', () => { + it('delegates discovery to the SDK', () => { + const result = middleware.startDiscovering({}); + + expect(mockSDK.startDiscovering).toHaveBeenCalledWith({}); + expect(result).toBe(mockDiscovery); + }); + }); + + describe('connect', () => { + it('connects with the SDK and stores the session ID', async () => { + const params = { + device: { id: 'device-id' }, + } as unknown as Parameters[0]; + + const sessionId = await middleware.connect(params); + + expect(mockSDK.connect).toHaveBeenCalledWith(params); + expect(sessionId).toBe('test-session-id'); + expect(middleware.getSessionId()).toBe('test-session-id'); + }); + }); + + describe('getEthSigner', () => { + it('builds a signer using the current session ID', async () => { + await middleware.connect({ + device: { id: 'device-id' }, + } as unknown as Parameters[0]); + + const signer = middleware.getEthSigner(); + + expect(buildSpy).toHaveBeenCalledTimes(1); + expect(signer).toBe(mockSigner); + }); + }); + + describe('dispose', () => { + it('disconnects the current session and clears it', async () => { + await middleware.connect({ + device: { id: 'device-id' }, + } as unknown as Parameters[0]); + + await middleware.dispose(); + + expect(mockSDK.disconnect).toHaveBeenCalledWith({ + sessionId: 'test-session-id', + }); + expect(() => middleware.getSessionId()).toThrow( + 'Instance `sessionId` is not initialized.', + ); + }); + }); +}); diff --git a/packages/keyring-eth-ledger-bridge/src/ledger-dmk-transport-middleware.ts b/packages/keyring-eth-ledger-bridge/src/ledger-dmk-transport-middleware.ts new file mode 100644 index 000000000..a9a8299ce --- /dev/null +++ b/packages/keyring-eth-ledger-bridge/src/ledger-dmk-transport-middleware.ts @@ -0,0 +1,108 @@ +import { type DeviceManagementKit } from '@ledgerhq/device-management-kit'; +import { SignerEthBuilder } from '@ledgerhq/device-signer-kit-ethereum'; + +type StartDiscoveringParameters = Parameters< + DeviceManagementKit['startDiscovering'] +>; + +type StartDiscoveringResult = ReturnType< + DeviceManagementKit['startDiscovering'] +>; + +type ConnectParameters = Parameters; + +type ConnectResult = ReturnType; + +/** + * LedgerMobileDMKTransportMiddleware is a middleware to communicate with the + * Ledger device via DMK on mobile. + * It adapts the new DMK Signer ETH to the existing bridging architectural patterns. + */ +export class LedgerMobileDMKTransportMiddleware { + readonly #sdk: DeviceManagementKit; + + #sessionId?: string; + + constructor(sdk: DeviceManagementKit) { + this.#sdk = sdk; + } + + /** + * Method to retrieve the DeviceManagementKit instance. + * + * @returns The DeviceManagementKit instance. + */ + getSDK(): DeviceManagementKit { + return this.#sdk; + } + + /** + * Starts device discovery using the configured DMK transport. + * + * @param args - Optional DMK discovery options. + * @returns An observable that emits discovered devices. + */ + startDiscovering( + ...args: StartDiscoveringParameters + ): StartDiscoveringResult { + return this.#sdk.startDiscovering(...args); + } + + /** + * Method to set the session ID. + * + * @param sessionId - The session ID for the connected Ledger device. + */ + setSessionId(sessionId: string): void { + this.#sessionId = sessionId; + } + + /** + * Method to retrieve the session ID. + * + * @returns The session ID. + */ + getSessionId(): string { + if (!this.#sessionId) { + throw new Error('Instance `sessionId` is not initialized.'); + } + return this.#sessionId; + } + + /** + * Connects to a discovered device and stores the resulting session ID. + * + * @param args - The DMK connection arguments. + * @returns The created session ID. + */ + async connect(...args: ConnectParameters): ConnectResult { + const sessionId = await this.#sdk.connect(...args); + this.setSessionId(sessionId); + + return sessionId; + } + + /** + * Method to get a SignerEth instance. + * + * @returns An Ethereum signer instance. + */ + getEthSigner(): ReturnType { + return new SignerEthBuilder({ + dmk: this.#sdk, + sessionId: this.getSessionId(), + }).build(); + } + + /** + * Method to close the session. + * + * @returns A promise that resolves when the session is closed. + */ + async dispose(): Promise { + if (this.#sessionId) { + await this.#sdk.disconnect({ sessionId: this.#sessionId }); + this.#sessionId = undefined; + } + } +} diff --git a/packages/keyring-eth-ledger-bridge/src/ledger-keyring.test.ts b/packages/keyring-eth-ledger-bridge/src/ledger-keyring.test.ts index a4be4895c..722eb2548 100644 --- a/packages/keyring-eth-ledger-bridge/src/ledger-keyring.test.ts +++ b/packages/keyring-eth-ledger-bridge/src/ledger-keyring.test.ts @@ -601,12 +601,13 @@ describe('LedgerKeyring', function () { describe('updateTransportMethod', function () { describe('when bridge is connected', function () { it('calls the bridge updateTransportMethod method', async function () { - jest.spyOn(bridge, 'updateTransportMethod').mockResolvedValue(true); + const updateTransportMethodSpy = jest + .spyOn(bridge, 'updateTransportMethod') + .mockResolvedValue(true); await keyring.updateTransportMethod('some-transport'); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.updateTransportMethod).toHaveBeenCalledTimes(1); + expect(updateTransportMethodSpy.mock.calls).toHaveLength(1); }); }); }); diff --git a/packages/keyring-eth-ledger-bridge/src/ledger-keyring.ts b/packages/keyring-eth-ledger-bridge/src/ledger-keyring.ts index ca5cdc39c..178dfd41b 100644 --- a/packages/keyring-eth-ledger-bridge/src/ledger-keyring.ts +++ b/packages/keyring-eth-ledger-bridge/src/ledger-keyring.ts @@ -31,7 +31,6 @@ import { LedgerBridgeOptions, } from './ledger-bridge'; import { handleLedgerTransportError } from './ledger-error-handler'; -import { LedgerIframeBridgeOptions } from './ledger-iframe-bridge'; const pathBase = 'm'; const hdPathString = `${pathBase}/44'/60'/0'`; @@ -48,7 +47,7 @@ enum NetworkApiUrls { } type SignTransactionPayload = Awaited< - ReturnType['deviceSignTransaction']> + ReturnType['deviceSignTransaction']> >; export type AccountPageEntry = { diff --git a/tsconfig.packages.json b/tsconfig.packages.json index 847976f51..989d27a23 100644 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -12,7 +12,7 @@ "noErrorTruncation": true, "noUncheckedIndexedAccess": true, "strict": true, - "skipLibCheck": false, + "skipLibCheck": true, // NOTE: DOM is being used with "@types/web" package, if you declare it there, the compiler // might complain about conflicting types. So everything has been setup to use "@types/web" // explicitly (even in the package.json, see the "resolutions"). diff --git a/yarn.lock b/yarn.lock index 0ddb96ba1..9bb727a74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.10.1": + version: 1.10.1 + resolution: "@adraffy/ens-normalize@npm:1.10.1" + checksum: 10/4cb938c4abb88a346d50cb0ea44243ab3574330c81d4f5aaaf9dfee584b96189d0faa404de0fcbef5a1b73909ea4ebc3e63d84bd23f9949e5c8d4085207a5091 + languageName: node + linkType: hard + "@ampproject/remapping@npm:^2.2.0": version: 2.3.0 resolution: "@ampproject/remapping@npm:2.3.0" @@ -861,6 +868,55 @@ __metadata: languageName: node linkType: hard +"@inversifyjs/common@npm:1.5.0": + version: 1.5.0 + resolution: "@inversifyjs/common@npm:1.5.0" + checksum: 10/3f4e484e45522684c0565399a4e4dc3fbd24133ee2405e6b8970e3188e3073765ae73331f3b0dd37a6df995c4537568650bcfec37ef386dc2dfdbdc89b839d80 + languageName: node + linkType: hard + +"@inversifyjs/container@npm:1.9.1": + version: 1.9.1 + resolution: "@inversifyjs/container@npm:1.9.1" + dependencies: + "@inversifyjs/common": "npm:1.5.0" + "@inversifyjs/core": "npm:5.2.0" + "@inversifyjs/reflect-metadata-utils": "npm:1.1.0" + peerDependencies: + reflect-metadata: ~0.2.2 + checksum: 10/036cd679b650dde3a7e0130cbe7a5500cceb5dd1f822abf24fa1faa4515ee33e9cb39228a0bbe186037b0041fca3e4b94c1c5ee35250561b4b2657145b4d01ec + languageName: node + linkType: hard + +"@inversifyjs/core@npm:5.2.0": + version: 5.2.0 + resolution: "@inversifyjs/core@npm:5.2.0" + dependencies: + "@inversifyjs/common": "npm:1.5.0" + "@inversifyjs/prototype-utils": "npm:0.1.0" + "@inversifyjs/reflect-metadata-utils": "npm:1.1.0" + checksum: 10/a553eb0d3f3147ff9c7504e35db43442a6c17ebf09cb7bdb2db5b44232c9589fbcfa26245bcf6dd8529918469d8f4e27c97a8b93c1ccc9f4147dc927f5ff8ae4 + languageName: node + linkType: hard + +"@inversifyjs/prototype-utils@npm:0.1.0": + version: 0.1.0 + resolution: "@inversifyjs/prototype-utils@npm:0.1.0" + dependencies: + "@inversifyjs/common": "npm:1.5.0" + checksum: 10/726a301a7f1a588de2eda8a8ffcc808bdc91897098e1ec17a7e343009e023a22dce102684bd3e99df22b259c2139b35e41fe66ac87c26ded3bd77649797d1b5d + languageName: node + linkType: hard + +"@inversifyjs/reflect-metadata-utils@npm:1.1.0": + version: 1.1.0 + resolution: "@inversifyjs/reflect-metadata-utils@npm:1.1.0" + peerDependencies: + reflect-metadata: 0.2.2 + checksum: 10/b543e34a0f70133239a5634635ec9567f8fe8c7f8501c01acdeb49c96777d1c6d0d680dfad5c67b4b823d1f4750f890621190da15486c9d814d698fff6bfef95 + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -1286,6 +1342,22 @@ __metadata: languageName: node linkType: hard +"@ledgerhq/context-module@npm:^1.14.1": + version: 1.14.1 + resolution: "@ledgerhq/context-module@npm:1.14.1" + dependencies: + axios: "npm:1.13.2" + crypto-js: "npm:^4.2.0" + ethers: "npm:6.14.1" + inversify: "npm:7.5.1" + purify-ts: "npm:2.1.0" + reflect-metadata: "npm:0.2.2" + peerDependencies: + "@ledgerhq/device-management-kit": ^1.1.0 + checksum: 10/2fc578215bb8f6576371917a95e660f381877795bbbb1dcf547d88f3686c630605c8a0ee63ba82066b613154021ec605a9d343660631b9c3dba444b08a67cbc0 + languageName: node + linkType: hard + "@ledgerhq/cryptoassets-evm-signatures@npm:^13.5.2": version: 13.5.2 resolution: "@ledgerhq/cryptoassets-evm-signatures@npm:13.5.2" @@ -1296,6 +1368,63 @@ __metadata: languageName: node linkType: hard +"@ledgerhq/device-management-kit@npm:^1.1.0": + version: 1.1.0 + resolution: "@ledgerhq/device-management-kit@npm:1.1.0" + dependencies: + "@noble/hashes": "npm:^1.8.0" + "@sentry/minimal": "npm:6.19.7" + axios: "npm:1.13.2" + inversify: "npm:7.5.1" + isomorphic-ws: "npm:^5.0.0" + purify-ts: "npm:2.1.0" + reflect-metadata: "npm:0.2.2" + semver: "npm:7.7.2" + url: "npm:^0.11.4" + uuid: "npm:11.0.3" + ws: "npm:^8.18.0" + xstate: "npm:5.19.2" + peerDependencies: + rxjs: 7.8.2 + checksum: 10/bb1c26a7e620dd393b6880c58826f7813c0948ff2e7bafb116956288287b1721556eca34d765807ff90d969a0c4f2b02cf7125827ba0e2cb220290a51b610a9f + languageName: node + linkType: hard + +"@ledgerhq/device-signer-kit-ethereum@npm:^1.11.1": + version: 1.11.1 + resolution: "@ledgerhq/device-signer-kit-ethereum@npm:1.11.1" + dependencies: + "@ledgerhq/signer-utils": "npm:^1.1.2" + ethers: "npm:6.14.1" + inversify: "npm:7.5.1" + purify-ts: "npm:2.1.0" + reflect-metadata: "npm:0.2.2" + semver: "npm:7.7.2" + xstate: "npm:5.19.2" + peerDependencies: + "@ledgerhq/context-module": ^1.14.1 + "@ledgerhq/device-management-kit": ^1.1.0 + checksum: 10/4123d1658d0eec8a73343afe95299e21cfc7fe4683030b3043e4dfc6acb92e1f62c5a4862de926bc5e65ecc65bb13b58da02d761e22d01f19a161445c0f2f06f + languageName: node + linkType: hard + +"@ledgerhq/device-transport-kit-react-native-ble@npm:^1.3.2": + version: 1.3.2 + resolution: "@ledgerhq/device-transport-kit-react-native-ble@npm:1.3.2" + dependencies: + "@sentry/minimal": "npm:6.19.7" + js-base64: "npm:^3.7.8" + purify-ts: "npm:2.1.0" + uuid: "npm:11.0.3" + peerDependencies: + "@ledgerhq/device-management-kit": ^1.0.0 + react-native: ">0.74.1" + react-native-ble-plx: 3.4.0 + rxjs: 7.8.2 + checksum: 10/50afe97ab1260f2537aa06ff107fddd3b1e906ca808c3a6bea672cfada8ef96a26088443a992a5795ec51441780225e84a8711b49e7987a720f4a8e02b9014ad + languageName: node + linkType: hard + "@ledgerhq/devices@npm:^8.4.4": version: 8.4.4 resolution: "@ledgerhq/devices@npm:8.4.4" @@ -1406,6 +1535,15 @@ __metadata: languageName: node linkType: hard +"@ledgerhq/signer-utils@npm:^1.1.2": + version: 1.1.2 + resolution: "@ledgerhq/signer-utils@npm:1.1.2" + peerDependencies: + "@ledgerhq/device-management-kit": ^1.0.0 + checksum: 10/2de50cb6f7eaf7193177776e822d9e4e629381bede9694cd6928e43b27cff44ccdbcd267d5ec201903061884af5d6935ba0497754fba3ad21cc974b396ef75db + languageName: node + linkType: hard + "@ledgerhq/types-cryptoassets@npm:^7.15.1": version: 7.15.1 resolution: "@ledgerhq/types-cryptoassets@npm:7.15.1" @@ -1715,6 +1853,10 @@ __metadata: "@ethereumjs/util": "npm:^9.1.0" "@lavamoat/allow-scripts": "npm:^3.2.1" "@lavamoat/preinstall-always-fail": "npm:^2.1.0" + "@ledgerhq/context-module": "npm:^1.14.1" + "@ledgerhq/device-management-kit": "npm:^1.1.0" + "@ledgerhq/device-signer-kit-ethereum": "npm:^1.11.1" + "@ledgerhq/device-transport-kit-react-native-ble": "npm:^1.3.2" "@ledgerhq/hw-app-eth": "npm:^6.42.0" "@ledgerhq/hw-transport": "npm:^6.31.3" "@ledgerhq/types-cryptoassets": "npm:^7.15.1" @@ -1731,14 +1873,15 @@ __metadata: "@types/ethereumjs-tx": "npm:^1.0.1" "@types/hdkey": "npm:^2.0.1" "@types/jest": "npm:^29.5.12" - "@types/node": "npm:^20.12.12" "@types/web": "npm:^0.0.69" + "@types/ws": "npm:^8.18.1" deepmerge: "npm:^4.2.2" depcheck: "npm:^1.4.7" ethereumjs-tx: "npm:^1.3.7" hdkey: "npm:^2.1.0" jest: "npm:^29.5.0" jest-it-up: "npm:^3.1.0" + rxjs: "npm:^7.8.2" ts-jest: "npm:^29.0.5" ts-node: "npm:^10.9.2" typedoc: "npm:^0.25.13" @@ -2545,6 +2688,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.2.0": + version: 1.2.0 + resolution: "@noble/curves@npm:1.2.0" + dependencies: + "@noble/hashes": "npm:1.3.2" + checksum: 10/94e02e9571a9fd42a3263362451849d2f54405cb3ce9fa7c45bc6b9b36dcd7d1d20e2e1e14cfded24937a13d82f1e60eefc4d7a14982ce0bc219a9fc0f51d1f9 + languageName: node + linkType: hard + "@noble/curves@npm:1.4.2, @noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" @@ -2572,6 +2724,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.3.2": + version: 1.3.2 + resolution: "@noble/hashes@npm:1.3.2" + checksum: 10/685f59d2d44d88e738114b71011d343a9f7dce9dfb0a121f1489132f9247baa60bc985e5ec6f3213d114fbd1e1168e7294644e46cbd0ce2eba37994f28eeb51b + languageName: node + linkType: hard + "@noble/hashes@npm:1.4.0, @noble/hashes@npm:~1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" @@ -2867,6 +3026,45 @@ __metadata: languageName: node linkType: hard +"@sentry/hub@npm:6.19.7": + version: 6.19.7 + resolution: "@sentry/hub@npm:6.19.7" + dependencies: + "@sentry/types": "npm:6.19.7" + "@sentry/utils": "npm:6.19.7" + tslib: "npm:^1.9.3" + checksum: 10/ef2381ec399305ee56f7cff990c5bf0f221119193ac1b0862d237c42c9e214a8a3dcabe55085e197710c9667f1c541fffc3fe37e89d7562f3c86432c22d7f09a + languageName: node + linkType: hard + +"@sentry/minimal@npm:6.19.7": + version: 6.19.7 + resolution: "@sentry/minimal@npm:6.19.7" + dependencies: + "@sentry/hub": "npm:6.19.7" + "@sentry/types": "npm:6.19.7" + tslib: "npm:^1.9.3" + checksum: 10/eac4f79f7116dee90bfd8ea284c777c267e70c0b51883bc419f176dd5283b2b1955ede0bc471759f26a8c686f78f7a664560684a8998fc4c6f85d9e1539d39f9 + languageName: node + linkType: hard + +"@sentry/types@npm:6.19.7": + version: 6.19.7 + resolution: "@sentry/types@npm:6.19.7" + checksum: 10/f9f70e94c4a3876f6119f7e3979051ea2a054adce6f5583de9f70a08642c7d2c2f80a70a1f9fe5f9fad4e99315f4483340ded1110ae2e7c825c4c1f210fc2507 + languageName: node + linkType: hard + +"@sentry/utils@npm:6.19.7": + version: 6.19.7 + resolution: "@sentry/utils@npm:6.19.7" + dependencies: + "@sentry/types": "npm:6.19.7" + tslib: "npm:^1.9.3" + checksum: 10/0ea94d32940705d77b019ca821e45a5866bb3d443e0f19b9bf5edf3d7ffed68c451803f3388913fec4da875e4b7df46b5f8a8681c4d69972fb3d775d864997b2 + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -4090,7 +4288,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:7.0.15, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10/1a3c3e06236e4c4aab89499c428d585527ce50c24fe8259e8b3926d3df4cfbbbcf306cfc73ddfb66cbafc973116efd15967020b0f738f63e09e64c7d260519e7 @@ -4257,6 +4455,15 @@ __metadata: languageName: node linkType: hard +"@types/ws@npm:^8.18.1": + version: 8.18.1 + resolution: "@types/ws@npm:8.18.1" + dependencies: + "@types/node": "npm:*" + checksum: 10/1ce05e3174dcacf28dae0e9b854ef1c9a12da44c7ed73617ab6897c5cbe4fccbb155a20be5508ae9a7dde2f83bd80f5cf3baa386b934fc4b40889ec963e94f3a + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 15.0.0 resolution: "@types/yargs-parser@npm:15.0.0" @@ -4680,6 +4887,13 @@ __metadata: languageName: node linkType: hard +"aes-js@npm:4.0.0-beta.5": + version: 4.0.0-beta.5 + resolution: "aes-js@npm:4.0.0-beta.5" + checksum: 10/8f745da2e8fb38e91297a8ec13c2febe3219f8383303cd4ed4660ca67190242ccfd5fdc2f0d1642fd1ea934818fb871cd4cc28d3f28e812e3dc6c3d0f1f97c24 + languageName: node + linkType: hard + "aes-js@npm:^3.1.2": version: 3.1.2 resolution: "aes-js@npm:3.1.2" @@ -4900,6 +5114,20 @@ __metadata: languageName: node linkType: hard +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10/3d49e7acbeee9e84537f4cb0e0f91893df8eba976759875ae8ee9e3d3c82f6ecdebdb347c2fad9926b92596d93cdfc78ecc988bcdf407e40433e8e8e6fe5d78e + languageName: node + linkType: hard + "async-mutex@npm:^0.5.0": version: 0.5.0 resolution: "async-mutex@npm:0.5.0" @@ -4932,25 +5160,25 @@ __metadata: languageName: node linkType: hard -"axios@npm:1.7.7": - version: 1.7.7 - resolution: "axios@npm:1.7.7" +"axios@npm:1.13.2, axios@npm:^1.8.4": + version: 1.13.2 + resolution: "axios@npm:1.13.2" dependencies: follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.0" + form-data: "npm:^4.0.4" proxy-from-env: "npm:^1.1.0" - checksum: 10/7f875ea13b9298cd7b40fd09985209f7a38d38321f1118c701520939de2f113c4ba137832fe8e3f811f99a38e12c8225481011023209a77b0c0641270e20cde1 + checksum: 10/ae4e06dcd18289f2fd18179256d550d27f9a53ecb2f9c59f2ccc4efd1d7151839ba8c3e0fb533dac793e4a59a576ca8689a19244dce5c396680837674a47a867 languageName: node linkType: hard -"axios@npm:^1.8.4": - version: 1.10.0 - resolution: "axios@npm:1.10.0" +"axios@npm:1.7.7": + version: 1.7.7 + resolution: "axios@npm:1.7.7" dependencies: follow-redirects: "npm:^1.15.6" form-data: "npm:^4.0.0" proxy-from-env: "npm:^1.1.0" - checksum: 10/d43c80316a45611fd395743e15d16ea69a95f2b7f7095f2bb12cb78f9ca0a905194a02e52a3bf4e0db9f85fd1186d6c690410644c10ecd8bb0a468e57c2040e4 + checksum: 10/7f875ea13b9298cd7b40fd09985209f7a38d38321f1118c701520939de2f113c4ba137832fe8e3f811f99a38e12c8225481011023209a77b0c0641270e20cde1 languageName: node linkType: hard @@ -5484,6 +5712,16 @@ __metadata: languageName: node linkType: hard +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10/00482c1f6aa7cfb30fb1dbeb13873edf81cfac7c29ed67a5957d60635a56b2a4a480f1016ddbdb3395cc37900d46037fb965043a51c5c789ffeab4fc535d18b5 + languageName: node + linkType: hard + "call-bind@npm:^1.0.0, call-bind@npm:^1.0.2, call-bind@npm:^1.0.7": version: 1.0.7 resolution: "call-bind@npm:1.0.7" @@ -5497,6 +5735,16 @@ __metadata: languageName: node linkType: hard +"call-bound@npm:^1.0.2": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10/ef2b96e126ec0e58a7ff694db43f4d0d44f80e641370c21549ed911fecbdbc2df3ebc9bddad918d6bbdefeafb60bb3337902006d5176d72bcd2da74820991af7 + languageName: node + linkType: hard + "callsite@npm:^1.0.0": version: 1.0.0 resolution: "callsite@npm:1.0.0" @@ -5942,7 +6190,7 @@ __metadata: languageName: node linkType: hard -"crypto-js@npm:4.2.0": +"crypto-js@npm:4.2.0, crypto-js@npm:^4.2.0": version: 4.2.0 resolution: "crypto-js@npm:4.2.0" checksum: 10/c7bcc56a6e01c3c397e95aa4a74e4241321f04677f9a618a8f48a63b5781617248afb9adb0629824792e7ec20ca0d4241a49b6b2938ae6f973ec4efc5c53c924 @@ -6234,6 +6482,17 @@ __metadata: languageName: node linkType: hard +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10/5add88a3d68d42d6e6130a0cac450b7c2edbe73364bbd2fc334564418569bea97c6943a8fcd70e27130bf32afc236f30982fc4905039b703f23e9e0433c29934 + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -6367,12 +6626,10 @@ __metadata: languageName: node linkType: hard -"es-define-property@npm:^1.0.0": - version: 1.0.0 - resolution: "es-define-property@npm:1.0.0" - dependencies: - get-intrinsic: "npm:^1.2.4" - checksum: 10/f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 languageName: node linkType: hard @@ -6383,6 +6640,27 @@ __metadata: languageName: node linkType: hard +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10/54fe77de288451dae51c37bfbfe3ec86732dc3778f98f3eb3bdb4bf48063b2c0b8f9c93542656986149d08aa5be3204286e2276053d19582b76753f1a2728867 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10/86814bf8afbcd8966653f731415888019d4bc4aca6b6c354132a7a75bb87566751e320369654a101d23a91c87a85c79b178bcf40332839bd347aff437c4fb65f + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.1.2": version: 3.1.2 resolution: "escalade@npm:3.1.2" @@ -6921,6 +7199,21 @@ __metadata: languageName: node linkType: hard +"ethers@npm:6.14.1": + version: 6.14.1 + resolution: "ethers@npm:6.14.1" + dependencies: + "@adraffy/ens-normalize": "npm:1.10.1" + "@noble/curves": "npm:1.2.0" + "@noble/hashes": "npm:1.3.2" + "@types/node": "npm:22.7.5" + aes-js: "npm:4.0.0-beta.5" + tslib: "npm:2.7.0" + ws: "npm:8.17.1" + checksum: 10/20bd332bef5f38a94ca79863e56d02dcde692e4eb1f492b7dbbb5817c2488dd3af8056e315f75afbdfd07119dd5fa2dc065cac72859ca0b71eb815690aebeb53 + languageName: node + linkType: hard + "ethjs-util@npm:0.1.6, ethjs-util@npm:^0.1.3, ethjs-util@npm:^0.1.6": version: 0.1.6 resolution: "ethjs-util@npm:0.1.6" @@ -7288,14 +7581,16 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" +"form-data@npm:^4.0.0, form-data@npm:^4.0.4": + version: 4.0.5 + resolution: "form-data@npm:4.0.5" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" mime-types: "npm:^2.1.12" - checksum: 10/7264aa760a8cf09482816d8300f1b6e2423de1b02bba612a136857413fdc96d7178298ced106817655facc6b89036c6e12ae31c9eb5bdc16aabf502ae8a5d805 + checksum: 10/52ecd6e927c8c4e215e68a7ad5e0f7c1031397439672fd9741654b4a94722c4182e74cc815b225dcb5be3f4180f36428f67c6dd39eaa98af0dcfdd26c00c19cd languageName: node linkType: hard @@ -7366,6 +7661,13 @@ __metadata: languageName: node linkType: hard +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10/eb7e7eb896c5433f3d40982b2ccacdb3dd990dd3499f14040e002b5d54572476513be8a2e6f9609f6e41ab29f2c4469307611ddbfc37ff4e46b765c326663805 + languageName: node + linkType: hard + "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -7387,16 +7689,24 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4": - version: 1.2.4 - resolution: "get-intrinsic@npm:1.2.4" +"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" function-bind: "npm:^1.1.2" - has-proto: "npm:^1.0.1" - has-symbols: "npm:^1.0.3" - hasown: "npm:^2.0.0" - checksum: 10/85bbf4b234c3940edf8a41f4ecbd4e25ce78e5e6ad4e24ca2f77037d983b9ef943fd72f00f3ee97a49ec622a506b67db49c36246150377efcda1c9eb03e5f06d + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10/bb579dda84caa4a3a41611bdd483dade7f00f246f2a7992eb143c5861155290df3fdb48a8406efa3dfb0b434e2c8fafa4eebd469e409d0439247f85fc3fa2cc1 languageName: node linkType: hard @@ -7414,6 +7724,16 @@ __metadata: languageName: node linkType: hard +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b + languageName: node + linkType: hard + "get-stdin@npm:^9.0.0": version: 9.0.0 resolution: "get-stdin@npm:9.0.0" @@ -7593,12 +7913,10 @@ __metadata: languageName: node linkType: hard -"gopd@npm:^1.0.1": - version: 1.0.1 - resolution: "gopd@npm:1.0.1" - dependencies: - get-intrinsic: "npm:^1.1.3" - checksum: 10/5fbc7ad57b368ae4cd2f41214bd947b045c1a4be2f194a7be1778d71f8af9dbf4004221f3b6f23e30820eb0d052b4f819fe6ebe8221e2a3c6f0ee4ef173421ca +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 languageName: node linkType: hard @@ -7639,17 +7957,10 @@ __metadata: languageName: node linkType: hard -"has-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "has-proto@npm:1.0.1" - checksum: 10/eab2ab0ed1eae6d058b9bbc4c1d99d2751b29717be80d02fd03ead8b62675488de0c7359bc1fdd4b87ef6fd11e796a9631ad4d7452d9324fdada70158c2e5be7 - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.3": - version: 1.0.3 - resolution: "has-symbols@npm:1.0.3" - checksum: 10/464f97a8202a7690dadd026e6d73b1ceeddd60fe6acfd06151106f050303eaa75855aaa94969df8015c11ff7c505f196114d22f7386b4a471038da5874cf5e9b +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa languageName: node linkType: hard @@ -7690,7 +8001,7 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.0, hasown@npm:^2.0.2": +"hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" dependencies: @@ -7963,6 +8274,19 @@ __metadata: languageName: node linkType: hard +"inversify@npm:7.5.1": + version: 7.5.1 + resolution: "inversify@npm:7.5.1" + dependencies: + "@inversifyjs/common": "npm:1.5.0" + "@inversifyjs/container": "npm:1.9.1" + "@inversifyjs/core": "npm:5.2.0" + peerDependencies: + reflect-metadata: ~0.2.2 + checksum: 10/3ce73d09611a947e601d86f025517d83c5187c1b8287c244d8955f297ba2850fbdffaa509a5e5c7f98617875cea68fde7d4ca23775b68edca4c197222cf9913e + languageName: node + linkType: hard + "ip-address@npm:^9.0.5": version: 9.0.5 resolution: "ip-address@npm:9.0.5" @@ -8217,6 +8541,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:^5.0.0": + version: 5.0.0 + resolution: "isomorphic-ws@npm:5.0.0" + peerDependencies: + ws: "*" + checksum: 10/e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-lib-coverage@npm:3.2.0" @@ -8806,6 +9139,13 @@ __metadata: languageName: node linkType: hard +"js-base64@npm:^3.7.8": + version: 3.7.8 + resolution: "js-base64@npm:3.7.8" + checksum: 10/4baa9a222bc094b072933e4894735653b59992477c731879784e3112f21862b08dd5c56b8b23b0b8e43bc5bb514d957621d1892a6c58950f29e12b731fe087b6 + languageName: node + linkType: hard + "js-sha3@npm:0.8.0": version: 0.8.0 resolution: "js-sha3@npm:0.8.0" @@ -9274,6 +9614,13 @@ __metadata: languageName: node linkType: hard +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd + languageName: node + linkType: hard + "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" @@ -9870,6 +10217,13 @@ __metadata: languageName: node linkType: hard +"object-inspect@npm:^1.13.3": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb + languageName: node + linkType: hard + "object-is@npm:^1.1.5": version: 1.1.6 resolution: "object-is@npm:1.1.6" @@ -10351,6 +10705,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^1.4.1": + version: 1.4.1 + resolution: "punycode@npm:1.4.1" + checksum: 10/af2700dde1a116791ff8301348ff344c47d6c224e875057237d1b5112035655fb07a6175cfdb8bf0e3a8cdfd2dc82b3a622e0aefd605566c0e949a6d0d1256a4 + languageName: node + linkType: hard + "punycode@npm:^2.1.0, punycode@npm:^2.1.1": version: 2.3.1 resolution: "punycode@npm:2.3.1" @@ -10365,6 +10726,15 @@ __metadata: languageName: node linkType: hard +"purify-ts@npm:2.1.0": + version: 2.1.0 + resolution: "purify-ts@npm:2.1.0" + dependencies: + "@types/json-schema": "npm:7.0.15" + checksum: 10/3498b5c1cb3bfbf154460768064278a9175cc676d212d361d9d2ead9bc087f470f4b0dc70a7e206400bb46d969639bee5436efcc28a26a97fe02b0a22232b9e4 + languageName: node + linkType: hard + "pushdata-bitcoin@npm:^1.0.1": version: 1.0.1 resolution: "pushdata-bitcoin@npm:1.0.1" @@ -10374,6 +10744,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.12.3": + version: 6.15.0 + resolution: "qs@npm:6.15.0" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10/a3458f2f389285c3512e0ebc55522ee370ac7cb720ba9f0eff3e30fb2bb07631caf556c08e2a3d4481a371ac14faa9ceb7442a0610c5a7e55b23a5bdee7b701c + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -10531,6 +10910,13 @@ __metadata: languageName: node linkType: hard +"reflect-metadata@npm:0.2.2": + version: 0.2.2 + resolution: "reflect-metadata@npm:0.2.2" + checksum: 10/1c93f9ac790fea1c852fde80c91b2760420069f4862f28e6fae0c00c6937a56508716b0ed2419ab02869dd488d123c4ab92d062ae84e8739ea7417fae10c4745 + languageName: node + linkType: hard + "require-addon@npm:^1.1.0": version: 1.1.0 resolution: "require-addon@npm:1.1.0" @@ -10759,12 +11145,12 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.8.1": - version: 7.8.1 - resolution: "rxjs@npm:7.8.1" +"rxjs@npm:^7.8.1, rxjs@npm:^7.8.2": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" dependencies: tslib: "npm:^2.1.0" - checksum: 10/b10cac1a5258f885e9dd1b70d23c34daeb21b61222ee735d2ec40a8685bdca40429000703a44f0e638c27a684ac139e1c37e835d2a0dc16f6fc061a138ae3abb + checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d languageName: node linkType: hard @@ -10842,7 +11228,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.6.3, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3": +"semver@npm:7.6.3": version: 7.6.3 resolution: "semver@npm:7.6.3" bin: @@ -10851,6 +11237,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:7.7.2, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3": + version: 7.7.2 + resolution: "semver@npm:7.7.2" + bin: + semver: bin/semver.js + checksum: 10/7a24cffcaa13f53c09ce55e05efe25cd41328730b2308678624f8b9f5fc3093fc4d189f47950f0b811ff8f3c3039c24a2c36717ba7961615c682045bf03e1dda + languageName: node + linkType: hard + "semver@npm:^6.0.0, semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -10946,6 +11341,54 @@ __metadata: languageName: node linkType: hard +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10/603b928997abd21c5a5f02ae6b9cc36b72e3176ad6827fab0417ead74580cc4fb4d5c7d0a8a2ff4ead34d0f9e35701ed7a41853dac8a6d1a664fcce1a044f86f + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10/5771861f77feefe44f6195ed077a9e4f389acc188f895f570d56445e251b861754b547ea9ef73ecee4e01fdada6568bfe9020d2ec2dfc5571e9fa1bbc4a10615 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10/a815c89bc78c5723c714ea1a77c938377ea710af20d4fb886d362b0d1f8ac73a17816a5f6640f354017d7e292a43da9c5e876c22145bac00b76cfb3468001736 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10/7d53b9db292c6262f326b6ff3bc1611db84ece36c2c7dc0e937954c13c73185b0406c56589e2bb8d071d6fee468e14c39fb5d203ee39be66b7b8174f179afaba + languageName: node + linkType: hard + "signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -11681,20 +12124,20 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.8.1": - version: 1.14.1 - resolution: "tslib@npm:1.14.1" - checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb - languageName: node - linkType: hard - -"tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2": +"tslib@npm:2.7.0, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2": version: 2.7.0 resolution: "tslib@npm:2.7.0" checksum: 10/9a5b47ddac65874fa011c20ff76db69f97cf90c78cff5934799ab8894a5342db2d17b4e7613a087046bc1d133d21547ddff87ac558abeec31ffa929c88b7fce6 languageName: node linkType: hard +"tslib@npm:^1.8.1, tslib@npm:^1.9.3": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb + languageName: node + linkType: hard + "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -11961,6 +12404,16 @@ __metadata: languageName: node linkType: hard +"url@npm:^0.11.4": + version: 0.11.4 + resolution: "url@npm:0.11.4" + dependencies: + punycode: "npm:^1.4.1" + qs: "npm:^6.12.3" + checksum: 10/e787d070f0756518b982a4653ef6cdf4d9030d8691eee2d483344faf2b530b71d302287fa63b292299455fea5075c502a5ad5f920cb790e95605847f957a65e4 + languageName: node + linkType: hard + "usb@npm:^2.15.0": version: 2.15.0 resolution: "usb@npm:2.15.0" @@ -12007,6 +12460,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:11.0.3": + version: 11.0.3 + resolution: "uuid@npm:11.0.3" + bin: + uuid: dist/esm/bin/uuid + checksum: 10/251385563195709eb0697c74a834764eef28e1656d61174e35edbd129288acb4d95a43f4ce8a77b8c2fc128e2b55924296a0945f964b05b9173469d045625ff2 + languageName: node + linkType: hard + "uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" @@ -12289,6 +12751,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.17.1": + version: 8.17.1 + resolution: "ws@npm:8.17.1" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10/4264ae92c0b3e59c7e309001e93079b26937aab181835fb7af79f906b22cd33b6196d96556dafb4e985742dd401e99139572242e9847661fdbc96556b9e6902d + languageName: node + linkType: hard + "ws@npm:^8.11.0, ws@npm:^8.13.0, ws@npm:^8.18.0": version: 8.18.2 resolution: "ws@npm:8.18.2" @@ -12335,6 +12812,13 @@ __metadata: languageName: node linkType: hard +"xstate@npm:5.19.2": + version: 5.19.2 + resolution: "xstate@npm:5.19.2" + checksum: 10/e482600e8df96d29df92e4a9498359a03450612a503ac33c6fa1e56f966ee6985ac8f455771f6c5b65e3e4bae06fea0e762c3ca0ba86eec8419c1f7c01a0f102 + languageName: node + linkType: hard + "xtend@npm:^4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2"