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.
Files changed (37) hide show
  1. package/dist/{Contract.d.ts → src/Contract.d.ts} +7 -5
  2. package/dist/{Contract.js → src/Contract.js} +12 -2
  3. package/dist/{Errors.d.ts → src/Errors.d.ts} +2 -2
  4. package/dist/{Errors.js → src/Errors.js} +3 -3
  5. package/dist/src/LibauthTemplate.d.ts +12 -0
  6. package/dist/src/LibauthTemplate.js +472 -0
  7. package/dist/{SignatureTemplate.d.ts → src/SignatureTemplate.d.ts} +1 -1
  8. package/dist/{Transaction.d.ts → src/Transaction.d.ts} +13 -12
  9. package/dist/{Transaction.js → src/Transaction.js} +76 -81
  10. package/dist/{TransactionBuilder.js → src/TransactionBuilder.js} +3 -3
  11. package/dist/{index.d.ts → src/index.d.ts} +3 -2
  12. package/dist/{index.js → src/index.js} +3 -2
  13. package/dist/{interfaces.d.ts → src/interfaces.d.ts} +1 -1
  14. package/dist/src/network/MockNetworkProvider.d.ts +14 -0
  15. package/dist/src/network/MockNetworkProvider.js +46 -0
  16. package/dist/{network → src/network}/index.d.ts +1 -0
  17. package/dist/{network → src/network}/index.js +1 -0
  18. package/dist/{utils.d.ts → src/utils.d.ts} +8 -5
  19. package/dist/{utils.js → src/utils.js} +61 -24
  20. package/dist/test/JestExtensions.d.ts +9 -0
  21. package/dist/test/JestExtensions.js +66 -0
  22. package/package.json +8 -6
  23. /package/dist/{Argument.d.ts → src/Argument.d.ts} +0 -0
  24. /package/dist/{Argument.js → src/Argument.js} +0 -0
  25. /package/dist/{SignatureTemplate.js → src/SignatureTemplate.js} +0 -0
  26. /package/dist/{TransactionBuilder.d.ts → src/TransactionBuilder.d.ts} +0 -0
  27. /package/dist/{constants.d.ts → src/constants.d.ts} +0 -0
  28. /package/dist/{constants.js → src/constants.js} +0 -0
  29. /package/dist/{interfaces.js → src/interfaces.js} +0 -0
  30. /package/dist/{network → src/network}/BitcoinRpcNetworkProvider.d.ts +0 -0
  31. /package/dist/{network → src/network}/BitcoinRpcNetworkProvider.js +0 -0
  32. /package/dist/{network → src/network}/ElectrumNetworkProvider.d.ts +0 -0
  33. /package/dist/{network → src/network}/ElectrumNetworkProvider.js +0 -0
  34. /package/dist/{network → src/network}/FullStackNetworkProvider.d.ts +0 -0
  35. /package/dist/{network → src/network}/FullStackNetworkProvider.js +0 -0
  36. /package/dist/{network → src/network}/NetworkProvider.d.ts +0 -0
  37. /package/dist/{network → src/network}/NetworkProvider.js +0 -0
@@ -1,17 +1,19 @@
1
1
  import bip68 from 'bip68';
2
- import { hexToBin, binToHex, encodeTransaction, decodeTransaction, } from '@bitauth/libauth';
2
+ import { hexToBin, decodeTransaction, } from '@bitauth/libauth';
3
3
  import delay from 'delay';
4
- import { hash256, placeholder, scriptToBytecode, } from '@cashscript/utils';
4
+ import { placeholder, scriptToBytecode, } from '@cashscript/utils';
5
5
  import deepEqual from 'fast-deep-equal';
6
6
  import { isUtxoP2PKH, } from './interfaces.js';
7
- import { meep, createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, buildError, createSighashPreimage, validateRecipient, utxoComparator, cashScriptOutputToLibauthOutput, calculateDust, getOutputSize, addressToLockScript, publicKeyToP2PKHLockingBytecode, utxoTokenComparator, } from './utils.js';
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(address, provider, redeemScript, abiFunction, args, selector) {
12
- this.address = address;
13
- this.provider = provider;
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(validateRecipient);
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 bytecode = scriptToBytecode(this.redeemScript);
87
- const lockingBytecode = addressToLockScript(this.address);
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
- const pubkey = utxo.template.getPublicKey();
115
- const prevOutScript = publicKeyToP2PKHLockingBytecode(pubkey);
116
- const hashtype = utxo.template.getHashType();
117
- const preimage = createSighashPreimage(transaction, sourceOutputs, i, prevOutScript, hashtype);
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
- return binToHex(encodeTransaction(transaction));
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
- const txid = await this.provider.sendRawTransaction(tx);
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 (e) {
153
- const reason = e.error ?? e.message;
154
- throw buildError(reason, meep(tx, this.inputs, this.redeemScript));
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
- const allUtxos = await this.provider.getUtxos(this.address);
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.address);
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.address, amount: BigInt(1000), token: tokenDetails };
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.outputs.push(output);
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 { Utxo, Recipient, SignatureAlgorithm, HashType, Network, isUtxoP2PKH, } from './interfaces.js';
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 { SignatureAlgorithm, HashType, Network, isUtxoP2PKH, } from './interfaces.js';
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
@@ -1,4 +1,4 @@
1
- import type { Transaction } from '@bitauth/libauth';
1
+ import { type Transaction } from '@bitauth/libauth';
2
2
  import type { NetworkProvider } from './network/index.js';
3
3
  import type SignatureTemplate from './SignatureTemplate.js';
4
4
  export interface Utxo {
@@ -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, Recipient, LibauthOutput } from './interfaces.js';
3
+ import { Utxo, Output, LibauthOutput, TokenDetails } from './interfaces.js';
4
4
  import { FailedTransactionError } from './Errors.js';
5
- export declare function validateRecipient(recipient: Recipient): void;
6
- export declare function calculateDust(recipient: Recipient): number;
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, meepStr?: string): FailedTransactionError;
18
- export declare function meep(tx: any, utxos: Utxo[], script: Script): string;
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 validateRecipient(recipient) {
8
- const minimumAmount = calculateDust(recipient);
9
- if (recipient.amount < minimumAmount) {
10
- throw new OutputSatoshisTooSmallError(recipient.amount, BigInt(minimumAmount));
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 (recipient.token) {
13
- if (!isTokenAddress(recipient.to)) {
14
- throw new TokensToNonTokenAddressError(recipient.to);
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(recipient) {
19
- const outputSize = getOutputSize(recipient);
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 += encodeInt(BigInt(outputs.length + 1)).byteLength;
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, meepStr) {
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, meepStr);
154
+ return new FailedRequireError(reason, debugStr);
140
155
  }
141
156
  if (toRegExp(timeCheck).test(reason)) {
142
- return new FailedTimeCheckError(reason, meepStr);
157
+ return new FailedTimeCheckError(reason, debugStr);
143
158
  }
144
159
  if (toRegExp(sigCheck).test(reason)) {
145
- return new FailedSigCheckError(reason, meepStr);
160
+ return new FailedSigCheckError(reason, debugStr);
146
161
  }
147
- return new FailedTransactionError(reason, meepStr);
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,9 @@
1
+ export {};
2
+ declare global {
3
+ namespace jest {
4
+ interface Matchers<R> {
5
+ toLog(value: RegExp | string): Promise<void>;
6
+ toFailRequireWith(value: RegExp | string): Promise<void>;
7
+ }
8
+ }
9
+ }
@@ -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