From 02dd5a13518a2d53da8fbe0d0ee837132d50381e Mon Sep 17 00:00:00 2001 From: netbonus <151201453+netbonus@users.noreply.github.com> Date: Wed, 9 Oct 2024 09:14:28 -0400 Subject: [PATCH] v2.7.1 (#575) * bumps dependencies * update integration test * bump node in test * revert eip712 `v` change * bumps node version to 18 * update getMessageToSign to latest ethers version * adds iteration index and test * update iteration * remove eslint * remove unused imports * updates snapshot * bumps upload artifacts * fix parse derivation path (#568) * fix parse derivation path * fix eslint errors with hardened values in strings * exports parseDerivationPath * bumps to v2.7.0 (#570) * remove extra test files * fixes issue with fetching addrs by derivation path (#572) * fixes issue with fetching addrs by derivation path * fix unit tests * bump to 2.7.1 * Merge branch 'main' into dev * bumps dependencies * update integration test * bump node in test * revert eip712 `v` change * bumps node version to 18 * update getMessageToSign to latest ethers version * adds iteration index and test * update iteration * remove eslint * remove unused imports * updates snapshot * bumps upload artifacts * fix parse derivation path (#568) * fix parse derivation path * fix eslint errors with hardened values in strings * exports parseDerivationPath * bumps to v2.7.0 (#570) * remove extra test files --- package.json | 2 +- src/__test__/e2e/api.test.ts | 11 +++++++++ src/api/addresses.ts | 43 ++++++++++++++++++++---------------- src/api/utilities.ts | 38 ++++++++++++++++++++----------- 4 files changed, 61 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 5bd32048..4221bcbe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gridplus-sdk", - "version": "2.7.0", + "version": "2.7.1", "description": "SDK to interact with GridPlus Lattice1 device", "scripts": { "build": "NODE_ENV=production tsc -p tsconfig.json", diff --git a/src/__test__/e2e/api.test.ts b/src/__test__/e2e/api.test.ts index 895f9805..31480925 100644 --- a/src/__test__/e2e/api.test.ts +++ b/src/__test__/e2e/api.test.ts @@ -254,6 +254,17 @@ describe('API', () => { addresses.forEach((address) => expect(address).toBeTruthy()); }); + test('fetch solana addresses with wildcard in middle of path', async () => { + const addresses = await fetchAddressesByDerivationPath( + "44'/501'/X'/0'", + { + n: 1, + }, + ); + expect(addresses).toHaveLength(1); + addresses.forEach((address) => expect(address).toBeTruthy()); + }); + test('error on invalid derivation path', async () => { await expect( fetchAddressesByDerivationPath('invalid/path'), diff --git a/src/api/addresses.ts b/src/api/addresses.ts index d058871e..293b3629 100644 --- a/src/api/addresses.ts +++ b/src/api/addresses.ts @@ -12,7 +12,12 @@ import { MAX_ADDR, SOLANA_DERIVATION, } from '../constants'; -import { getStartPath, parseDerivationPath, queue } from './utilities'; +import { + getStartPath, + parseDerivationPathComponents, + queue, + getFlagFromPath, +} from './utilities'; type FetchAddressesParams = { n?: number; @@ -160,12 +165,10 @@ export const fetchLedgerLegacyAddresses = async ( return Promise.all(addresses); }; -export const fetchBip44ChangeAddresses = async ( - { n, startPathIndex }: FetchAddressesParams = { - n: MAX_ADDR, - startPathIndex: 0, - }, -) => { +export const fetchBip44ChangeAddresses = async ({ + n = MAX_ADDR, + startPathIndex = 0, +}: FetchAddressesParams = {}) => { const addresses = []; for (let i = 0; i < n; i++) { addresses.push( @@ -193,31 +196,33 @@ export async function fetchAddressesByDerivationPath( path: string, { n = 1, startPathIndex = 0 }: FetchAddressesParams = {}, ): Promise { - const parsedPath = parseDerivationPath(path); - const hasWildcard = path.toLowerCase().includes('x'); + const components = path.split('/').filter(Boolean); + const parsedPath = parseDerivationPathComponents(components); + const flag = getFlagFromPath(parsedPath); + const wildcardIndex = components.findIndex((part) => + part.toLowerCase().includes('x'), + ); - if (!hasWildcard) { + if (wildcardIndex === -1) { return queue((client) => client.getAddresses({ startPath: parsedPath, - n: 1, + flag, + n, }), ); } - 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 currentPath = [...parsedPath]; + currentPath[wildcardIndex] = + currentPath[wildcardIndex] + startPathIndex + i; + const result = await queue((client) => client.getAddresses({ startPath: currentPath, + flag, n: 1, }), ); diff --git a/src/api/utilities.ts b/src/api/utilities.ts index d15127a7..606e64dc 100644 --- a/src/api/utilities.ts +++ b/src/api/utilities.ts @@ -5,6 +5,7 @@ import { saveClient, setFunctionQueue, } from './state'; +import { EXTERNAL, HARDENED_OFFSET } from '../constants'; /** * `queue` is a function that wraps all functional API calls. It limits the number of concurrent @@ -91,17 +92,28 @@ export const isEIP712Payload = (payload: any) => export function parseDerivationPath(path: string): number[] { if (!path) return []; - return path - .split('/') - .filter(Boolean) - .map((part) => { - if (part.toLowerCase() === 'x') return 0; - if (part.toLowerCase() === "x'") return 0x80000000; // Hardened zero - if (part.endsWith("'")) return parseInt(part.slice(0, -1)) + 0x80000000; - const val = parseInt(part); - if (isNaN(val)) { - throw new Error(`Invalid part in derivation path: ${part}`); - } - return val; - }); + const components = path.split('/').filter(Boolean); + return parseDerivationPathComponents(components); +} + +export function parseDerivationPathComponents(components: string[]): number[] { + return components.map((part) => { + const lowerPart = part.toLowerCase(); + if (lowerPart === 'x') return 0; // Wildcard + if (lowerPart === "x'") return HARDENED_OFFSET; // Hardened wildcard + if (part.endsWith("'")) + return parseInt(part.slice(0, -1)) + HARDENED_OFFSET; + const val = parseInt(part); + if (isNaN(val)) { + throw new Error(`Invalid part in derivation path: ${part}`); + } + return val; + }); +} + +export function getFlagFromPath(path: number[]): number | undefined { + if (path.length >= 2 && path[1] === 501 + HARDENED_OFFSET) { + return EXTERNAL.GET_ADDR_FLAGS.ED25519_PUB; // SOLANA + } + return undefined; }