From 6c9caf3d2a675212441f8ceb3ccbd166343d3c2d Mon Sep 17 00:00:00 2001 From: Aditya Tiwari Date: Mon, 27 Apr 2026 17:08:14 +0530 Subject: [PATCH] fix: surface error details in sendAccountConsolidations failure array Error objects pushed to failedTxs were serializing to empty {} in the API response due to non-enumerable properties on JS Error instances. Convert caught errors to plain objects with message and name before storing, so failures are visible to callers. Ticket: COIN-7828 --- .../test/v2/unit/accountConsolidations.ts | 35 +++++++++++++++++++ modules/sdk-core/src/bitgo/wallet/wallet.ts | 4 +-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/modules/bitgo/test/v2/unit/accountConsolidations.ts b/modules/bitgo/test/v2/unit/accountConsolidations.ts index 115660942e..431103d61a 100644 --- a/modules/bitgo/test/v2/unit/accountConsolidations.ts +++ b/modules/bitgo/test/v2/unit/accountConsolidations.ts @@ -266,11 +266,46 @@ describe('Account Consolidations:', function () { consolidations.success.length.should.equal(1); consolidations.failure.length.should.equal(1); + consolidations.failure[0].should.have.property('message'); + consolidations.failure[0].should.have.property('name'); scopeWithSuccess.isDone().should.be.True(); scopeWithError.isDone().should.be.True(); scopeBuild.isDone().should.be.True(); }); + + it('should return serializable error objects in the failure array', async function () { + const scopeBuild = nock(bgUrl) + .post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/consolidateAccount/build`) + .query({}) + .reply(200, fixtures.buildAccountConsolidation); + + sinon.stub(wallet, 'getKeychainsAndValidatePassphrase').resolves([]); + + const firstError = new Error('unable to decrypt keychain with the given wallet passphrase'); + firstError.name = 'KeyDecryptionError'; + const secondError = new Error('insufficient funds'); + secondError.name = 'InsufficientFundsError'; + + sinon.stub(wallet, 'sendAccountConsolidation').onCall(0).rejects(firstError).onCall(1).rejects(secondError); + + const consolidations = await wallet.sendAccountConsolidations(); + + consolidations.failure.length.should.equal(2); + + // failure entries must be plain serializable objects, not Error instances + (consolidations.failure[0] instanceof Error).should.be.False(); + consolidations.failure[0].message.should.equal('unable to decrypt keychain with the given wallet passphrase'); + consolidations.failure[0].name.should.equal('KeyDecryptionError'); + JSON.stringify(consolidations.failure[0]).should.not.equal('{}'); + + (consolidations.failure[1] instanceof Error).should.be.False(); + consolidations.failure[1].message.should.equal('insufficient funds'); + consolidations.failure[1].name.should.equal('InsufficientFundsError'); + JSON.stringify(consolidations.failure[1]).should.not.equal('{}'); + + scopeBuild.isDone().should.be.True(); + }); }); }); } diff --git a/modules/sdk-core/src/bitgo/wallet/wallet.ts b/modules/sdk-core/src/bitgo/wallet/wallet.ts index 9fc187939c..8bc607b891 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallet.ts @@ -3389,7 +3389,7 @@ export class Wallet implements IWallet { } } const successfulTxs: any[] = []; - const failedTxs = new Array(); + const failedTxs: { message: string; name: string }[] = []; for (const unsignedBuild of unsignedBuilds) { // fold any of the parameters we used to build this transaction into the unsignedBuild const unsignedBuildWithOptions: PrebuildAndSignTransactionOptions = Object.assign({}, params); @@ -3399,7 +3399,7 @@ export class Wallet implements IWallet { const sendTx = await this.sendAccountConsolidation(unsignedBuildWithOptions); successfulTxs.push(sendTx); } catch (e) { - failedTxs.push(e); + failedTxs.push({ message: e.message, name: e.name }); } }