From 9651f36b7c003cc98be7b0c43ee819a89d10c9f9 Mon Sep 17 00:00:00 2001 From: Mohammad Al Faiyaz Date: Thu, 30 Apr 2026 15:33:24 -0400 Subject: [PATCH] fix(abstract-utxo): guard undefined address in preprocessBuildParams [WAL-375] What changed: - guard against undefined address in preprocessBuildParams before calling fromExtendedAddressFormat, passing OP_RETURN recipients through unchanged - add unit test for preprocessBuildParams with mixed normal + OP_RETURN recipients - add WCN-323 repro script to examples/ts/btc/wcn323Repro.ts Why: Recipients with no address field (e.g. OP_RETURN outputs using { script, amount }) caused a TypeError crash in preprocessBuildParams when fromExtendedAddressFormat called isScriptRecipient(undefined) -> undefined.toLowerCase(). Co-Authored-By: Claude Sonnet 4.6 TICKET: WAL-375 --- modules/abstract-utxo/src/abstractUtxoCoin.ts | 3 +++ .../test/unit/transaction/recipient.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/modules/abstract-utxo/src/abstractUtxoCoin.ts b/modules/abstract-utxo/src/abstractUtxoCoin.ts index 4296357d17..29aca40bdb 100644 --- a/modules/abstract-utxo/src/abstractUtxoCoin.ts +++ b/modules/abstract-utxo/src/abstractUtxoCoin.ts @@ -558,6 +558,9 @@ export abstract class AbstractUtxoCoin params.recipients instanceof Array ? params?.recipients?.map((recipient) => { const { address, ...rest } = recipient; + if (address === undefined) { + return recipient; // Already { script, amount } — pass through unchanged + } return { ...rest, ...fromExtendedAddressFormat(address) }; }) : params.recipients; diff --git a/modules/abstract-utxo/test/unit/transaction/recipient.ts b/modules/abstract-utxo/test/unit/transaction/recipient.ts index 1dbf838fba..2729b250ca 100644 --- a/modules/abstract-utxo/test/unit/transaction/recipient.ts +++ b/modules/abstract-utxo/test/unit/transaction/recipient.ts @@ -2,6 +2,22 @@ import assert from 'assert'; import { getUtxoCoin } from '../util/utxoCoins'; +describe('AbstractUtxoCoin.preprocessBuildParams', function () { + const coin = getUtxoCoin('btc'); + + it('does not crash when recipients includes an OP_RETURN output with no address field', function () { + const params = { + recipients: [ + { address: '3L3jdUJ9YCpGFjYB2Tuu7iBJes6ZHJFmnS', amount: '999612' }, + { amount: '0', script: '6a0c3230323651312d6175646974' }, // OP_RETURN, no address + ], + }; + assert.doesNotThrow(() => coin.preprocessBuildParams(params)); + // The OP_RETURN recipient should be passed through unchanged + assert.deepStrictEqual(params.recipients[1], { amount: '0', script: '6a0c3230323651312d6175646974' }); + }); +}); + describe('AbstractUtxoCoin.checkRecipient', function () { const coin = getUtxoCoin('btc');