cashscript 0.9.1 → 0.9.3

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.
@@ -1,7 +1,8 @@
1
- import { Artifact } from '@cashscript/utils';
1
+ import { Artifact, Script } from '@cashscript/utils';
2
2
  import { Transaction } from './Transaction.js';
3
3
  import { Argument } from './Argument.js';
4
4
  import { Unlocker, ContractOptions, Utxo } from './interfaces.js';
5
+ import NetworkProvider from './network/NetworkProvider.js';
5
6
  export declare class Contract {
6
7
  private artifact;
7
8
  private options?;
@@ -13,8 +14,8 @@ export declare class Contract {
13
14
  opcount: number;
14
15
  functions: Record<string, ContractFunction>;
15
16
  unlock: Record<string, ContractUnlocker>;
16
- private redeemScript;
17
- private provider;
17
+ redeemScript: Script;
18
+ provider: NetworkProvider;
18
19
  private addressType;
19
20
  constructor(artifact: Artifact, constructorArgs: Argument[], options?: ContractOptions | undefined);
20
21
  getBalance(): Promise<bigint>;
package/dist/Contract.js CHANGED
@@ -73,7 +73,8 @@ export class Contract {
73
73
  // Encode passed args (this also performs type checking)
74
74
  const encodedArgs = args
75
75
  .map((arg, i) => encodeArgument(arg, abiFunction.inputs[i].type));
76
- return new Transaction(this.address, this.provider, this.redeemScript, abiFunction, encodedArgs, selector);
76
+ const unlocker = this.createUnlocker(abiFunction, selector)(...args);
77
+ return new Transaction(this, unlocker, abiFunction, encodedArgs, selector);
77
78
  };
78
79
  }
79
80
  createUnlocker(abiFunction, selector) {
@@ -82,14 +83,22 @@ export class Contract {
82
83
  const encodedArgs = args
83
84
  .map((arg, i) => encodeArgument(arg, abiFunction.inputs[i].type));
84
85
  const generateUnlockingBytecode = ({ transaction, sourceOutputs, inputIndex }) => {
86
+ // TODO: Remove old-style covenant code for v1.0 release
87
+ let covenantHashType = -1;
85
88
  const completeArgs = encodedArgs.map((arg) => {
86
89
  if (!(arg instanceof SignatureTemplate))
87
90
  return arg;
91
+ // First signature is used for sighash preimage (maybe not the best way)
92
+ if (covenantHashType < 0)
93
+ covenantHashType = arg.getHashType();
88
94
  const preimage = createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, arg.getHashType());
89
95
  const sighash = hash256(preimage);
90
96
  return arg.generateSignature(sighash);
91
97
  });
92
- const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector);
98
+ const preimage = abiFunction.covenant
99
+ ? createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, covenantHashType)
100
+ : undefined;
101
+ const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector, preimage);
93
102
  return unlockingBytecode;
94
103
  };
95
104
  const generateLockingBytecode = () => addressToLockScript(this.address);
@@ -1,11 +1,10 @@
1
- import { AbiFunction, Script } from '@cashscript/utils';
2
- import { Utxo, Recipient, TokenDetails, TransactionDetails } from './interfaces.js';
3
- import NetworkProvider from './network/NetworkProvider.js';
1
+ import { AbiFunction } from '@cashscript/utils';
2
+ import { Utxo, Recipient, TokenDetails, TransactionDetails, Unlocker } from './interfaces.js';
4
3
  import SignatureTemplate from './SignatureTemplate.js';
4
+ import { Contract } from './Contract.js';
5
5
  export declare class Transaction {
6
- private address;
7
- private provider;
8
- private redeemScript;
6
+ private contract;
7
+ private unlocker;
9
8
  private abiFunction;
10
9
  private args;
11
10
  private selector?;
@@ -17,7 +16,7 @@ export declare class Transaction {
17
16
  private hardcodedFee;
18
17
  private minChange;
19
18
  private tokenChange;
20
- constructor(address: string, provider: NetworkProvider, redeemScript: Script, abiFunction: AbiFunction, args: (Uint8Array | SignatureTemplate)[], selector?: number | undefined);
19
+ constructor(contract: Contract, unlocker: Unlocker, abiFunction: AbiFunction, args: (Uint8Array | SignatureTemplate)[], selector?: number | undefined);
21
20
  from(input: Utxo): this;
22
21
  from(inputs: Utxo[]): this;
23
22
  fromP2PKH(input: Utxo, template: SignatureTemplate): this;
@@ -1,17 +1,17 @@
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 { meep, 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';
10
11
  export class Transaction {
11
- constructor(address, provider, redeemScript, abiFunction, args, selector) {
12
- this.address = address;
13
- this.provider = provider;
14
- this.redeemScript = redeemScript;
12
+ constructor(contract, unlocker, abiFunction, args, selector) {
13
+ this.contract = contract;
14
+ this.unlocker = unlocker;
15
15
  this.abiFunction = abiFunction;
16
16
  this.args = args;
17
17
  this.selector = selector;
@@ -43,7 +43,7 @@ export class Transaction {
43
43
  return this.to([recipient]);
44
44
  }
45
45
  if (Array.isArray(toOrOutputs) && amount === undefined) {
46
- toOrOutputs.forEach(validateRecipient);
46
+ toOrOutputs.forEach(validateOutput);
47
47
  this.outputs = this.outputs.concat(toOrOutputs);
48
48
  return this;
49
49
  }
@@ -81,84 +81,37 @@ export class Transaction {
81
81
  return this;
82
82
  }
83
83
  async build() {
84
- this.locktime = this.locktime ?? await this.provider.getBlockHeight();
84
+ this.locktime = this.locktime ?? await this.contract.provider.getBlockHeight();
85
85
  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
86
+ const builder = new TransactionBuilder({ provider: this.contract.provider });
87
+ this.inputs.forEach((utxo) => {
113
88
  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;
89
+ builder.addInput(utxo, utxo.template.unlockP2PKH(), { sequence: this.sequence });
90
+ }
91
+ else {
92
+ builder.addInput(utxo, this.unlocker, { sequence: this.sequence });
123
93
  }
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
94
  });
144
- return binToHex(encodeTransaction(transaction));
95
+ builder.addOutputs(this.outputs);
96
+ builder.setLocktime(this.locktime);
97
+ return builder.build();
145
98
  }
146
99
  async send(raw) {
147
100
  const tx = await this.build();
148
101
  try {
149
- const txid = await this.provider.sendRawTransaction(tx);
102
+ const txid = await this.contract.provider.sendRawTransaction(tx);
150
103
  return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
151
104
  }
152
105
  catch (e) {
153
106
  const reason = e.error ?? e.message;
154
- throw buildError(reason, meep(tx, this.inputs, this.redeemScript));
107
+ throw buildError(reason, meep(tx, this.inputs, this.contract.redeemScript));
155
108
  }
156
109
  }
157
110
  async getTxDetails(txid, raw) {
158
111
  for (let retries = 0; retries < 1200; retries += 1) {
159
112
  await delay(500);
160
113
  try {
161
- const hex = await this.provider.getRawTransaction(txid);
114
+ const hex = await this.contract.provider.getRawTransaction(txid);
162
115
  if (raw)
163
116
  return hex;
164
117
  const libauthTransaction = decodeTransaction(hexToBin(hex));
@@ -173,13 +126,16 @@ export class Transaction {
173
126
  }
174
127
  async meep() {
175
128
  const tx = await this.build();
176
- return meep(tx, this.inputs, this.redeemScript);
129
+ return meep(tx, this.inputs, this.contract.redeemScript);
177
130
  }
178
131
  async setInputsAndOutputs() {
179
132
  if (this.outputs.length === 0) {
180
133
  throw Error('Attempted to build a transaction without outputs');
181
134
  }
182
- const allUtxos = await this.provider.getUtxos(this.address);
135
+ // Fetched utxos are only used when no inputs are available, so only fetch in that case.
136
+ const allUtxos = this.inputs.length === 0
137
+ ? await this.contract.provider.getUtxos(this.contract.address)
138
+ : [];
183
139
  const tokenInputs = this.inputs.length > 0
184
140
  ? this.inputs.filter((input) => input.token)
185
141
  : selectAllTokenUtxos(allUtxos, this.outputs);
@@ -188,7 +144,7 @@ export class Transaction {
188
144
  selectAllTokenUtxos(this.inputs, this.outputs);
189
145
  }
190
146
  if (this.tokenChange) {
191
- const tokenChangeOutputs = createFungibleTokenChangeOutputs(tokenInputs, this.outputs, this.address);
147
+ const tokenChangeOutputs = createFungibleTokenChangeOutputs(tokenInputs, this.outputs, this.contract.tokenAddress);
192
148
  this.outputs.push(...tokenChangeOutputs);
193
149
  }
194
150
  // Construct list with all nfts in inputs
@@ -268,7 +224,7 @@ export class Transaction {
268
224
  commitment: unusedNft.commitment,
269
225
  },
270
226
  };
271
- const nftChangeOutput = { to: this.address, amount: BigInt(1000), token: tokenDetails };
227
+ const nftChangeOutput = { to: this.contract.tokenAddress, amount: BigInt(1000), token: tokenDetails };
272
228
  this.outputs.push(nftChangeOutput);
273
229
  }
274
230
  }
@@ -277,11 +233,11 @@ export class Transaction {
277
233
  const placeholderArgs = this.args.map((arg) => (arg instanceof SignatureTemplate ? placeholder(65) : arg));
278
234
  // Create a placeholder preimage of the correct size
279
235
  const placeholderPreimage = this.abiFunction.covenant
280
- ? placeholder(getPreimageSize(scriptToBytecode(this.redeemScript)))
236
+ ? placeholder(getPreimageSize(scriptToBytecode(this.contract.redeemScript)))
281
237
  : undefined;
282
238
  // Create a placeholder input script for size calculation using the placeholder
283
239
  // arguments and correctly sized placeholder preimage
284
- const placeholderScript = createInputScript(this.redeemScript, placeholderArgs, this.selector, placeholderPreimage);
240
+ const placeholderScript = createInputScript(this.contract.redeemScript, placeholderArgs, this.selector, placeholderPreimage);
285
241
  // Add one extra byte per input to over-estimate tx-in count
286
242
  const contractInputSize = getInputSize(placeholderScript) + 1;
287
243
  // Note that we use the addPrecision function to add "decimal points" to BigInt numbers
@@ -331,11 +287,11 @@ export class Transaction {
331
287
  }
332
288
  // Account for the fee of adding a change output
333
289
  if (!this.hardcodedFee) {
334
- const changeOutputSize = getOutputSize({ to: this.address, amount: 0n });
290
+ const changeOutputSize = getOutputSize({ to: this.contract.address, amount: 0n });
335
291
  change -= BigInt(changeOutputSize * this.feePerByte);
336
292
  }
337
293
  // Add a change output if applicable
338
- const changeOutput = { to: this.address, amount: change };
294
+ const changeOutput = { to: this.contract.address, amount: change };
339
295
  if (change >= this.minChange && change >= calculateDust(changeOutput)) {
340
296
  this.outputs.push(changeOutput);
341
297
  }
@@ -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
  }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,6 @@ 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
11
  export { NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, } from './network/index.js';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ 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
10
  export { BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, } from './network/index.js';
11
11
  //# sourceMappingURL=index.js.map
package/dist/utils.d.ts CHANGED
@@ -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 } 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;
package/dist/utils.js CHANGED
@@ -1,22 +1,24 @@
1
- import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex, generateSigningSerializationBCH, utf8ToBin, hexToBin, flattenBinArray, LockingBytecodeType, encodeTransactionOutput, } from '@bitauth/libauth';
1
+ import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex, generateSigningSerializationBCH, utf8ToBin, hexToBin, flattenBinArray, LockingBytecodeType, encodeTransactionOutput, isHex, bigIntToCompactSize, } from '@bitauth/libauth';
2
2
  import { encodeInt, hash160, hash256, 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 ///////////////////////////////////////////////////
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashscript",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "Easily write and interact with Bitcoin Cash contracts",
5
5
  "keywords": [
6
6
  "bitcoin cash",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@bitauth/libauth": "^2.0.0-alpha.8",
47
- "@cashscript/utils": "^0.9.1",
47
+ "@cashscript/utils": "^0.9.3",
48
48
  "bip68": "^1.0.4",
49
49
  "bitcoin-rpc-promise-retry": "^1.3.0",
50
50
  "delay": "^5.0.0",
@@ -59,5 +59,5 @@
59
59
  "jest": "^29.4.1",
60
60
  "typescript": "^4.1.5"
61
61
  },
62
- "gitHead": "0043ea317d554436cf2c8927e82a8a8a04bee615"
62
+ "gitHead": "01f9b9bb552c3c4d63b0c7c8f065a0e23b536ca6"
63
63
  }