From 0f0b7515798f19b9a89cc742389b74cce7a21c31 Mon Sep 17 00:00:00 2001 From: Support Bot Date: Thu, 23 Jul 2026 09:23:18 +0000 Subject: [PATCH] fix(sdk-core): handle 404 key not found in getEncryptedUserKeychain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When sharing a wallet (e.g. cold storage wallets), getEncryptedUserKeychain iterates through wallet.keys and fetches each key from the backend. For cold storage wallets like Trade Republic's, some key records may not exist on the server (they return 404 "key not found" instead of a 200 with no encryptedPrv). Previously this 404 ApiResponseError was uncaught and propagated to the caller, breaking the wallet share flow even though the correct behaviour is to treat a missing key record the same as a key with no encryptedPrv: skip it and try the next key. If all keys are absent or have no encryptedPrv, the function throws MissingEncryptedKeychainError, which prepareSharedKeychain already handles by returning {} (skipKeychain: true) — the cold-wallet code path. The fix catches ApiResponseError with status 404 inside tryKeyChain and falls through to the next key, mirroring the existing no-encryptedPrv skip. No behaviour change for hot wallets or any 4xx/5xx other than 404. Ticket: WCN-1586 Session-Id: 5f46d7d1-4497-4665-8b70-7c1a40459cb2 Task-Id: 6119806c-2d1f-4106-896f-7bf304043f2c --- modules/bitgo/test/v2/unit/wallet.ts | 31 +++++++++++++++++++++ modules/sdk-core/src/bitgo/wallet/wallet.ts | 14 +++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/modules/bitgo/test/v2/unit/wallet.ts b/modules/bitgo/test/v2/unit/wallet.ts index 81013ade88..95bff09e69 100644 --- a/modules/bitgo/test/v2/unit/wallet.ts +++ b/modules/bitgo/test/v2/unit/wallet.ts @@ -1994,6 +1994,37 @@ describe('V2 Wallet:', function () { createShareNock.isDone().should.be.True(); }); + it('should share wallet when key fetch returns 404 (cold wallet with missing key records)', async function () { + const userId = '123'; + const email = 'shareto@sdktest.com'; + const permissions = 'view,spend'; + + const getSharingKeyNock = nock(bgUrl).post('/api/v1/user/sharingkey', { email }).reply(200, { userId }); + + // Simulate cold storage wallets where key records do not exist on the backend (404) + const getKeyNock = nock(bgUrl) + .get(`/api/v2/tbtc/key/${coldWallet.keyIds()[0]}`) + .reply(404, { error: 'key not found' }) + .get(`/api/v2/tbtc/key/${coldWallet.keyIds()[1]}`) + .reply(404, { error: 'key not found' }) + .get(`/api/v2/tbtc/key/${coldWallet.keyIds()[2]}`) + .reply(404, { error: 'key not found' }); + + const createShareNock = nock(bgUrl) + .post(`/api/v2/tbtc/wallet/${coldWallet.id()}/share`, { + user: userId, + permissions, + skipKeychain: true, + }) + .reply(200, {}); + + await coldWallet.shareWallet({ email, permissions }); + + getSharingKeyNock.isDone().should.be.True(); + getKeyNock.isDone().should.be.True(); + createShareNock.isDone().should.be.True(); + }); + describe('Hot Wallet Sharing', function () { const userId = '123'; const email = 'shareto@sdktest.com'; diff --git a/modules/sdk-core/src/bitgo/wallet/wallet.ts b/modules/sdk-core/src/bitgo/wallet/wallet.ts index 91a2ba63da..68ed106910 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallet.ts @@ -26,6 +26,7 @@ import { BitGoBase } from '../bitgoBase'; import { getSharedSecret } from '../ecdh'; import { AddressGenerationError, + ApiResponseError, IncorrectPasswordError, MethodNotImplementedError, MissingEncryptedKeychainError, @@ -1714,7 +1715,18 @@ export class Wallet implements IWallet { const params = { id: this._wallet.keys[index] }; - const keychain = await this.baseCoin.keychains().get(params); + let keychain: Keychain; + try { + keychain = await this.baseCoin.keychains().get(params); + } catch (e) { + // A 404 means the key record does not exist on the server (e.g. cold wallet keys + // or KMS-backed keys that have no keychain entry). Treat this the same as a key + // without an encryptedPrv and try the next one. + if (e instanceof ApiResponseError && e.status === 404) { + return tryKeyChain(index + 1); + } + throw e; + } // If we find the prv, then this is probably the user keychain we're looking for if (keychain.encryptedPrv) { return keychain as KeychainWithEncryptedPrv;