@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.
- package/browser/buffer/BinaryReader.d.ts +1 -1
- package/browser/index.js +1 -1
- package/browser/index.js.LICENSE.txt +1 -3
- package/browser/keypair/Address.d.ts +1 -0
- package/browser/keypair/AddressVerificator.d.ts +1 -0
- package/browser/keypair/EcKeyPair.d.ts +4 -1
- package/browser/transaction/TransactionFactory.d.ts +0 -1
- package/browser/transaction/browser/Web3Provider.d.ts +2 -0
- package/browser/transaction/builders/CustomScriptTransaction.d.ts +7 -4
- package/browser/transaction/builders/DeploymentTransaction.d.ts +3 -0
- package/build/buffer/BinaryReader.d.ts +1 -1
- package/build/buffer/BinaryReader.js +1 -1
- package/build/buffer/BinaryWriter.js +2 -2
- package/build/keypair/Address.d.ts +1 -0
- package/build/keypair/Address.js +15 -2
- package/build/keypair/AddressVerificator.d.ts +1 -0
- package/build/keypair/AddressVerificator.js +4 -0
- package/build/keypair/EcKeyPair.d.ts +4 -1
- package/build/keypair/EcKeyPair.js +48 -21
- package/build/transaction/TransactionFactory.d.ts +0 -1
- package/build/transaction/TransactionFactory.js +18 -4
- package/build/transaction/browser/Web3Provider.d.ts +2 -0
- package/build/transaction/builders/CustomScriptTransaction.d.ts +7 -4
- package/build/transaction/builders/CustomScriptTransaction.js +24 -3
- package/build/transaction/builders/DeploymentTransaction.d.ts +3 -0
- package/build/transaction/builders/DeploymentTransaction.js +9 -1
- package/build/transaction/builders/TransactionBuilder.js +3 -2
- package/build/transaction/shared/TweakedTransaction.js +2 -2
- package/package.json +18 -18
- package/src/buffer/BinaryReader.ts +2 -2
- package/src/buffer/BinaryWriter.ts +2 -2
- package/src/keypair/Address.ts +21 -0
- package/src/keypair/AddressVerificator.ts +6 -1
- package/src/keypair/EcKeyPair.ts +91 -34
- package/src/transaction/TransactionFactory.ts +21 -7
- package/src/transaction/browser/Web3Provider.ts +6 -0
- package/src/transaction/builders/CustomScriptTransaction.ts +38 -7
- package/src/transaction/builders/DeploymentTransaction.ts +17 -3
- package/src/transaction/builders/TransactionBuilder.ts +4 -3
- 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
|
+
"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.
|
|
67
|
+
"@babel/core": "^7.27.1",
|
|
68
68
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
69
|
-
"@babel/plugin-transform-runtime": "^7.
|
|
70
|
-
"@babel/preset-env": "^7.
|
|
71
|
-
"@babel/preset-flow": "^7.
|
|
72
|
-
"@babel/preset-react": "^7.
|
|
73
|
-
"@babel/preset-typescript": "^7.
|
|
74
|
-
"@types/node": "^22.
|
|
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.
|
|
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.
|
|
86
|
-
"typescript-eslint": "^8.
|
|
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.
|
|
93
|
-
"@btc-vision/bitcoin-rpc": "^1.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.
|
|
96
|
-
"@noble/secp256k1": "^2.
|
|
95
|
+
"@eslint/js": "^9.27.0",
|
|
96
|
+
"@noble/secp256k1": "^2.2.3",
|
|
97
97
|
"assert": "^2.1.0",
|
|
98
|
-
"babel-loader": "^
|
|
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.
|
|
116
|
-
"webpack": "^5.
|
|
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:
|
|
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.
|
|
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(
|
|
163
|
+
this.allocSafe(U32_BYTE_LENGTH + value.length);
|
|
164
164
|
|
|
165
|
-
this.
|
|
165
|
+
this.writeU32(value.length);
|
|
166
166
|
this.writeString(value);
|
|
167
167
|
}
|
|
168
168
|
|
package/src/keypair/Address.ts
CHANGED
|
@@ -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;
|
package/src/keypair/EcKeyPair.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 {
|
|
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(
|
|
233
|
-
if (typeof
|
|
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
|
-
|
|
242
|
-
|
|
272
|
+
const P = Point.fromHex(pub);
|
|
273
|
+
const Peven = (P.y & 1n) === 0n ? P : P.negate();
|
|
243
274
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
23
|
-
|
|
22
|
+
script: (Buffer | Stack)[];
|
|
23
|
+
witnesses: Buffer[];
|
|
24
24
|
|
|
25
|
-
|
|
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
|
-
|
|
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
|
|
286
|
+
* @param input
|
|
265
287
|
*/
|
|
266
|
-
private customFinalizer = (_inputIndex: number,
|
|
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.
|
|
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.
|
|
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 =
|
|
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.
|
|
41
|
-
|
|
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 +=
|
|
155
|
-
return varint;
|
|
154
|
+
offset += varint.bytes;
|
|
155
|
+
return varint.numberValue || 0;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
function readVarSlice(): Buffer {
|