cashscript 0.8.0-next.2 → 0.8.0-next.4

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,7 @@
1
1
  import { decodePrivateKeyWif, secp256k1, SigningSerializationFlag } from '@bitauth/libauth';
2
2
  import { HashType, SignatureAlgorithm } from './interfaces.js';
3
3
  export default class SignatureTemplate {
4
- constructor(signer, hashtype = HashType.SIGHASH_ALL, signatureAlgorithm = SignatureAlgorithm.SCHNORR) {
4
+ constructor(signer, hashtype = HashType.SIGHASH_ALL | HashType.SIGHASH_UTXOS, signatureAlgorithm = SignatureAlgorithm.SCHNORR) {
5
5
  this.hashtype = hashtype;
6
6
  this.signatureAlgorithm = signatureAlgorithm;
7
7
  if (isKeypair(signer)) {
@@ -20,8 +20,8 @@ export declare class Transaction {
20
20
  constructor(address: string, provider: NetworkProvider, redeemScript: Script, abiFunction: AbiFunction, args: (Uint8Array | SignatureTemplate)[], selector?: number | undefined);
21
21
  from(input: Utxo): this;
22
22
  from(inputs: Utxo[]): this;
23
- experimentalFromP2PKH(input: Utxo, template: SignatureTemplate): this;
24
- experimentalFromP2PKH(inputs: Utxo[], template: SignatureTemplate): this;
23
+ fromP2PKH(input: Utxo, template: SignatureTemplate): this;
24
+ fromP2PKH(inputs: Utxo[], template: SignatureTemplate): this;
25
25
  to(to: string, amount: bigint, token?: TokenDetails): this;
26
26
  to(outputs: Recipient[]): this;
27
27
  withOpReturn(chunks: string[]): this;
@@ -1,9 +1,10 @@
1
- import { hexToBin, binToHex, encodeTransaction, addressContentsToLockingBytecode, decodeTransaction, LockingBytecodeType, } from '@bitauth/libauth';
1
+ import { hexToBin, binToHex, encodeTransaction, decodeTransaction, } from '@bitauth/libauth';
2
2
  import delay from 'delay';
3
- import { hash160, hash256, placeholder, scriptToBytecode, } from '@cashscript/utils';
3
+ import { hash256, placeholder, scriptToBytecode, } from '@cashscript/utils';
4
4
  import { isSignableUtxo, } from './interfaces.js';
5
- import { meep, createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, buildError, createSighashPreimage, validateRecipient, utxoComparator, cashScriptOutputToLibauthOutput, calculateDust, getOutputSize, } from './utils.js';
5
+ import { meep, createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, buildError, createSighashPreimage, validateRecipient, utxoComparator, cashScriptOutputToLibauthOutput, calculateDust, getOutputSize, addressToLockScript, publicKeyToP2PKHLockingBytecode, utxoTokenComparator, } from './utils.js';
6
6
  import SignatureTemplate from './SignatureTemplate.js';
7
+ import { P2PKH_INPUT_SIZE } from './constants.js';
7
8
  const bip68 = await import('bip68');
8
9
  export class Transaction {
9
10
  constructor(address, provider, redeemScript, abiFunction, args, selector) {
@@ -27,7 +28,7 @@ export class Transaction {
27
28
  this.inputs = this.inputs.concat(inputOrInputs);
28
29
  return this;
29
30
  }
30
- experimentalFromP2PKH(inputOrInputs, template) {
31
+ fromP2PKH(inputOrInputs, template) {
31
32
  if (!Array.isArray(inputOrInputs)) {
32
33
  inputOrInputs = [inputOrInputs];
33
34
  }
@@ -82,12 +83,22 @@ export class Transaction {
82
83
  this.locktime = this.locktime ?? await this.provider.getBlockHeight();
83
84
  await this.setInputsAndOutputs();
84
85
  const bytecode = scriptToBytecode(this.redeemScript);
86
+ const lockingBytecode = addressToLockScript(this.address);
85
87
  const inputs = this.inputs.map((utxo) => ({
86
88
  outpointIndex: utxo.vout,
87
89
  outpointTransactionHash: hexToBin(utxo.txid),
88
90
  sequenceNumber: this.sequence,
89
91
  unlockingBytecode: new Uint8Array(),
90
92
  }));
93
+ // Generate source outputs from inputs (for signing with SIGHASH_UTXOS)
94
+ const sourceOutputs = this.inputs.map((input) => {
95
+ const sourceOutput = {
96
+ amount: input.satoshis,
97
+ to: isSignableUtxo(input) ? publicKeyToP2PKHLockingBytecode(input.template.getPublicKey()) : lockingBytecode,
98
+ token: input.token,
99
+ };
100
+ return cashScriptOutputToLibauthOutput(sourceOutput);
101
+ });
91
102
  const outputs = this.outputs.map(cashScriptOutputToLibauthOutput);
92
103
  const transaction = {
93
104
  inputs,
@@ -100,11 +111,9 @@ export class Transaction {
100
111
  // UTXO's with signature templates are signed using P2PKH
101
112
  if (isSignableUtxo(utxo)) {
102
113
  const pubkey = utxo.template.getPublicKey();
103
- const pubkeyHash = hash160(pubkey);
104
- const addressContents = { payload: pubkeyHash, type: LockingBytecodeType.p2pkh };
105
- const prevOutScript = addressContentsToLockingBytecode(addressContents);
114
+ const prevOutScript = publicKeyToP2PKHLockingBytecode(pubkey);
106
115
  const hashtype = utxo.template.getHashType();
107
- const preimage = createSighashPreimage(transaction, this.inputs, i, prevOutScript, hashtype);
116
+ const preimage = createSighashPreimage(transaction, sourceOutputs, i, prevOutScript, hashtype);
108
117
  const sighash = hash256(preimage);
109
118
  const signature = utxo.template.generateSignature(sighash);
110
119
  const inputScript = scriptToBytecode([signature, pubkey]);
@@ -118,12 +127,12 @@ export class Transaction {
118
127
  // First signature is used for sighash preimage (maybe not the best way)
119
128
  if (covenantHashType < 0)
120
129
  covenantHashType = arg.getHashType();
121
- const preimage = createSighashPreimage(transaction, this.inputs, i, bytecode, arg.getHashType());
130
+ const preimage = createSighashPreimage(transaction, sourceOutputs, i, bytecode, arg.getHashType());
122
131
  const sighash = hash256(preimage);
123
132
  return arg.generateSignature(sighash);
124
133
  });
125
134
  const preimage = this.abiFunction.covenant
126
- ? createSighashPreimage(transaction, this.inputs, i, bytecode, covenantHashType)
135
+ ? createSighashPreimage(transaction, sourceOutputs, i, bytecode, covenantHashType)
127
136
  : undefined;
128
137
  const inputScript = createInputScript(this.redeemScript, completeArgs, this.selector, preimage);
129
138
  inputScripts.push(inputScript);
@@ -169,67 +178,48 @@ export class Transaction {
169
178
  if (this.outputs.length === 0) {
170
179
  throw Error('Attempted to build a transaction without outputs');
171
180
  }
172
- // Construct object with total output of fungible tokens by tokenId
173
- const netBalanceTokens = {};
181
+ const allUtxos = await this.provider.getUtxos(this.address);
182
+ const manualTokenInputs = this.inputs.filter((input) => input.token);
183
+ // This will throw if the amount is not enough
184
+ if (manualTokenInputs.length > 0) {
185
+ selectAllTokenUtxos(manualTokenInputs, this.outputs);
186
+ }
187
+ const automaticTokenInputs = selectAllTokenUtxos(allUtxos, this.outputs);
188
+ const tokenInputs = manualTokenInputs.length > 0 ? manualTokenInputs : automaticTokenInputs;
189
+ if (this.tokenChange) {
190
+ const tokenChangeOutputs = createFungibleTokenChangeOutputs(tokenInputs, this.outputs, this.address);
191
+ this.outputs.push(...tokenChangeOutputs);
192
+ }
174
193
  // Construct list with all nfts in inputs
175
194
  const listNftsInputs = [];
176
195
  // If inputs are manually selected, add their tokens to balance
177
- for (const input of this.inputs) {
196
+ this.inputs.forEach((input) => {
178
197
  if (!input.token)
179
- continue;
180
- const tokenCategory = input.token.category;
181
- if (!netBalanceTokens[tokenCategory]) {
182
- netBalanceTokens[tokenCategory] = input.token.amount;
183
- }
184
- else {
185
- netBalanceTokens[tokenCategory] += input.token.amount;
186
- }
198
+ return;
187
199
  if (input.token.nft) {
188
200
  listNftsInputs.push({ ...input.token.nft, category: input.token.category });
189
201
  }
190
- }
202
+ });
191
203
  // Construct list with all nfts in outputs
192
204
  let listNftsOutputs = [];
193
205
  // Subtract all token outputs from the token balances
194
- for (const output of this.outputs) {
206
+ this.outputs.forEach((output) => {
195
207
  if (!output.token)
196
- continue;
197
- const tokenCategory = output.token.category;
198
- if (!netBalanceTokens[tokenCategory]) {
199
- netBalanceTokens[tokenCategory] = -output.token.amount;
200
- }
201
- else {
202
- netBalanceTokens[tokenCategory] -= output.token.amount;
203
- }
208
+ return;
204
209
  if (output.token.nft) {
205
210
  listNftsOutputs.push({ ...output.token.nft, category: output.token.category });
206
211
  }
207
- }
212
+ });
208
213
  // If inputs are manually provided, check token balances
209
214
  if (this.inputs.length > 0) {
210
- for (const [category, balance] of Object.entries(netBalanceTokens)) {
211
- // Add token change outputs if applicable
212
- if (this.tokenChange && balance > 0) {
213
- const tokenDetails = {
214
- category,
215
- amount: balance,
216
- };
217
- const tokenChangeOutput = { to: this.address, amount: BigInt(1000), token: tokenDetails };
218
- this.outputs.push(tokenChangeOutput);
219
- }
220
- // Throw error when token balance is insufficient
221
- if (balance < 0) {
222
- throw new Error(`Insufficient token balance for token with category ${category}.`);
223
- }
224
- }
225
215
  // Compare nfts in- and outputs, check if inputs have nfts corresponding to outputs
226
216
  // Keep list of nfts in inputs without matching output
227
217
  // First check immutable nfts, then mutable & minting nfts together
228
- // this is so the mutable nft in input does not get match to an output nft corresponding to an immutable nft in the inputs
218
+ // This is so an immutible input gets matched first and is removed from the list of unused nfts
229
219
  let unusedNfts = listNftsInputs;
230
220
  for (const nftInput of listNftsInputs) {
231
221
  if (nftInput.capability === 'none') {
232
- for (let i = 0; i < listNftsOutputs.length; i++) {
222
+ for (let i = 0; i < listNftsOutputs.length; i += 1) {
233
223
  // Deep equality check token objects
234
224
  if (JSON.stringify(listNftsOutputs[i]) === JSON.stringify(nftInput)) {
235
225
  listNftsOutputs.splice(i, 1);
@@ -241,6 +231,7 @@ export class Transaction {
241
231
  }
242
232
  for (const nftInput of listNftsInputs) {
243
233
  if (nftInput.capability === 'minting') {
234
+ // eslint-disable-next-line max-len
244
235
  const newListNftsOutputs = listNftsOutputs.filter((nftOutput) => nftOutput.category !== nftInput.category);
245
236
  if (newListNftsOutputs !== listNftsOutputs) {
246
237
  unusedNfts = unusedNfts.filter((nft) => nft !== nftInput);
@@ -248,7 +239,7 @@ export class Transaction {
248
239
  }
249
240
  }
250
241
  if (nftInput.capability === 'mutable') {
251
- for (let i = 0; i < listNftsOutputs.length; i++) {
242
+ for (let i = 0; i < listNftsOutputs.length; i += 1) {
252
243
  if (listNftsOutputs[i].category === nftInput.category) {
253
244
  listNftsOutputs.splice(i, 1);
254
245
  unusedNfts = unusedNfts.filter((nft) => nft !== nftInput);
@@ -257,8 +248,14 @@ export class Transaction {
257
248
  }
258
249
  }
259
250
  }
251
+ for (const nftOutput of listNftsOutputs) {
252
+ const genesisUtxo = getTokenGenesisUtxo(this.inputs, nftOutput.category);
253
+ if (genesisUtxo) {
254
+ listNftsOutputs = listNftsOutputs.filter((nft) => nft !== nftOutput);
255
+ }
256
+ }
260
257
  if (listNftsOutputs.length !== 0) {
261
- throw new Error('Nfts in outputs don\'t have corresponding nfts in inputs!');
258
+ throw new Error(`NFT output with token category ${listNftsOutputs[0].category} does not have corresponding input`);
262
259
  }
263
260
  if (this.tokenChange) {
264
261
  for (const unusedNft of unusedNfts) {
@@ -285,7 +282,7 @@ export class Transaction {
285
282
  // arguments and correctly sized placeholder preimage
286
283
  const placeholderScript = createInputScript(this.redeemScript, placeholderArgs, this.selector, placeholderPreimage);
287
284
  // Add one extra byte per input to over-estimate tx-in count
288
- const inputSize = getInputSize(placeholderScript) + 1;
285
+ const contractInputSize = getInputSize(placeholderScript) + 1;
289
286
  // Note that we use the addPrecision function to add "decimal points" to BigInt numbers
290
287
  // Calculate amount to send and base fee (excluding additional fees per UTXO)
291
288
  let amount = addPrecision(this.outputs.reduce((acc, output) => acc + output.amount, 0n));
@@ -294,23 +291,32 @@ export class Transaction {
294
291
  let satsAvailable = 0n;
295
292
  if (this.inputs.length > 0) {
296
293
  // If inputs are already defined, the user provided the UTXOs and we perform no further UTXO selection
297
- if (!this.hardcodedFee)
298
- fee += addPrecision(this.inputs.length * inputSize * this.feePerByte);
294
+ if (!this.hardcodedFee) {
295
+ const totalInputSize = this.inputs.reduce((acc, input) => acc + (isSignableUtxo(input) ? P2PKH_INPUT_SIZE : contractInputSize), 0);
296
+ fee += addPrecision(totalInputSize * this.feePerByte);
297
+ }
299
298
  satsAvailable = addPrecision(this.inputs.reduce((acc, input) => acc + input.satoshis, 0n));
300
299
  }
301
300
  else {
302
301
  // If inputs are not defined yet, we retrieve the contract's UTXOs and perform selection
303
- const utxos = await this.provider.getUtxos(this.address);
302
+ const bchUtxos = allUtxos.filter((utxo) => !utxo.token);
304
303
  // We sort the UTXOs mainly so there is consistent behaviour between network providers
305
304
  // even if they report UTXOs in a different order
306
- utxos.sort(utxoComparator).reverse();
307
- for (const utxo of utxos) {
305
+ bchUtxos.sort(utxoComparator).reverse();
306
+ // Add all automatically added token inputs to the transaction
307
+ for (const utxo of automaticTokenInputs) {
308
308
  this.inputs.push(utxo);
309
309
  satsAvailable += addPrecision(utxo.satoshis);
310
310
  if (!this.hardcodedFee)
311
- fee += addPrecision(inputSize * this.feePerByte);
311
+ fee += addPrecision(contractInputSize * this.feePerByte);
312
+ }
313
+ for (const utxo of bchUtxos) {
312
314
  if (satsAvailable > amount + fee)
313
315
  break;
316
+ this.inputs.push(utxo);
317
+ satsAvailable += addPrecision(utxo.satoshis);
318
+ if (!this.hardcodedFee)
319
+ fee += addPrecision(contractInputSize * this.feePerByte);
314
320
  }
315
321
  }
316
322
  // Remove "decimal points" from BigInt numbers (rounding up for fee, down for others)
@@ -334,6 +340,54 @@ export class Transaction {
334
340
  }
335
341
  }
336
342
  }
343
+ const getTokenGenesisUtxo = (utxos, tokenCategory) => {
344
+ const creationUtxo = utxos.find((utxo) => utxo.vout === 0 && utxo.txid === tokenCategory);
345
+ return creationUtxo;
346
+ };
347
+ const getTokenCategories = (outputs) => (outputs
348
+ .filter((output) => output.token)
349
+ .map((output) => output.token.category));
350
+ const calculateTotalTokenAmount = (outputs, tokenCategory) => (outputs
351
+ .filter((output) => output.token?.category === tokenCategory)
352
+ .reduce((acc, output) => acc + output.token.amount, 0n));
353
+ const selectTokenUtxos = (utxos, amountNeeded, tokenCategory) => {
354
+ const genesisUtxo = getTokenGenesisUtxo(utxos, tokenCategory);
355
+ if (genesisUtxo)
356
+ return [genesisUtxo];
357
+ const tokenUtxos = utxos.filter((utxo) => utxo.token?.category === tokenCategory && utxo.token?.amount > 0n);
358
+ // We sort the UTXOs mainly so there is consistent behaviour between network providers
359
+ // even if they report UTXOs in a different order
360
+ tokenUtxos.sort(utxoTokenComparator).reverse();
361
+ let amountAvailable = 0n;
362
+ const selectedUtxos = [];
363
+ // Add token UTXOs until we have enough to cover the amount needed (no fee calculation because it's a token)
364
+ for (const utxo of tokenUtxos) {
365
+ if (amountAvailable >= amountNeeded)
366
+ break;
367
+ selectedUtxos.push(utxo);
368
+ amountAvailable += utxo.token.amount;
369
+ }
370
+ if (amountAvailable < amountNeeded) {
371
+ throw new Error(`Insufficient funds for token ${tokenCategory}: available (${amountAvailable}) < needed (${amountNeeded}).`);
372
+ }
373
+ return selectedUtxos;
374
+ };
375
+ const selectAllTokenUtxos = (utxos, outputs) => {
376
+ const tokenCategories = getTokenCategories(outputs);
377
+ return tokenCategories.flatMap((tokenCategory) => selectTokenUtxos(utxos, calculateTotalTokenAmount(outputs, tokenCategory), tokenCategory));
378
+ };
379
+ const createFungibleTokenChangeOutputs = (utxos, outputs, address) => {
380
+ const tokenCategories = getTokenCategories(utxos);
381
+ const changeOutputs = tokenCategories.map((tokenCategory) => {
382
+ const required = calculateTotalTokenAmount(outputs, tokenCategory);
383
+ const available = calculateTotalTokenAmount(utxos, tokenCategory);
384
+ const change = available - required;
385
+ if (change === 0n)
386
+ return undefined;
387
+ return { to: address, amount: BigInt(1000), token: { category: tokenCategory, amount: change } };
388
+ });
389
+ return changeOutputs.filter((output) => output !== undefined);
390
+ };
337
391
  // Note: the below is a very simple implementation of a "decimal point" system for BigInt numbers
338
392
  // It is safe to use for UTXO fee calculations due to its low numbers, but should not be used for other purposes
339
393
  // Also note that multiplication and division between two "decimal" bigints is not supported
@@ -1,2 +1,3 @@
1
1
  export declare const VERSION_SIZE = 4;
2
2
  export declare const LOCKTIME_SIZE = 4;
3
+ export declare const P2PKH_INPUT_SIZE: number;
package/dist/constants.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export const VERSION_SIZE = 4;
2
2
  export const LOCKTIME_SIZE = 4;
3
+ export const P2PKH_INPUT_SIZE = 32 + 4 + 1 + 1 + 65 + 1 + 33 + 4;
3
4
  //# sourceMappingURL=constants.js.map
@@ -29,6 +29,11 @@ export interface TokenDetails {
29
29
  commitment: string;
30
30
  };
31
31
  }
32
+ export interface NftObject {
33
+ category: string;
34
+ capability: 'none' | 'mutable' | 'minting';
35
+ commitment: string;
36
+ }
32
37
  export interface LibauthOutput {
33
38
  lockingBytecode: Uint8Array;
34
39
  valueSatoshis: bigint;
@@ -50,6 +55,7 @@ export declare enum HashType {
50
55
  SIGHASH_ALL = 1,
51
56
  SIGHASH_NONE = 2,
52
57
  SIGHASH_SINGLE = 3,
58
+ SIGHASH_UTXOS = 32,
53
59
  SIGHASH_ANYONECANPAY = 128
54
60
  }
55
61
  export declare const Network: {
@@ -11,6 +11,7 @@ export var HashType;
11
11
  HashType[HashType["SIGHASH_ALL"] = 1] = "SIGHASH_ALL";
12
12
  HashType[HashType["SIGHASH_NONE"] = 2] = "SIGHASH_NONE";
13
13
  HashType[HashType["SIGHASH_SINGLE"] = 3] = "SIGHASH_SINGLE";
14
+ HashType[HashType["SIGHASH_UTXOS"] = 32] = "SIGHASH_UTXOS";
14
15
  HashType[HashType["SIGHASH_ANYONECANPAY"] = 128] = "SIGHASH_ANYONECANPAY";
15
16
  })(HashType || (HashType = {}));
16
17
  // Weird setup to allow both Enum parameters, as well as literal strings
package/dist/utils.d.ts CHANGED
@@ -13,12 +13,14 @@ export declare function getPreimageSize(script: Uint8Array): number;
13
13
  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
- export declare function createSighashPreimage(transaction: Transaction, inputs: Utxo[], inputIndex: number, coveredBytecode: Uint8Array, hashtype: number): Uint8Array;
16
+ export declare function createSighashPreimage(transaction: Transaction, sourceOutputs: LibauthOutput[], inputIndex: number, coveredBytecode: Uint8Array, hashtype: number): Uint8Array;
17
17
  export declare function buildError(reason: string, meepStr: string): FailedTransactionError;
18
18
  export declare function meep(tx: any, utxos: Utxo[], script: Script): string;
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
+ export declare function publicKeyToP2PKHLockingBytecode(publicKey: Uint8Array): Uint8Array;
21
22
  export declare function utxoComparator(a: Utxo, b: Utxo): number;
23
+ export declare function utxoTokenComparator(a: Utxo, b: Utxo): number;
22
24
  /**
23
25
  * Helper function to convert an address to a locking script
24
26
  *
package/dist/utils.js CHANGED
@@ -119,15 +119,7 @@ function toBin(output) {
119
119
  const encode = data === output ? utf8ToBin : hexToBin;
120
120
  return encode(data);
121
121
  }
122
- export function createSighashPreimage(transaction, inputs, inputIndex, coveredBytecode, hashtype) {
123
- const sourceOutputs = inputs.map((input) => {
124
- const sourceOutput = {
125
- amount: input.satoshis,
126
- to: Uint8Array.of(),
127
- token: input.token,
128
- };
129
- return cashScriptOutputToLibauthOutput(sourceOutput);
130
- });
122
+ export function createSighashPreimage(transaction, sourceOutputs, inputIndex, coveredBytecode, hashtype) {
131
123
  const context = { inputIndex, sourceOutputs, transaction };
132
124
  const signingSerializationType = new Uint8Array([hashtype]);
133
125
  const sighashPreimage = generateSigningSerializationBCH(context, { coveredBytecode, signingSerializationType });
@@ -175,6 +167,12 @@ export function scriptToLockingBytecode(script, addressType) {
175
167
  const lockingBytecode = addressContentsToLockingBytecode(addressContents);
176
168
  return lockingBytecode;
177
169
  }
170
+ export function publicKeyToP2PKHLockingBytecode(publicKey) {
171
+ const pubkeyHash = hash160(publicKey);
172
+ const addressContents = { payload: pubkeyHash, type: LockingBytecodeType.p2pkh };
173
+ const lockingBytecode = addressContentsToLockingBytecode(addressContents);
174
+ return lockingBytecode;
175
+ }
178
176
  export function utxoComparator(a, b) {
179
177
  if (a.satoshis > b.satoshis)
180
178
  return 1;
@@ -182,6 +180,17 @@ export function utxoComparator(a, b) {
182
180
  return -1;
183
181
  return 0;
184
182
  }
183
+ export function utxoTokenComparator(a, b) {
184
+ if (!a.token || !b.token)
185
+ throw new Error('UTXO does not have token data');
186
+ if (!a.token.category !== !b.token.category)
187
+ throw new Error('UTXO token categories do not match');
188
+ if (a.token.amount > b.token.amount)
189
+ return 1;
190
+ if (a.token.amount < b.token.amount)
191
+ return -1;
192
+ return 0;
193
+ }
185
194
  /**
186
195
  * Helper function to convert an address to a locking script
187
196
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashscript",
3
- "version": "0.8.0-next.2",
3
+ "version": "0.8.0-next.4",
4
4
  "description": "Easily write and interact with Bitcoin Cash contracts",
5
5
  "keywords": [
6
6
  "bitcoin cash",
@@ -38,12 +38,13 @@
38
38
  "compile:test": "tsc -p tsconfig.test.json",
39
39
  "lint": "eslint . --ext .ts --ignore-path ../../.eslintignore",
40
40
  "prepare": "yarn build",
41
+ "prepublishOnly": "yarn test && yarn lint",
41
42
  "pretest": "yarn build:test",
42
43
  "test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"
43
44
  },
44
45
  "dependencies": {
45
46
  "@bitauth/libauth": "^2.0.0-alpha.8",
46
- "@cashscript/utils": "^0.8.0-next.2",
47
+ "@cashscript/utils": "^0.8.0-next.4",
47
48
  "bip68": "^1.0.4",
48
49
  "bitcoin-rpc-promise-retry": "^1.3.0",
49
50
  "delay": "^5.0.0",
@@ -57,5 +58,5 @@
57
58
  "jest": "^29.4.1",
58
59
  "typescript": "^4.1.5"
59
60
  },
60
- "gitHead": "37bcb8e924fc56ef8da2cb2a2640eb5478de39b2"
61
+ "gitHead": "f4c3f9c21cca98465e5cad919e64b4c6292e9a1f"
61
62
  }