@@ -260,6 +260,83 @@ export class SecretsService {
260260 return walletList
261261 }
262262
263+ public async getWalletsByProtocolIdentifier ( protocolIdentifier : ProtocolSymbols , secret ?: MnemonicSecret ) : Promise < AirGapWallet [ ] > {
264+ const wallets = secret ? secret . wallets : this . getWallets ( )
265+ const filtered = await Promise . all (
266+ wallets . map ( async ( wallet ) => {
267+ const identifier = await wallet . protocol . getIdentifier ( )
268+ return identifier === protocolIdentifier && wallet . status === AirGapWalletStatus . ACTIVE ? wallet : undefined
269+ } )
270+ )
271+ return filtered . filter ( ( w ) : w is AirGapWallet => w !== undefined )
272+ }
273+
274+ private isBtcProtocol ( protocolIdentifier : ProtocolSymbols ) : boolean {
275+ return (
276+ protocolIdentifier === MainProtocolSymbols . BTC ||
277+ protocolIdentifier === MainProtocolSymbols . BTC_SEGWIT ||
278+ protocolIdentifier === MainProtocolSymbols . BTC_TAPROOT
279+ )
280+ }
281+
282+ public async getNextDerivationPathForProtocol (
283+ protocol : ICoinProtocol ,
284+ secret : MnemonicSecret
285+ ) : Promise < { derivationPath : string ; isHDWallet : boolean } > {
286+ const protocolIdentifier = await protocol . getIdentifier ( )
287+ const existingWallets = await this . getWalletsByProtocolIdentifier ( protocolIdentifier , secret )
288+ const standardPath = await protocol . getStandardDerivationPath ( )
289+ const isBtc = this . isBtcProtocol ( protocolIdentifier )
290+ const supportsHD = await protocol . getSupportsHD ( )
291+
292+ if ( existingWallets . length === 0 ) {
293+ // First wallet - use standard path
294+ // For BTC and HD-capable protocols, use HD wallet
295+ // For non-HD protocols, use non-HD wallet
296+ return { derivationPath : standardPath , isHDWallet : isBtc || supportsHD }
297+ }
298+
299+ if ( isBtc ) {
300+ // BTC protocols: Always HD, increment account index
301+ // e.g., m/44'/0'/0' -> m/44'/0'/1'
302+ const lastIndices = existingWallets . map ( ( wallet ) => {
303+ const match = wallet . derivationPath . match ( / ( \d + ) [ h ' ] ? \/ ? $ / )
304+ return match ? parseInt ( match [ 1 ] , 10 ) : 0
305+ } )
306+ const maxIndex = Math . max ( ...lastIndices )
307+ const nextIndex = maxIndex + 1
308+ const newPath = standardPath . replace ( / ( \d + ) ( [ h ' ] ? ) ( \/ ? ) ? $ / , `${ nextIndex } $2$3` )
309+ return { derivationPath : newPath , isHDWallet : true }
310+ } else if ( supportsHD ) {
311+ // HD-capable protocols (ETH, OP, etc.): First is HD, subsequent are non-HD
312+ // First wallet at m/44'/60'/0' is equivalent to m/44'/60'/0'/0/0
313+ // Subsequent wallets use m/44'/60'/0'/0/1, m/44'/60'/0'/0/2, etc.
314+ const addressIndices = existingWallets . map ( ( wallet ) => {
315+ if ( wallet . isExtendedPublicKey ) {
316+ // HD wallet is equivalent to /0/0
317+ return 0
318+ }
319+ // Non-HD wallet - extract last number from path
320+ const match = wallet . derivationPath . match ( / \/ ( \d + ) $ / )
321+ return match ? parseInt ( match [ 1 ] , 10 ) : 0
322+ } )
323+ const maxIndex = Math . max ( ...addressIndices )
324+ const nextIndex = maxIndex + 1
325+ return { derivationPath : `${ standardPath } /0/${ nextIndex } ` , isHDWallet : false }
326+ } else {
327+ // Non-HD protocols: Increment last number in path
328+ // e.g., m/44h/1729h/0h/0h -> m/44h/1729h/0h/1h
329+ const lastIndices = existingWallets . map ( ( wallet ) => {
330+ const match = wallet . derivationPath . match ( / ( \d + ) [ h ' ] ? \/ ? $ / )
331+ return match ? parseInt ( match [ 1 ] , 10 ) : 0
332+ } )
333+ const maxIndex = Math . max ( ...lastIndices )
334+ const nextIndex = maxIndex + 1
335+ const newPath = standardPath . replace ( / ( \d + ) ( [ h ' ] ? ) ( \/ ? ) ? $ / , `${ nextIndex } $2$3` )
336+ return { derivationPath : newPath , isHDWallet : false }
337+ }
338+ }
339+
263340 public async removeWallets ( wallets : AirGapWallet [ ] ) : Promise < void [ ] > {
264341 return Promise . all ( wallets . map ( ( wallet ) => this . removeWallet ( wallet ) ) )
265342 }
@@ -456,7 +533,7 @@ export class SecretsService {
456533 await this . addOrUpdateSecret ( secret )
457534 }
458535
459- public async addWallets ( secret : MnemonicSecret , configs : AddWalletConifg [ ] ) : Promise < void > {
536+ public async addWallets ( secret : MnemonicSecret , configs : AddWalletConifg [ ] ) : Promise < AirGapWallet [ ] > {
460537 const loading : HTMLIonLoadingElement = await this . loadingCtrl . create ( {
461538 message : 'Deriving your wallet...'
462539 } )
@@ -465,18 +542,27 @@ export class SecretsService {
465542 try {
466543 const entropy : string = await this . retrieveEntropyForSecret ( secret )
467544
545+ const activeConfigs = configs . filter ( ( config ) => config . isActive )
546+
468547 const createdOrUpdated : Either < AirGapWallet , AirGapWallet > [ ] = (
469- await Promise . all ( configs . map ( ( config : AddWalletConifg ) => this . activateOrCreateWallet ( entropy , config ) ) )
548+ await Promise . all ( activeConfigs . map ( ( config : AddWalletConifg ) => this . activateOrCreateWallet ( entropy , config ) ) )
470549 ) . filter ( ( createdOrUpdated : Either < AirGapWallet , AirGapWallet > | undefined ) => createdOrUpdated !== undefined )
471550
472551 const [ createdWallets , updatedWallets ] : [ AirGapWallet [ ] , AirGapWallet [ ] ] = merged ( createdOrUpdated )
473552
474553 if ( createdWallets . length > 0 || updatedWallets . length > 0 ) {
475554 secret . wallets . push ( ...createdWallets )
476555 await this . addOrUpdateSecret ( secret )
556+ } else if ( activeConfigs . length > 0 ) {
557+ this . showAlert (
558+ 'Account already exists' ,
559+ 'This account already exists with the same derivation path. The account was not added.'
560+ ) . catch ( handleErrorLocal ( ErrorCategory . IONIC_ALERT ) )
477561 }
478562
479563 loading . dismiss ( ) . catch ( handleErrorLocal ( ErrorCategory . IONIC_LOADER ) )
564+
565+ return [ ...createdWallets , ...updatedWallets ]
480566 } catch ( error ) {
481567 loading . dismiss ( ) . catch ( handleErrorLocal ( ErrorCategory . IONIC_LOADER ) )
482568
0 commit comments