From 3676b03ea6f63a46e4314d3352971a00bc2e5487 Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Mon, 8 Jul 2024 09:41:52 +0800 Subject: [PATCH 1/5] fix: Current iOS will throw disconnect error when we reset the transport, and because eth app hasn't been recreated with new transport even that transport has exactly same property (android will not throw error, only ios did) --- src/ledger-transport-middleware.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ledger-transport-middleware.ts b/src/ledger-transport-middleware.ts index 5481b0e8..4fcd36c5 100644 --- a/src/ledger-transport-middleware.ts +++ b/src/ledger-transport-middleware.ts @@ -39,6 +39,7 @@ export class LedgerTransportMiddleware implements TransportMiddleware { */ setTransport(transport: Transport): void { this.#transport = transport; + this.#app = new MetaMaskLedgerHwAppEth(this.#transport); } /** From 40787313fdb0c5006b7211259f35175a8215b4a6 Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Mon, 8 Jul 2024 11:23:07 +0800 Subject: [PATCH 2/5] fix: Improve the code coverage for unit tests. --- src/ledger-transport-middleware.test.ts | 7 +++++++ src/ledger-transport-middleware.ts | 10 ++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/ledger-transport-middleware.test.ts b/src/ledger-transport-middleware.test.ts index daba4263..b0f05702 100644 --- a/src/ledger-transport-middleware.test.ts +++ b/src/ledger-transport-middleware.test.ts @@ -62,5 +62,12 @@ describe('LedgerTransportMiddleware', function () { const app = transportMiddleware.getEthApp(); expect(app).toBeDefined(); }); + + it('throw error when transport not set', async function () { + transportMiddleware = new LedgerTransportMiddleware(); + expect(() => transportMiddleware.getEthApp()).toThrow( + 'Instance `transport` is not initialized.', + ); + }); }); }); diff --git a/src/ledger-transport-middleware.ts b/src/ledger-transport-middleware.ts index 4fcd36c5..0eb796bb 100644 --- a/src/ledger-transport-middleware.ts +++ b/src/ledger-transport-middleware.ts @@ -20,8 +20,6 @@ export class LedgerTransportMiddleware implements TransportMiddleware { readonly transportEncoding = 'ascii'; - #app?: MetaMaskLedgerHwAppEth; - #transport?: Transport; /** @@ -39,7 +37,6 @@ export class LedgerTransportMiddleware implements TransportMiddleware { */ setTransport(transport: Transport): void { this.#transport = transport; - this.#app = new MetaMaskLedgerHwAppEth(this.#transport); } /** @@ -61,9 +58,10 @@ export class LedgerTransportMiddleware implements TransportMiddleware { * @returns An generic interface for communicating with a Ledger hardware wallet to perform operation. */ getEthApp(): MetaMaskLedgerHwAppEth { - if (!this.#app) { - this.#app = new MetaMaskLedgerHwAppEth(this.getTransport()); + if (!this.getTransport()) { + throw new Error('Instance `transport` is not initialized.'); } - return this.#app; + + return new MetaMaskLedgerHwAppEth(this.getTransport()); } } From c5bfddb657871a2bd93ec3bbbe8e827f1592881f Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Mon, 8 Jul 2024 14:48:44 +0800 Subject: [PATCH 3/5] fix: fix the unit tests coverage --- jest.config.js | 6 ++--- src/ledger-keyring.test.ts | 38 ++++++++++++++++++++++++++---- src/ledger-keyring.ts | 1 + src/ledger-transport-middleware.ts | 4 ---- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/jest.config.js b/jest.config.js index 1f5c3327..36153ef4 100644 --- a/jest.config.js +++ b/jest.config.js @@ -41,10 +41,10 @@ module.exports = { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 87.94, + branches: 89.28, functions: 95.91, - lines: 91.13, - statements: 91.22, + lines: 91.32, + statements: 91.41, }, }, diff --git a/src/ledger-keyring.test.ts b/src/ledger-keyring.test.ts index 3f60d5a7..956fe2d7 100644 --- a/src/ledger-keyring.test.ts +++ b/src/ledger-keyring.test.ts @@ -191,7 +191,7 @@ describe('LedgerKeyring', function () { expect(serialized.deviceId).toBe('some-device'); }); - it('should migrate accountIndexes to accountDetails', async function () { + it('migrates accountIndexes to accountDetails', async function () { const someHdPath = `m/44'/60'/0'/0/0`; const account = fakeAccounts[1]; const checksum = ethUtil.toChecksumAddress(account); @@ -211,7 +211,7 @@ describe('LedgerKeyring', function () { }); }); - it('should migrate non-bip44 accounts to accountDetails', async function () { + it('migrates non-bip44 accounts to accountDetails', async function () { const someHdPath = `m/44'/60'/0'`; const account = fakeAccounts[1]; const checksum = ethUtil.toChecksumAddress(account); @@ -229,6 +229,28 @@ describe('LedgerKeyring', function () { hdPath: `m/44'/60'/0'/1`, }); }); + + it('throws errors when address is not found', async function () { + const hdPath = `m/44'/60'/0'`; + const account = '0x90A5b70d94418d6c25C19071e5b8170607f6302D'; + + let thrownError; + const accountIndexes: Record = {}; + accountIndexes['0x90a'] = 1; + + try { + await keyring.deserialize({ + hdPath, + accounts: [account], + deviceId: 'some-device', + implementFullBIP44: true, + accountIndexes, + }); + } catch (error) { + thrownError = error; + } + expect(thrownError).toStrictEqual(new Error('Unknown address')); + }); }); describe('setDeviceId', function () { @@ -440,12 +462,12 @@ describe('LedgerKeyring', function () { }); describe('getFirstPage', function () { - it('should set the currentPage to 1', async function () { + it('sets the currentPage to 1', async function () { await keyring.getFirstPage(); expect(keyring.page).toBe(1); }); - it('should return the list of accounts for current page', async function () { + it('returns the list of accounts for current page', async function () { const accounts = await keyring.getFirstPage(); expect(accounts).toHaveLength(keyring.perPage); @@ -978,6 +1000,14 @@ describe('LedgerKeyring', function () { ); }); + it('throws an error if the version is not provided', async function () { + await expect( + keyring.signTypedData(fakeAccounts[0], fixtureData), + ).rejects.toThrow( + 'Ledger: Only version 4 of typed data signing is supported', + ); + }); + it('throws an error if the hdPath is not found', async function () { jest .spyOn(keyring, 'unlockAccountByAddress') diff --git a/src/ledger-keyring.ts b/src/ledger-keyring.ts index ee8a7f68..691b4d2a 100644 --- a/src/ledger-keyring.ts +++ b/src/ledger-keyring.ts @@ -22,6 +22,7 @@ const pathBase = 'm'; const hdPathString = `${pathBase}/44'/60'/0'`; const keyringType = 'Ledger Hardware'; +// This number make one of our failure test very slow which for loop need to run 1000 times. const MAX_INDEX = 1000; enum NetworkApiUrls { diff --git a/src/ledger-transport-middleware.ts b/src/ledger-transport-middleware.ts index 0eb796bb..15edbada 100644 --- a/src/ledger-transport-middleware.ts +++ b/src/ledger-transport-middleware.ts @@ -58,10 +58,6 @@ export class LedgerTransportMiddleware implements TransportMiddleware { * @returns An generic interface for communicating with a Ledger hardware wallet to perform operation. */ getEthApp(): MetaMaskLedgerHwAppEth { - if (!this.getTransport()) { - throw new Error('Instance `transport` is not initialized.'); - } - return new MetaMaskLedgerHwAppEth(this.getTransport()); } } From 7bb9bb7eb32cd2c76dfd6efaac97c756ec580263 Mon Sep 17 00:00:00 2001 From: Xiaoming Wang <7315988+dawnseeker8@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:06:07 +0800 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Gustavo Antunes <17601467+gantunesr@users.noreply.github.com> --- src/ledger-keyring.test.ts | 15 +++++---------- src/ledger-keyring.ts | 2 +- src/ledger-transport-middleware.test.ts | 2 +- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/ledger-keyring.test.ts b/src/ledger-keyring.test.ts index 956fe2d7..e26aeea0 100644 --- a/src/ledger-keyring.test.ts +++ b/src/ledger-keyring.test.ts @@ -230,27 +230,22 @@ describe('LedgerKeyring', function () { }); }); - it('throws errors when address is not found', async function () { + it('throws an error when the address is not found', async function () { const hdPath = `m/44'/60'/0'`; const account = '0x90A5b70d94418d6c25C19071e5b8170607f6302D'; - let thrownError; const accountIndexes: Record = {}; accountIndexes['0x90a'] = 1; - try { - await keyring.deserialize({ + await expect( + keyring.deserialize({ hdPath, accounts: [account], deviceId: 'some-device', implementFullBIP44: true, accountIndexes, - }); - } catch (error) { - thrownError = error; - } - expect(thrownError).toStrictEqual(new Error('Unknown address')); - }); + }), + ).rejects.toThrow('Unknown address'); }); describe('setDeviceId', function () { diff --git a/src/ledger-keyring.ts b/src/ledger-keyring.ts index 691b4d2a..727aa6da 100644 --- a/src/ledger-keyring.ts +++ b/src/ledger-keyring.ts @@ -22,7 +22,7 @@ const pathBase = 'm'; const hdPathString = `${pathBase}/44'/60'/0'`; const keyringType = 'Ledger Hardware'; -// This number make one of our failure test very slow which for loop need to run 1000 times. +// This number causes one of our failing tests to run very slowly, as the for loop needs to iterate 1000 times. const MAX_INDEX = 1000; enum NetworkApiUrls { diff --git a/src/ledger-transport-middleware.test.ts b/src/ledger-transport-middleware.test.ts index b0f05702..fafc79e2 100644 --- a/src/ledger-transport-middleware.test.ts +++ b/src/ledger-transport-middleware.test.ts @@ -63,7 +63,7 @@ describe('LedgerTransportMiddleware', function () { expect(app).toBeDefined(); }); - it('throw error when transport not set', async function () { + it('throws an error when the transport object is not set', async function () { transportMiddleware = new LedgerTransportMiddleware(); expect(() => transportMiddleware.getEthApp()).toThrow( 'Instance `transport` is not initialized.', From 978488cd57964ee30b1b7a8991cc8658521a517a Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Wed, 10 Jul 2024 11:53:57 +0800 Subject: [PATCH 5/5] feat: Refactor all unit test comments to remove `should` in the description and follow metamask unit tests guideline --- src/ledger-keyring.test.ts | 1326 ++++++++++++++++++------------------ 1 file changed, 668 insertions(+), 658 deletions(-) diff --git a/src/ledger-keyring.test.ts b/src/ledger-keyring.test.ts index e26aeea0..f82c3828 100644 --- a/src/ledger-keyring.test.ts +++ b/src/ledger-keyring.test.ts @@ -147,7 +147,7 @@ describe('LedgerKeyring', function () { }); describe('init', function () { - it('should call bridge init', async function () { + it('calls bridge init', async function () { jest.spyOn(bridge, 'init').mockResolvedValue(undefined); await keyring.init(); @@ -246,817 +246,827 @@ describe('LedgerKeyring', function () { accountIndexes, }), ).rejects.toThrow('Unknown address'); - }); - - describe('setDeviceId', function () { - it('sets the deviceId', function () { - keyring.setDeviceId('some-device'); - expect(keyring.getDeviceId()).toBe('some-device'); }); - }); - describe('getDeviceId', function () { - it('returns the deviceId', function () { - keyring.setDeviceId('some-device'); - expect(keyring.getDeviceId()).toBe('some-device'); + describe('setDeviceId', function () { + it('sets the deviceId', function () { + keyring.setDeviceId('some-device'); + expect(keyring.getDeviceId()).toBe('some-device'); + }); }); - }); - describe('isConnected', function () { - it('returns true if bridge is connected', function () { - bridge.isDeviceConnected = true; - expect(keyring.isConnected()).toBe(true); + describe('getDeviceId', function () { + it('returns the deviceId', function () { + keyring.setDeviceId('some-device'); + expect(keyring.getDeviceId()).toBe('some-device'); + }); }); - it('returns false if bridge is not connected', function () { - bridge.isDeviceConnected = false; - expect(keyring.isConnected()).toBe(false); - }); - }); + describe('isConnected', function () { + it('returns true if bridge is connected', function () { + bridge.isDeviceConnected = true; + expect(keyring.isConnected()).toBe(true); + }); - describe('getName', function () { - it('returns the keyring name', function () { - expect(keyring.getName()).toBe('Ledger Hardware'); + it('returns false if bridge is not connected', function () { + bridge.isDeviceConnected = false; + expect(keyring.isConnected()).toBe(false); + }); }); - }); - describe('isUnlocked', function () { - it('returns true if there is a public key', function () { - expect(keyring.isUnlocked()).toBe(true); + describe('getName', function () { + it('returns the keyring name', function () { + expect(keyring.getName()).toBe('Ledger Hardware'); + }); }); - }); - describe('setAccountToUnlock', function () { - it('sets unlockedAccount to new value', function () { - keyring.setAccountToUnlock(3); - expect(keyring.unlockedAccount).toBe(3); + describe('isUnlocked', function () { + it('returns true if there is a public key', function () { + expect(keyring.isUnlocked()).toBe(true); + }); }); - }); - describe('setHdPath', function () { - it('sets the hdPath', function () { - const someHDPath = `m/44'/99'/0`; - keyring.setHdPath(someHDPath); - expect(keyring.hdPath).toBe(someHDPath); + describe('setAccountToUnlock', function () { + it('sets unlockedAccount to new value', function () { + keyring.setAccountToUnlock(3); + expect(keyring.unlockedAccount).toBe(3); + }); }); - it('resets the HDKey if the path changes', function () { - const someHDPath = `m/44'/99'/0`; - keyring.setHdPath(someHDPath); - expect(keyring.hdk.publicKey).toBeNull(); - }); - }); + describe('setHdPath', function () { + it('sets the hdPath', function () { + const someHDPath = `m/44'/99'/0`; + keyring.setHdPath(someHDPath); + expect(keyring.hdPath).toBe(someHDPath); + }); - describe('unlock', function () { - it('resolves to a public key if it exists', async function () { - expect(async () => { - await keyring.unlock(); - }).not.toThrow(); + it('resets the HDKey if the path changes', function () { + const someHDPath = `m/44'/99'/0`; + keyring.setHdPath(someHDPath); + expect(keyring.hdk.publicKey).toBeNull(); + }); }); - it('should update hdk.publicKey if updateHdk is true', async function () { - // @ts-expect-error we want to bypass the set publicKey property set method - keyring.hdk = { publicKey: 'ABC' }; - - jest.spyOn(bridge, 'getPublicKey').mockResolvedValue({ - publicKey: - '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', - chainCode: - 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', - address: fakeAccounts[1], + describe('unlock', function () { + it('resolves to a public key if it exists', async function () { + expect(async () => { + await keyring.unlock(); + }).not.toThrow(); }); - await keyring.unlock(`m/44'/60'/0'/1`); - expect(keyring.hdk.publicKey).not.toBe('ABC'); - }); + it('updates hdk.publicKey if updateHdk is true', async function () { + // @ts-expect-error we want to bypass the set publicKey property set method + keyring.hdk = { publicKey: 'ABC' }; - it('should not update hdk.publicKey if updateHdk is false', async function () { - // @ts-expect-error we want to bypass the publicKey property set method - keyring.hdk = { publicKey: 'ABC' }; + jest.spyOn(bridge, 'getPublicKey').mockResolvedValue({ + publicKey: + '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', + chainCode: + 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', + address: fakeAccounts[1], + }); - jest.spyOn(bridge, 'getPublicKey').mockResolvedValue({ - publicKey: - '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', - chainCode: - 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', - address: fakeAccounts[1], + await keyring.unlock(`m/44'/60'/0'/1`); + expect(keyring.hdk.publicKey).not.toBe('ABC'); }); - await keyring.unlock(`m/44'/60'/0'/1`, false); - expect(keyring.hdk.publicKey).toBe('ABC'); - }); - - it('unlocks with the set hdPath if a new one is not provided', async function () { - keyring.setHdPath(`m/44'/60'/0'/0`); - jest.spyOn(bridge, 'getPublicKey').mockResolvedValue( - Promise.resolve({ - publicKey: '0x1234', - chainCode: '0x1234', - address: fakeAccounts[0], - }), - ); - const account = await keyring.unlock(undefined, false); - expect(account).toBe(fakeAccounts[0]); - }); + it('does not update hdk.publicKey if updateHdk is false', async function () { + // @ts-expect-error we want to bypass the publicKey property set method + keyring.hdk = { publicKey: 'ABC' }; - it('throws an error if the bridge getPublicKey method throws an error', async function () { - keyring.setHdPath(`m/44'/60'/0'/0`); - jest - .spyOn(bridge, 'getPublicKey') - .mockRejectedValue(new Error('Some error')); - await expect(keyring.unlock()).rejects.toThrow('Some error'); - }); + jest.spyOn(bridge, 'getPublicKey').mockResolvedValue({ + publicKey: + '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', + chainCode: + 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', + address: fakeAccounts[1], + }); - it('throws an error when the bridge getPublicKey method throws an error and it is not an error type', async function () { - keyring.setHdPath(`m/44'/60'/0'/0`); - jest.spyOn(bridge, 'getPublicKey').mockRejectedValue('Some error'); - await expect(keyring.unlock()).rejects.toThrow('Unknown error'); - }); - }); + await keyring.unlock(`m/44'/60'/0'/1`, false); + expect(keyring.hdk.publicKey).toBe('ABC'); + }); - describe('addAccounts', function () { - describe('with no arguments', function () { - it('returns a single account', async function () { - keyring.setAccountToUnlock(0); - const accounts = await keyring.addAccounts(); - expect(accounts).toHaveLength(1); + it('unlocks with the set hdPath if a new one is not provided', async function () { + keyring.setHdPath(`m/44'/60'/0'/0`); + jest.spyOn(bridge, 'getPublicKey').mockResolvedValue( + Promise.resolve({ + publicKey: '0x1234', + chainCode: '0x1234', + address: fakeAccounts[0], + }), + ); + const account = await keyring.unlock(undefined, false); + expect(account).toBe(fakeAccounts[0]); }); - }); - describe('with a numeric argument', function () { - it('returns that number of accounts', async function () { - keyring.setAccountToUnlock(0); - const accounts = await keyring.addAccounts(5); - expect(accounts).toHaveLength(5); + it('throws an error if the bridge getPublicKey method throws an error', async function () { + keyring.setHdPath(`m/44'/60'/0'/0`); + jest + .spyOn(bridge, 'getPublicKey') + .mockRejectedValue(new Error('Some error')); + await expect(keyring.unlock()).rejects.toThrow('Some error'); }); - it('returns the expected accounts', async function () { - keyring.setAccountToUnlock(0); - const accounts = await keyring.addAccounts(3); - expect(accounts[0]).toBe(fakeAccounts[0]); - expect(accounts[1]).toBe(fakeAccounts[1]); - expect(accounts[2]).toBe(fakeAccounts[2]); + it('throws an error when the bridge getPublicKey method throws an error and it is not an error type', async function () { + keyring.setHdPath(`m/44'/60'/0'/0`); + jest.spyOn(bridge, 'getPublicKey').mockRejectedValue('Some error'); + await expect(keyring.unlock()).rejects.toThrow('Unknown error'); }); }); - it('stores account details for bip44 accounts', async function () { - keyring.setHdPath(`m/44'/60'/0'/0/0`); - keyring.setAccountToUnlock(1); - jest.spyOn(keyring, 'unlock').mockResolvedValue(fakeAccounts[0]); - const accounts = await keyring.addAccounts(1); - expect(keyring.accountDetails[accounts[0] as string]).toStrictEqual({ - bip44: true, - hdPath: `m/44'/60'/1'/0/0`, + describe('addAccounts', function () { + describe('with no arguments', function () { + it('returns a single account', async function () { + keyring.setAccountToUnlock(0); + const accounts = await keyring.addAccounts(); + expect(accounts).toHaveLength(1); + }); }); - }); - it('stores account details for non-bip44 accounts', async function () { - keyring.setHdPath(`m/44'/60'/0'`); - keyring.setAccountToUnlock(2); - const accounts = await keyring.addAccounts(1); - expect(keyring.accountDetails[accounts[0] as string]).toStrictEqual({ - bip44: false, - hdPath: `m/44'/60'/0'/2`, + describe('with a numeric argument', function () { + it('returns that number of accounts', async function () { + keyring.setAccountToUnlock(0); + const accounts = await keyring.addAccounts(5); + expect(accounts).toHaveLength(5); + }); + + it('returns the expected accounts', async function () { + keyring.setAccountToUnlock(0); + const accounts = await keyring.addAccounts(3); + expect(accounts[0]).toBe(fakeAccounts[0]); + expect(accounts[1]).toBe(fakeAccounts[1]); + expect(accounts[2]).toBe(fakeAccounts[2]); + }); }); - }); - describe('when called multiple times', function () { - it('should not remove existing accounts', async function () { - keyring.setAccountToUnlock(0); - await keyring.addAccounts(1); + it('stores account details for bip44 accounts', async function () { + keyring.setHdPath(`m/44'/60'/0'/0/0`); keyring.setAccountToUnlock(1); + jest.spyOn(keyring, 'unlock').mockResolvedValue(fakeAccounts[0]); const accounts = await keyring.addAccounts(1); + expect(keyring.accountDetails[accounts[0] as string]).toStrictEqual({ + bip44: true, + hdPath: `m/44'/60'/1'/0/0`, + }); + }); - expect(accounts).toHaveLength(2); - expect(accounts[0]).toBe(fakeAccounts[0]); - expect(accounts[1]).toBe(fakeAccounts[1]); + it('stores account details for non-bip44 accounts', async function () { + keyring.setHdPath(`m/44'/60'/0'`); + keyring.setAccountToUnlock(2); + const accounts = await keyring.addAccounts(1); + expect(keyring.accountDetails[accounts[0] as string]).toStrictEqual({ + bip44: false, + hdPath: `m/44'/60'/0'/2`, + }); }); - }); - }); - describe('removeAccount', function () { - describe('if the account exists', function () { - it('should remove that account', async function () { - keyring.setAccountToUnlock(0); - const accounts = await keyring.addAccounts(); - expect(accounts).toHaveLength(1); - keyring.removeAccount(fakeAccounts[0]); - const accountsAfterRemoval = await keyring.getAccounts(); - expect(accountsAfterRemoval).toHaveLength(0); + describe('when called multiple times', function () { + it('does not remove existing accounts', async function () { + keyring.setAccountToUnlock(0); + await keyring.addAccounts(1); + keyring.setAccountToUnlock(1); + const accounts = await keyring.addAccounts(1); + + expect(accounts).toHaveLength(2); + expect(accounts[0]).toBe(fakeAccounts[0]); + expect(accounts[1]).toBe(fakeAccounts[1]); + }); }); }); - describe('if the account does not exist', function () { - it('should throw an error', function () { - const unexistingAccount = '0x0000000000000000000000000000000000000000'; - expect(() => { - keyring.removeAccount(unexistingAccount); - }).toThrow(`Address ${unexistingAccount} not found in this keyring`); + describe('removeAccount', function () { + describe('if the account exists', function () { + it('removes that account', async function () { + keyring.setAccountToUnlock(0); + const accounts = await keyring.addAccounts(); + expect(accounts).toHaveLength(1); + keyring.removeAccount(fakeAccounts[0]); + const accountsAfterRemoval = await keyring.getAccounts(); + expect(accountsAfterRemoval).toHaveLength(0); + }); }); - }); - }); - describe('getFirstPage', function () { - it('sets the currentPage to 1', async function () { - await keyring.getFirstPage(); - expect(keyring.page).toBe(1); + describe('if the account does not exist', function () { + it('throws an error', function () { + const unexistingAccount = + '0x0000000000000000000000000000000000000000'; + expect(() => { + keyring.removeAccount(unexistingAccount); + }).toThrow(`Address ${unexistingAccount} not found in this keyring`); + }); + }); }); - it('returns the list of accounts for current page', async function () { - const accounts = await keyring.getFirstPage(); + describe('getFirstPage', function () { + it('sets the currentPage to 1', async function () { + await keyring.getFirstPage(); + expect(keyring.page).toBe(1); + }); - expect(accounts).toHaveLength(keyring.perPage); - expect(accounts[0]?.address).toBe(fakeAccounts[0]); - expect(accounts[1]?.address).toBe(fakeAccounts[1]); - expect(accounts[2]?.address).toBe(fakeAccounts[2]); - expect(accounts[3]?.address).toBe(fakeAccounts[3]); - expect(accounts[4]?.address).toBe(fakeAccounts[4]); - }); + it('returns the list of accounts for current page', async function () { + const accounts = await keyring.getFirstPage(); - it('returns the list of accounts when isLedgerLiveHdPath is true', async function () { - keyring.setHdPath(`m/44'/60'/0'/0/0`); - jest.spyOn(keyring, 'unlock').mockResolvedValue(fakeAccounts[0]); - const accounts = await keyring.getFirstPage(); + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); + }); - expect(accounts).toHaveLength(keyring.perPage); - expect(accounts[0]?.address).toBe( - '0xF30952A1c534CDE7bC471380065726fa8686dfB3', - ); - }); - }); + it('returns the list of accounts when isLedgerLiveHdPath is true', async function () { + keyring.setHdPath(`m/44'/60'/0'/0/0`); + jest.spyOn(keyring, 'unlock').mockResolvedValue(fakeAccounts[0]); + const accounts = await keyring.getFirstPage(); - describe('getNextPage', function () { - it('should return the list of accounts for current page', async function () { - const accounts = await keyring.getNextPage(); - expect(accounts).toHaveLength(keyring.perPage); - expect(accounts[0]?.address).toBe(fakeAccounts[0]); - expect(accounts[1]?.address).toBe(fakeAccounts[1]); - expect(accounts[2]?.address).toBe(fakeAccounts[2]); - expect(accounts[3]?.address).toBe(fakeAccounts[3]); - expect(accounts[4]?.address).toBe(fakeAccounts[4]); + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe( + '0xF30952A1c534CDE7bC471380065726fa8686dfB3', + ); + }); }); - }); - describe('getPreviousPage', function () { - it('should return the list of accounts for current page', async function () { - // manually advance 1 page - await keyring.getNextPage(); - const accounts = await keyring.getPreviousPage(); - - expect(accounts).toHaveLength(keyring.perPage); - expect(accounts[0]?.address).toBe(fakeAccounts[0]); - expect(accounts[1]?.address).toBe(fakeAccounts[1]); - expect(accounts[2]?.address).toBe(fakeAccounts[2]); - expect(accounts[3]?.address).toBe(fakeAccounts[3]); - expect(accounts[4]?.address).toBe(fakeAccounts[4]); + describe('getNextPage', function () { + it('returns the list of accounts for current page', async function () { + const accounts = await keyring.getNextPage(); + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); + }); }); - it('should be able to go back to the previous page', async function () { - // manually advance 1 page - await keyring.getNextPage(); - const accounts = await keyring.getPreviousPage(); - - expect(accounts).toHaveLength(keyring.perPage); - expect(accounts[0]?.address).toBe(fakeAccounts[0]); - expect(accounts[1]?.address).toBe(fakeAccounts[1]); - expect(accounts[2]?.address).toBe(fakeAccounts[2]); - expect(accounts[3]?.address).toBe(fakeAccounts[3]); - expect(accounts[4]?.address).toBe(fakeAccounts[4]); - }); - }); + describe('getPreviousPage', function () { + it('returns the list of accounts for current page', async function () { + // manually advance 1 page + await keyring.getNextPage(); + const accounts = await keyring.getPreviousPage(); - describe('getAccounts', function () { - const accountIndex = 5; - let accounts: string[] = []; - beforeEach(async function () { - keyring.setAccountToUnlock(accountIndex); - await keyring.addAccounts(); - accounts = await keyring.getAccounts(); - }); + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); + }); - it('returns an array of accounts', function () { - expect(Array.isArray(accounts)).toBe(true); - expect(accounts).toHaveLength(1); + it('is able to go back to the previous page', async function () { + // manually advance 1 page + await keyring.getNextPage(); + const accounts = await keyring.getPreviousPage(); + + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); + }); }); - it('returns the expected', function () { - const expectedAccount = fakeAccounts[accountIndex]; - expect(accounts[0]).toStrictEqual(expectedAccount); + describe('getAccounts', function () { + const accountIndex = 5; + let accounts: string[] = []; + beforeEach(async function () { + keyring.setAccountToUnlock(accountIndex); + await keyring.addAccounts(); + accounts = await keyring.getAccounts(); + }); + + it('returns an array of accounts', function () { + expect(Array.isArray(accounts)).toBe(true); + expect(accounts).toHaveLength(1); + }); + + it('returns the expected', function () { + const expectedAccount = fakeAccounts[accountIndex]; + expect(accounts[0]).toStrictEqual(expectedAccount); + }); }); - }); - describe('exportAccount', function () { - it('should throw an error because it is not supported', function () { - expect(() => { - keyring.exportAccount(); - }).toThrow('Not supported on this device'); + describe('exportAccount', function () { + it('throws an error because it is not supported', function () { + expect(() => { + keyring.exportAccount(); + }).toThrow('Not supported on this device'); + }); }); - }); - describe('forgetDevice', function () { - it('should clear the content of the keyring', async function () { - // Add an account - keyring.setAccountToUnlock(0); - await keyring.addAccounts(); + describe('forgetDevice', function () { + it('clears the content of the keyring', async function () { + // Add an account + keyring.setAccountToUnlock(0); + await keyring.addAccounts(); - // Wipe the keyring - keyring.forgetDevice(); + // Wipe the keyring + keyring.forgetDevice(); - const accounts = await keyring.getAccounts(); + const accounts = await keyring.getAccounts(); - expect(keyring.isUnlocked()).toBe(false); - expect(accounts).toHaveLength(0); + expect(keyring.isUnlocked()).toBe(false); + expect(accounts).toHaveLength(0); + }); }); - }); - describe('attemptMakeApp', function () { - it('calls the bridge attemptMakeApp method', async function () { - jest.spyOn(bridge, 'attemptMakeApp').mockResolvedValue(true); + describe('attemptMakeApp', function () { + it('calls the bridge attemptMakeApp method', async function () { + jest.spyOn(bridge, 'attemptMakeApp').mockResolvedValue(true); - await keyring.attemptMakeApp(); + await keyring.attemptMakeApp(); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.attemptMakeApp).toHaveBeenCalledTimes(1); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(bridge.attemptMakeApp).toHaveBeenCalledTimes(1); + }); }); - }); - describe('updateTransportMethod', function () { - describe('when bridge is connected', function () { - it('calls the bridge updateTransportMethod method', async function () { - jest.spyOn(bridge, 'updateTransportMethod').mockResolvedValue(true); + describe('updateTransportMethod', function () { + describe('when bridge is connected', function () { + it('calls the bridge updateTransportMethod method', async function () { + jest.spyOn(bridge, 'updateTransportMethod').mockResolvedValue(true); - await keyring.updateTransportMethod('some-transport'); + await keyring.updateTransportMethod('some-transport'); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.updateTransportMethod).toHaveBeenCalledTimes(1); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(bridge.updateTransportMethod).toHaveBeenCalledTimes(1); + }); }); }); - }); - describe('signTransaction', function () { - describe('using old versions of ethereumjs/tx', function () { - it('should pass serialized transaction to ledger and return signed tx', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring.bridge, 'deviceSignTransaction') - .mockImplementation(async (params) => { - expect(params).toStrictEqual({ - hdPath: "m/44'/60'/0'/0", - tx: fakeTx.serialize().toString('hex'), + describe('signTransaction', function () { + describe('using old versions of ethereumjs/tx', function () { + it('passes serialized transaction to ledger and return signed tx', async function () { + await basicSetupToUnlockOneAccount(); + jest + .spyOn(keyring.bridge, 'deviceSignTransaction') + .mockImplementation(async (params) => { + expect(params).toStrictEqual({ + hdPath: "m/44'/60'/0'/0", + tx: fakeTx.serialize().toString('hex'), + }); + return { v: '0x1', r: '0x0', s: '0x0' }; }); - return { v: '0x1', r: '0x0', s: '0x0' }; - }); - jest.spyOn(fakeTx, 'verifySignature').mockReturnValue(true); + jest.spyOn(fakeTx, 'verifySignature').mockReturnValue(true); - const returnedTx = await keyring.signTransaction( - fakeAccounts[0], - fakeTx, - ); + const returnedTx = await keyring.signTransaction( + fakeAccounts[0], + fakeTx, + ); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); - expect(returnedTx).toHaveProperty('v'); - expect(returnedTx).toHaveProperty('r'); - expect(returnedTx).toHaveProperty('s'); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); + expect(returnedTx).toHaveProperty('v'); + expect(returnedTx).toHaveProperty('r'); + expect(returnedTx).toHaveProperty('s'); + }); }); - }); - - describe('using new versions of ethereumjs/tx', function () { - it('should pass correctly encoded legacy transaction to ledger and return signed tx', async function () { - // Generated by signing newFakeTx with private key eee0290acfa88cf7f97be7525437db1624293f829b8a2cba380390618d62662b - const expectedRSV = { - v: '0x26', - r: '0xf3a7718999d1b87beda810b25cc025153e74df0745279826b9b2f3d1d1b6318', - s: '0x7e33bdfbf5272dc4f55649e9ba729849670171a68ef8c0fbeed3b879b90b8954', - }; - - await basicSetupToUnlockOneAccount(); - const signedNewFakeTx = TransactionFactory.fromTxData( - { - ...newFakeTx.toJSON(), - ...expectedRSV, - }, - { freeze: false }, - ); + describe('using new versions of ethereumjs/tx', function () { + it('passes correctly encoded legacy transaction to ledger and return signed tx', async function () { + // Generated by signing newFakeTx with private key eee0290acfa88cf7f97be7525437db1624293f829b8a2cba380390618d62662b + const expectedRSV = { + v: '0x26', + r: '0xf3a7718999d1b87beda810b25cc025153e74df0745279826b9b2f3d1d1b6318', + s: '0x7e33bdfbf5272dc4f55649e9ba729849670171a68ef8c0fbeed3b879b90b8954', + }; + + await basicSetupToUnlockOneAccount(); + + const signedNewFakeTx = TransactionFactory.fromTxData( + { + ...newFakeTx.toJSON(), + ...expectedRSV, + }, + { freeze: false }, + ); + + jest + .spyOn(TransactionFactory, 'fromTxData') + .mockReturnValue(signedNewFakeTx); + + jest + .spyOn(signedNewFakeTx, 'verifySignature') + .mockImplementation(() => true); + + jest + .spyOn(keyring.bridge, 'deviceSignTransaction') + .mockImplementation(async (params) => { + expect(params).toStrictEqual({ + hdPath: "m/44'/60'/0'/0", + tx: Buffer.from( + RLP.encode(newFakeTx.getMessageToSign(false)), + ).toString('hex'), + }); + return expectedRSV; + }); - jest - .spyOn(TransactionFactory, 'fromTxData') - .mockReturnValue(signedNewFakeTx); + const returnedTx = await keyring.signTransaction( + fakeAccounts[0], + newFakeTx, + ); - jest - .spyOn(signedNewFakeTx, 'verifySignature') - .mockImplementation(() => true); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); + expect(returnedTx.toJSON()).toStrictEqual(signedNewFakeTx.toJSON()); + }); - jest - .spyOn(keyring.bridge, 'deviceSignTransaction') - .mockImplementation(async (params) => { - expect(params).toStrictEqual({ - hdPath: "m/44'/60'/0'/0", - tx: Buffer.from( - RLP.encode(newFakeTx.getMessageToSign(false)), - ).toString('hex'), + it('passes correctly encoded EIP1559 transaction to ledger and return signed tx', async function () { + // Generated by signing fakeTypeTwoTx with private key eee0290acfa88cf7f97be7525437db1624293f829b8a2cba380390618d62662b + const expectedRSV = { + v: '0x0', + r: '0x5ffb3adeaec80e430e7a7b02d95c5108b6f09a0bdf3cf69869dc1b38d0fb8d3a', + s: '0x28b234a5403d31564e18258df84c51a62683e3f54fa2b106fdc1a9058006a112', + }; + + await basicSetupToUnlockOneAccount(); + + const signedFakeTypeTwoTx = TransactionFactory.fromTxData( + { + ...fakeTypeTwoTx.toJSON(), + type: fakeTypeTwoTx.type, + ...expectedRSV, + }, + { common: commonEIP1559, freeze: false }, + ); + jest + .spyOn(TransactionFactory, 'fromTxData') + .mockReturnValue(signedFakeTypeTwoTx); + jest + .spyOn(signedFakeTypeTwoTx, 'verifySignature') + .mockReturnValue(true); + + jest.spyOn(fakeTypeTwoTx, 'verifySignature').mockReturnValue(true); + jest + .spyOn(keyring.bridge, 'deviceSignTransaction') + .mockImplementation(async (params) => { + expect(params).toStrictEqual({ + hdPath: "m/44'/60'/0'/0", + tx: fakeTypeTwoTx.getMessageToSign(false).toString('hex'), + }); + return expectedRSV; }); - return expectedRSV; - }); - const returnedTx = await keyring.signTransaction( - fakeAccounts[0], - newFakeTx, - ); + const returnedTx = await keyring.signTransaction( + fakeAccounts[0], + fakeTypeTwoTx, + ); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); - expect(returnedTx.toJSON()).toStrictEqual(signedNewFakeTx.toJSON()); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); + expect(returnedTx.toJSON()).toStrictEqual( + signedFakeTypeTwoTx.toJSON(), + ); + }); }); - it('should pass correctly encoded EIP1559 transaction to ledger and return signed tx', async function () { - // Generated by signing fakeTypeTwoTx with private key eee0290acfa88cf7f97be7525437db1624293f829b8a2cba380390618d62662b - const expectedRSV = { - v: '0x0', - r: '0x5ffb3adeaec80e430e7a7b02d95c5108b6f09a0bdf3cf69869dc1b38d0fb8d3a', - s: '0x28b234a5403d31564e18258df84c51a62683e3f54fa2b106fdc1a9058006a112', - }; - + it('throws default error in signTransaction when unlockAccountByAddress returns undefined hdPath', async function () { await basicSetupToUnlockOneAccount(); - - const signedFakeTypeTwoTx = TransactionFactory.fromTxData( - { - ...fakeTypeTwoTx.toJSON(), - type: fakeTypeTwoTx.type, - ...expectedRSV, - }, - { common: commonEIP1559, freeze: false }, - ); - jest - .spyOn(TransactionFactory, 'fromTxData') - .mockReturnValue(signedFakeTypeTwoTx); jest - .spyOn(signedFakeTypeTwoTx, 'verifySignature') - .mockReturnValue(true); + .spyOn(keyring, 'unlockAccountByAddress') + .mockResolvedValue(undefined); + + await expect( + keyring.signTransaction(fakeAccounts[0], fakeTx), + ).rejects.toThrow('Ledger: Unknown error while signing transaction'); + }); - jest.spyOn(fakeTypeTwoTx, 'verifySignature').mockReturnValue(true); + it('throws different error to the default one if the bridge error is an instance of the Error object', async function () { + await basicSetupToUnlockOneAccount(); jest .spyOn(keyring.bridge, 'deviceSignTransaction') - .mockImplementation(async (params) => { - expect(params).toStrictEqual({ - hdPath: "m/44'/60'/0'/0", - tx: fakeTypeTwoTx.getMessageToSign(false).toString('hex'), - }); - return expectedRSV; - }); + .mockRejectedValue(new Error('some error')); - const returnedTx = await keyring.signTransaction( - fakeAccounts[0], - fakeTypeTwoTx, - ); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); - expect(returnedTx.toJSON()).toStrictEqual(signedFakeTypeTwoTx.toJSON()); + await expect( + keyring.signTransaction(fakeAccounts[0], fakeTx), + ).rejects.toThrow('some error'); }); - }); - - it('throws default error in signTransaction when unlockAccountByAddress returns undefined hdPath', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring, 'unlockAccountByAddress') - .mockResolvedValue(undefined); - - await expect( - keyring.signTransaction(fakeAccounts[0], fakeTx), - ).rejects.toThrow('Ledger: Unknown error while signing transaction'); - }); - - it('throws different error to the default one if the bridge error is an instance of the Error object', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring.bridge, 'deviceSignTransaction') - .mockRejectedValue(new Error('some error')); - - await expect( - keyring.signTransaction(fakeAccounts[0], fakeTx), - ).rejects.toThrow('some error'); - }); - it('throws the default error if the bridge error is not an instance of the Error object', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring.bridge, 'deviceSignTransaction') - .mockRejectedValue('some error'); + it('throws the default error if the bridge error is not an instance of the Error object', async function () { + await basicSetupToUnlockOneAccount(); + jest + .spyOn(keyring.bridge, 'deviceSignTransaction') + .mockRejectedValue('some error'); - await expect( - keyring.signTransaction(fakeAccounts[0], fakeTx), - ).rejects.toThrow('Ledger: Unknown error while signing transaction'); - }); + await expect( + keyring.signTransaction(fakeAccounts[0], fakeTx), + ).rejects.toThrow('Ledger: Unknown error while signing transaction'); + }); - it('throws an error if the signature is invalid', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring.bridge, 'deviceSignTransaction') - .mockResolvedValue({ v: '0x1', r: '0x0', s: '0x0' }); + it('throws an error if the signature is invalid', async function () { + await basicSetupToUnlockOneAccount(); + jest + .spyOn(keyring.bridge, 'deviceSignTransaction') + .mockResolvedValue({ v: '0x1', r: '0x0', s: '0x0' }); - jest.spyOn(fakeTx, 'verifySignature').mockReturnValue(false); + jest.spyOn(fakeTx, 'verifySignature').mockReturnValue(false); - await expect( - keyring.signTransaction(fakeAccounts[0], fakeTx), - ).rejects.toThrow('Ledger: The transaction signature is not valid'); + await expect( + keyring.signTransaction(fakeAccounts[0], fakeTx), + ).rejects.toThrow('Ledger: The transaction signature is not valid'); + }); }); - }); - describe('signPersonalMessage', function () { - it('should call create a listener waiting for the iframe response', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring.bridge, 'deviceSignMessage') - .mockImplementation(async (params) => { - expect(params).toStrictEqual({ - hdPath: "m/44'/60'/0'/0", - message: 'some message', + describe('signPersonalMessage', function () { + it('calls create a listener waiting for the iframe response', async function () { + await basicSetupToUnlockOneAccount(); + jest + .spyOn(keyring.bridge, 'deviceSignMessage') + .mockImplementation(async (params) => { + expect(params).toStrictEqual({ + hdPath: "m/44'/60'/0'/0", + message: 'some message', + }); + return { v: 1, r: '0x0', s: '0x0' }; }); - return { v: 1, r: '0x0', s: '0x0' }; - }); - jest - .spyOn(sigUtil, 'recoverPersonalSignature') - .mockReturnValue(fakeAccounts[0]); + jest + .spyOn(sigUtil, 'recoverPersonalSignature') + .mockReturnValue(fakeAccounts[0]); - await keyring.signPersonalMessage(fakeAccounts[0], 'some message'); + await keyring.signPersonalMessage(fakeAccounts[0], 'some message'); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(keyring.bridge.deviceSignMessage).toHaveBeenCalled(); - }); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(keyring.bridge.deviceSignMessage).toHaveBeenCalled(); + }); - it('throws the default error in personal sign if the unlockAccountByAddress method returns an undefined hdPath', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring, 'unlockAccountByAddress') - .mockResolvedValue(undefined); + it('throws the default error in personal sign if the unlockAccountByAddress method returns an undefined hdPath', async function () { + await basicSetupToUnlockOneAccount(); + jest + .spyOn(keyring, 'unlockAccountByAddress') + .mockResolvedValue(undefined); - await expect( - keyring.signPersonalMessage(fakeAccounts[0], 'fakeTx'), - ).rejects.toThrow('Ledger: Unknown error while signing message'); - }); + await expect( + keyring.signPersonalMessage(fakeAccounts[0], 'fakeTx'), + ).rejects.toThrow('Ledger: Unknown error while signing message'); + }); - it('throws an error in personal sign if the deviceSignTransaction rejects with an error', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring.bridge, 'deviceSignMessage') - .mockRejectedValue(new Error('some error')); + it('throws an error in personal sign if the deviceSignTransaction rejects with an error', async function () { + await basicSetupToUnlockOneAccount(); + jest + .spyOn(keyring.bridge, 'deviceSignMessage') + .mockRejectedValue(new Error('some error')); - await expect( - keyring.signPersonalMessage(fakeAccounts[0], 'fakeTx'), - ).rejects.toThrow('some error'); - }); + await expect( + keyring.signPersonalMessage(fakeAccounts[0], 'fakeTx'), + ).rejects.toThrow('some error'); + }); - it('throws default error in personal sign when deviceSignTransaction rejects with an error that is not an instance of Error object', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring.bridge, 'deviceSignMessage') - .mockRejectedValue('some error'); + it('throws default error in personal sign when deviceSignTransaction rejects with an error that is not an instance of Error object', async function () { + await basicSetupToUnlockOneAccount(); + jest + .spyOn(keyring.bridge, 'deviceSignMessage') + .mockRejectedValue('some error'); - await expect( - keyring.signPersonalMessage(fakeAccounts[0], 'fakeTx'), - ).rejects.toThrow('Ledger: Unknown error while signing message'); - }); + await expect( + keyring.signPersonalMessage(fakeAccounts[0], 'fakeTx'), + ).rejects.toThrow('Ledger: Unknown error while signing message'); + }); - it('throws an error if the signature does not match the address', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring.bridge, 'deviceSignMessage') - .mockResolvedValue({ v: 1, r: '0x0', s: '0x0' }); + it('throws an error if the signature does not match the address', async function () { + await basicSetupToUnlockOneAccount(); + jest + .spyOn(keyring.bridge, 'deviceSignMessage') + .mockResolvedValue({ v: 1, r: '0x0', s: '0x0' }); - jest.spyOn(sigUtil, 'recoverPersonalSignature').mockReturnValue('0x0'); + jest.spyOn(sigUtil, 'recoverPersonalSignature').mockReturnValue('0x0'); - await expect( - keyring.signPersonalMessage(fakeAccounts[0], 'some message'), - ).rejects.toThrow('Ledger: The signature doesnt match the right address'); + await expect( + keyring.signPersonalMessage(fakeAccounts[0], 'some message'), + ).rejects.toThrow( + 'Ledger: The signature doesnt match the right address', + ); + }); }); - }); - describe('signMessage', function () { - it('should call create a listener waiting for the iframe response', async function () { - await basicSetupToUnlockOneAccount(); - jest - .spyOn(keyring.bridge, 'deviceSignMessage') - .mockImplementation(async (params) => { - expect(params).toStrictEqual({ - hdPath: "m/44'/60'/0'/0", - message: 'some message', + describe('signMessage', function () { + it('calls create a listener waiting for the iframe response', async function () { + await basicSetupToUnlockOneAccount(); + jest + .spyOn(keyring.bridge, 'deviceSignMessage') + .mockImplementation(async (params) => { + expect(params).toStrictEqual({ + hdPath: "m/44'/60'/0'/0", + message: 'some message', + }); + return { v: 1, r: '0x0', s: '0x0' }; }); - return { v: 1, r: '0x0', s: '0x0' }; - }); - jest - .spyOn(sigUtil, 'recoverPersonalSignature') - .mockReturnValue(fakeAccounts[0]); + jest + .spyOn(sigUtil, 'recoverPersonalSignature') + .mockReturnValue(fakeAccounts[0]); - await keyring.signMessage(fakeAccounts[0], 'some message'); + await keyring.signMessage(fakeAccounts[0], 'some message'); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(keyring.bridge.deviceSignMessage).toHaveBeenCalled(); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(keyring.bridge.deviceSignMessage).toHaveBeenCalled(); + }); }); - }); - describe('unlockAccountByAddress', function () { - it('should unlock the given account if found on device', async function () { - await basicSetupToUnlockOneAccount(); - await keyring.unlockAccountByAddress(fakeAccounts[0]).then((hdPath) => { - expect(hdPath).toBe("m/44'/60'/0'/0"); + describe('unlockAccountByAddress', function () { + it('unlocks the given account if found on device', async function () { + await basicSetupToUnlockOneAccount(); + await keyring.unlockAccountByAddress(fakeAccounts[0]).then((hdPath) => { + expect(hdPath).toBe("m/44'/60'/0'/0"); + }); }); - }); - it('should reject if the account is not found on device', async function () { - const requestedAccount = fakeAccounts[0]; - const incorrectAccount = fakeAccounts[1]; - keyring.setAccountToUnlock(0); - await keyring.addAccounts(); - jest.spyOn(keyring, 'unlock').mockResolvedValue(incorrectAccount); + it('rejects if the account is not found on device', async function () { + const requestedAccount = fakeAccounts[0]; + const incorrectAccount = fakeAccounts[1]; + keyring.setAccountToUnlock(0); + await keyring.addAccounts(); + jest.spyOn(keyring, 'unlock').mockResolvedValue(incorrectAccount); - await expect( - keyring.unlockAccountByAddress(requestedAccount), - ).rejects.toThrow( - `Ledger: Account ${fakeAccounts[0]} does not belong to the connected device`, - ); - }); + await expect( + keyring.unlockAccountByAddress(requestedAccount), + ).rejects.toThrow( + `Ledger: Account ${fakeAccounts[0]} does not belong to the connected device`, + ); + }); - it('throws an error if the account detail is not found', async function () { - await basicSetupToUnlockOneAccount(); - keyring.accountDetails = {}; - await expect( - keyring.unlockAccountByAddress(fakeAccounts[0]), - ).rejects.toThrow( - `Ledger: Account for address '${fakeAccounts[0]}' not found`, - ); + it('throws an error if the account detail is not found', async function () { + await basicSetupToUnlockOneAccount(); + keyring.accountDetails = {}; + await expect( + keyring.unlockAccountByAddress(fakeAccounts[0]), + ).rejects.toThrow( + `Ledger: Account for address '${fakeAccounts[0]}' not found`, + ); + }); }); - }); - describe('signTypedData', function () { - // This data matches demo data is MetaMask's test dapp - const fixtureData = { - domain: { - chainId: 1, - name: 'Ether Mail', - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - version: '1', - }, - message: { - contents: 'Hello, Bob!', - from: { - name: 'Cow', - wallets: [ - '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF', - ], + describe('signTypedData', function () { + // This data matches demo data is MetaMask's test dapp + const fixtureData = { + domain: { + chainId: 1, + name: 'Ether Mail', + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + version: '1', }, - to: [ - { - name: 'Bob', + message: { + contents: 'Hello, Bob!', + from: { + name: 'Cow', wallets: [ - '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57', - '0xB0B0b0b0b0b0B000000000000000000000000000', + '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF', ], }, - ], - }, - primaryType: 'Mail' as const, - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Group: [ - { name: 'name', type: 'string' }, - { name: 'members', type: 'Person[]' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person[]' }, - { name: 'contents', type: 'string' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallets', type: 'address[]' }, - ], - }, - }; - const options = { version: 'V4' }; - - beforeEach(async function () { - jest - .spyOn(keyring, 'unlockAccountByAddress') - .mockResolvedValue(`m/44'/60'/15'`); - await basicSetupToUnlockOneAccount(15); - }); + to: [ + { + name: 'Bob', + wallets: [ + '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57', + '0xB0B0b0b0b0b0B000000000000000000000000000', + ], + }, + ], + }, + primaryType: 'Mail' as const, + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Group: [ + { name: 'name', type: 'string' }, + { name: 'members', type: 'Person[]' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person[]' }, + { name: 'contents', type: 'string' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallets', type: 'address[]' }, + ], + }, + }; + const options = { version: 'V4' }; - it('should resolve properly when called', async function () { - jest - .spyOn(keyring.bridge, 'deviceSignTypedData') - .mockImplementation(async () => ({ - v: 27, - r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', - s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', - })); - - const result = await keyring.signTypedData( - fakeAccounts[15], - fixtureData, - options, - ); - expect(result).toBe( - '0x72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b946759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e321b', - ); - }); + beforeEach(async function () { + jest + .spyOn(keyring, 'unlockAccountByAddress') + .mockResolvedValue(`m/44'/60'/15'`); + await basicSetupToUnlockOneAccount(15); + }); - it('should error when address does not match', async function () { - jest - .spyOn(keyring.bridge, 'deviceSignTypedData') - // Changing v to 28 should cause a validation error - .mockImplementation(async () => ({ - v: 28, - r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', - s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', - })); + it('resolves properly when called', async function () { + jest + .spyOn(keyring.bridge, 'deviceSignTypedData') + .mockImplementation(async () => ({ + v: 27, + r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', + s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', + })); + + const result = await keyring.signTypedData( + fakeAccounts[15], + fixtureData, + options, + ); + expect(result).toBe( + '0x72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b946759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e321b', + ); + }); - await expect( - keyring.signTypedData(fakeAccounts[15], fixtureData, options), - ).rejects.toThrow('Ledger: The signature doesnt match the right address'); - }); + it('throws error when address does not match', async function () { + jest + .spyOn(keyring.bridge, 'deviceSignTypedData') + // Changing v to 28 should cause a validation error + .mockImplementation(async () => ({ + v: 28, + r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', + s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', + })); + + await expect( + keyring.signTypedData(fakeAccounts[15], fixtureData, options), + ).rejects.toThrow( + 'Ledger: The signature doesnt match the right address', + ); + }); - it('throws an error if the signTypedData version is not v4', async function () { - await expect( - keyring.signTypedData(fakeAccounts[0], fixtureData, { version: 'V3' }), - ).rejects.toThrow( - 'Ledger: Only version 4 of typed data signing is supported', - ); - }); + it('throws an error if the signTypedData version is not v4', async function () { + await expect( + keyring.signTypedData(fakeAccounts[0], fixtureData, { + version: 'V3', + }), + ).rejects.toThrow( + 'Ledger: Only version 4 of typed data signing is supported', + ); + }); - it('throws an error if the version is not provided', async function () { - await expect( - keyring.signTypedData(fakeAccounts[0], fixtureData), - ).rejects.toThrow( - 'Ledger: Only version 4 of typed data signing is supported', - ); - }); + it('throws an error if the version is not provided', async function () { + await expect( + keyring.signTypedData(fakeAccounts[0], fixtureData), + ).rejects.toThrow( + 'Ledger: Only version 4 of typed data signing is supported', + ); + }); - it('throws an error if the hdPath is not found', async function () { - jest - .spyOn(keyring, 'unlockAccountByAddress') - .mockResolvedValue(undefined); - await expect( - keyring.signTypedData(fakeAccounts[0], fixtureData, options), - ).rejects.toThrow('Ledger: Unknown error while signing message'); - }); + it('throws an error if the hdPath is not found', async function () { + jest + .spyOn(keyring, 'unlockAccountByAddress') + .mockResolvedValue(undefined); + await expect( + keyring.signTypedData(fakeAccounts[0], fixtureData, options), + ).rejects.toThrow('Ledger: Unknown error while signing message'); + }); - it('throws an error when deviceSignTypedData rejects with an error', async function () { - jest - .spyOn(keyring.bridge, 'deviceSignTypedData') - .mockRejectedValue(new Error('some error')); + it('throws an error when deviceSignTypedData rejects with an error', async function () { + jest + .spyOn(keyring.bridge, 'deviceSignTypedData') + .mockRejectedValue(new Error('some error')); - await expect( - keyring.signTypedData(fakeAccounts[15], fixtureData, options), - ).rejects.toThrow('some error'); - }); + await expect( + keyring.signTypedData(fakeAccounts[15], fixtureData, options), + ).rejects.toThrow('some error'); + }); - it('throws default error when deviceSignTypedData reject with an error that is not an instance of Error object', async function () { - jest - .spyOn(keyring.bridge, 'deviceSignTypedData') - .mockRejectedValue('some error'); + it('throws default error when deviceSignTypedData reject with an error that is not an instance of Error object', async function () { + jest + .spyOn(keyring.bridge, 'deviceSignTypedData') + .mockRejectedValue('some error'); - await expect( - keyring.signTypedData(fakeAccounts[15], fixtureData, options), - ).rejects.toThrow('Ledger: Unknown error while signing message'); - }); + await expect( + keyring.signTypedData(fakeAccounts[15], fixtureData, options), + ).rejects.toThrow('Ledger: Unknown error while signing message'); + }); - it('returns signature when recoveryId length < 2', async function () { - jest.spyOn(keyring.bridge, 'deviceSignTypedData').mockResolvedValue({ - v: 0, - r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', - s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', - }); - const result = await keyring.signTypedData( - fakeAccounts[15], - fixtureData, - options, - ); - expect(result).toBe( - '0x72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b946759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e3200', - ); + it('returns signature when recoveryId length < 2', async function () { + jest.spyOn(keyring.bridge, 'deviceSignTypedData').mockResolvedValue({ + v: 0, + r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', + s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', + }); + const result = await keyring.signTypedData( + fakeAccounts[15], + fixtureData, + options, + ); + expect(result).toBe( + '0x72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b946759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e3200', + ); + }); }); - }); - describe('destroy', function () { - it('should call the destroy bridge method', async function () { - jest.spyOn(keyring.bridge, 'destroy').mockResolvedValue(undefined); + describe('destroy', function () { + it('calls the destroy bridge method', async function () { + jest.spyOn(keyring.bridge, 'destroy').mockResolvedValue(undefined); - await keyring.destroy(); + await keyring.destroy(); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.destroy).toHaveBeenCalled(); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(bridge.destroy).toHaveBeenCalled(); + }); }); }); });