diff --git a/modules/abstract-utxo/src/recovery/crossChainRecovery.ts b/modules/abstract-utxo/src/recovery/crossChainRecovery.ts index 95485453f7..4455b1b297 100644 --- a/modules/abstract-utxo/src/recovery/crossChainRecovery.ts +++ b/modules/abstract-utxo/src/recovery/crossChainRecovery.ts @@ -108,7 +108,10 @@ export async function isWalletAddress(wallet: IWallet | WalletV1, address: strin return addressData !== undefined; } catch (e) { - return false; + if (e.status === 404) { + return false; + } + throw e; } } diff --git a/modules/abstract-utxo/test/unit/recovery/crossChainRecovery.ts b/modules/abstract-utxo/test/unit/recovery/crossChainRecovery.ts index ed397b1fac..9da3e0e230 100644 --- a/modules/abstract-utxo/test/unit/recovery/crossChainRecovery.ts +++ b/modules/abstract-utxo/test/unit/recovery/crossChainRecovery.ts @@ -11,6 +11,7 @@ import { CrossChainRecoverySigned, CrossChainRecoveryUnsigned, getWallet, + isWalletAddress, supportedCrossChainRecoveries, generateAddress, convertLtcAddressToLegacyFormat, @@ -297,6 +298,70 @@ describe(`Cross-Chain Recovery getWallet`, async function () { }); }); +describe('isWalletAddress', function () { + const coin = getUtxoCoin('btc'); + const walletId = '5abacebe28d72fbd07e0b8cbba0ff39e'; + const testAddress = '3GBygsGPvTdfKMbq4AKZZRu1sPMWPEsBfd'; + + function nockAddress(statusCode: number, body?: unknown): nock.Scope { + return nockBitGo() + .get(`/api/v2/${coin.getChain()}/wallet/${walletId}/address/${testAddress}`) + .reply(statusCode, body ?? {}); + } + + before('setup wallet nock', function () { + nockBitGo() + .get(`/api/v2/${coin.getChain()}/wallet/${walletId}`) + .reply(200, { + id: walletId, + coin: coin.getChain(), + label: 'isWalletAddress test', + keys: keychainsBase58.map((k) => getSeed(k.pub).toString('hex')), + }) + .persist(); + keychainsBase58.forEach((k) => { + nockBitGo() + .get(`/api/v2/${coin.getChain()}/key/${getSeed(k.pub).toString('hex')}`) + .reply(200, k) + .persist(); + }); + }); + + after(function () { + nock.cleanAll(); + }); + + it('returns true when address belongs to wallet', async function () { + nockAddress(200, { address: testAddress, chain: 0, index: 0, coin: coin.getChain(), wallet: walletId }); + const wallet = await getWallet(defaultBitGo, coin, walletId); + assert.strictEqual(await isWalletAddress(wallet, testAddress), true); + }); + + it('returns false for 404 (address not found in wallet)', async function () { + nockAddress(404); + const wallet = await getWallet(defaultBitGo, coin, walletId); + assert.strictEqual(await isWalletAddress(wallet, testAddress), false); + }); + + it('re-throws on 503 (server error during recovery silently excludes outputs)', async function () { + nockAddress(503); + const wallet = await getWallet(defaultBitGo, coin, walletId); + await assert.rejects(() => isWalletAddress(wallet, testAddress), { status: 503 }); + }); + + it('re-throws on 500', async function () { + nockAddress(500); + const wallet = await getWallet(defaultBitGo, coin, walletId); + await assert.rejects(() => isWalletAddress(wallet, testAddress), { status: 500 }); + }); + + it('re-throws on 401 (auth failure)', async function () { + nockAddress(401); + const wallet = await getWallet(defaultBitGo, coin, walletId); + await assert.rejects(() => isWalletAddress(wallet, testAddress), { status: 401 }); + }); +}); + describe('convertLtcAddressToLegacyFormat', function () { const scriptPubKey = Buffer.from('a9149f0bf51fab4d33ab21977e1b89f776f64161ef4287', 'hex'); it('should convert M... P2SH address to 3... legacy format', function () {