cashscript 0.9.1 → 0.10.0-next.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/dist/{Contract.d.ts → src/Contract.d.ts} +7 -5
- package/dist/{Contract.js → src/Contract.js} +12 -2
- package/dist/{Errors.d.ts → src/Errors.d.ts} +2 -2
- package/dist/{Errors.js → src/Errors.js} +3 -3
- package/dist/src/LibauthTemplate.d.ts +12 -0
- package/dist/src/LibauthTemplate.js +472 -0
- package/dist/{SignatureTemplate.d.ts → src/SignatureTemplate.d.ts} +1 -1
- package/dist/{Transaction.d.ts → src/Transaction.d.ts} +13 -12
- package/dist/{Transaction.js → src/Transaction.js} +76 -81
- package/dist/{TransactionBuilder.js → src/TransactionBuilder.js} +3 -3
- package/dist/{index.d.ts → src/index.d.ts} +3 -2
- package/dist/{index.js → src/index.js} +3 -2
- package/dist/{interfaces.d.ts → src/interfaces.d.ts} +1 -1
- package/dist/src/network/MockNetworkProvider.d.ts +14 -0
- package/dist/src/network/MockNetworkProvider.js +46 -0
- package/dist/{network → src/network}/index.d.ts +1 -0
- package/dist/{network → src/network}/index.js +1 -0
- package/dist/{utils.d.ts → src/utils.d.ts} +8 -5
- package/dist/{utils.js → src/utils.js} +61 -24
- package/dist/test/JestExtensions.d.ts +9 -0
- package/dist/test/JestExtensions.js +66 -0
- package/package.json +8 -6
- /package/dist/{Argument.d.ts → src/Argument.d.ts} +0 -0
- /package/dist/{Argument.js → src/Argument.js} +0 -0
- /package/dist/{SignatureTemplate.js → src/SignatureTemplate.js} +0 -0
- /package/dist/{TransactionBuilder.d.ts → src/TransactionBuilder.d.ts} +0 -0
- /package/dist/{constants.d.ts → src/constants.d.ts} +0 -0
- /package/dist/{constants.js → src/constants.js} +0 -0
- /package/dist/{interfaces.js → src/interfaces.js} +0 -0
- /package/dist/{network → src/network}/BitcoinRpcNetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/BitcoinRpcNetworkProvider.js +0 -0
- /package/dist/{network → src/network}/ElectrumNetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/ElectrumNetworkProvider.js +0 -0
- /package/dist/{network → src/network}/FullStackNetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/FullStackNetworkProvider.js +0 -0
- /package/dist/{network → src/network}/NetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/NetworkProvider.js +0 -0
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import bip68 from 'bip68';
|
|
2
|
-
import { hexToBin,
|
|
2
|
+
import { hexToBin, decodeTransaction, } from '@bitauth/libauth';
|
|
3
3
|
import delay from 'delay';
|
|
4
|
-
import {
|
|
4
|
+
import { placeholder, scriptToBytecode, } from '@cashscript/utils';
|
|
5
5
|
import deepEqual from 'fast-deep-equal';
|
|
6
6
|
import { isUtxoP2PKH, } from './interfaces.js';
|
|
7
|
-
import {
|
|
7
|
+
import { createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, buildError, validateOutput, utxoComparator, calculateDust, getOutputSize, utxoTokenComparator, } from './utils.js';
|
|
8
8
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
9
9
|
import { P2PKH_INPUT_SIZE } from './constants.js';
|
|
10
|
+
import { TransactionBuilder } from './TransactionBuilder.js';
|
|
11
|
+
import MockNetworkProvider from './network/MockNetworkProvider.js';
|
|
12
|
+
import { buildTemplate, debugTemplate, evaluateTemplate, getBitauthUri, } from './LibauthTemplate.js';
|
|
10
13
|
export class Transaction {
|
|
11
|
-
constructor(
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
14
|
-
this.redeemScript = redeemScript;
|
|
14
|
+
constructor(contract, unlocker, abiFunction, args, selector) {
|
|
15
|
+
this.contract = contract;
|
|
16
|
+
this.unlocker = unlocker;
|
|
15
17
|
this.abiFunction = abiFunction;
|
|
16
18
|
this.args = args;
|
|
17
19
|
this.selector = selector;
|
|
@@ -43,7 +45,7 @@ export class Transaction {
|
|
|
43
45
|
return this.to([recipient]);
|
|
44
46
|
}
|
|
45
47
|
if (Array.isArray(toOrOutputs) && amount === undefined) {
|
|
46
|
-
toOrOutputs.forEach(
|
|
48
|
+
toOrOutputs.forEach(validateOutput);
|
|
47
49
|
this.outputs = this.outputs.concat(toOrOutputs);
|
|
48
50
|
return this;
|
|
49
51
|
}
|
|
@@ -81,84 +83,78 @@ export class Transaction {
|
|
|
81
83
|
return this;
|
|
82
84
|
}
|
|
83
85
|
async build() {
|
|
84
|
-
this.locktime = this.locktime ?? await this.provider.getBlockHeight();
|
|
86
|
+
this.locktime = this.locktime ?? await this.contract.provider.getBlockHeight();
|
|
85
87
|
await this.setInputsAndOutputs();
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
const inputs = this.inputs.map((utxo) => ({
|
|
89
|
-
outpointIndex: utxo.vout,
|
|
90
|
-
outpointTransactionHash: hexToBin(utxo.txid),
|
|
91
|
-
sequenceNumber: this.sequence,
|
|
92
|
-
unlockingBytecode: new Uint8Array(),
|
|
93
|
-
}));
|
|
94
|
-
// Generate source outputs from inputs (for signing with SIGHASH_UTXOS)
|
|
95
|
-
const sourceOutputs = this.inputs.map((input) => {
|
|
96
|
-
const sourceOutput = {
|
|
97
|
-
amount: input.satoshis,
|
|
98
|
-
to: isUtxoP2PKH(input) ? publicKeyToP2PKHLockingBytecode(input.template.getPublicKey()) : lockingBytecode,
|
|
99
|
-
token: input.token,
|
|
100
|
-
};
|
|
101
|
-
return cashScriptOutputToLibauthOutput(sourceOutput);
|
|
102
|
-
});
|
|
103
|
-
const outputs = this.outputs.map(cashScriptOutputToLibauthOutput);
|
|
104
|
-
const transaction = {
|
|
105
|
-
inputs,
|
|
106
|
-
locktime: this.locktime,
|
|
107
|
-
outputs,
|
|
108
|
-
version: 2,
|
|
109
|
-
};
|
|
110
|
-
const inputScripts = [];
|
|
111
|
-
this.inputs.forEach((utxo, i) => {
|
|
112
|
-
// UTXO's with signature templates are signed using P2PKH
|
|
88
|
+
const builder = new TransactionBuilder({ provider: this.contract.provider });
|
|
89
|
+
this.inputs.forEach((utxo) => {
|
|
113
90
|
if (isUtxoP2PKH(utxo)) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const sighash = hash256(preimage);
|
|
119
|
-
const signature = utxo.template.generateSignature(sighash);
|
|
120
|
-
const inputScript = scriptToBytecode([signature, pubkey]);
|
|
121
|
-
inputScripts.push(inputScript);
|
|
122
|
-
return;
|
|
91
|
+
builder.addInput(utxo, utxo.template.unlockP2PKH(), { sequence: this.sequence });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
builder.addInput(utxo, this.unlocker, { sequence: this.sequence });
|
|
123
95
|
}
|
|
124
|
-
let covenantHashType = -1;
|
|
125
|
-
const completeArgs = this.args.map((arg) => {
|
|
126
|
-
if (!(arg instanceof SignatureTemplate))
|
|
127
|
-
return arg;
|
|
128
|
-
// First signature is used for sighash preimage (maybe not the best way)
|
|
129
|
-
if (covenantHashType < 0)
|
|
130
|
-
covenantHashType = arg.getHashType();
|
|
131
|
-
const preimage = createSighashPreimage(transaction, sourceOutputs, i, bytecode, arg.getHashType());
|
|
132
|
-
const sighash = hash256(preimage);
|
|
133
|
-
return arg.generateSignature(sighash);
|
|
134
|
-
});
|
|
135
|
-
const preimage = this.abiFunction.covenant
|
|
136
|
-
? createSighashPreimage(transaction, sourceOutputs, i, bytecode, covenantHashType)
|
|
137
|
-
: undefined;
|
|
138
|
-
const inputScript = createInputScript(this.redeemScript, completeArgs, this.selector, preimage);
|
|
139
|
-
inputScripts.push(inputScript);
|
|
140
|
-
});
|
|
141
|
-
inputScripts.forEach((script, i) => {
|
|
142
|
-
transaction.inputs[i].unlockingBytecode = script;
|
|
143
96
|
});
|
|
144
|
-
|
|
97
|
+
builder.addOutputs(this.outputs);
|
|
98
|
+
builder.setLocktime(this.locktime);
|
|
99
|
+
return builder.build();
|
|
145
100
|
}
|
|
146
101
|
async send(raw) {
|
|
147
102
|
const tx = await this.build();
|
|
103
|
+
let template;
|
|
148
104
|
try {
|
|
149
|
-
|
|
105
|
+
if (this.contract.provider instanceof MockNetworkProvider) {
|
|
106
|
+
template = await buildTemplate({
|
|
107
|
+
transaction: this,
|
|
108
|
+
transactionHex: tx,
|
|
109
|
+
});
|
|
110
|
+
evaluateTemplate(template);
|
|
111
|
+
}
|
|
112
|
+
const txid = await this.contract.provider.sendRawTransaction(tx);
|
|
150
113
|
return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
|
|
151
114
|
}
|
|
152
|
-
catch (
|
|
153
|
-
|
|
154
|
-
|
|
115
|
+
catch (maybeNodeError) {
|
|
116
|
+
if (!template) {
|
|
117
|
+
template = await buildTemplate({
|
|
118
|
+
transaction: this,
|
|
119
|
+
transactionHex: tx,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const reason = maybeNodeError.error ?? maybeNodeError.message ?? maybeNodeError;
|
|
123
|
+
const bitauthUri = getBitauthUri(template);
|
|
124
|
+
try {
|
|
125
|
+
debugTemplate(template, this.contract.artifact);
|
|
126
|
+
}
|
|
127
|
+
catch (libauthError) {
|
|
128
|
+
if (this.contract.provider instanceof MockNetworkProvider) {
|
|
129
|
+
throw buildError(libauthError, bitauthUri);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const message = `${libauthError}\n\nUnderlying node error: ${reason}`;
|
|
133
|
+
throw buildError(message, bitauthUri);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// this must be unreachable
|
|
137
|
+
throw buildError(reason, bitauthUri);
|
|
155
138
|
}
|
|
156
139
|
}
|
|
140
|
+
// method to debug the transaction with libauth VM, throws upon evaluation error
|
|
141
|
+
async debug() {
|
|
142
|
+
const template = await buildTemplate({
|
|
143
|
+
transaction: this,
|
|
144
|
+
});
|
|
145
|
+
return debugTemplate(template, this.contract.artifact);
|
|
146
|
+
}
|
|
147
|
+
async bitauthUri() {
|
|
148
|
+
const template = await buildTemplate({
|
|
149
|
+
transaction: this,
|
|
150
|
+
});
|
|
151
|
+
return getBitauthUri(template);
|
|
152
|
+
}
|
|
157
153
|
async getTxDetails(txid, raw) {
|
|
158
154
|
for (let retries = 0; retries < 1200; retries += 1) {
|
|
159
155
|
await delay(500);
|
|
160
156
|
try {
|
|
161
|
-
const hex = await this.provider.getRawTransaction(txid);
|
|
157
|
+
const hex = await this.contract.provider.getRawTransaction(txid);
|
|
162
158
|
if (raw)
|
|
163
159
|
return hex;
|
|
164
160
|
const libauthTransaction = decodeTransaction(hexToBin(hex));
|
|
@@ -171,15 +167,14 @@ export class Transaction {
|
|
|
171
167
|
// Should not happen
|
|
172
168
|
throw new Error('Could not retrieve transaction details for over 10 minutes');
|
|
173
169
|
}
|
|
174
|
-
async meep() {
|
|
175
|
-
const tx = await this.build();
|
|
176
|
-
return meep(tx, this.inputs, this.redeemScript);
|
|
177
|
-
}
|
|
178
170
|
async setInputsAndOutputs() {
|
|
179
171
|
if (this.outputs.length === 0) {
|
|
180
172
|
throw Error('Attempted to build a transaction without outputs');
|
|
181
173
|
}
|
|
182
|
-
|
|
174
|
+
// Fetched utxos are only used when no inputs are available, so only fetch in that case.
|
|
175
|
+
const allUtxos = this.inputs.length === 0
|
|
176
|
+
? await this.contract.provider.getUtxos(this.contract.address)
|
|
177
|
+
: [];
|
|
183
178
|
const tokenInputs = this.inputs.length > 0
|
|
184
179
|
? this.inputs.filter((input) => input.token)
|
|
185
180
|
: selectAllTokenUtxos(allUtxos, this.outputs);
|
|
@@ -188,7 +183,7 @@ export class Transaction {
|
|
|
188
183
|
selectAllTokenUtxos(this.inputs, this.outputs);
|
|
189
184
|
}
|
|
190
185
|
if (this.tokenChange) {
|
|
191
|
-
const tokenChangeOutputs = createFungibleTokenChangeOutputs(tokenInputs, this.outputs, this.
|
|
186
|
+
const tokenChangeOutputs = createFungibleTokenChangeOutputs(tokenInputs, this.outputs, this.contract.tokenAddress);
|
|
192
187
|
this.outputs.push(...tokenChangeOutputs);
|
|
193
188
|
}
|
|
194
189
|
// Construct list with all nfts in inputs
|
|
@@ -268,7 +263,7 @@ export class Transaction {
|
|
|
268
263
|
commitment: unusedNft.commitment,
|
|
269
264
|
},
|
|
270
265
|
};
|
|
271
|
-
const nftChangeOutput = { to: this.
|
|
266
|
+
const nftChangeOutput = { to: this.contract.tokenAddress, amount: BigInt(1000), token: tokenDetails };
|
|
272
267
|
this.outputs.push(nftChangeOutput);
|
|
273
268
|
}
|
|
274
269
|
}
|
|
@@ -277,11 +272,11 @@ export class Transaction {
|
|
|
277
272
|
const placeholderArgs = this.args.map((arg) => (arg instanceof SignatureTemplate ? placeholder(65) : arg));
|
|
278
273
|
// Create a placeholder preimage of the correct size
|
|
279
274
|
const placeholderPreimage = this.abiFunction.covenant
|
|
280
|
-
? placeholder(getPreimageSize(scriptToBytecode(this.redeemScript)))
|
|
275
|
+
? placeholder(getPreimageSize(scriptToBytecode(this.contract.redeemScript)))
|
|
281
276
|
: undefined;
|
|
282
277
|
// Create a placeholder input script for size calculation using the placeholder
|
|
283
278
|
// arguments and correctly sized placeholder preimage
|
|
284
|
-
const placeholderScript = createInputScript(this.redeemScript, placeholderArgs, this.selector, placeholderPreimage);
|
|
279
|
+
const placeholderScript = createInputScript(this.contract.redeemScript, placeholderArgs, this.selector, placeholderPreimage);
|
|
285
280
|
// Add one extra byte per input to over-estimate tx-in count
|
|
286
281
|
const contractInputSize = getInputSize(placeholderScript) + 1;
|
|
287
282
|
// Note that we use the addPrecision function to add "decimal points" to BigInt numbers
|
|
@@ -331,11 +326,11 @@ export class Transaction {
|
|
|
331
326
|
}
|
|
332
327
|
// Account for the fee of adding a change output
|
|
333
328
|
if (!this.hardcodedFee) {
|
|
334
|
-
const changeOutputSize = getOutputSize({ to: this.address, amount: 0n });
|
|
329
|
+
const changeOutputSize = getOutputSize({ to: this.contract.address, amount: 0n });
|
|
335
330
|
change -= BigInt(changeOutputSize * this.feePerByte);
|
|
336
331
|
}
|
|
337
332
|
// Add a change output if applicable
|
|
338
|
-
const changeOutput = { to: this.address, amount: change };
|
|
333
|
+
const changeOutput = { to: this.contract.address, amount: change };
|
|
339
334
|
if (change >= this.minChange && change >= calculateDust(changeOutput)) {
|
|
340
335
|
this.outputs.push(changeOutput);
|
|
341
336
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { binToHex, decodeTransaction, encodeTransaction, hexToBin, } from '@bitauth/libauth';
|
|
2
2
|
import delay from 'delay';
|
|
3
3
|
import { isUnlockableUtxo, } from './interfaces.js';
|
|
4
|
-
import { buildError, cashScriptOutputToLibauthOutput, createOpReturnOutput } from './utils.js';
|
|
4
|
+
import { buildError, cashScriptOutputToLibauthOutput, createOpReturnOutput, validateOutput, } from './utils.js';
|
|
5
5
|
const DEFAULT_SEQUENCE = 0xfffffffe;
|
|
6
6
|
export class TransactionBuilder {
|
|
7
7
|
constructor(options) {
|
|
@@ -26,10 +26,10 @@ export class TransactionBuilder {
|
|
|
26
26
|
return this;
|
|
27
27
|
}
|
|
28
28
|
addOutput(output) {
|
|
29
|
-
this.
|
|
30
|
-
return this;
|
|
29
|
+
return this.addOutputs([output]);
|
|
31
30
|
}
|
|
32
31
|
addOutputs(outputs) {
|
|
32
|
+
outputs.forEach(validateOutput);
|
|
33
33
|
this.outputs = this.outputs.concat(outputs);
|
|
34
34
|
return this;
|
|
35
35
|
}
|
|
@@ -6,6 +6,7 @@ export { TransactionBuilder } from './TransactionBuilder.js';
|
|
|
6
6
|
export { Argument, encodeArgument } from './Argument.js';
|
|
7
7
|
export { Artifact, AbiFunction, AbiInput } from '@cashscript/utils';
|
|
8
8
|
export * as utils from '@cashscript/utils';
|
|
9
|
-
export
|
|
9
|
+
export * from './interfaces.js';
|
|
10
10
|
export * from './Errors.js';
|
|
11
|
-
export { NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, } from './network/index.js';
|
|
11
|
+
export { NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
|
|
12
|
+
export { randomUtxo, randomToken, randomNFT } from './utils.js';
|
|
@@ -5,7 +5,8 @@ export { Transaction } from './Transaction.js';
|
|
|
5
5
|
export { TransactionBuilder } from './TransactionBuilder.js';
|
|
6
6
|
export { encodeArgument } from './Argument.js';
|
|
7
7
|
export * as utils from '@cashscript/utils';
|
|
8
|
-
export
|
|
8
|
+
export * from './interfaces.js';
|
|
9
9
|
export * from './Errors.js';
|
|
10
|
-
export { BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, } from './network/index.js';
|
|
10
|
+
export { BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
|
|
11
|
+
export { randomUtxo, randomToken, randomNFT } from './utils.js';
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Utxo, Network } from '../interfaces.js';
|
|
2
|
+
import NetworkProvider from './NetworkProvider.js';
|
|
3
|
+
export default class MockNetworkProvider implements NetworkProvider {
|
|
4
|
+
private utxoMap;
|
|
5
|
+
private transactionMap;
|
|
6
|
+
network: Network;
|
|
7
|
+
constructor();
|
|
8
|
+
getUtxos(address: string): Promise<Utxo[]>;
|
|
9
|
+
getBlockHeight(): Promise<number>;
|
|
10
|
+
getRawTransaction(txid: string): Promise<string>;
|
|
11
|
+
sendRawTransaction(txHex: string): Promise<string>;
|
|
12
|
+
addUtxo(address: string, utxo: Utxo): void;
|
|
13
|
+
reset(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { binToHex, hexToBin } from '@bitauth/libauth';
|
|
2
|
+
import { sha256 } from '@cashscript/utils';
|
|
3
|
+
import { Network } from '../interfaces.js';
|
|
4
|
+
import { randomUtxo } from '../utils.js';
|
|
5
|
+
// redeclare the addresses from vars.ts instead of importing them
|
|
6
|
+
const aliceAddress = 'bchtest:qpgjmwev3spwlwkgmyjrr2s2cvlkkzlewq62mzgjnp';
|
|
7
|
+
const bobAddress = 'bchtest:qz6q5gqnxdldkr07xpls5474mmzmlesd6qnux4skuc';
|
|
8
|
+
const carolAddress = 'bchtest:qqsr7nqwe6rq5crj63gy5gdqchpnwmguusmr7tfmsj';
|
|
9
|
+
export default class MockNetworkProvider {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.utxoMap = {};
|
|
12
|
+
this.transactionMap = {};
|
|
13
|
+
this.network = Network.CHIPNET;
|
|
14
|
+
for (let i = 0; i < 3; i += 1) {
|
|
15
|
+
this.addUtxo(aliceAddress, randomUtxo());
|
|
16
|
+
this.addUtxo(bobAddress, randomUtxo());
|
|
17
|
+
this.addUtxo(carolAddress, randomUtxo());
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async getUtxos(address) {
|
|
21
|
+
return this.utxoMap[address];
|
|
22
|
+
}
|
|
23
|
+
async getBlockHeight() {
|
|
24
|
+
return 133700;
|
|
25
|
+
}
|
|
26
|
+
async getRawTransaction(txid) {
|
|
27
|
+
return this.transactionMap[txid];
|
|
28
|
+
}
|
|
29
|
+
async sendRawTransaction(txHex) {
|
|
30
|
+
const transactionBin = hexToBin(txHex);
|
|
31
|
+
const txid = binToHex(sha256(sha256(transactionBin)).reverse());
|
|
32
|
+
this.transactionMap[txid] = txHex;
|
|
33
|
+
return txid;
|
|
34
|
+
}
|
|
35
|
+
addUtxo(address, utxo) {
|
|
36
|
+
if (!this.utxoMap[address]) {
|
|
37
|
+
this.utxoMap[address] = [];
|
|
38
|
+
}
|
|
39
|
+
this.utxoMap[address].push(utxo);
|
|
40
|
+
}
|
|
41
|
+
reset() {
|
|
42
|
+
this.utxoMap = {};
|
|
43
|
+
this.transactionMap = {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=MockNetworkProvider.js.map
|
|
@@ -2,3 +2,4 @@ export { default as NetworkProvider } from './NetworkProvider.js';
|
|
|
2
2
|
export { default as BitcoinRpcNetworkProvider } from './BitcoinRpcNetworkProvider.js';
|
|
3
3
|
export { default as ElectrumNetworkProvider } from './ElectrumNetworkProvider.js';
|
|
4
4
|
export { default as FullStackNetworkProvider } from './FullStackNetworkProvider.js';
|
|
5
|
+
export { default as MockNetworkProvider } from './MockNetworkProvider.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { default as BitcoinRpcNetworkProvider } from './BitcoinRpcNetworkProvider.js';
|
|
2
2
|
export { default as ElectrumNetworkProvider } from './ElectrumNetworkProvider.js';
|
|
3
3
|
export { default as FullStackNetworkProvider } from './FullStackNetworkProvider.js';
|
|
4
|
+
export { default as MockNetworkProvider } from './MockNetworkProvider.js';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Transaction } from '@bitauth/libauth';
|
|
2
2
|
import { Script } from '@cashscript/utils';
|
|
3
|
-
import { Utxo, Output,
|
|
3
|
+
import { Utxo, Output, LibauthOutput, TokenDetails } from './interfaces.js';
|
|
4
4
|
import { FailedTransactionError } from './Errors.js';
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function calculateDust(
|
|
5
|
+
export declare function validateOutput(output: Output): void;
|
|
6
|
+
export declare function calculateDust(output: Output): number;
|
|
7
7
|
export declare function getOutputSize(output: Output): number;
|
|
8
8
|
export declare function encodeOutput(output: Output): Uint8Array;
|
|
9
9
|
export declare function cashScriptOutputToLibauthOutput(output: Output): LibauthOutput;
|
|
@@ -14,8 +14,8 @@ export declare function getTxSizeWithoutInputs(outputs: Output[]): number;
|
|
|
14
14
|
export declare function createInputScript(redeemScript: Script, encodedArgs: Uint8Array[], selector?: number, preimage?: Uint8Array): Uint8Array;
|
|
15
15
|
export declare function createOpReturnOutput(opReturnData: string[]): Output;
|
|
16
16
|
export declare function createSighashPreimage(transaction: Transaction, sourceOutputs: LibauthOutput[], inputIndex: number, coveredBytecode: Uint8Array, hashtype: number): Uint8Array;
|
|
17
|
-
export declare function buildError(reason: string,
|
|
18
|
-
export declare function
|
|
17
|
+
export declare function buildError(reason: string, debugStr?: string): FailedTransactionError;
|
|
18
|
+
export declare function toRegExp(reasons: string[]): RegExp;
|
|
19
19
|
export declare function scriptToAddress(script: Script, network: string, addressType: 'p2sh20' | 'p2sh32', tokenSupport: boolean): string;
|
|
20
20
|
export declare function scriptToLockingBytecode(script: Script, addressType: 'p2sh20' | 'p2sh32'): Uint8Array;
|
|
21
21
|
export declare function publicKeyToP2PKHLockingBytecode(publicKey: Uint8Array): Uint8Array;
|
|
@@ -30,3 +30,6 @@ export declare function utxoTokenComparator(a: Utxo, b: Utxo): number;
|
|
|
30
30
|
*/
|
|
31
31
|
export declare function addressToLockScript(address: string): Uint8Array;
|
|
32
32
|
export declare function getNetworkPrefix(network: string): 'bitcoincash' | 'bchtest' | 'bchreg';
|
|
33
|
+
export declare const randomUtxo: (defaults?: Partial<Utxo> | undefined) => Utxo;
|
|
34
|
+
export declare const randomToken: (defaults?: Partial<TokenDetails> | undefined) => TokenDetails;
|
|
35
|
+
export declare const randomNFT: (defaults?: Partial<TokenDetails> | undefined) => TokenDetails;
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex, generateSigningSerializationBCH, utf8ToBin, hexToBin, flattenBinArray, LockingBytecodeType, encodeTransactionOutput, } from '@bitauth/libauth';
|
|
2
|
-
import { encodeInt, hash160, hash256, Op, scriptToBytecode, } from '@cashscript/utils';
|
|
1
|
+
import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex, generateSigningSerializationBCH, utf8ToBin, hexToBin, flattenBinArray, LockingBytecodeType, encodeTransactionOutput, isHex, bigIntToCompactSize, AuthenticationErrorCommon, NonFungibleTokenCapability, bigIntToVmNumber, } from '@bitauth/libauth';
|
|
2
|
+
import { encodeInt, hash160, hash256, sha256, Op, scriptToBytecode, } from '@cashscript/utils';
|
|
3
3
|
import { Network, } from './interfaces.js';
|
|
4
4
|
import { VERSION_SIZE, LOCKTIME_SIZE } from './constants.js';
|
|
5
5
|
import { OutputSatoshisTooSmallError, TokensToNonTokenAddressError, Reason, FailedTransactionError, FailedRequireError, FailedTimeCheckError, FailedSigCheckError, } from './Errors.js';
|
|
6
6
|
// ////////// PARAMETER VALIDATION ////////////////////////////////////////////
|
|
7
|
-
export function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
export function validateOutput(output) {
|
|
8
|
+
if (typeof output.to !== 'string')
|
|
9
|
+
return;
|
|
10
|
+
const minimumAmount = calculateDust(output);
|
|
11
|
+
if (output.amount < minimumAmount) {
|
|
12
|
+
throw new OutputSatoshisTooSmallError(output.amount, BigInt(minimumAmount));
|
|
11
13
|
}
|
|
12
|
-
if (
|
|
13
|
-
if (!isTokenAddress(
|
|
14
|
-
throw new TokensToNonTokenAddressError(
|
|
14
|
+
if (output.token) {
|
|
15
|
+
if (!isTokenAddress(output.to)) {
|
|
16
|
+
throw new TokensToNonTokenAddressError(output.to);
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
|
-
export function calculateDust(
|
|
19
|
-
const outputSize = getOutputSize(
|
|
20
|
+
export function calculateDust(output) {
|
|
21
|
+
const outputSize = getOutputSize(output);
|
|
20
22
|
// Formula used to calculate the minimum allowed output
|
|
21
23
|
const dustAmount = 444 + outputSize * 3;
|
|
22
24
|
return dustAmount;
|
|
@@ -29,6 +31,14 @@ export function encodeOutput(output) {
|
|
|
29
31
|
return encodeTransactionOutput(cashScriptOutputToLibauthOutput(output));
|
|
30
32
|
}
|
|
31
33
|
export function cashScriptOutputToLibauthOutput(output) {
|
|
34
|
+
if (output.token) {
|
|
35
|
+
if (typeof output.token.category !== 'string' || !isHex(output.token.category)) {
|
|
36
|
+
throw new Error(`Provided token category ${output.token?.category} is not a hex string`);
|
|
37
|
+
}
|
|
38
|
+
if (output.token.nft && (typeof output.token.nft.commitment !== 'string' || !isHex(output.token.nft.commitment))) {
|
|
39
|
+
throw new Error(`Provided token commitment ${output.token.nft?.commitment} is not a hex string`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
32
42
|
return {
|
|
33
43
|
lockingBytecode: typeof output.to === 'string' ? addressToLockScript(output.to) : output.to,
|
|
34
44
|
valueSatoshis: output.amount,
|
|
@@ -92,7 +102,7 @@ export function getTxSizeWithoutInputs(outputs) {
|
|
|
92
102
|
let size = VERSION_SIZE + LOCKTIME_SIZE;
|
|
93
103
|
size += outputs.reduce((acc, output) => acc + getOutputSize(output), 0);
|
|
94
104
|
// Add tx-out count (accounting for a potential change output)
|
|
95
|
-
size +=
|
|
105
|
+
size += bigIntToCompactSize(BigInt(outputs.length + 1)).byteLength;
|
|
96
106
|
return size;
|
|
97
107
|
}
|
|
98
108
|
// ////////// BUILD OBJECTS ///////////////////////////////////////////////////
|
|
@@ -125,35 +135,35 @@ export function createSighashPreimage(transaction, sourceOutputs, inputIndex, co
|
|
|
125
135
|
const sighashPreimage = generateSigningSerializationBCH(context, { coveredBytecode, signingSerializationType });
|
|
126
136
|
return sighashPreimage;
|
|
127
137
|
}
|
|
128
|
-
export function buildError(reason,
|
|
138
|
+
export function buildError(reason, debugStr) {
|
|
129
139
|
const require = [
|
|
130
140
|
Reason.EVAL_FALSE, Reason.VERIFY, Reason.EQUALVERIFY, Reason.CHECKMULTISIGVERIFY,
|
|
131
141
|
Reason.CHECKSIGVERIFY, Reason.CHECKDATASIGVERIFY, Reason.NUMEQUALVERIFY,
|
|
142
|
+
AuthenticationErrorCommon.failedVerify,
|
|
143
|
+
];
|
|
144
|
+
const timeCheck = [
|
|
145
|
+
Reason.NEGATIVE_LOCKTIME, Reason.UNSATISFIED_LOCKTIME,
|
|
146
|
+
AuthenticationErrorCommon.negativeLocktime, AuthenticationErrorCommon.unsatisfiedLocktime,
|
|
132
147
|
];
|
|
133
|
-
const timeCheck = [Reason.NEGATIVE_LOCKTIME, Reason.UNSATISFIED_LOCKTIME];
|
|
134
148
|
const sigCheck = [
|
|
135
149
|
Reason.SIG_COUNT, Reason.PUBKEY_COUNT, Reason.SIG_HASHTYPE, Reason.SIG_DER,
|
|
136
150
|
Reason.SIG_HIGH_S, Reason.SIG_NULLFAIL, Reason.SIG_BADLENGTH, Reason.SIG_NONSCHNORR,
|
|
151
|
+
AuthenticationErrorCommon.nonNullSignatureFailure,
|
|
137
152
|
];
|
|
138
153
|
if (toRegExp(require).test(reason)) {
|
|
139
|
-
return new FailedRequireError(reason,
|
|
154
|
+
return new FailedRequireError(reason, debugStr);
|
|
140
155
|
}
|
|
141
156
|
if (toRegExp(timeCheck).test(reason)) {
|
|
142
|
-
return new FailedTimeCheckError(reason,
|
|
157
|
+
return new FailedTimeCheckError(reason, debugStr);
|
|
143
158
|
}
|
|
144
159
|
if (toRegExp(sigCheck).test(reason)) {
|
|
145
|
-
return new FailedSigCheckError(reason,
|
|
160
|
+
return new FailedSigCheckError(reason, debugStr);
|
|
146
161
|
}
|
|
147
|
-
return new FailedTransactionError(reason,
|
|
162
|
+
return new FailedTransactionError(reason, debugStr);
|
|
148
163
|
}
|
|
149
|
-
function toRegExp(reasons) {
|
|
164
|
+
export function toRegExp(reasons) {
|
|
150
165
|
return new RegExp(reasons.join('|').replace(/\(/g, '\\(').replace(/\)/g, '\\)'));
|
|
151
166
|
}
|
|
152
|
-
// ////////// MISC ////////////////////////////////////////////////////////////
|
|
153
|
-
export function meep(tx, utxos, script) {
|
|
154
|
-
const scriptPubkey = binToHex(scriptToLockingBytecode(script, 'p2sh20'));
|
|
155
|
-
return `meep debug --tx=${tx} --idx=0 --amt=${utxos[0].satoshis} --pkscript=${scriptPubkey}`;
|
|
156
|
-
}
|
|
157
167
|
export function scriptToAddress(script, network, addressType, tokenSupport) {
|
|
158
168
|
const lockingBytecode = scriptToLockingBytecode(script, addressType);
|
|
159
169
|
const prefix = getNetworkPrefix(network);
|
|
@@ -239,4 +249,31 @@ function getPushDataOpcode(data) {
|
|
|
239
249
|
return Uint8Array.from([0x4c, byteLength]);
|
|
240
250
|
throw Error('Pushdata too large');
|
|
241
251
|
}
|
|
252
|
+
const randomInt = () => BigInt(Math.floor(Math.random() * 10000));
|
|
253
|
+
export const randomUtxo = (defaults) => ({
|
|
254
|
+
...{
|
|
255
|
+
txid: binToHex(sha256(bigIntToVmNumber(randomInt()))),
|
|
256
|
+
vout: Math.floor(Math.random() * 10),
|
|
257
|
+
satoshis: 20000n + randomInt(),
|
|
258
|
+
},
|
|
259
|
+
...defaults,
|
|
260
|
+
});
|
|
261
|
+
export const randomToken = (defaults) => ({
|
|
262
|
+
...{
|
|
263
|
+
category: binToHex(sha256(bigIntToVmNumber(randomInt()))),
|
|
264
|
+
amount: 10000n + randomInt(),
|
|
265
|
+
},
|
|
266
|
+
...defaults,
|
|
267
|
+
});
|
|
268
|
+
export const randomNFT = (defaults) => ({
|
|
269
|
+
...{
|
|
270
|
+
category: binToHex(sha256(bigIntToVmNumber(randomInt()))),
|
|
271
|
+
amount: 0n,
|
|
272
|
+
nft: {
|
|
273
|
+
commitment: binToHex(sha256(bigIntToVmNumber(randomInt()))).slice(0, 8),
|
|
274
|
+
capability: NonFungibleTokenCapability.none,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
...defaults,
|
|
278
|
+
});
|
|
242
279
|
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { printExpected, printReceived, matcherHint } from 'jest-matcher-utils';
|
|
2
|
+
expect.extend({
|
|
3
|
+
async toLog(transaction, match) {
|
|
4
|
+
const spyOnLoggerError = jest.spyOn(console, 'log');
|
|
5
|
+
// silence actual stdout output
|
|
6
|
+
spyOnLoggerError.mockImplementation(() => { });
|
|
7
|
+
try {
|
|
8
|
+
await transaction.debug();
|
|
9
|
+
}
|
|
10
|
+
catch { }
|
|
11
|
+
let message = '';
|
|
12
|
+
const failMessage = (received, expected) => () => `${matcherHint('.toLog', 'received', 'expected')}
|
|
13
|
+
|
|
14
|
+
Expected: ${printExpected(expected)}
|
|
15
|
+
Received: ${printReceived(received)}`;
|
|
16
|
+
try {
|
|
17
|
+
expect(spyOnLoggerError).toBeCalledWith(expect.stringMatching(match));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
message = error;
|
|
21
|
+
}
|
|
22
|
+
const received = spyOnLoggerError.mock.calls[0][0];
|
|
23
|
+
spyOnLoggerError.mockClear();
|
|
24
|
+
return {
|
|
25
|
+
message: failMessage(received, match),
|
|
26
|
+
pass: !message,
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
expect.extend({
|
|
31
|
+
async toFailRequireWith(transaction, match) {
|
|
32
|
+
let message = '';
|
|
33
|
+
let failMessage = () => { };
|
|
34
|
+
try {
|
|
35
|
+
await transaction.debug();
|
|
36
|
+
failMessage = () => () => `${matcherHint('.toFailRequireWith', undefined, '')}
|
|
37
|
+
|
|
38
|
+
Contract function did not fail a require statement`;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
message = error;
|
|
42
|
+
}
|
|
43
|
+
// should not have failed
|
|
44
|
+
if (this.isNot) {
|
|
45
|
+
return {
|
|
46
|
+
message: () => `${matcherHint('.toFailRequireWith', 'received', '', { isNot: true })}`,
|
|
47
|
+
pass: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (message) {
|
|
51
|
+
try {
|
|
52
|
+
expect(message).toMatch(match);
|
|
53
|
+
message = '';
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
message = error.message;
|
|
57
|
+
failMessage = () => () => message.replace('.toMatch', '.toFailRequireWith');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
message: failMessage(message, match),
|
|
62
|
+
pass: !message,
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=JestExtensions.js.map
|