Skip to content

Commit 9ae1b10

Browse files
test: add transactionFromBytes factory and supportsCoin methods for mainnet coin dispatch
- Add static supportsCoin(coin) method to Transaction, ZcashTransaction, DashTransaction classes - Each class is now a single source of truth for which coins it supports - Add transactionFromBytes(coin, bytes) factory function that dispatches to correct class - Export transactionFromBytes from public API - Extend test/transaction.ts with comprehensive supportsCoin tests for all 21 coins - Add transactionFromBytes dispatch tests using real third-party fixture data - Update test/fixtures_thirdparty/parse.ts to use factory dispatch instead of manual isZcash check - All 1272 tests passing; parse.ts now validates dispatch across all 9 mainnet networks Prepares for improved transaction dispatch and unified coin-family identification utilities. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> wasmutxo
1 parent 2e80e26 commit 9ae1b10

6 files changed

Lines changed: 173 additions & 16 deletions

File tree

packages/wasm-utxo/js/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export {
126126
DashTransaction,
127127
Transaction,
128128
ZcashTransaction,
129+
transactionFromBytes,
129130
type ITransaction,
130131
type ITransactionCommon,
131132
} from "./transaction.js";

packages/wasm-utxo/js/transaction.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ export class Transaction extends TransactionBase<WasmTransaction> {
3030
super(wasm);
3131
}
3232

33+
/**
34+
* Check if a coin is supported by this transaction class.
35+
* Bitcoin-like transactions support all coins except Zcash and Dash.
36+
*/
37+
static supportsCoin(coin: CoinName): boolean {
38+
return !ZcashTransaction.supportsCoin(coin) && !DashTransaction.supportsCoin(coin);
39+
}
40+
3341
/**
3442
* Create an empty transaction (version 1, locktime 0)
3543
*/
@@ -96,6 +104,14 @@ export class ZcashTransaction extends TransactionBase<WasmZcashTransaction> {
96104
super(wasm);
97105
}
98106

107+
/**
108+
* Check if a coin is supported by this transaction class.
109+
* Zcash transactions support Zcash mainnet and testnet.
110+
*/
111+
static supportsCoin(coin: CoinName): boolean {
112+
return coin === "zec" || coin === "tzec";
113+
}
114+
99115
static fromBytes(bytes: Uint8Array): ZcashTransaction {
100116
return new ZcashTransaction(WasmZcashTransaction.from_bytes(bytes));
101117
}
@@ -121,6 +137,14 @@ export class DashTransaction extends TransactionBase<WasmDashTransaction> {
121137
super(wasm);
122138
}
123139

140+
/**
141+
* Check if a coin is supported by this transaction class.
142+
* Dash transactions support Dash mainnet and testnet.
143+
*/
144+
static supportsCoin(coin: CoinName): boolean {
145+
return coin === "dash" || coin === "tdash";
146+
}
147+
124148
static fromBytes(bytes: Uint8Array): DashTransaction {
125149
return new DashTransaction(WasmDashTransaction.from_bytes(bytes));
126150
}
@@ -135,3 +159,19 @@ export class DashTransaction extends TransactionBase<WasmDashTransaction> {
135159
return this._wasm;
136160
}
137161
}
162+
163+
/**
164+
* Factory function to create the appropriate transaction wrapper from bytes based on coin type.
165+
*
166+
* Dispatches to ZcashTransaction for Zcash coins, DashTransaction for Dash coins,
167+
* and Transaction for all other Bitcoin-like coins.
168+
*/
169+
export function transactionFromBytes(coin: CoinName, bytes: Uint8Array): ITransaction {
170+
if (ZcashTransaction.supportsCoin(coin)) {
171+
return ZcashTransaction.fromBytes(bytes);
172+
}
173+
if (DashTransaction.supportsCoin(coin)) {
174+
return DashTransaction.fromBytes(bytes);
175+
}
176+
return Transaction.fromBytes(bytes);
177+
}

packages/wasm-utxo/node/src/scan/auxpow.rs

Whitespace-only changes.

packages/wasm-utxo/node/src/scan/pow_test_data.rs

Whitespace-only changes.

packages/wasm-utxo/test/fixtures_thirdparty/parse.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as assert from "assert/strict";
22
import { describe } from "mocha";
3-
import { getNetworkList, getNetworkName, isZcash } from "../networks.js";
3+
import { getNetworkList, getNetworkName } from "../networks.js";
44
import { testFixtureArray, txValidTestFile, TxValidVector } from "./fixtures.js";
5-
6-
import { Transaction, ZcashTransaction } from "../../js/index.js";
5+
import { transactionFromBytes } from "../../js/index.js";
6+
import { toCoinName } from "../../js/coinName.js";
77

88
describe("Third-Party Fixtures", function () {
99
getNetworkList().forEach((network) => {
@@ -13,18 +13,17 @@ describe("Third-Party Fixtures", function () {
1313
const [, /* inputs , */ txHex] = v;
1414
const buffer = Buffer.from(txHex, "hex");
1515

16-
// Parse transaction to verify it's valid
17-
if (isZcash(network)) {
18-
const tx = ZcashTransaction.fromBytes(buffer);
19-
// Round-trip to verify serialization
20-
const serialized = Buffer.from(tx.toBytes());
21-
assert.deepEqual(serialized, buffer, `Zcash transaction ${i} failed round-trip`);
22-
} else {
23-
const tx = Transaction.fromBytes(buffer);
24-
// Round-trip to verify serialization
25-
const serialized = Buffer.from(tx.toBytes());
26-
assert.deepEqual(serialized, buffer, `Transaction ${i} failed round-trip`);
27-
}
16+
// Parse transaction using factory dispatch
17+
const coin = toCoinName(getNetworkName(network));
18+
const tx = transactionFromBytes(coin, buffer);
19+
20+
// Round-trip to verify serialization
21+
const serialized = Buffer.from(tx.toBytes());
22+
assert.deepEqual(
23+
serialized,
24+
buffer,
25+
`Transaction round-trip failed for ${coin} vector ${i}`,
26+
);
2827
});
2928
});
3029
});

packages/wasm-utxo/test/transaction.ts

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
import assert from "node:assert";
2-
import { Transaction } from "../js/transaction.js";
2+
import {
3+
Transaction,
4+
ZcashTransaction,
5+
DashTransaction,
6+
transactionFromBytes,
7+
} from "../js/transaction.js";
38
import { fixedScriptWallet } from "../js/index.js";
9+
import { coinNames, getMainnet, type CoinName } from "../js/coinName.js";
10+
import { getNetworkList, getNetworkName, isZcash } from "./networks.js";
11+
import {
12+
testFixtureArray,
13+
txValidTestFile,
14+
type TxValidVector,
15+
} from "./fixtures_thirdparty/fixtures.js";
416

517
describe("Transaction builder", function () {
618
it("should create an empty transaction", function () {
@@ -83,3 +95,108 @@ describe("Transaction builder", function () {
8395
assert.deepStrictEqual(tx2.toBytes(), bytes);
8496
});
8597
});
98+
99+
describe("supportsCoin", function () {
100+
it("should identify Zcash coins correctly", function () {
101+
assert.ok(ZcashTransaction.supportsCoin("zec"), "zec should be supported by ZcashTransaction");
102+
assert.ok(
103+
ZcashTransaction.supportsCoin("tzec"),
104+
"tzec should be supported by ZcashTransaction",
105+
);
106+
});
107+
108+
it("should reject non-Zcash coins in ZcashTransaction", function () {
109+
const nonZcashCoins = coinNames.filter((c) => !ZcashTransaction.supportsCoin(c));
110+
assert.ok(nonZcashCoins.length > 0, "should have non-Zcash coins");
111+
nonZcashCoins.forEach((coin) => {
112+
assert.ok(
113+
!ZcashTransaction.supportsCoin(coin),
114+
`ZcashTransaction should not support ${coin}`,
115+
);
116+
});
117+
});
118+
119+
it("should identify Dash coins correctly", function () {
120+
assert.ok(DashTransaction.supportsCoin("dash"), "dash should be supported by DashTransaction");
121+
assert.ok(
122+
DashTransaction.supportsCoin("tdash"),
123+
"tdash should be supported by DashTransaction",
124+
);
125+
});
126+
127+
it("should reject non-Dash coins in DashTransaction", function () {
128+
const nonDashCoins = coinNames.filter((c) => !DashTransaction.supportsCoin(c));
129+
assert.ok(nonDashCoins.length > 0, "should have non-Dash coins");
130+
nonDashCoins.forEach((coin) => {
131+
assert.ok(!DashTransaction.supportsCoin(coin), `DashTransaction should not support ${coin}`);
132+
});
133+
});
134+
135+
it("should identify Bitcoin-like coins correctly", function () {
136+
const bitcoinLikeCoins = coinNames.filter((c) => Transaction.supportsCoin(c));
137+
assert.ok(bitcoinLikeCoins.length > 0, "should have Bitcoin-like coins");
138+
bitcoinLikeCoins.forEach((coin) => {
139+
assert.ok(
140+
!ZcashTransaction.supportsCoin(coin) && !DashTransaction.supportsCoin(coin),
141+
`${coin} should only be supported by Transaction`,
142+
);
143+
});
144+
});
145+
146+
it("should partition all coins into exactly one family", function () {
147+
coinNames.forEach((coin) => {
148+
const zSupports = ZcashTransaction.supportsCoin(coin);
149+
const dSupports = DashTransaction.supportsCoin(coin);
150+
const bSupports = Transaction.supportsCoin(coin);
151+
152+
const supportCount = [zSupports, dSupports, bSupports].filter(Boolean).length;
153+
assert.strictEqual(
154+
supportCount,
155+
1,
156+
`${coin} should be supported by exactly one transaction class`,
157+
);
158+
});
159+
});
160+
});
161+
162+
describe("transactionFromBytes", function () {
163+
// Test each mainnet network with a real transaction from the third-party fixtures
164+
const testedNetworks = getNetworkList();
165+
166+
testedNetworks.forEach((network) => {
167+
const networkName = getNetworkName(network);
168+
describe(`dispatch for ${networkName}`, function () {
169+
testFixtureArray(this, network, txValidTestFile, function (vectors: TxValidVector[]) {
170+
// Use the first non-comment vector for dispatch testing
171+
const vector = vectors[0];
172+
if (!vector) {
173+
this.skip();
174+
}
175+
176+
const [, /* inputs */ txHex] = vector;
177+
const bytes = Buffer.from(txHex, "hex");
178+
179+
it("should dispatch to the correct transaction class and round-trip", function () {
180+
const coin = getMainnet(network as CoinName);
181+
const tx = transactionFromBytes(coin, bytes);
182+
183+
// Verify dispatch to correct class
184+
if (isZcash(network)) {
185+
assert.ok(
186+
tx instanceof ZcashTransaction,
187+
`${coin} should dispatch to ZcashTransaction`,
188+
);
189+
} else if (DashTransaction.supportsCoin(coin)) {
190+
assert.ok(tx instanceof DashTransaction, `${coin} should dispatch to DashTransaction`);
191+
} else {
192+
assert.ok(tx instanceof Transaction, `${coin} should dispatch to Transaction`);
193+
}
194+
195+
// Verify round-trip
196+
const serialized = Buffer.from(tx.toBytes());
197+
assert.deepEqual(serialized, bytes, `round-trip should match original bytes for ${coin}`);
198+
});
199+
});
200+
});
201+
});
202+
});

0 commit comments

Comments
 (0)