@btc-vision/transaction 1.4.0 → 1.5.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.
Files changed (40) hide show
  1. package/browser/buffer/BinaryReader.d.ts +1 -1
  2. package/browser/index.js +1 -1
  3. package/browser/index.js.LICENSE.txt +1 -3
  4. package/browser/keypair/Address.d.ts +1 -0
  5. package/browser/keypair/AddressVerificator.d.ts +1 -0
  6. package/browser/keypair/EcKeyPair.d.ts +4 -1
  7. package/browser/transaction/TransactionFactory.d.ts +0 -1
  8. package/browser/transaction/browser/Web3Provider.d.ts +2 -0
  9. package/browser/transaction/builders/CustomScriptTransaction.d.ts +7 -4
  10. package/browser/transaction/builders/DeploymentTransaction.d.ts +3 -0
  11. package/build/buffer/BinaryReader.d.ts +1 -1
  12. package/build/buffer/BinaryReader.js +1 -1
  13. package/build/buffer/BinaryWriter.js +2 -2
  14. package/build/keypair/Address.d.ts +1 -0
  15. package/build/keypair/Address.js +15 -2
  16. package/build/keypair/AddressVerificator.d.ts +1 -0
  17. package/build/keypair/AddressVerificator.js +4 -0
  18. package/build/keypair/EcKeyPair.d.ts +4 -1
  19. package/build/keypair/EcKeyPair.js +48 -21
  20. package/build/transaction/TransactionFactory.d.ts +0 -1
  21. package/build/transaction/TransactionFactory.js +18 -4
  22. package/build/transaction/browser/Web3Provider.d.ts +2 -0
  23. package/build/transaction/builders/CustomScriptTransaction.d.ts +7 -4
  24. package/build/transaction/builders/CustomScriptTransaction.js +24 -3
  25. package/build/transaction/builders/DeploymentTransaction.d.ts +3 -0
  26. package/build/transaction/builders/DeploymentTransaction.js +9 -1
  27. package/build/transaction/builders/TransactionBuilder.js +3 -2
  28. package/build/transaction/shared/TweakedTransaction.js +2 -2
  29. package/package.json +18 -18
  30. package/src/buffer/BinaryReader.ts +2 -2
  31. package/src/buffer/BinaryWriter.ts +2 -2
  32. package/src/keypair/Address.ts +21 -0
  33. package/src/keypair/AddressVerificator.ts +6 -1
  34. package/src/keypair/EcKeyPair.ts +91 -34
  35. package/src/transaction/TransactionFactory.ts +21 -7
  36. package/src/transaction/browser/Web3Provider.ts +6 -0
  37. package/src/transaction/builders/CustomScriptTransaction.ts +38 -7
  38. package/src/transaction/builders/DeploymentTransaction.ts +17 -3
  39. package/src/transaction/builders/TransactionBuilder.ts +4 -3
  40. package/src/transaction/shared/TweakedTransaction.ts +2 -2
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@btc-vision/transaction",
3
3
  "type": "module",
4
- "version": "1.4.0",
4
+ "version": "1.5.0",
5
5
  "author": "BlobMaster41",
6
6
  "description": "OPNet transaction library allows you to create and sign transactions for the OPNet network.",
7
7
  "engines": {
@@ -64,16 +64,16 @@
64
64
  "docs": "typedoc --out docs --exclude 'src/tests/*.ts' --tsconfig tsconfig.json --readme README.md --name OPNet --plugin typedoc-material-theme --themeColor '#cb9820' --exclude src/tests/test.ts --exclude src/index.ts src"
65
65
  },
66
66
  "devDependencies": {
67
- "@babel/core": "^7.26.9",
67
+ "@babel/core": "^7.27.1",
68
68
  "@babel/plugin-proposal-class-properties": "^7.18.6",
69
- "@babel/plugin-transform-runtime": "^7.26.9",
70
- "@babel/preset-env": "^7.26.9",
71
- "@babel/preset-flow": "^7.25.9",
72
- "@babel/preset-react": "^7.26.3",
73
- "@babel/preset-typescript": "^7.26.0",
74
- "@types/node": "^22.13.10",
69
+ "@babel/plugin-transform-runtime": "^7.27.1",
70
+ "@babel/preset-env": "^7.27.2",
71
+ "@babel/preset-flow": "^7.27.1",
72
+ "@babel/preset-react": "^7.27.1",
73
+ "@babel/preset-typescript": "^7.27.1",
74
+ "@types/node": "^22.15.21",
75
75
  "@types/sha.js": "^2.4.4",
76
- "eslint": "^9.22.0",
76
+ "eslint": "^9.27.0",
77
77
  "gulp": "^5.0.0",
78
78
  "gulp-cached": "^1.1.1",
79
79
  "gulp-typescript": "^6.0.0-alpha.1",
@@ -82,20 +82,20 @@
82
82
  "prettier": "^3.5.3",
83
83
  "stream-browserify": "^3.0.0",
84
84
  "stream-http": "^3.2.0",
85
- "typedoc": "^0.27.9",
86
- "typescript-eslint": "^8.26.0",
85
+ "typedoc": "^0.28.5",
86
+ "typescript-eslint": "^8.32.1",
87
87
  "webpack-cli": "^6.0.1"
88
88
  },
89
89
  "dependencies": {
90
90
  "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
91
91
  "@bitcoinerlab/secp256k1": "^1.1.1",
92
- "@btc-vision/bitcoin": "^6.3.6",
93
- "@btc-vision/bitcoin-rpc": "^1.0.0",
92
+ "@btc-vision/bitcoin": "^6.4.1",
93
+ "@btc-vision/bitcoin-rpc": "^1.0.1",
94
94
  "@btc-vision/logger": "^1.0.6",
95
- "@eslint/js": "^9.22.0",
96
- "@noble/secp256k1": "^2.1.0",
95
+ "@eslint/js": "^9.27.0",
96
+ "@noble/secp256k1": "^2.2.3",
97
97
  "assert": "^2.1.0",
98
- "babel-loader": "^9.2.1",
98
+ "babel-loader": "^10.0.0",
99
99
  "babel-plugin-transform-import-meta": "^2.3.2",
100
100
  "babel-preset-react": "^6.24.1",
101
101
  "babelify": "^10.0.0",
@@ -112,7 +112,7 @@
112
112
  "sha.js": "^2.4.11",
113
113
  "ts-loader": "^9.5.2",
114
114
  "ts-node": "^10.9.2",
115
- "typescript": "^5.7.3",
116
- "webpack": "^5.98.0"
115
+ "typescript": "^5.8.3",
116
+ "webpack": "^5.99.9"
117
117
  }
118
118
  }
@@ -182,7 +182,7 @@ export class BinaryReader {
182
182
  * Reads a string of the given length in raw bytes. By default, do NOT zero-stop
183
183
  * (matching how we wrote the raw bytes).
184
184
  */
185
- public readString(length: u16): string {
185
+ public readString(length: u32): string {
186
186
  const textDecoder = new TextDecoder();
187
187
  const bytes = this.readBytes(length, false);
188
188
  return textDecoder.decode(bytes);
@@ -192,7 +192,7 @@ export class BinaryReader {
192
192
  * Reads a string that was written as [u16 length][raw bytes].
193
193
  */
194
194
  public readStringWithLength(be: boolean = true): string {
195
- const length = this.readU16(be);
195
+ const length = this.readU32(be);
196
196
  return this.readString(length);
197
197
  }
198
198
 
@@ -160,9 +160,9 @@ export class BinaryWriter {
160
160
  }
161
161
 
162
162
  public writeStringWithLength(value: string): void {
163
- this.allocSafe(U16_BYTE_LENGTH + value.length);
163
+ this.allocSafe(U32_BYTE_LENGTH + value.length);
164
164
 
165
- this.writeU16(value.length);
165
+ this.writeU32(value.length);
166
166
  this.writeString(value);
167
167
  }
168
168
 
@@ -12,6 +12,7 @@ import { BitcoinUtils } from '../utils/BitcoinUtils.js';
12
12
  */
13
13
  export class Address extends Uint8Array {
14
14
  #p2tr: string | undefined;
15
+ #p2op: string | undefined;
15
16
  #network: Network | undefined;
16
17
  #originalPublicKey: Uint8Array | undefined;
17
18
  #keyPair: ECPairInterface | undefined;
@@ -316,6 +317,26 @@ export class Address extends Uint8Array {
316
317
  throw new Error('Public key not set');
317
318
  }
318
319
 
320
+ /**
321
+ * Get an opnet address encoded in bech32m format.
322
+ * @param network
323
+ */
324
+ public p2op(network: Network): string {
325
+ if (this.#p2op && this.#network === network) {
326
+ return this.#p2op;
327
+ }
328
+
329
+ const p2opAddy: string | undefined = EcKeyPair.p2op(this, network);
330
+ if (p2opAddy) {
331
+ this.#network = network;
332
+ this.#p2op = p2opAddy;
333
+
334
+ return p2opAddy;
335
+ }
336
+
337
+ throw new Error('Public key not set');
338
+ }
339
+
319
340
  public toTweakedHybridPublicKeyHex(): string {
320
341
  if (!this.#tweakedUncompressed) {
321
342
  throw new Error('Public key not set');
@@ -7,6 +7,7 @@ initEccLib(ecc);
7
7
 
8
8
  export enum AddressTypes {
9
9
  P2PKH = 'P2PKH',
10
+ P2OP = 'P2OP',
10
11
  P2SH_OR_P2SH_P2WPKH = 'P2SH_OR_P2SH-P2WPKH',
11
12
  P2PK = 'P2PK',
12
13
  P2TR = 'P2TR',
@@ -169,11 +170,11 @@ export class AddressVerificator {
169
170
  try {
170
171
  // First, try to decode as a Base58Check address (P2PKH, P2SH, or P2SH-P2WPKH)
171
172
  const decodedBase58 = address.fromBase58Check(addy);
172
-
173
173
  if (decodedBase58.version === network.pubKeyHash) {
174
174
  // P2PKH: Legacy address (starting with '1' for mainnet, 'm/n' for testnet)
175
175
  return AddressTypes.P2PKH;
176
176
  }
177
+
177
178
  if (decodedBase58.version === network.scriptHash) {
178
179
  // P2SH: Could be P2SH (general) or P2SH-P2WPKH (wrapped SegWit)
179
180
  return AddressTypes.P2SH_OR_P2SH_P2WPKH;
@@ -183,12 +184,16 @@ export class AddressVerificator {
183
184
  try {
184
185
  // Try to decode as a Bech32 or Bech32m address (P2WPKH or P2TR)
185
186
  const decodedBech32 = address.fromBech32(addy);
187
+ if (decodedBech32.prefix === network.bech32Opnet && decodedBech32.version === 16) {
188
+ return AddressTypes.P2OP;
189
+ }
186
190
 
187
191
  if (decodedBech32.prefix === network.bech32) {
188
192
  // P2WPKH: SegWit address (starting with 'bc1q' for mainnet, 'tb1q' for testnet)
189
193
  if (decodedBech32.version === 0 && decodedBech32.data.length === 20) {
190
194
  return AddressTypes.P2WPKH;
191
195
  }
196
+
192
197
  // P2TR: Taproot address (starting with 'bc1p' for mainnet, 'tb1p' for testnet)
193
198
  if (decodedBech32.version === 1 && decodedBech32.data.length === 32) {
194
199
  return AddressTypes.P2TR;
@@ -1,18 +1,23 @@
1
1
  import * as ecc from '@bitcoinerlab/secp256k1';
2
2
  import bip32, { BIP32API, BIP32Factory, BIP32Interface } from 'bip32';
3
- import {
3
+ import bitcoin, {
4
4
  address,
5
+ fromOutputScript,
5
6
  initEccLib,
6
7
  Network,
7
8
  networks,
9
+ opcodes,
8
10
  payments,
11
+ script,
9
12
  Signer,
10
- taggedHash,
11
13
  toXOnly,
12
14
  } from '@btc-vision/bitcoin';
13
15
  import { ECPairAPI, ECPairFactory, ECPairInterface } from 'ecpair';
14
16
  import { IWallet } from './interfaces/IWallet.js';
15
- import { CURVE, ProjectivePoint as Point } from '@noble/secp256k1';
17
+ import { secp256k1 } from '@noble/curves/secp256k1';
18
+ import { bytesToNumberBE, concatBytes, utf8ToBytes } from '@noble/curves/abstract/utils';
19
+ import { mod } from '@noble/curves/abstract/modular';
20
+ import { sha256 } from '@noble/hashes/sha2';
16
21
 
17
22
  initEccLib(ecc);
18
23
 
@@ -21,10 +26,16 @@ if (!BIP32factory) {
21
26
  throw new Error('Failed to load BIP32 library');
22
27
  }
23
28
 
24
- const mod = (a: bigint, b: bigint): bigint => {
25
- const result = a % b;
26
- return result >= 0n ? result : result + b;
27
- };
29
+ secp256k1.utils.precompute(8);
30
+
31
+ const { ProjectivePoint: Point, CURVE } = secp256k1;
32
+
33
+ const TAP_TAG = utf8ToBytes('TapTweak');
34
+ const TAP_TAG_HASH = sha256(TAP_TAG);
35
+
36
+ function tapTweakHash(x: Uint8Array): Uint8Array {
37
+ return sha256(concatBytes(TAP_TAG_HASH, TAP_TAG_HASH, x));
38
+ }
28
39
 
29
40
  /**
30
41
  * Class for handling EC key pairs
@@ -195,6 +206,32 @@ export class EcKeyPair {
195
206
  return address;
196
207
  }
197
208
 
209
+ /**
210
+ * Generate a P2OP address
211
+ * @param bytes - The bytes to use for the P2OP address
212
+ * @param network - The network to use
213
+ * @param deploymentVersion - The deployment version (default is 0)
214
+ * @returns {string} - The generated P2OP address
215
+ */
216
+ public static p2op(
217
+ bytes: Buffer | Uint8Array,
218
+ network: Network = networks.bitcoin,
219
+ deploymentVersion: number = 0,
220
+ ): string {
221
+ // custom opnet contract addresses
222
+ const witnessProgram = Buffer.concat([
223
+ Buffer.from([deploymentVersion]),
224
+ bitcoin.crypto.hash160(Buffer.from(bytes)),
225
+ ]);
226
+
227
+ if (witnessProgram.length < 2 || witnessProgram.length > 40) {
228
+ throw new Error('Witness program must be 2-40 bytes.');
229
+ }
230
+
231
+ const scriptData = script.compile([opcodes.OP_16, witnessProgram]);
232
+ return fromOutputScript(scriptData, network);
233
+ }
234
+
198
235
  /**
199
236
  * Get the address of a xOnly tweaked public key
200
237
  * @param {string} tweakedPubKeyHex - The xOnly tweaked public key hex string
@@ -225,42 +262,44 @@ export class EcKeyPair {
225
262
 
226
263
  /**
227
264
  * Tweak a public key
228
- * @param {string | Buffer} compressedPubKeyHex - The compressed public key hex string
265
+ * @param {Buffer | Uint8Array | string} pub - The public key to tweak
229
266
  * @returns {Buffer} - The tweaked public key hex string
230
267
  * @throws {Error} - If the public key cannot be tweaked
231
268
  */
232
- public static tweakPublicKey(compressedPubKeyHex: string | Buffer): Buffer {
233
- if (typeof compressedPubKeyHex === 'string' && compressedPubKeyHex.startsWith('0x')) {
234
- compressedPubKeyHex = compressedPubKeyHex.slice(2);
235
- }
236
-
237
- if (typeof compressedPubKeyHex !== 'string') {
238
- compressedPubKeyHex = compressedPubKeyHex.toString('hex');
239
- }
269
+ public static tweakPublicKey(pub: Uint8Array | Buffer | string): Buffer {
270
+ if (typeof pub === 'string' && pub.startsWith('0x')) pub = pub.slice(2);
240
271
 
241
- // Convert the compressed public key hex string to a Point on the curve
242
- let P = Point.fromHex(compressedPubKeyHex);
272
+ const P = Point.fromHex(pub);
273
+ const Peven = (P.y & 1n) === 0n ? P : P.negate();
243
274
 
244
- // Ensure the point has an even y-coordinate
245
- if ((P.y & 1n) !== 0n) {
246
- // Negate the point to get an even y-coordinate
247
- P = P.negate();
248
- }
249
-
250
- // Get the x-coordinate (32 bytes) of the point
251
- const x = P.toRawBytes(true).slice(1); // Remove the prefix byte
252
-
253
- // Compute the tweak t = H_tapTweak(x)
254
- const tHash = taggedHash('TapTweak', Buffer.from(x));
255
- const t = mod(BigInt('0x' + Buffer.from(tHash).toString('hex')), CURVE.n);
275
+ const xBytes = Peven.toRawBytes(true).subarray(1);
276
+ const tBytes = tapTweakHash(xBytes);
277
+ const t = mod(bytesToNumberBE(tBytes), CURVE.n);
256
278
 
257
- // Compute Q = P + t*G (where G is the generator point)
258
- const Q = P.add(Point.BASE.mul(t));
259
-
260
- // Return the tweaked public key in compressed form (hex string)
279
+ const Q = Peven.add(Point.BASE.multiply(t));
261
280
  return Buffer.from(Q.toRawBytes(true));
262
281
  }
263
282
 
283
+ /**
284
+ * Tweak a batch of public keys
285
+ * @param {readonly Uint8Array[]} pubkeys - The public keys to tweak
286
+ * @param {bigint} tweakScalar - The scalar to use for tweaking
287
+ * @returns {Uint8Array[]} - The tweaked public keys
288
+ */
289
+ public static tweakBatchSharedT(
290
+ pubkeys: readonly Uint8Array[],
291
+ tweakScalar: bigint,
292
+ ): Uint8Array[] {
293
+ const T = Point.BASE.multiply(tweakScalar);
294
+
295
+ return pubkeys.map((bytes) => {
296
+ const P = Point.fromHex(bytes);
297
+ const P_even = P.hasEvenY() ? P : P.negate();
298
+ const Q = P_even.add(T);
299
+ return Q.toRawBytes(true);
300
+ });
301
+ }
302
+
264
303
  /**
265
304
  * Generate a random wallet
266
305
  * @param {Network} network - The network to use
@@ -337,6 +376,24 @@ export class EcKeyPair {
337
376
  return wallet.address;
338
377
  }
339
378
 
379
+ /**
380
+ * Get the legacy address from a keypair
381
+ * @param publicKey
382
+ * @param {Network} network - The network to use
383
+ * @returns {string} - The legacy address
384
+ */
385
+ public static getP2PKH(
386
+ publicKey: Buffer | Uint8Array,
387
+ network: Network = networks.bitcoin,
388
+ ): string {
389
+ const wallet = payments.p2pkh({ pubkey: Buffer.from(publicKey), network: network });
390
+ if (!wallet.address) {
391
+ throw new Error('Failed to generate wallet');
392
+ }
393
+
394
+ return wallet.address;
395
+ }
396
+
340
397
  /**
341
398
  * Get the legacy address from a keypair
342
399
  * @param {ECPairInterface} keyPair - The keypair to get the address for
@@ -30,8 +30,6 @@ export interface DeploymentResult {
30
30
 
31
31
  readonly contractAddress: string;
32
32
  readonly contractPubKey: string;
33
- readonly p2trAddress: string;
34
-
35
33
  readonly preimage: string;
36
34
 
37
35
  readonly utxos: UTXO[];
@@ -89,13 +87,22 @@ export class TransactionFactory {
89
87
  throw new Error('Field "from" not provided.');
90
88
  }
91
89
 
90
+ if (!interactionParameters.utxos[0]) {
91
+ throw new Error('Missing at least one UTXO.');
92
+ }
93
+
94
+ if (!('signer' in interactionParameters)) {
95
+ throw new Error('Field "signer" not provided, OP_WALLET not detected.');
96
+ }
97
+
98
+ const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs);
92
99
  const preTransaction: CustomScriptTransaction = new CustomScriptTransaction({
93
100
  ...interactionParameters,
94
101
  utxos: [interactionParameters.utxos[0]], // we simulate one input here.
102
+ optionalInputs: inputs,
95
103
  });
96
104
 
97
105
  // we don't sign that transaction, we just need the parameters.
98
-
99
106
  await preTransaction.generateTransactionMinimalSignatures();
100
107
 
101
108
  const parameters: IFundingTransactionParameters =
@@ -119,7 +126,12 @@ export class TransactionFactory {
119
126
 
120
127
  parameters.estimatedFees = feeEstimationFundingTransaction.estimatedFees;
121
128
 
122
- const signedTransaction = await this.createFundTransaction(parameters);
129
+ const signedTransaction = await this.createFundTransaction({
130
+ ...parameters,
131
+ optionalOutputs: [],
132
+ optionalInputs: [],
133
+ });
134
+
123
135
  if (!signedTransaction) {
124
136
  throw new Error('Could not sign funding transaction.');
125
137
  }
@@ -132,10 +144,13 @@ export class TransactionFactory {
132
144
 
133
145
  const newParams: ICustomTransactionParameters = {
134
146
  ...interactionParameters,
135
- utxos: this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.to, 0), // always 0
147
+ utxos: [
148
+ ...this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.to, 0),
149
+ ], // always 0
136
150
  randomBytes: preTransaction.getRndBytes(),
137
151
  nonWitnessUtxo: signedTransaction.tx.toBuffer(),
138
152
  estimatedFees: preTransaction.estimatedFees,
153
+ optionalInputs: inputs,
139
154
  };
140
155
 
141
156
  const finalTransaction: CustomScriptTransaction = new CustomScriptTransaction(newParams);
@@ -352,9 +367,8 @@ export class TransactionFactory {
352
367
 
353
368
  return {
354
369
  transaction: [signedTransaction.toHex(), outTx.toHex()],
355
- contractAddress: finalTransaction.contractAddress.p2tr(deploymentParameters.network),
370
+ contractAddress: finalTransaction.getContractAddress(), //finalTransaction.contractAddress.p2tr(deploymentParameters.network),
356
371
  contractPubKey: finalTransaction.contractPubKey,
357
- p2trAddress: finalTransaction.p2trAddress,
358
372
  utxos: [refundUTXO],
359
373
  preimage: preTransaction.getPreimage().toString('hex'),
360
374
  };
@@ -4,6 +4,7 @@ import {
4
4
  } from '../interfaces/ITransactionParameters.js';
5
5
  import { UTXO } from '../../utxo/interfaces/IUTXO.js';
6
6
  import { DeploymentResult, InteractionResponse } from '../TransactionFactory';
7
+ import { ICustomTransactionParameters } from '../builders/CustomScriptTransaction.js';
7
8
 
8
9
  export type InteractionParametersWithoutSigner = Omit<
9
10
  IInteractionParameters,
@@ -15,6 +16,11 @@ export type IDeploymentParametersWithoutSigner = Omit<
15
16
  'signer' | 'network' | 'preimage'
16
17
  >;
17
18
 
19
+ export type CustomTransactionWithoutSigner = Omit<
20
+ ICustomTransactionParameters,
21
+ 'signer' | 'preimage'
22
+ >;
23
+
18
24
  export interface BroadcastTransactionOptions {
19
25
  raw: string;
20
26
  psbt: boolean;
@@ -19,10 +19,13 @@ import { AddressGenerator } from '../../generators/AddressGenerator.js';
19
19
  import { ECPairInterface } from 'ecpair';
20
20
 
21
21
  export interface ICustomTransactionParameters extends SharedInteractionParameters {
22
- readonly script: (Buffer | Stack)[];
23
- readonly witnesses: Buffer[];
22
+ script: (Buffer | Stack)[];
23
+ witnesses: Buffer[];
24
24
 
25
- readonly to: string;
25
+ /** optional Taproot annex payload (without the 0x50 prefix) */
26
+ annex?: Buffer;
27
+
28
+ to: string;
26
29
  }
27
30
 
28
31
  /**
@@ -91,6 +94,7 @@ export class CustomScriptTransaction extends TransactionBuilder<TransactionType.
91
94
  * @private
92
95
  */
93
96
  private readonly witnesses: Buffer[];
97
+ private readonly annexData?: Buffer;
94
98
 
95
99
  public constructor(parameters: ICustomTransactionParameters) {
96
100
  super(parameters);
@@ -202,7 +206,10 @@ export class CustomScriptTransaction extends TransactionBuilder<TransactionType.
202
206
  for (let i = 0; i < transaction.data.inputs.length; i++) {
203
207
  if (i === 0) {
204
208
  // multi sig input
205
- transaction.signInput(0, this.contractSigner);
209
+ try {
210
+ transaction.signInput(0, this.contractSigner);
211
+ } catch (e) {}
212
+
206
213
  transaction.signInput(0, this.getSignerKey());
207
214
 
208
215
  transaction.finalizeInput(0, this.customFinalizer);
@@ -250,6 +257,21 @@ export class CustomScriptTransaction extends TransactionBuilder<TransactionType.
250
257
  };
251
258
  }
252
259
 
260
+ protected getScriptSolution(input: PsbtInput): Buffer[] {
261
+ if (!input.tapScriptSig) {
262
+ throw new Error('Tap script signature is required');
263
+ }
264
+
265
+ const witnesses: Buffer[] = [...this.witnesses];
266
+ if (input.tapScriptSig) {
267
+ for (const sig of input.tapScriptSig) {
268
+ witnesses.push(sig.signature);
269
+ }
270
+ }
271
+
272
+ return witnesses;
273
+ }
274
+
253
275
  /**
254
276
  * Generate the contract seed for the deployment
255
277
  * @private
@@ -261,18 +283,27 @@ export class CustomScriptTransaction extends TransactionBuilder<TransactionType.
261
283
  /**
262
284
  * Finalize the transaction
263
285
  * @param _inputIndex
264
- * @param _input
286
+ * @param input
265
287
  */
266
- private customFinalizer = (_inputIndex: number, _input: PsbtInput) => {
288
+ private customFinalizer = (_inputIndex: number, input: PsbtInput) => {
267
289
  if (!this.tapLeafScript) {
268
290
  throw new Error('Tap leaf script is required');
269
291
  }
270
292
 
271
- const scriptSolution = this.witnesses;
293
+ const scriptSolution = this.getScriptSolution(input);
272
294
  const witness = scriptSolution
273
295
  .concat(this.tapLeafScript.script)
274
296
  .concat(this.tapLeafScript.controlBlock);
275
297
 
298
+ if (this.annexData && this.annexData.length > 0) {
299
+ const annex =
300
+ this.annexData[0] === 0x50
301
+ ? this.annexData
302
+ : Buffer.concat([Buffer.from([0x50]), this.annexData]);
303
+
304
+ witness.push(annex);
305
+ }
306
+
276
307
  return {
277
308
  finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witness),
278
309
  };
@@ -30,10 +30,8 @@ const p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn;
30
30
  export class DeploymentTransaction extends TransactionBuilder<TransactionType.DEPLOYMENT> {
31
31
  public static readonly MAXIMUM_CONTRACT_SIZE = 128 * 1024;
32
32
  public type: TransactionType.DEPLOYMENT = TransactionType.DEPLOYMENT;
33
-
34
33
  protected readonly preimage: Buffer; // ALWAYS 128 bytes for the preimage
35
34
  protected readonly rewardChallenge: IMineableReward;
36
-
37
35
  /**
38
36
  * The contract address
39
37
  * @protected
@@ -44,6 +42,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
44
42
  * @private
45
43
  */
46
44
  protected tapLeafScript: TapLeafScript | null = null;
45
+ private readonly deploymentVersion: number = 0x00;
47
46
  /**
48
47
  * The target script redeem
49
48
  * @private
@@ -105,6 +104,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
105
104
  * @private
106
105
  */
107
106
  private readonly randomBytes: Buffer;
107
+ private _computedAddress: string | undefined;
108
108
 
109
109
  public constructor(parameters: IDeploymentParameters) {
110
110
  // TODO: Add legacy deployment, this is only p2tr.
@@ -190,6 +190,20 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
190
190
  return this.preimage;
191
191
  }
192
192
 
193
+ public getContractAddress(): string {
194
+ if (this._computedAddress) {
195
+ return this._computedAddress;
196
+ }
197
+
198
+ this._computedAddress = EcKeyPair.p2op(
199
+ this.contractSeed,
200
+ this.network,
201
+ this.deploymentVersion,
202
+ );
203
+
204
+ return this._computedAddress;
205
+ }
206
+
193
207
  /**
194
208
  * Get the contract signer public key
195
209
  * @protected
@@ -243,7 +257,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
243
257
  // ALWAYS THE FIRST INPUT.
244
258
  this.addOutput({
245
259
  value: Number(amountToCA),
246
- address: this.contractAddress.p2tr(this.network),
260
+ address: this.getContractAddress(),
247
261
  });
248
262
 
249
263
  // ALWAYS SECOND.
@@ -27,7 +27,7 @@ import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
27
27
  initEccLib(ecc);
28
28
 
29
29
  export const MINIMUM_AMOUNT_REWARD: bigint = 540n;
30
- export const MINIMUM_AMOUNT_CA: bigint = 330n;
30
+ export const MINIMUM_AMOUNT_CA: bigint = 297n;
31
31
 
32
32
  /**
33
33
  * Allows to build a transaction like you would on Ethereum.
@@ -36,9 +36,10 @@ export const MINIMUM_AMOUNT_CA: bigint = 330n;
36
36
  * @class TransactionBuilder
37
37
  */
38
38
  export abstract class TransactionBuilder<T extends TransactionType> extends TweakedTransaction {
39
+ // Cancel script
39
40
  public static readonly LOCK_LEAF_SCRIPT: Buffer = script.compile([
40
- opcodes.OP_0,
41
- //opcodes.OP_VERIFY, - verify that this is not needed.
41
+ opcodes.OP_FALSE,
42
+ opcodes.OP_VERIFY,
42
43
  ]);
43
44
 
44
45
  public static readonly MINIMUM_DUST: bigint = 50n;
@@ -151,8 +151,8 @@ export abstract class TweakedTransaction extends Logger {
151
151
 
152
152
  function readVarInt(): number {
153
153
  const varint = varuint.decode(Buffer, offset);
154
- offset += varuint.decode.bytes;
155
- return varint;
154
+ offset += varint.bytes;
155
+ return varint.numberValue || 0;
156
156
  }
157
157
 
158
158
  function readVarSlice(): Buffer {