Skip to content

Commit 0d223d5

Browse files
feat(express): type coinSpecific.userKeySigningRequired on WalletResponse codec
Narrow the WalletResponse `coinSpecific` codec from a bare `t.UnknownRecord` to an intersection that adds typed visibility for `coinSpecific.userKeySigningRequired` while keeping the unknown-record permissiveness intact for unrelated subdocument fields. The OFC subdocument toJSON in bitgo-microservices flattens its fields directly into coinSpecific, so the field surfaces unwrapped on the response. Add codec-decode tests covering wallets with arbitrary coinSpecific fields, the typed userKeySigningRequired shape, an empty coinSpecific, and rejection of wrong-typed values. Ticket: WCN-471 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 14fd451 commit 0d223d5

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

modules/express/src/typedRoutes/schemas/wallet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export const WalletResponse = t.partial({
189189
/** Multisig type version (e.g., 'MPCv2') */
190190
multisigTypeVersion: t.string,
191191
/** Coin-specific wallet data */
192-
coinSpecific: t.UnknownRecord,
192+
coinSpecific: t.intersection([t.partial({ userKeySigningRequired: t.boolean }), t.UnknownRecord]),
193193
/** Admin settings including policy */
194194
admin: WalletAdmin,
195195
/** Users with access to this wallet */

modules/express/test/unit/typedRoutes/decode.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
ExpressWalletUpdateParams,
2121
} from '../../../src/typedRoutes/api/v2/expressWalletUpdate';
2222
import { SignerMacaroonBody, SignerMacaroonParams } from '../../../src/typedRoutes/api/v2/signerMacaroon';
23+
import { WalletResponse } from '../../../src/typedRoutes/schemas/wallet';
2324

2425
export function assertDecode<T>(codec: t.Type<T, unknown>, input: unknown): T {
2526
const result = codec.decode(input);
@@ -306,4 +307,33 @@ describe('io-ts decode tests', function () {
306307
it('express.lightning.signerMacaroon params invalid', function () {
307308
assert.throws(() => assertDecode(t.type(SignerMacaroonParams), { coin: 'lnbtc' }));
308309
});
310+
describe('WalletResponse coinSpecific', function () {
311+
it('decodes wallets with arbitrary coinSpecific fields and no userKeySigningRequired', function () {
312+
// OFC subdocument toJSON in WP flattens fields directly into coinSpecific; non-OFC
313+
// wallets carry unrelated keys. Both shapes must decode through the permissive intersection.
314+
const decoded = assertDecode(WalletResponse, {
315+
id: 'wallet123',
316+
coinSpecific: { baseAddress: '0xabc', someEthField: 1 },
317+
});
318+
assert.deepStrictEqual(decoded.coinSpecific, { baseAddress: '0xabc', someEthField: 1 });
319+
});
320+
it('decodes wallets with coinSpecific.userKeySigningRequired', function () {
321+
const decoded = assertDecode(WalletResponse, {
322+
id: 'wallet123',
323+
coinSpecific: { userKeySigningRequired: true, needsKeyReshareAfterPasswordReset: false },
324+
});
325+
assert.strictEqual(decoded.coinSpecific?.userKeySigningRequired, true);
326+
});
327+
it('decodes wallets with empty coinSpecific', function () {
328+
assertDecode(WalletResponse, { id: 'wallet123', coinSpecific: {} });
329+
});
330+
it('rejects coinSpecific.userKeySigningRequired of wrong type', function () {
331+
assert.throws(() =>
332+
assertDecode(WalletResponse, {
333+
id: 'wallet123',
334+
coinSpecific: { userKeySigningRequired: 'yes' },
335+
})
336+
);
337+
});
338+
});
309339
});

0 commit comments

Comments
 (0)