@@ -260,6 +260,81 @@ 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+ const lastIndices = existingWallets . map ( ( wallet ) => {
302+ const match = wallet . derivationPath . match ( / ( \d + ) [ h ' ] ? \/ ? $ / )
303+ return match ? parseInt ( match [ 1 ] , 10 ) : 0
304+ } )
305+ const maxIndex = Math . max ( ...lastIndices )
306+ const nextIndex = maxIndex + 1
307+ const newPath = standardPath . replace ( / ( \d + ) ( [ h ' ] ? ) ( \/ ? ) ? $ / , `${ nextIndex } $2$3` )
308+ return { derivationPath : newPath , isHDWallet : true }
309+ } else if ( supportsHD ) {
310+ // HD-capable protocols (ETH, OP, etc.): First is HD, subsequent are non-HD
311+
312+ const addressIndices = existingWallets . map ( ( wallet ) => {
313+ if ( wallet . isExtendedPublicKey ) {
314+ // HD wallet is equivalent to /0/0
315+ return 0
316+ }
317+ // Non-HD wallet - extract last number from path
318+ const match = wallet . derivationPath . match ( / \/ ( \d + ) $ / )
319+ return match ? parseInt ( match [ 1 ] , 10 ) : 0
320+ } )
321+ const maxIndex = Math . max ( ...addressIndices )
322+ const nextIndex = maxIndex + 1
323+ return { derivationPath : `${ standardPath } /0/${ nextIndex } ` , isHDWallet : false }
324+ } else {
325+ // Non-HD protocols: Increment last number in path
326+ // e.g., m/44h/1729h/0h/0h -> m/44h/1729h/0h/1h
327+ const lastIndices = existingWallets . map ( ( wallet ) => {
328+ const match = wallet . derivationPath . match ( / ( \d + ) [ h ' ] ? \/ ? $ / )
329+ return match ? parseInt ( match [ 1 ] , 10 ) : 0
330+ } )
331+ const maxIndex = Math . max ( ...lastIndices )
332+ const nextIndex = maxIndex + 1
333+ const newPath = standardPath . replace ( / ( \d + ) ( [ h ' ] ? ) ( \/ ? ) ? $ / , `${ nextIndex } $2$3` )
334+ return { derivationPath : newPath , isHDWallet : false }
335+ }
336+ }
337+
263338 public async removeWallets ( wallets : AirGapWallet [ ] ) : Promise < void [ ] > {
264339 return Promise . all ( wallets . map ( ( wallet ) => this . removeWallet ( wallet ) ) )
265340 }
@@ -456,7 +531,7 @@ export class SecretsService {
456531 await this . addOrUpdateSecret ( secret )
457532 }
458533
459- public async addWallets ( secret : MnemonicSecret , configs : AddWalletConifg [ ] ) : Promise < void > {
534+ public async addWallets ( secret : MnemonicSecret , configs : AddWalletConifg [ ] ) : Promise < AirGapWallet [ ] > {
460535 const loading : HTMLIonLoadingElement = await this . loadingCtrl . create ( {
461536 message : 'Deriving your wallet...'
462537 } )
@@ -465,18 +540,27 @@ export class SecretsService {
465540 try {
466541 const entropy : string = await this . retrieveEntropyForSecret ( secret )
467542
543+ const activeConfigs = configs . filter ( ( config ) => config . isActive )
544+
468545 const createdOrUpdated : Either < AirGapWallet , AirGapWallet > [ ] = (
469- await Promise . all ( configs . map ( ( config : AddWalletConifg ) => this . activateOrCreateWallet ( entropy , config ) ) )
546+ await Promise . all ( activeConfigs . map ( ( config : AddWalletConifg ) => this . activateOrCreateWallet ( entropy , config ) ) )
470547 ) . filter ( ( createdOrUpdated : Either < AirGapWallet , AirGapWallet > | undefined ) => createdOrUpdated !== undefined )
471548
472549 const [ createdWallets , updatedWallets ] : [ AirGapWallet [ ] , AirGapWallet [ ] ] = merged ( createdOrUpdated )
473550
474551 if ( createdWallets . length > 0 || updatedWallets . length > 0 ) {
475552 secret . wallets . push ( ...createdWallets )
476553 await this . addOrUpdateSecret ( secret )
554+ } else if ( activeConfigs . length > 0 ) {
555+ this . showAlert (
556+ 'Account already exists' ,
557+ 'This account already exists with the same derivation path. The account was not added.'
558+ ) . catch ( handleErrorLocal ( ErrorCategory . IONIC_ALERT ) )
477559 }
478560
479561 loading . dismiss ( ) . catch ( handleErrorLocal ( ErrorCategory . IONIC_LOADER ) )
562+
563+ return [ ...createdWallets , ...updatedWallets ]
480564 } catch ( error ) {
481565 loading . dismiss ( ) . catch ( handleErrorLocal ( ErrorCategory . IONIC_LOADER ) )
482566
0 commit comments