diff --git a/packages/connect-multichain/CHANGELOG.md b/packages/connect-multichain/CHANGELOG.md index 1540a598..abc35294 100644 --- a/packages/connect-multichain/CHANGELOG.md +++ b/packages/connect-multichain/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fix `PrivateKey is not a constructor` when connecting over the MWP transport from a bundled browser app. `eciesjs` publishes CommonJS only — no `module` field, and no `import` condition in its `exports` map — so bundlers shape `await import('eciesjs')` as `{ default: }` rather than a namespace carrying the named exports, leaving `decrypt`, `encrypt`, `PrivateKey` and `PublicKey` `undefined`. `createKeyManager` now unwraps `default` when it is present. Node and dev servers that synthesize the named exports were unaffected. ([#343](https://github.com/MetaMask/connect-monorepo/pull/343)) + ## [1.2.0] ### Added diff --git a/packages/connect-multichain/src/multichain/transports/mwp/KeyManager.test.ts b/packages/connect-multichain/src/multichain/transports/mwp/KeyManager.test.ts new file mode 100644 index 00000000..4bc1626c --- /dev/null +++ b/packages/connect-multichain/src/multichain/transports/mwp/KeyManager.test.ts @@ -0,0 +1,47 @@ +/* eslint-disable id-length -- vitest alias */ +import type { IKeyManager } from '@metamask/mobile-wallet-protocol-core'; +import * as t from 'vitest'; +import { vi } from 'vitest'; + +import { createKeyManager } from './KeyManager'; + +const PLAINTEXT = 'hello from the dapp'; + +/** + * Exercises every method of the key manager against real `eciesjs`. + * + * @param keymanager - The key manager under test. + * @returns The decrypted plaintext, which should match what was encrypted. + */ +async function roundTrip(keymanager: IKeyManager): Promise { + const { privateKey, publicKey } = keymanager.generateKeyPair(); + keymanager.validatePeerKey(publicKey); + const ciphertext = await keymanager.encrypt(PLAINTEXT, publicKey); + return keymanager.decrypt(ciphertext, privateKey); +} + +t.describe('createKeyManager', () => { + t.it('round-trips with the namespace shape Node resolves', async () => { + const keymanager = await createKeyManager(); + + t.expect(await roundTrip(keymanager)).toBe(PLAINTEXT); + }); + + t.it('round-trips when the import resolves to `{ default }`', async () => { + // The shape bundlers produce for `eciesjs`, which publishes CommonJS only. + // Destructuring it directly yields `undefined` for every named export. + const actual = await vi.importActual('eciesjs'); + vi.resetModules(); + vi.doMock('eciesjs', () => ({ default: actual })); + + try { + const { createKeyManager: create } = await import('./KeyManager'); + const keymanager = await create(); + + t.expect(await roundTrip(keymanager)).toBe(PLAINTEXT); + } finally { + vi.doUnmock('eciesjs'); + vi.resetModules(); + } + }); +}); diff --git a/packages/connect-multichain/src/multichain/transports/mwp/KeyManager.ts b/packages/connect-multichain/src/multichain/transports/mwp/KeyManager.ts index 4d3862b6..2f87a85b 100644 --- a/packages/connect-multichain/src/multichain/transports/mwp/KeyManager.ts +++ b/packages/connect-multichain/src/multichain/transports/mwp/KeyManager.ts @@ -16,7 +16,10 @@ import type { * @returns A ready-to-use key manager instance. */ export async function createKeyManager(): Promise { - const { decrypt, encrypt, PrivateKey, PublicKey } = await import('eciesjs'); + // `eciesjs` is CommonJS-only, so bundlers shape this import as + // `{ default: }` rather than a namespace of named exports. + const ecies = await import('eciesjs'); + const { decrypt, encrypt, PrivateKey, PublicKey } = ecies.default ?? ecies; return { generateKeyPair(): KeyPair {