@@ -636,9 +636,13 @@ function Home({ navigation }: Props) {
636636 ) {
637637 const xprk = CryptoJS . AES . decrypt ( xprivKey , pwForEncryption ) ;
638638 const xprivKeyDecrypted = xprk . toString ( CryptoJS . enc . Utf8 ) ;
639+ // Consumer wallet on-the-fly migration: derive at typeIndex=0
640+ // (receiving slot). Enterprise vaults take a different code path
641+ // (handleVaultXpubAction) which passes vault.vaultIndex.
639642 const keyPubkeys = generateSolanaPubkeyArray (
640643 xprivKeyDecrypted ,
641644 chain ,
645+ 0 ,
642646 ) ;
643647 xpubKeyDecrypted = JSON . stringify ( keyPubkeys ) ;
644648 // Persist the JSON-encoded form back to encrypted storage so
@@ -2318,15 +2322,48 @@ function Home({ navigation }: Props) {
23182322 throw new Error ( 'Unsupported chain: ' + vaultXpubData . chain ) ;
23192323 }
23202324
2321- // Derive xpub at m/48'/coin'/orgIndex'/scriptType'
2322- const vaultXpub = getMasterXpub (
2323- mnemonicPhrase ,
2324- 48 ,
2325- blockchainConfig . slip ,
2326- vaultXpubData . orgIndex ,
2327- blockchainConfig . scriptType ,
2328- vaultChain ,
2329- ) ;
2325+ // For UTXO/EVM: BIP32 xpub at m/48'/coin'/orgIndex'/scriptType'. Backend
2326+ // derives child pubkeys per addressIndex on demand.
2327+ // For Solana: pre-derive 20 ed25519 pubkeys at /[vaultIndex]/0..19 from
2328+ // the master xpriv and send as JSON array. vaultIndex (from the wallet's
2329+ // relay payload) provides per-vault key separation — mirrors EVM/UTXO
2330+ // behavior where vault.vaultIndex shifts the HD derivation.
2331+ let vaultXpub : string ;
2332+ if (
2333+ typeof vaultXpubData . vaultIndex !== 'number' ||
2334+ ! Number . isInteger ( vaultXpubData . vaultIndex ) ||
2335+ vaultXpubData . vaultIndex < 0
2336+ ) {
2337+ throw new Error (
2338+ 'vaultXpub request missing valid vaultIndex (wallet must send a non-negative integer)' ,
2339+ ) ;
2340+ }
2341+ const solVaultTypeIndex = vaultXpubData . vaultIndex ;
2342+ if ( blockchainConfig . chainType === 'sol' ) {
2343+ const solVaultXpriv = getMasterXpriv (
2344+ mnemonicPhrase ,
2345+ 48 ,
2346+ blockchainConfig . slip ,
2347+ vaultXpubData . orgIndex ,
2348+ blockchainConfig . scriptType ,
2349+ vaultChain ,
2350+ ) ;
2351+ const pubkeys = generateSolanaPubkeyArray (
2352+ solVaultXpriv ,
2353+ vaultChain ,
2354+ solVaultTypeIndex ,
2355+ ) ;
2356+ vaultXpub = JSON . stringify ( pubkeys ) ;
2357+ } else {
2358+ vaultXpub = getMasterXpub (
2359+ mnemonicPhrase ,
2360+ 48 ,
2361+ blockchainConfig . slip ,
2362+ vaultXpubData . orgIndex ,
2363+ blockchainConfig . scriptType ,
2364+ vaultChain ,
2365+ ) ;
2366+ }
23302367
23312368 // Sign the keyXpub with identity key for verification
23322369 const { xprivKey : idXprivKey } = identityChainState || { } ;
@@ -2505,6 +2542,11 @@ function Home({ navigation }: Props) {
25052542 typeof solInputDetailsParsed [ 0 ] ?. addressIndex === 'number'
25062543 ? solInputDetailsParsed [ 0 ] . addressIndex
25072544 : 0 ;
2545+ // Sign at HD path [vaultIndex][addressIndex]. Mirrors the per-vault
2546+ // xpub flow (generateSolanaPubkeyArray now also derives at
2547+ // typeIndex=vault.vaultIndex), so the wallet's signing pubkey
2548+ // matches the multisig slot pubkey computed from the stored xpub
2549+ // array. Identical to EVM/UTXO per-vault key separation.
25082550 const signingKeypair = generateAddressKeypair (
25092551 vaultXpriv ,
25102552 vaultSigningData . vaultIndex ,
0 commit comments