From f40c23417dd08a792caac223724e5d82db210781 Mon Sep 17 00:00:00 2001 From: Harvey Zuccon Date: Sat, 26 Jul 2025 22:46:10 +0000 Subject: [PATCH] Resolve "WalletHD Genesis: Refactor to use Mnemonic Primitive" --- packages/stack/src/storage/activities.ts | 6 +--- packages/stack/src/utils/index.ts | 1 + packages/stack/src/utils/type-utils.ts | 11 +++++++ packages/stack/src/wallet/wallet-hd.ts | 40 +++++++++++------------ packages/stack/src/wallet/wallet-p2pkh.ts | 13 ++++---- 5 files changed, 39 insertions(+), 32 deletions(-) create mode 100644 packages/stack/src/utils/type-utils.ts diff --git a/packages/stack/src/storage/activities.ts b/packages/stack/src/storage/activities.ts index 4f6fb72..b9178b2 100644 --- a/packages/stack/src/storage/activities.ts +++ b/packages/stack/src/storage/activities.ts @@ -1,11 +1,7 @@ import { BaseStore } from './base-store.js'; import { StoreComposite } from './store-composite.js'; -import { EventEmitter, UUIDV7 } from '../utils/index.js'; - -// Make the given property in a type optional. -// TODO: This is an extremely useful utility. If we use it elsewhere, move to own "type-utils.ts" file. -type Optional = Omit & Partial>; +import { type Optional, EventEmitter, UUIDV7 } from '../utils/index.js'; // Base Activity interface export interface Activity { diff --git a/packages/stack/src/utils/index.ts b/packages/stack/src/utils/index.ts index 8a554a3..e506e78 100644 --- a/packages/stack/src/utils/index.ts +++ b/packages/stack/src/utils/index.ts @@ -6,4 +6,5 @@ export * from './memoizer.js'; export * from './misc.js'; export * from './sse-session.js'; export * from './sse-session-msgpackr.js'; +export * from './type-utils.js'; export * from './uuid.js'; diff --git a/packages/stack/src/utils/type-utils.ts b/packages/stack/src/utils/type-utils.ts new file mode 100644 index 0000000..95c5720 --- /dev/null +++ b/packages/stack/src/utils/type-utils.ts @@ -0,0 +1,11 @@ +/** + * Utility for removing keys that works with distributive unions. + */ +export type DistributiveOmit = T extends any + ? Omit + : never; + +/** + * Make the given property in a type optional. + */ +export type Optional = Omit & Partial>; diff --git a/packages/stack/src/wallet/wallet-hd.ts b/packages/stack/src/wallet/wallet-hd.ts index 9a99f23..35c5d16 100644 --- a/packages/stack/src/wallet/wallet-hd.ts +++ b/packages/stack/src/wallet/wallet-hd.ts @@ -5,6 +5,7 @@ import { type WalletBlockchain, type WalletDependencies } from './types.js'; import { WalletP2PKH } from './wallet-p2pkh.js'; import { type BaseStore, Activities, StoreInMemory } from '../storage/index.js'; +import { type DistributiveOmit } from '../utils/index.js'; import { type TokenBalances, @@ -23,15 +24,20 @@ export const ADDRESS_GAP = 20; export const CHAIN_EXTERNAL = 0; export const CHAIN_INTERNAL = 1; -export type WalletHDGenesisData = { - type: 'WalletHD'; +export type WalletHDEntropy = + | { seed: string } + | { xpriv: string } + | { mnemonic: Mnemonic }; + +export type WalletHDDerivationData = { derivationPath: string; addressGap?: number; -} & ( - | { seed: string } - | { mnemonic: string; passphrase?: string } - | { xpriv: string } -); +}; + +export type WalletHDGenesisData = { + type: 'WalletHD'; +} & WalletHDEntropy & + WalletHDDerivationData; /** * A Hierarhical Determinstic (BIP44) Wallet. @@ -61,7 +67,7 @@ export class WalletHD extends BaseWallet { static async from( this: T, deps: WalletDependencies, - genesisData: WalletHDGenesisData, + genesisData: DistributiveOmit, ) { // Assign defaults if not otherwise provideed. const activities = deps.activities @@ -99,7 +105,7 @@ export class WalletHD extends BaseWallet { constructor( dependencies: Required, - genesisData: WalletHDGenesisData, + genesisData: DistributiveOmit, ) { super(); @@ -109,22 +115,17 @@ export class WalletHD extends BaseWallet { this.cache = dependencies.cache; // Assign our state. - this.genesisData = genesisData; + this.genesisData = { + type: 'WalletHD', + ...genesisData, + }; // Instantiate the HDPrivateNode from the type specified in our genesis data. if ('seed' in genesisData) { this.hdPrivateNode = HDPrivateNode.fromSeed(genesisData.seed); } else if ('mnemonic' in genesisData) { - // TODO: We really want to support multiple word-lists somehow. - // But this is not trivial because other word-lists are technically not standard. - // For now, we are allowing any phrase (BIP39 derives seed from literal phrase, not word indexes). - const mnemonic = Mnemonic.fromRaw({ - words: genesisData.mnemonic.split(' '), - passphrase: genesisData.passphrase, - }); - // Create the HD Node from our Mnemonic instance. - this.hdPrivateNode = HDPrivateNode.fromMnemonic(mnemonic); + this.hdPrivateNode = HDPrivateNode.fromMnemonic(genesisData.mnemonic); } else { this.hdPrivateNode = HDPrivateNode.fromXPriv(genesisData.xpriv); } @@ -364,7 +365,6 @@ export class WalletHD extends BaseWallet { cache: this.cache, }, { - type: 'WalletP2PKH', privateKey: privateKeyHex, }, ); diff --git a/packages/stack/src/wallet/wallet-p2pkh.ts b/packages/stack/src/wallet/wallet-p2pkh.ts index b84b6f0..7414c65 100644 --- a/packages/stack/src/wallet/wallet-p2pkh.ts +++ b/packages/stack/src/wallet/wallet-p2pkh.ts @@ -45,7 +45,7 @@ export class WalletP2PKH extends BaseWallet { static async from( this: T, deps: WalletDependencies, - genesisData: WalletP2PKHGenesisData, + genesisData: Omit, ) { // Assign defaults if not otherwise provideed. const activities = deps.activities @@ -77,7 +77,6 @@ export class WalletP2PKH extends BaseWallet { // Return a new instance of Wallet P2PKH. return this.from(services, { - type: 'WalletP2PKH', privateKey: privateKey.toHex(), }); } @@ -92,7 +91,6 @@ export class WalletP2PKH extends BaseWallet { // Return a new instance of Wallet P2PKH. return this.from(services, { - type: 'WalletP2PKH', privateKey: privateKey.toHex(), }); } @@ -107,7 +105,6 @@ export class WalletP2PKH extends BaseWallet { // Return a new instance of Wallet P2PKH. return this.from(services, { - type: 'WalletP2PKH', privateKey: privateKey.toHex(), }); } @@ -121,7 +118,6 @@ export class WalletP2PKH extends BaseWallet { // Return a new instance of Wallet P2PKH. return this.from(services, { - type: 'WalletP2PKH', privateKey: privateKey.toHex(), }); } @@ -143,7 +139,7 @@ export class WalletP2PKH extends BaseWallet { constructor( dependencies: Required, - genesisData: WalletP2PKHGenesisData, + genesisData: Omit, ) { super(); @@ -153,7 +149,10 @@ export class WalletP2PKH extends BaseWallet { this.cache = dependencies.cache; // Assign our Genesis Data. - this.genesisData = genesisData; + this.genesisData = { + type: 'WalletP2PKH', + ...genesisData, + }; // Optimizations/Simplifications. this.privateKey = PrivateKey.fromHex(genesisData.privateKey);