From 15ff78306107503b542e32b8f3f205554c1fa285 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:43:22 -0400 Subject: [PATCH 1/5] adds solana addresses fetching for bip44change --- src/__test__/e2e/api.test.ts | 11 +++++++++++ src/api/addresses.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/__test__/e2e/api.test.ts b/src/__test__/e2e/api.test.ts index 0078c5dc..0050385f 100644 --- a/src/__test__/e2e/api.test.ts +++ b/src/__test__/e2e/api.test.ts @@ -7,8 +7,10 @@ import { fetchActiveWallets, fetchAddress, fetchAddresses, + fetchBip44ChangeAddresses, fetchBtcLegacyAddresses, fetchBtcSegwitAddresses, + fetchSolanaAddresses, pair, signBtcLegacyTx, signBtcSegwitTx, @@ -184,6 +186,15 @@ describe('API', () => { const addresses = await fetchLedgerLiveAddresses(); expect(addresses).toHaveLength(10); }); + test('fetchSolanaAddresses', async () => { + const addresses = await fetchSolanaAddresses(); + expect(addresses).toHaveLength(10); + }); + + test('fetchBip44ChangeAddresses', async () => { + const addresses = await fetchBip44ChangeAddresses(); + expect(addresses).toHaveLength(10); + }); }); describe('fetchAddress', () => { diff --git a/src/api/addresses.ts b/src/api/addresses.ts index 0ff974b7..2792ee50 100644 --- a/src/api/addresses.ts +++ b/src/api/addresses.ts @@ -159,3 +159,32 @@ export const fetchLedgerLegacyAddresses = async ( } return Promise.all(addresses); }; + +export const fetchBip44ChangeAddresses = async ( + { n, startPathIndex }: FetchAddressesParams = { + n: MAX_ADDR, + startPathIndex: 0, + }, +) => { + const addresses = []; + for (let i = 0; i < n; i++) { + addresses.push( + queue((client) => { + const startPath = [ + 44 + HARDENED_OFFSET, + 501 + HARDENED_OFFSET, + startPathIndex + i + HARDENED_OFFSET, + 0 + HARDENED_OFFSET, + ]; + return client + .getAddresses({ + startPath, + n: 1, + flag: 4, + }) + .then((addresses) => addresses.map((address) => `${address}`)); + }), + ); + } + return Promise.all(addresses); +}; From 43c426a33125f7e06a832d9c29589fcb350b8ee0 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:44:30 -0400 Subject: [PATCH 2/5] adds fetch derivation address --- src/__test__/e2e/api.test.ts | 84 ++++++++++++++++++++++++++++++++---- src/api/addresses.ts | 54 +++++++++++++++++++++++ 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/src/__test__/e2e/api.test.ts b/src/__test__/e2e/api.test.ts index 0050385f..3f62e2ba 100644 --- a/src/__test__/e2e/api.test.ts +++ b/src/__test__/e2e/api.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable quotes */ import { getClient } from './../../api/utilities'; import { Chain, Common, Hardfork } from '@ethereumjs/common'; import { TransactionFactory } from '@ethereumjs/tx'; @@ -10,6 +11,7 @@ import { fetchBip44ChangeAddresses, fetchBtcLegacyAddresses, fetchBtcSegwitAddresses, + fetchByDerivationPath, fetchSolanaAddresses, pair, signBtcLegacyTx, @@ -40,7 +42,7 @@ describe('API', () => { } }); - describe('signing', () => { + describe.skip('signing', () => { describe('bitcoin', () => { const btcTxData = { prevOuts: [ @@ -138,7 +140,7 @@ describe('API', () => { }); }); - describe('address tags', () => { + describe.skip('address tags', () => { test('addAddressTags', async () => { await addAddressTags([{ test: 'test' }]); }); @@ -157,35 +159,36 @@ describe('API', () => { describe('addresses', () => { describe('fetchAddresses', () => { - test('fetchAddresses', async () => { + test.skip('fetchAddresses', async () => { const addresses = await fetchAddresses(); expect(addresses).toHaveLength(10); }); - test('fetchAddresses[1]', async () => { + test.skip('fetchAddresses[1]', async () => { const addresses = await fetchAddresses({ n: 1 }); expect(addresses).toHaveLength(1); }); - test('fetchAddresses[12]', async () => { + test.skip('fetchAddresses[12]', async () => { const addresses = await fetchAddresses({ n: 12 }); expect(addresses).toHaveLength(12); }); - test('fetchBtcLegacyAddresses', async () => { + test.skip('fetchBtcLegacyAddresses', async () => { const addresses = await fetchBtcLegacyAddresses(); expect(addresses).toHaveLength(10); }); - test('fetchBtcSegwitAddresses[12]', async () => { + test.skip('fetchBtcSegwitAddresses[12]', async () => { const addresses = await fetchBtcSegwitAddresses({ n: 12 }); expect(addresses).toHaveLength(12); }); - test('fetchLedgerLiveAddresses', async () => { + test.skip('fetchLedgerLiveAddresses', async () => { const addresses = await fetchLedgerLiveAddresses(); expect(addresses).toHaveLength(10); }); + test('fetchSolanaAddresses', async () => { const addresses = await fetchSolanaAddresses(); expect(addresses).toHaveLength(10); @@ -197,6 +200,69 @@ describe('API', () => { }); }); + describe('fetchByDerivationPath', () => { + test('fetch single specific address', async () => { + const addresses = await fetchByDerivationPath("44'/60'/0'/0/0"); + expect(addresses).toHaveLength(1); + console.log(addresses[0]); + expect(addresses[0]).toBeTruthy(); + }); + + test('fetch multiple addresses with wildcard', async () => { + const addresses = await fetchByDerivationPath("44'/60'/0'/0/X", { + n: 5, + }); + console.log(addresses[0]); + expect(addresses).toHaveLength(5); + addresses.forEach((address) => expect(address).toBeTruthy()); + }); + + test('fetch addresses with offset', async () => { + const addresses = await fetchByDerivationPath("44'/60'/0'/0/X", { + n: 3, + startPathIndex: 10, + }); + console.log(addresses[0]); + expect(addresses).toHaveLength(3); + addresses.forEach((address) => expect(address).toBeTruthy()); + }); + + test('fetch addresses with lowercase x wildcard', async () => { + const addresses = await fetchByDerivationPath("44'/60'/0'/0/x", { + n: 2, + }); + expect(addresses).toHaveLength(2); + addresses.forEach((address) => expect(address).toBeTruthy()); + }); + + test('fetch addresses with wildcard in middle of path', async () => { + const addresses = await fetchByDerivationPath("44'/60'/X'/0/0", { + n: 3, + }); + expect(addresses).toHaveLength(3); + addresses.forEach((address) => expect(address).toBeTruthy()); + }); + + test('error on invalid derivation path', async () => { + await expect(fetchByDerivationPath('invalid/path')).rejects.toThrow(); + }); + + test('fetch single address when n=1 with wildcard', async () => { + const addresses = await fetchByDerivationPath("44'/60'/0'/0/X", { + n: 1, + }); + expect(addresses).toHaveLength(1); + expect(addresses[0]).toBeTruthy(); + }); + + test('fetch no addresses when n=0', async () => { + const addresses = await fetchByDerivationPath("44'/60'/0'/0/X", { + n: 0, + }); + expect(addresses).toHaveLength(0); + }); + }); + describe('fetchAddress', () => { test('fetchAddress', async () => { const address = await fetchAddress(); @@ -205,7 +271,7 @@ describe('API', () => { }); }); - describe('fetchActiveWallets', () => { + describe.skip('fetchActiveWallets', () => { test('fetchActiveWallets', async () => { const wallet = await fetchActiveWallets(); expect(wallet).toBeTruthy(); diff --git a/src/api/addresses.ts b/src/api/addresses.ts index 2792ee50..d45df3ec 100644 --- a/src/api/addresses.ts +++ b/src/api/addresses.ts @@ -3,6 +3,7 @@ import { BTC_SEGWIT_DERIVATION, BTC_WRAPPED_SEGWIT_DERIVATION, DEFAULT_ETH_DERIVATION, + HARDENED_OFFSET, LEDGER_LEGACY_DERIVATION, LEDGER_LIVE_DERIVATION, MAX_ADDR, @@ -105,6 +106,7 @@ export const fetchSolanaAddresses = async ( return fetchAddresses({ startPath: getStartPath(SOLANA_DERIVATION, startPathIndex, 2), n, + flag: 4, }); }; @@ -188,3 +190,55 @@ export const fetchBip44ChangeAddresses = async ( } return Promise.all(addresses); }; + +// type FetchAddressesParams = { +// n?: number; +// startPathIndex?: number; +// }; + +function parseDerivationPath(path: string): number[] { + return path.split('/').map((part) => { + if (part.endsWith("'")) { + return parseInt(part.slice(0, -1)) + 0x80000000; + } + return part.toLowerCase() === 'x' ? 0 : parseInt(part); + }); +} + +export async function fetchByDerivationPath( + path: string, + { n = 1, startPathIndex = 0 }: FetchAddressesParams = {}, +): Promise { + const parsedPath = parseDerivationPath(path); + const hasWildcard = path.toLowerCase().includes('x'); + + if (!hasWildcard) { + return queue((client) => + client.getAddresses({ + startPath: parsedPath, + n: 1, + }), + ); + } + + const wildcardIndex = parsedPath.findIndex((part) => part === 0); + const basePath = parsedPath.slice(0, wildcardIndex); + + const addresses: string[] = []; + for (let i = 0; i < n; i++) { + const currentPath = [ + ...basePath, + startPathIndex + i, + ...parsedPath.slice(wildcardIndex + 1), + ]; + const result = await queue((client) => + client.getAddresses({ + startPath: currentPath, + n: 1, + }), + ); + addresses.push(...result); + } + + return addresses; +} From d1fe2f220a2ae3e9059625124580604fae4e90f4 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:21:05 -0400 Subject: [PATCH 3/5] update solana offset --- src/constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/constants.ts b/src/constants.ts index 9a07248a..21316129 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -544,6 +544,7 @@ export const SOLANA_DERIVATION = [ HARDENED_OFFSET + 44, HARDENED_OFFSET + 501, HARDENED_OFFSET, + HARDENED_OFFSET, ]; /** @internal */ From f1b678d4d85ea02334baca537d12c199cffa85b2 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:22:11 -0400 Subject: [PATCH 4/5] remove test skip --- src/__test__/e2e/api.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/__test__/e2e/api.test.ts b/src/__test__/e2e/api.test.ts index 3f62e2ba..140cf98e 100644 --- a/src/__test__/e2e/api.test.ts +++ b/src/__test__/e2e/api.test.ts @@ -42,7 +42,7 @@ describe('API', () => { } }); - describe.skip('signing', () => { + describe('signing', () => { describe('bitcoin', () => { const btcTxData = { prevOuts: [ @@ -140,7 +140,7 @@ describe('API', () => { }); }); - describe.skip('address tags', () => { + describe('address tags', () => { test('addAddressTags', async () => { await addAddressTags([{ test: 'test' }]); }); @@ -159,32 +159,32 @@ describe('API', () => { describe('addresses', () => { describe('fetchAddresses', () => { - test.skip('fetchAddresses', async () => { + test('fetchAddresses', async () => { const addresses = await fetchAddresses(); expect(addresses).toHaveLength(10); }); - test.skip('fetchAddresses[1]', async () => { + test('fetchAddresses[1]', async () => { const addresses = await fetchAddresses({ n: 1 }); expect(addresses).toHaveLength(1); }); - test.skip('fetchAddresses[12]', async () => { + test('fetchAddresses[12]', async () => { const addresses = await fetchAddresses({ n: 12 }); expect(addresses).toHaveLength(12); }); - test.skip('fetchBtcLegacyAddresses', async () => { + test('fetchBtcLegacyAddresses', async () => { const addresses = await fetchBtcLegacyAddresses(); expect(addresses).toHaveLength(10); }); - test.skip('fetchBtcSegwitAddresses[12]', async () => { + test('fetchBtcSegwitAddresses[12]', async () => { const addresses = await fetchBtcSegwitAddresses({ n: 12 }); expect(addresses).toHaveLength(12); }); - test.skip('fetchLedgerLiveAddresses', async () => { + test('fetchLedgerLiveAddresses', async () => { const addresses = await fetchLedgerLiveAddresses(); expect(addresses).toHaveLength(10); }); @@ -271,7 +271,7 @@ describe('API', () => { }); }); - describe.skip('fetchActiveWallets', () => { + describe('fetchActiveWallets', () => { test('fetchActiveWallets', async () => { const wallet = await fetchActiveWallets(); expect(wallet).toBeTruthy(); From b783c6d4b0c1f904af2b34e7d884b7a8e1a10754 Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:24:16 -0400 Subject: [PATCH 5/5] fix name of new func --- src/api/addresses.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/api/addresses.ts b/src/api/addresses.ts index d45df3ec..2224890b 100644 --- a/src/api/addresses.ts +++ b/src/api/addresses.ts @@ -191,13 +191,9 @@ export const fetchBip44ChangeAddresses = async ( return Promise.all(addresses); }; -// type FetchAddressesParams = { -// n?: number; -// startPathIndex?: number; -// }; - function parseDerivationPath(path: string): number[] { return path.split('/').map((part) => { + // eslint-disable-next-line quotes if (part.endsWith("'")) { return parseInt(part.slice(0, -1)) + 0x80000000; } @@ -205,7 +201,7 @@ function parseDerivationPath(path: string): number[] { }); } -export async function fetchByDerivationPath( +export async function fetchAddressesByDerivationPath( path: string, { n = 1, startPathIndex = 0 }: FetchAddressesParams = {}, ): Promise {