@xchainjs/zcash-js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 THORChain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @xchainjs/zcash-js
2
+
3
+ Zcash JavaScript library for XChainJS - a fork of @mayaprotocol/zcash-js with critical fixes.
4
+
5
+ ## Key Fixes
6
+
7
+ ### NU6.1 Consensus Branch ID (CRITICAL)
8
+
9
+ This package includes the updated NU6.1 consensus branch ID (`0x4DEC4DF0`) which activated on November 24, 2025 at block height 3,146,400.
10
+
11
+ The original @mayaprotocol/zcash-js package uses the outdated NU5 branch ID (`0xc8e71055`), causing all transactions to be rejected by the network with "bad-txns-wrong-version" errors.
12
+
13
+ ### Browser Compatibility
14
+
15
+ - Replaced `blake2b-wasm` with `@noble/hashes/blake2b` - pure JavaScript implementation that works in browsers
16
+ - Uses synchronous hashing instead of async WASM loading
17
+ - No Node.js-specific APIs required
18
+
19
+ ## Usage
20
+
21
+ ```typescript
22
+ import { buildTx, signAndFinalize, getFee } from '@xchainjs/zcash-js'
23
+
24
+ // Build an unsigned transaction
25
+ const tx = await buildTx(
26
+ currentHeight,
27
+ senderAddress,
28
+ recipientAddress,
29
+ amount, // in satoshis
30
+ utxos,
31
+ true, // isMainnet
32
+ 'optional memo'
33
+ )
34
+
35
+ // Sign and finalize
36
+ const signedTx = await signAndFinalize(
37
+ currentHeight,
38
+ privateKeyHex,
39
+ tx.inputs,
40
+ tx.outputs
41
+ )
42
+
43
+ // Broadcast the hex-encoded transaction
44
+ const txHex = signedTx.toString('hex')
45
+ ```
46
+
47
+ ## API
48
+
49
+ ### `buildTx(height, from, to, amount, utxos, isMainnet, memo?)`
50
+
51
+ Creates an unsigned transaction with automatic UTXO selection.
52
+
53
+ ### `signAndFinalize(height, privateKey, utxos, outputs)`
54
+
55
+ Signs the transaction and returns the serialized transaction buffer.
56
+
57
+ ### `getFee(inputCount, outputCount, memo?)`
58
+
59
+ Calculates the transaction fee using Zcash's ZIP 317 fee algorithm.
60
+
61
+ ## Network Upgrade History
62
+
63
+ | Version | Branch ID | Activation |
64
+ |---------|-----------|------------|
65
+ | NU5 | 0xc8e71055 | May 2022 |
66
+ | NU6 | 0xc8e71055 | Nov 2024 |
67
+ | NU6.1 | 0x4DEC4DF0 | Nov 24, 2025 |
68
+
69
+ ## License
70
+
71
+ MIT
package/lib/addr.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare const testnetPrefix: number[];
2
+ export declare const mainnetPrefix: number[];
3
+ export declare function isValidAddr(address: string, prefix: number[] | Buffer | Uint8Array): boolean;
4
+ export declare function pkToAddr(pk: Uint8Array, prefix: number[] | Uint8Array): string;
5
+ export declare function skToAddr(sk: Uint8Array, prefix: number[] | Uint8Array): string;
@@ -0,0 +1,4 @@
1
+ import { Output, Tx, UTXO } from './types';
2
+ export declare function getFee(inCount: number, outCount: number, memo?: string): number;
3
+ export declare function buildTx(height: number, from: string, to: string, amount: number, utxos: UTXO[], isMainnet: boolean, memo?: string): Promise<Tx>;
4
+ export declare function signAndFinalize(height: number, skb: string, utxos: UTXO[], outputs: Output[]): Promise<Buffer>;
package/lib/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './addr';
3
+ export * from './rpc';
4
+ export * from './builder';
5
+ export * from './script';
@@ -0,0 +1,517 @@
1
+ import { secp256k1 } from '@noble/curves/secp256k1';
2
+ import { ripemd160 } from '@noble/hashes/ripemd160';
3
+ import { sha256 } from '@noble/hashes/sha2';
4
+ import bs58check from 'bs58check';
5
+ import axios from 'axios';
6
+ import { JSONRPCClient } from 'json-rpc-2.0';
7
+ import { blake2b } from '@noble/hashes/blake2b';
8
+ import { sumBy, min } from 'lodash';
9
+
10
+ const testnetPrefix = [0x1d, 0x25];
11
+ const mainnetPrefix = [0x1c, 0xb8];
12
+ function isValidAddr(address, prefix) {
13
+ try {
14
+ const addrb = bs58check.decode(address);
15
+ if (Buffer.from(addrb.slice(0, 2)).compare(Buffer.from(prefix)) != 0) {
16
+ throw new Error('Invalid prefix');
17
+ }
18
+ return true;
19
+ }
20
+ catch (_a) {
21
+ return false;
22
+ }
23
+ }
24
+ function pkToAddr(pk, prefix) {
25
+ const hash = sha256(pk);
26
+ const pkh = ripemd160(hash);
27
+ const addrb = Buffer.alloc(22);
28
+ Buffer.from(prefix).copy(addrb);
29
+ Buffer.from(pkh).copy(addrb, 2);
30
+ const addr = bs58check.encode(addrb);
31
+ return addr;
32
+ }
33
+ function skToAddr(sk, prefix) {
34
+ const pk = secp256k1.getPublicKey(sk, true);
35
+ return pkToAddr(pk, prefix);
36
+ }
37
+
38
+ /******************************************************************************
39
+ Copyright (c) Microsoft Corporation.
40
+
41
+ Permission to use, copy, modify, and/or distribute this software for any
42
+ purpose with or without fee is hereby granted.
43
+
44
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
45
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
46
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
47
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
48
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
49
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
50
+ PERFORMANCE OF THIS SOFTWARE.
51
+ ***************************************************************************** */
52
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
53
+
54
+
55
+ function __awaiter(thisArg, _arguments, P, generator) {
56
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
57
+ return new (P || (P = Promise))(function (resolve, reject) {
58
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
59
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
60
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
61
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
62
+ });
63
+ }
64
+
65
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
66
+ var e = new Error(message);
67
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
68
+ };
69
+
70
+ const RPC_TIMEOUT = 30000; // 30 seconds
71
+ function makeClient(config) {
72
+ const client = new JSONRPCClient((jsonRPCRequest) => __awaiter(this, void 0, void 0, function* () {
73
+ const response = yield axios.post(config.server.host, jsonRPCRequest, {
74
+ headers: { 'Content-Type': 'application/json' },
75
+ auth: {
76
+ username: config.server.user,
77
+ password: config.server.password,
78
+ },
79
+ timeout: RPC_TIMEOUT,
80
+ });
81
+ client.receive(response.data);
82
+ }));
83
+ return client;
84
+ }
85
+ function getUTXOS(from, config) {
86
+ return __awaiter(this, void 0, void 0, function* () {
87
+ const client = makeClient(config);
88
+ const utxos = yield client.request('getaddressutxos', [from]);
89
+ return utxos;
90
+ });
91
+ }
92
+ function waitForTransaction(txid_1, config_1) {
93
+ return __awaiter(this, arguments, void 0, function* (txid, config, maxAttempts = 30) {
94
+ const client = makeClient(config);
95
+ for (let i = 0; i < maxAttempts; i++) {
96
+ try {
97
+ const tx = yield client.request('gettransaction', [txid]);
98
+ if (tx && tx.confirmations > 0) {
99
+ return;
100
+ }
101
+ }
102
+ catch (_a) {
103
+ // Transaction might not be in wallet
104
+ }
105
+ yield new Promise((resolve) => setTimeout(resolve, 1000));
106
+ }
107
+ throw new Error(`Transaction ${txid} not confirmed after ${maxAttempts} attempts`);
108
+ });
109
+ }
110
+ function sendRawTransaction(txb, config) {
111
+ return __awaiter(this, void 0, void 0, function* () {
112
+ var _a, _b;
113
+ try {
114
+ // Direct axios call to get better error details
115
+ const response = yield axios.post(config.server.host, {
116
+ jsonrpc: '2.0',
117
+ method: 'sendrawtransaction',
118
+ params: [txb.toString('hex')],
119
+ id: 1,
120
+ }, {
121
+ headers: { 'Content-Type': 'application/json' },
122
+ auth: {
123
+ username: config.server.user,
124
+ password: config.server.password,
125
+ },
126
+ timeout: RPC_TIMEOUT,
127
+ });
128
+ if (response.data.error) {
129
+ console.error('RPC Error:', response.data.error);
130
+ throw new Error(response.data.error.message);
131
+ }
132
+ return response.data.result;
133
+ }
134
+ catch (error) {
135
+ const axiosError = error;
136
+ if ((_b = (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) {
137
+ throw new Error(axiosError.response.data.error.message);
138
+ }
139
+ throw error;
140
+ }
141
+ });
142
+ }
143
+
144
+ function writeCompactInt(value) {
145
+ if (value < 0xfd) {
146
+ return Buffer.from([value]);
147
+ }
148
+ else if (value <= 0xffff) {
149
+ // 0xFD followed by 16-bit integer
150
+ const buffer = Buffer.alloc(3);
151
+ buffer[0] = 0xfd;
152
+ buffer.writeUInt16LE(value, 1);
153
+ return buffer;
154
+ }
155
+ else if (value <= 0xffffffff) {
156
+ // 0xFE followed by 32-bit integer
157
+ const buffer = Buffer.alloc(5);
158
+ buffer[0] = 0xfe;
159
+ buffer.writeUInt32LE(value, 1);
160
+ return buffer;
161
+ }
162
+ else {
163
+ // 0xFF followed by 64-bit integer
164
+ const buffer = Buffer.alloc(9);
165
+ buffer[0] = 0xff;
166
+ const bigValue = BigInt(value);
167
+ buffer.writeBigUInt64LE(bigValue, 1);
168
+ return buffer;
169
+ }
170
+ }
171
+ function pushData(length) {
172
+ const buf = Buffer.alloc(2);
173
+ let offset = 0;
174
+ if (length < 0x4c) {
175
+ buf[0] = length;
176
+ offset += 1;
177
+ }
178
+ else {
179
+ buf[0] = 0x4c;
180
+ buf[1] = length;
181
+ offset += 2;
182
+ }
183
+ return buf.subarray(0, offset);
184
+ }
185
+
186
+ function memoToScript(memo) {
187
+ const opr = Buffer.alloc(memo.length + 4);
188
+ opr[1] = 0x6a;
189
+ let offset = 2;
190
+ const pml = pushData(memo.length);
191
+ pml.copy(opr, offset);
192
+ offset += pml.length;
193
+ Buffer.from(memo).copy(opr, offset);
194
+ offset += memo.length;
195
+ opr[0] = offset - 1;
196
+ const script = opr.subarray(0, offset);
197
+ return script;
198
+ }
199
+ function addressToScript(address) {
200
+ const addrb = bs58check.decode(address);
201
+ const pkh = Buffer.alloc(20);
202
+ Buffer.from(addrb).copy(pkh, 0, 2);
203
+ const script = Buffer.alloc(26);
204
+ Buffer.from('1976a914', 'hex').copy(script);
205
+ Buffer.from(pkh).copy(script, 4);
206
+ Buffer.from('88ac', 'hex').copy(script, 24);
207
+ return script;
208
+ }
209
+ function writeSigScript(signature, pk) {
210
+ const buf = Buffer.alloc(5 + signature.length + pk.length);
211
+ let offset = 0;
212
+ const psl = pushData(signature.length + 1);
213
+ psl.copy(buf, offset);
214
+ offset += psl.length;
215
+ Buffer.from(signature).copy(buf, offset);
216
+ offset += signature.length;
217
+ buf[offset] = 1;
218
+ offset += 1;
219
+ const pkl = pushData(pk.length);
220
+ pkl.copy(buf, offset);
221
+ offset += pkl.length;
222
+ Buffer.from(pk).copy(buf, offset);
223
+ offset += pk.length;
224
+ return buf.subarray(0, offset);
225
+ }
226
+
227
+ // NU6.1 Consensus Branch ID - activated November 24, 2025 at block height 3146400
228
+ // See ZIP 255: https://zips.z.cash/zip-0255
229
+ const NU6_1_CONSENSUS_BRANCH_ID = 0x4dec4df0;
230
+ // Version Group ID for transaction version 5
231
+ const TX_VERSION_GROUP_ID = 0x26a7270a;
232
+ // Transaction version 5 with overwinter flag
233
+ const TX_VERSION = 0x80000005;
234
+ const PKH_OUTPUT_SIZE = 34;
235
+ const MARGINAL_FEE = 5000;
236
+ const GRACE_ACTIONS = 2;
237
+ function calculateFee(inCount, outCount) {
238
+ const logicalActions = inCount + outCount;
239
+ return MARGINAL_FEE * Math.max(GRACE_ACTIONS, logicalActions);
240
+ }
241
+ function getFee(inCount, outCount, memo) {
242
+ if (memo && memo.length > 0) {
243
+ const memoLenWithOverhead = memo.length + 2;
244
+ const memoOutputSlots = Math.floor((memoLenWithOverhead + PKH_OUTPUT_SIZE - 1) / PKH_OUTPUT_SIZE);
245
+ outCount += memoOutputSlots;
246
+ }
247
+ return calculateFee(inCount, outCount);
248
+ }
249
+ function selectUTXOS(utxos, amount, memo) {
250
+ let currentFee = 0;
251
+ const selected = [];
252
+ let remaining = amount;
253
+ for (const utxo of utxos) {
254
+ if (remaining == 0)
255
+ break;
256
+ selected.push(utxo);
257
+ const fee = getFee(selected.length, 2, memo);
258
+ const deltaFee = fee - currentFee;
259
+ currentFee = fee;
260
+ remaining += deltaFee;
261
+ const used = min([utxo.satoshis, remaining]);
262
+ remaining -= used;
263
+ }
264
+ return selected;
265
+ }
266
+ /**
267
+ * Create a blake2b hash with personalization string
268
+ */
269
+ function blake2bWithPersonal(data, personal) {
270
+ const personalBytes = typeof personal === 'string' ? new TextEncoder().encode(personal) : personal;
271
+ // Pad personal to 16 bytes
272
+ const paddedPersonal = new Uint8Array(16);
273
+ paddedPersonal.set(personalBytes.slice(0, 16));
274
+ return blake2b(data, { dkLen: 32, personalization: paddedPersonal });
275
+ }
276
+ function buildTx(height, from, to, amount, utxos, isMainnet, memo) {
277
+ return __awaiter(this, void 0, void 0, function* () {
278
+ const prefixb = isMainnet ? mainnetPrefix : testnetPrefix;
279
+ const prefix = Buffer.from(prefixb);
280
+ if (!isValidAddr(from, prefix))
281
+ throw new Error('Invalid "from" address');
282
+ if (!isValidAddr(to, prefix))
283
+ throw new Error('Invalid "to" address');
284
+ if (amount > 1e14)
285
+ throw new Error('Amount too large');
286
+ if (memo && memo.length > 80)
287
+ throw new Error('Memo too long');
288
+ const inputs = selectUTXOS(utxos, amount, memo);
289
+ const outputCount = memo ? 3 : 2; // change + to + memo (if exists)
290
+ const fee = getFee(inputs.length, outputCount, memo);
291
+ const change = sumBy(inputs, (u) => u.satoshis) - amount - fee;
292
+ if (change < 0)
293
+ throw new Error('Not enough funds');
294
+ const outputs = [];
295
+ outputs.push({
296
+ type: 'pkh',
297
+ address: from,
298
+ amount: change,
299
+ });
300
+ outputs.push({
301
+ type: 'pkh',
302
+ address: to,
303
+ amount: amount,
304
+ });
305
+ if (memo) {
306
+ outputs.push({
307
+ type: 'op_return',
308
+ memo: memo,
309
+ });
310
+ }
311
+ return {
312
+ height: height,
313
+ inputs: inputs,
314
+ outputs: outputs,
315
+ fee: fee,
316
+ };
317
+ });
318
+ }
319
+ function signAndFinalize(height, skb, utxos, outputs) {
320
+ return __awaiter(this, void 0, void 0, function* () {
321
+ const sk = new Uint8Array(Buffer.from(skb, 'hex'));
322
+ const pk = secp256k1.getPublicKey(sk, true);
323
+ let offset = 0;
324
+ // HEADER with NU6.1 consensus branch ID
325
+ let buf = Buffer.alloc(20);
326
+ buf.writeUInt32LE(TX_VERSION, 0); // Transaction version 5 with overwinter flag
327
+ buf.writeUInt32LE(TX_VERSION_GROUP_ID, 4); // Version group ID
328
+ buf.writeUInt32LE(NU6_1_CONSENSUS_BRANCH_ID, 8); // NU6.1 consensus branch ID (FIXED!)
329
+ buf.writeUInt32LE(0x00000000, 12); // Lock time
330
+ buf.writeUInt32LE(height, 16); // Expiry height
331
+ const headerHash = Buffer.from(blake2bWithPersonal(buf, 'ZTxIdHeadersHash')).toString('hex');
332
+ buf = Buffer.alloc(36 * utxos.length);
333
+ for (const [i, utxo] of utxos.entries()) {
334
+ const txid = Buffer.from(utxo.txid, 'hex');
335
+ txid.reverse();
336
+ txid.copy(buf, 36 * i);
337
+ buf.writeUInt32LE(utxo.outputIndex, 36 * i + 32);
338
+ }
339
+ const prevoutputsHash = Buffer.from(blake2bWithPersonal(buf, 'ZTxIdPrevoutHash')).toString('hex');
340
+ buf = Buffer.alloc(4 * utxos.length);
341
+ for (const [i] of utxos.entries()) {
342
+ buf.writeInt32LE(-1, 4 * i);
343
+ }
344
+ const sequencesHash = Buffer.from(blake2bWithPersonal(buf, 'ZTxIdSequencHash')).toString('hex');
345
+ // Calculate the actual buffer size needed for outputs
346
+ let outputsBufferSize = 0;
347
+ for (const output of outputs) {
348
+ if (output.type === 'pkh') {
349
+ outputsBufferSize += 34; // 8 bytes amount + 26 bytes script
350
+ }
351
+ else if (output.type === 'op_return') {
352
+ outputsBufferSize += 8 + memoToScript(output.memo).length;
353
+ }
354
+ }
355
+ buf = Buffer.alloc(outputsBufferSize);
356
+ offset = 0;
357
+ for (const output of outputs) {
358
+ switch (output.type) {
359
+ case 'pkh': {
360
+ buf.writeUIntLE(output.amount, offset, 6); // 6 is the max
361
+ offset += 8;
362
+ const pkhscript = addressToScript(output.address);
363
+ pkhscript.copy(buf, offset);
364
+ offset += 26;
365
+ break;
366
+ }
367
+ case 'op_return': {
368
+ offset += 8;
369
+ const oprscript = memoToScript(output.memo);
370
+ oprscript.copy(buf, offset);
371
+ offset += oprscript.length;
372
+ break;
373
+ }
374
+ }
375
+ }
376
+ const outputsHash = Buffer.from(blake2bWithPersonal(buf.subarray(0, offset), 'ZTxIdOutputsHash')).toString('hex');
377
+ buf = Buffer.alloc(8 * utxos.length);
378
+ for (const [i, utxo] of utxos.entries()) {
379
+ buf.writeUIntLE(utxo.satoshis, 8 * i, 6);
380
+ }
381
+ const amountsHash = Buffer.from(blake2bWithPersonal(buf, 'ZTxTrAmountsHash')).toString('hex');
382
+ buf = Buffer.alloc(26 * utxos.length);
383
+ for (const [i, utxo] of utxos.entries()) {
384
+ const script = addressToScript(utxo.address);
385
+ script.copy(buf, 26 * i);
386
+ }
387
+ const scriptsHash = Buffer.from(blake2bWithPersonal(buf, 'ZTxTrScriptsHash')).toString('hex');
388
+ const signatures = [];
389
+ for (const utxo of utxos) {
390
+ buf = Buffer.alloc(32 + 4 + 8 + 26 + 4);
391
+ offset = 0;
392
+ const txid = Buffer.from(utxo.txid, 'hex');
393
+ txid.reverse();
394
+ txid.copy(buf, offset);
395
+ offset += 32;
396
+ buf.writeUInt32LE(utxo.outputIndex, offset);
397
+ offset += 4;
398
+ buf.writeUIntLE(utxo.satoshis, offset, 6);
399
+ offset += 8;
400
+ const script = addressToScript(utxo.address);
401
+ script.copy(buf, offset);
402
+ offset += 26;
403
+ buf.writeInt32LE(-1, offset);
404
+ const txInHash = Buffer.from(blake2bWithPersonal(buf, 'Zcash___TxInHash')).toString('hex');
405
+ buf = Buffer.alloc(1 + 32 * 6);
406
+ offset = 1;
407
+ buf[0] = 1;
408
+ Buffer.from(prevoutputsHash, 'hex').copy(buf, offset);
409
+ offset += 32;
410
+ Buffer.from(amountsHash, 'hex').copy(buf, offset);
411
+ offset += 32;
412
+ Buffer.from(scriptsHash, 'hex').copy(buf, offset);
413
+ offset += 32;
414
+ Buffer.from(sequencesHash, 'hex').copy(buf, offset);
415
+ offset += 32;
416
+ Buffer.from(outputsHash, 'hex').copy(buf, offset);
417
+ offset += 32;
418
+ Buffer.from(txInHash, 'hex').copy(buf, offset);
419
+ offset += 32;
420
+ const transparentHash = Buffer.from(blake2bWithPersonal(buf, 'ZTxIdTranspaHash')).toString('hex');
421
+ buf = Buffer.alloc(32 * 4);
422
+ offset = 0;
423
+ Buffer.from(headerHash, 'hex').copy(buf, offset);
424
+ offset += 32;
425
+ Buffer.from(transparentHash, 'hex').copy(buf, offset);
426
+ offset += 32;
427
+ Buffer.from('6f2fc8f98feafd94e74a0df4bed74391ee0b5a69945e4ced8ca8a095206f00ae', 'hex').copy(buf, offset);
428
+ offset += 32;
429
+ Buffer.from('9fbe4ed13b0c08e671c11a3407d84e1117cd45028a2eee1b9feae78b48a6e2c1', 'hex').copy(buf, offset);
430
+ offset += 32;
431
+ // Create personalization with NU6.1 branch ID
432
+ const personal = Buffer.alloc(16);
433
+ Buffer.from('ZcashTxHash_').copy(personal);
434
+ personal.writeUInt32LE(NU6_1_CONSENSUS_BRANCH_ID, 12); // NU6.1 consensus branch ID (FIXED!)
435
+ const sigHash = blake2bWithPersonal(buf, personal);
436
+ const signature = secp256k1.sign(sigHash, sk, { lowS: true, prehash: false });
437
+ const signatureDER = signature.toDERRawBytes();
438
+ signatures.push(signatureDER);
439
+ }
440
+ // Build final transaction - calculate required buffer size dynamically
441
+ // Header: 20 bytes (version + versionGroupId + consensusBranchId + lockTime + expiryHeight)
442
+ // Per input: 32 (txid) + 4 (vout) + ~1 (compactInt) + ~110 (sigScript: 5 + ~72 sig + 33 pubkey) + 4 (sequence) = ~151 bytes
443
+ // Per output: 8 (amount) + ~26-80 (script) = ~34-88 bytes
444
+ // Trailing: 3 bytes (empty sapling/orchard bundles)
445
+ const maxSigScriptSize = 5 + 73 + 33; // overhead + max DER sig + compressed pubkey
446
+ const perInputSize = 32 + 4 + 3 + maxSigScriptSize + 4; // txid + vout + compactInt + sigScript + sequence
447
+ const perOutputSize = 8 + 80; // amount + max script size (memo can be up to 80 chars)
448
+ const headerSize = 20;
449
+ const trailingSize = 3;
450
+ const compactIntSize = 3; // max for reasonable counts
451
+ const txBufferSize = headerSize +
452
+ compactIntSize +
453
+ utxos.length * perInputSize +
454
+ compactIntSize +
455
+ outputs.length * perOutputSize +
456
+ trailingSize;
457
+ buf = Buffer.alloc(txBufferSize);
458
+ offset = 0;
459
+ buf.writeUInt32LE(TX_VERSION, offset); // Transaction version
460
+ offset += 4;
461
+ buf.writeUInt32LE(TX_VERSION_GROUP_ID, offset); // Version group ID
462
+ offset += 4;
463
+ buf.writeUInt32LE(NU6_1_CONSENSUS_BRANCH_ID, offset); // NU6.1 consensus branch ID (FIXED!)
464
+ offset += 4;
465
+ buf.writeUInt32LE(0x00000000, offset); // Lock time
466
+ offset += 4;
467
+ buf.writeUInt32LE(height, offset); // Expiry height
468
+ offset += 4;
469
+ const txinc = writeCompactInt(utxos.length);
470
+ txinc.copy(buf, offset);
471
+ offset += txinc.length;
472
+ for (const [i, utxo] of utxos.entries()) {
473
+ const txid = Buffer.from(utxo.txid, 'hex');
474
+ txid.reverse();
475
+ txid.copy(buf, offset);
476
+ offset += 32;
477
+ buf.writeUInt32LE(utxo.outputIndex, offset);
478
+ offset += 4;
479
+ const ss = writeSigScript(signatures[i], pk);
480
+ const ssl = writeCompactInt(ss.length);
481
+ ssl.copy(buf, offset);
482
+ offset += ssl.length;
483
+ ss.copy(buf, offset);
484
+ offset += ss.length;
485
+ buf.writeInt32LE(-1, offset);
486
+ offset += 4;
487
+ }
488
+ const txoutc = writeCompactInt(outputs.length);
489
+ txoutc.copy(buf, offset);
490
+ offset += txoutc.length;
491
+ for (const out of outputs) {
492
+ switch (out.type) {
493
+ case 'pkh': {
494
+ buf.writeBigInt64LE(BigInt(out.amount), offset);
495
+ offset += 8;
496
+ const pkhscript = addressToScript(out.address);
497
+ pkhscript.copy(buf, offset);
498
+ offset += pkhscript.length;
499
+ break;
500
+ }
501
+ case 'op_return': {
502
+ offset += 8;
503
+ const memoscript = memoToScript(out.memo);
504
+ memoscript.copy(buf, offset);
505
+ offset += memoscript.length;
506
+ break;
507
+ }
508
+ }
509
+ }
510
+ // Add 000000 (empty sapling/orchard bundles)
511
+ offset += 3;
512
+ return buf.subarray(0, offset);
513
+ });
514
+ }
515
+
516
+ export { addressToScript, buildTx, getFee, getUTXOS, isValidAddr, mainnetPrefix, memoToScript, pkToAddr, sendRawTransaction, signAndFinalize, skToAddr, testnetPrefix, waitForTransaction, writeSigScript };
517
+ //# sourceMappingURL=index.esm.js.map