Skip to content

Commit

Permalink
Merge pull request #559 from GridPlus/nb/test-derivation-fetch
Browse files Browse the repository at this point in the history
improves solana address fetching
  • Loading branch information
netbonus authored Jul 1, 2024
2 parents 68a8242 + b783c6d commit 56b40b9
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 0 deletions.
77 changes: 77 additions & 0 deletions src/__test__/e2e/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable quotes */
import { getClient } from './../../api/utilities';
import { Chain, Common, Hardfork } from '@ethereumjs/common';
import { TransactionFactory } from '@ethereumjs/tx';
Expand All @@ -7,8 +8,11 @@ import {
fetchActiveWallets,
fetchAddress,
fetchAddresses,
fetchBip44ChangeAddresses,
fetchBtcLegacyAddresses,
fetchBtcSegwitAddresses,
fetchByDerivationPath,
fetchSolanaAddresses,
pair,
signBtcLegacyTx,
signBtcSegwitTx,
Expand Down Expand Up @@ -184,6 +188,79 @@ 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('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', () => {
Expand Down
79 changes: 79 additions & 0 deletions src/api/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -105,6 +106,7 @@ export const fetchSolanaAddresses = async (
return fetchAddresses({
startPath: getStartPath(SOLANA_DERIVATION, startPathIndex, 2),
n,
flag: 4,
});
};

Expand Down Expand Up @@ -159,3 +161,80 @@ 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);
};

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;
}
return part.toLowerCase() === 'x' ? 0 : parseInt(part);
});
}

export async function fetchAddressesByDerivationPath(
path: string,
{ n = 1, startPathIndex = 0 }: FetchAddressesParams = {},
): Promise<string[]> {
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;
}
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ export const SOLANA_DERIVATION = [
HARDENED_OFFSET + 44,
HARDENED_OFFSET + 501,
HARDENED_OFFSET,
HARDENED_OFFSET,
];

/** @internal */
Expand Down

0 comments on commit 56b40b9

Please sign in to comment.