@@ -113,6 +113,40 @@ export async function isWalletAddress(wallet: IWallet | WalletV1, address: strin
113113 }
114114}
115115
116+ /**
117+ * Convert a Litecoin P2SH address from M... format (scriptHash 0x32) to the legacy 3... format (scriptHash 0x05).
118+ * This is needed for cross-chain recovery when LTC was sent to a BTC address, because the BTC wallet
119+ * stores addresses in the 3... format while the LTC blockchain returns addresses in M... format.
120+ *
121+ * @param address - LTC address to convert
122+ * @param network - The Litecoin network
123+ * @returns The address in legacy 3... format, or the original address if it's not a P2SH address
124+ */
125+ export function convertLtcAddressToLegacyFormat ( address : string , network : utxolib . Network ) : string {
126+ try {
127+ // Try to decode as bech32 - these don't need conversion
128+ utxolib . address . fromBech32 ( address ) ;
129+ return address ;
130+ } catch ( e ) {
131+ // Not bech32, continue to base58
132+ }
133+
134+ try {
135+ const decoded = utxolib . address . fromBase58Check ( address , network ) ;
136+ // Only convert P2SH addresses (scriptHash), not P2PKH (pubKeyHash)
137+ if ( decoded . version === network . scriptHash ) {
138+ // Convert to legacy format using Bitcoin's scriptHash (0x05)
139+ const legacyScriptHash = utxolib . networks . bitcoin . scriptHash ;
140+ return utxolib . address . toBase58Check ( decoded . hash , legacyScriptHash , network ) ;
141+ }
142+ // P2PKH or other - return unchanged
143+ return address ;
144+ } catch ( e ) {
145+ // If decoding fails, return the original address
146+ return address ;
147+ }
148+ }
149+
116150/**
117151 * @param coin
118152 * @param txid
@@ -137,7 +171,18 @@ async function getAllRecoveryOutputs<TNumber extends number | bigint = number>(
137171 // in non legacy format. However, we want to keep the address in the same format as the response since we
138172 // are going to hit the API again to fetch address unspents.
139173 const canonicalAddress = coin . canonicalAddress ( output . address ) ;
140- const isWalletOwned = await isWalletAddress ( wallet , canonicalAddress ) ;
174+ let isWalletOwned = await isWalletAddress ( wallet , canonicalAddress ) ;
175+
176+ // For LTC cross-chain recovery: if the address isn't found, try the legacy format.
177+ // When LTC is sent to a BTC address, the LTC blockchain returns M... addresses
178+ // but the BTC wallet stores addresses in 3... format.
179+ if ( ! isWalletOwned && coin . getFamily ( ) === 'ltc' ) {
180+ const legacyAddress = convertLtcAddressToLegacyFormat ( output . address , coin . network ) ;
181+ if ( legacyAddress !== output . address ) {
182+ isWalletOwned = await isWalletAddress ( wallet , legacyAddress ) ;
183+ }
184+ }
185+
141186 return isWalletOwned ? output . address : null ;
142187 } )
143188 )
0 commit comments