cashscript 0.8.2 → 0.9.1

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 { Artifact } from '@cashscript/utils';
2
2
  import { Transaction } from './Transaction.js';
3
3
  import { Argument } from './Argument.js';
4
- import { ContractOptions, Utxo } from './interfaces.js';
4
+ import { Unlocker, ContractOptions, Utxo } from './interfaces.js';
5
5
  export declare class Contract {
6
6
  private artifact;
7
7
  private options?;
@@ -11,9 +11,8 @@ export declare class Contract {
11
11
  bytecode: string;
12
12
  bytesize: number;
13
13
  opcount: number;
14
- functions: {
15
- [name: string]: ContractFunction;
16
- };
14
+ functions: Record<string, ContractFunction>;
15
+ unlock: Record<string, ContractUnlocker>;
17
16
  private redeemScript;
18
17
  private provider;
19
18
  private addressType;
@@ -21,5 +20,7 @@ export declare class Contract {
21
20
  getBalance(): Promise<bigint>;
22
21
  getUtxos(): Promise<Utxo[]>;
23
22
  private createFunction;
23
+ private createUnlocker;
24
24
  }
25
25
  export declare type ContractFunction = (...args: Argument[]) => Transaction;
26
+ export declare type ContractUnlocker = (...args: Argument[]) => Unlocker;
package/dist/Contract.js CHANGED
@@ -1,18 +1,16 @@
1
1
  import { binToHex } from '@bitauth/libauth';
2
- import { asmToScript, calculateBytesize, countOpcodes, generateRedeemScript, scriptToBytecode, } from '@cashscript/utils';
2
+ import { asmToScript, calculateBytesize, countOpcodes, generateRedeemScript, hash256, scriptToBytecode, } from '@cashscript/utils';
3
3
  import { Transaction } from './Transaction.js';
4
4
  import { encodeArgument } from './Argument.js';
5
- import { scriptToAddress } from './utils.js';
5
+ import { addressToLockScript, createInputScript, createSighashPreimage, scriptToAddress, } from './utils.js';
6
6
  import SignatureTemplate from './SignatureTemplate.js';
7
7
  import { ElectrumNetworkProvider } from './network/index.js';
8
8
  export class Contract {
9
9
  constructor(artifact, constructorArgs, options) {
10
10
  this.artifact = artifact;
11
11
  this.options = options;
12
- const defaultProvider = new ElectrumNetworkProvider();
13
- const defaultAddressType = 'p2sh32';
14
- this.provider = this.options?.provider ?? defaultProvider;
15
- this.addressType = this.options?.addressType ?? defaultAddressType;
12
+ this.provider = this.options?.provider ?? new ElectrumNetworkProvider();
13
+ this.addressType = this.options?.addressType ?? 'p2sh32';
16
14
  const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName'];
17
15
  if (!expectedProperties.every((property) => property in artifact)) {
18
16
  throw new Error('Invalid or incomplete artifact provided');
@@ -41,6 +39,18 @@ export class Contract {
41
39
  this.functions[f.name] = this.createFunction(f, i);
42
40
  });
43
41
  }
42
+ // Populate the functions object with the contract's functions
43
+ // (with a special case for single function, which has no "function selector")
44
+ this.unlock = {};
45
+ if (artifact.abi.length === 1) {
46
+ const f = artifact.abi[0];
47
+ this.unlock[f.name] = this.createUnlocker(f);
48
+ }
49
+ else {
50
+ artifact.abi.forEach((f, i) => {
51
+ this.unlock[f.name] = this.createUnlocker(f, i);
52
+ });
53
+ }
44
54
  this.name = artifact.contractName;
45
55
  this.address = scriptToAddress(this.redeemScript, this.provider.network, this.addressType, false);
46
56
  this.tokenAddress = scriptToAddress(this.redeemScript, this.provider.network, this.addressType, true);
@@ -66,5 +76,25 @@ export class Contract {
66
76
  return new Transaction(this.address, this.provider, this.redeemScript, abiFunction, encodedArgs, selector);
67
77
  };
68
78
  }
79
+ createUnlocker(abiFunction, selector) {
80
+ return (...args) => {
81
+ const bytecode = scriptToBytecode(this.redeemScript);
82
+ const encodedArgs = args
83
+ .map((arg, i) => encodeArgument(arg, abiFunction.inputs[i].type));
84
+ const generateUnlockingBytecode = ({ transaction, sourceOutputs, inputIndex }) => {
85
+ const completeArgs = encodedArgs.map((arg) => {
86
+ if (!(arg instanceof SignatureTemplate))
87
+ return arg;
88
+ const preimage = createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, arg.getHashType());
89
+ const sighash = hash256(preimage);
90
+ return arg.generateSignature(sighash);
91
+ });
92
+ const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector);
93
+ return unlockingBytecode;
94
+ };
95
+ const generateLockingBytecode = () => addressToLockScript(this.address);
96
+ return { generateUnlockingBytecode, generateLockingBytecode };
97
+ };
98
+ }
69
99
  }
70
100
  //# sourceMappingURL=Contract.js.map
package/dist/Errors.d.ts CHANGED
@@ -10,8 +10,8 @@ export declare class TokensToNonTokenAddressError extends Error {
10
10
  }
11
11
  export declare class FailedTransactionError extends Error {
12
12
  reason: string;
13
- meep: string;
14
- constructor(reason: string, meep: string);
13
+ meep?: string | undefined;
14
+ constructor(reason: string, meep?: string | undefined);
15
15
  }
16
16
  export declare class FailedRequireError extends FailedTransactionError {
17
17
  }
package/dist/Errors.js CHANGED
@@ -15,7 +15,7 @@ export class TokensToNonTokenAddressError extends Error {
15
15
  }
16
16
  export class FailedTransactionError extends Error {
17
17
  constructor(reason, meep) {
18
- super(`Transaction failed with reason: ${reason}\n${meep}`);
18
+ super(`Transaction failed with reason: ${reason}${meep ? `\n${meep}` : ''}`);
19
19
  this.reason = reason;
20
20
  this.meep = meep;
21
21
  }
@@ -1,4 +1,4 @@
1
- import { HashType, SignatureAlgorithm } from './interfaces.js';
1
+ import { Unlocker, HashType, SignatureAlgorithm } from './interfaces.js';
2
2
  export default class SignatureTemplate {
3
3
  private hashtype;
4
4
  private signatureAlgorithm;
@@ -7,6 +7,7 @@ export default class SignatureTemplate {
7
7
  generateSignature(payload: Uint8Array, bchForkId?: boolean): Uint8Array;
8
8
  getHashType(bchForkId?: boolean): number;
9
9
  getPublicKey(): Uint8Array;
10
+ unlockP2PKH(): Unlocker;
10
11
  }
11
12
  interface Keypair {
12
13
  toWIF(): string;
@@ -1,5 +1,7 @@
1
1
  import { decodePrivateKeyWif, secp256k1, SigningSerializationFlag } from '@bitauth/libauth';
2
- import { HashType, SignatureAlgorithm } from './interfaces.js';
2
+ import { hash256, scriptToBytecode } from '@cashscript/utils';
3
+ import { HashType, SignatureAlgorithm, } from './interfaces.js';
4
+ import { createSighashPreimage, publicKeyToP2PKHLockingBytecode } from './utils.js';
3
5
  export default class SignatureTemplate {
4
6
  constructor(signer, hashtype = HashType.SIGHASH_ALL | HashType.SIGHASH_UTXOS, signatureAlgorithm = SignatureAlgorithm.SCHNORR) {
5
7
  this.hashtype = hashtype;
@@ -27,6 +29,21 @@ export default class SignatureTemplate {
27
29
  getPublicKey() {
28
30
  return secp256k1.derivePublicKeyCompressed(this.privateKey);
29
31
  }
32
+ unlockP2PKH() {
33
+ const publicKey = this.getPublicKey();
34
+ const prevOutScript = publicKeyToP2PKHLockingBytecode(publicKey);
35
+ const hashtype = this.getHashType();
36
+ return {
37
+ generateLockingBytecode: () => prevOutScript,
38
+ generateUnlockingBytecode: ({ transaction, sourceOutputs, inputIndex }) => {
39
+ const preimage = createSighashPreimage(transaction, sourceOutputs, inputIndex, prevOutScript, hashtype);
40
+ const sighash = hash256(preimage);
41
+ const signature = this.generateSignature(sighash);
42
+ const unlockingBytecode = scriptToBytecode([signature, publicKey]);
43
+ return unlockingBytecode;
44
+ },
45
+ };
46
+ }
30
47
  }
31
48
  function isKeypair(obj) {
32
49
  return typeof obj.toWIF === 'function';
@@ -3,7 +3,7 @@ import { hexToBin, binToHex, encodeTransaction, decodeTransaction, } from '@bita
3
3
  import delay from 'delay';
4
4
  import { hash256, placeholder, scriptToBytecode, } from '@cashscript/utils';
5
5
  import deepEqual from 'fast-deep-equal';
6
- import { isSignableUtxo, } from './interfaces.js';
6
+ import { isUtxoP2PKH, } from './interfaces.js';
7
7
  import { meep, createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, buildError, createSighashPreimage, validateRecipient, utxoComparator, cashScriptOutputToLibauthOutput, calculateDust, getOutputSize, addressToLockScript, publicKeyToP2PKHLockingBytecode, utxoTokenComparator, } from './utils.js';
8
8
  import SignatureTemplate from './SignatureTemplate.js';
9
9
  import { P2PKH_INPUT_SIZE } from './constants.js';
@@ -95,7 +95,7 @@ export class Transaction {
95
95
  const sourceOutputs = this.inputs.map((input) => {
96
96
  const sourceOutput = {
97
97
  amount: input.satoshis,
98
- to: isSignableUtxo(input) ? publicKeyToP2PKHLockingBytecode(input.template.getPublicKey()) : lockingBytecode,
98
+ to: isUtxoP2PKH(input) ? publicKeyToP2PKHLockingBytecode(input.template.getPublicKey()) : lockingBytecode,
99
99
  token: input.token,
100
100
  };
101
101
  return cashScriptOutputToLibauthOutput(sourceOutput);
@@ -110,7 +110,7 @@ export class Transaction {
110
110
  const inputScripts = [];
111
111
  this.inputs.forEach((utxo, i) => {
112
112
  // UTXO's with signature templates are signed using P2PKH
113
- if (isSignableUtxo(utxo)) {
113
+ if (isUtxoP2PKH(utxo)) {
114
114
  const pubkey = utxo.template.getPublicKey();
115
115
  const prevOutScript = publicKeyToP2PKHLockingBytecode(pubkey);
116
116
  const hashtype = utxo.template.getHashType();
@@ -293,7 +293,7 @@ export class Transaction {
293
293
  if (this.inputs.length > 0) {
294
294
  // If inputs are already defined, the user provided the UTXOs and we perform no further UTXO selection
295
295
  if (!this.hardcodedFee) {
296
- const totalInputSize = this.inputs.reduce((acc, input) => acc + (isSignableUtxo(input) ? P2PKH_INPUT_SIZE : contractInputSize), 0);
296
+ const totalInputSize = this.inputs.reduce((acc, input) => acc + (isUtxoP2PKH(input) ? P2PKH_INPUT_SIZE : contractInputSize), 0);
297
297
  fee += addPrecision(totalInputSize * this.feePerByte);
298
298
  }
299
299
  satsAvailable = addPrecision(this.inputs.reduce((acc, input) => acc + input.satoshis, 0n));
@@ -0,0 +1,26 @@
1
+ import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions } from './interfaces.js';
2
+ import { NetworkProvider } from './network/index.js';
3
+ export interface TransactionBuilderOptions {
4
+ provider: NetworkProvider;
5
+ }
6
+ export declare class TransactionBuilder {
7
+ private provider;
8
+ private inputs;
9
+ private outputs;
10
+ private locktime;
11
+ private maxFee?;
12
+ constructor(options: TransactionBuilderOptions);
13
+ addInput(utxo: Utxo, unlocker: Unlocker, options?: InputOptions): this;
14
+ addInputs(utxos: Utxo[], unlocker: Unlocker, options?: InputOptions): this;
15
+ addInputs(utxos: UnlockableUtxo[]): this;
16
+ addOutput(output: Output): this;
17
+ addOutputs(outputs: Output[]): this;
18
+ addOpReturnOutput(chunks: string[]): this;
19
+ setLocktime(locktime: number): this;
20
+ setMaxFee(maxFee: bigint): this;
21
+ private checkMaxFee;
22
+ build(): string;
23
+ send(): Promise<TransactionDetails>;
24
+ send(raw: true): Promise<string>;
25
+ private getTxDetails;
26
+ }
@@ -0,0 +1,118 @@
1
+ import { binToHex, decodeTransaction, encodeTransaction, hexToBin, } from '@bitauth/libauth';
2
+ import delay from 'delay';
3
+ import { isUnlockableUtxo, } from './interfaces.js';
4
+ import { buildError, cashScriptOutputToLibauthOutput, createOpReturnOutput } from './utils.js';
5
+ const DEFAULT_SEQUENCE = 0xfffffffe;
6
+ export class TransactionBuilder {
7
+ constructor(options) {
8
+ this.inputs = [];
9
+ this.outputs = [];
10
+ this.provider = options.provider;
11
+ }
12
+ addInput(utxo, unlocker, options) {
13
+ this.inputs.push({ ...utxo, unlocker, options });
14
+ return this;
15
+ }
16
+ addInputs(utxos, unlocker, options) {
17
+ if ((!unlocker && utxos.some((utxo) => !isUnlockableUtxo(utxo)))
18
+ || (unlocker && utxos.some((utxo) => isUnlockableUtxo(utxo)))) {
19
+ throw new Error('Either all UTXOs must have an individual unlocker speciifed, or no UTXOs must have an individual unlocker specified and a shared unlocker must be provided');
20
+ }
21
+ if (!unlocker) {
22
+ this.inputs = this.inputs.concat(utxos);
23
+ return this;
24
+ }
25
+ this.inputs = this.inputs.concat(utxos.map(((utxo) => ({ ...utxo, unlocker, options }))));
26
+ return this;
27
+ }
28
+ addOutput(output) {
29
+ this.outputs.push(output);
30
+ return this;
31
+ }
32
+ addOutputs(outputs) {
33
+ this.outputs = this.outputs.concat(outputs);
34
+ return this;
35
+ }
36
+ // TODO: allow uint8array for chunks
37
+ addOpReturnOutput(chunks) {
38
+ this.outputs.push(createOpReturnOutput(chunks));
39
+ return this;
40
+ }
41
+ setLocktime(locktime) {
42
+ this.locktime = locktime;
43
+ return this;
44
+ }
45
+ setMaxFee(maxFee) {
46
+ this.maxFee = maxFee;
47
+ return this;
48
+ }
49
+ checkMaxFee() {
50
+ if (!this.maxFee)
51
+ return;
52
+ const totalInputAmount = this.inputs.reduce((total, input) => total + input.satoshis, 0n);
53
+ const totalOutputAmount = this.outputs.reduce((total, output) => total + output.amount, 0n);
54
+ const fee = totalInputAmount - totalOutputAmount;
55
+ if (fee > this.maxFee) {
56
+ throw new Error(`Transaction fee of ${fee} is higher than max fee of ${this.maxFee}`);
57
+ }
58
+ }
59
+ build() {
60
+ this.checkMaxFee();
61
+ const inputs = this.inputs.map((utxo) => ({
62
+ outpointIndex: utxo.vout,
63
+ outpointTransactionHash: hexToBin(utxo.txid),
64
+ sequenceNumber: utxo.options?.sequence ?? DEFAULT_SEQUENCE,
65
+ unlockingBytecode: new Uint8Array(),
66
+ }));
67
+ const outputs = this.outputs.map(cashScriptOutputToLibauthOutput);
68
+ const transaction = {
69
+ inputs,
70
+ locktime: this.locktime,
71
+ outputs,
72
+ version: 2,
73
+ };
74
+ // Generate source outputs from inputs (for signing with SIGHASH_UTXOS)
75
+ const sourceOutputs = this.inputs.map((input) => {
76
+ const sourceOutput = {
77
+ amount: input.satoshis,
78
+ to: input.unlocker.generateLockingBytecode(),
79
+ token: input.token,
80
+ };
81
+ return cashScriptOutputToLibauthOutput(sourceOutput);
82
+ });
83
+ const inputScripts = this.inputs.map((input, inputIndex) => (input.unlocker.generateUnlockingBytecode({ transaction, sourceOutputs, inputIndex })));
84
+ inputScripts.forEach((script, i) => {
85
+ transaction.inputs[i].unlockingBytecode = script;
86
+ });
87
+ return binToHex(encodeTransaction(transaction));
88
+ }
89
+ async send(raw) {
90
+ const tx = this.build();
91
+ try {
92
+ const txid = await this.provider.sendRawTransaction(tx);
93
+ return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
94
+ }
95
+ catch (e) {
96
+ const reason = e.error ?? e.message;
97
+ throw buildError(reason);
98
+ }
99
+ }
100
+ async getTxDetails(txid, raw) {
101
+ for (let retries = 0; retries < 1200; retries += 1) {
102
+ await delay(500);
103
+ try {
104
+ const hex = await this.provider.getRawTransaction(txid);
105
+ if (raw)
106
+ return hex;
107
+ const libauthTransaction = decodeTransaction(hexToBin(hex));
108
+ return { ...libauthTransaction, txid, hex };
109
+ }
110
+ catch (ignored) {
111
+ // ignored
112
+ }
113
+ }
114
+ // Should not happen
115
+ throw new Error('Could not retrieve transaction details for over 10 minutes');
116
+ }
117
+ }
118
+ //# sourceMappingURL=TransactionBuilder.js.map
package/dist/index.d.ts CHANGED
@@ -2,9 +2,10 @@ import SignatureTemplate from './SignatureTemplate.js';
2
2
  export { SignatureTemplate };
3
3
  export { Contract, ContractFunction } from './Contract.js';
4
4
  export { Transaction } from './Transaction.js';
5
+ export { TransactionBuilder } from './TransactionBuilder.js';
5
6
  export { Argument, encodeArgument } from './Argument.js';
6
7
  export { Artifact, AbiFunction, AbiInput } from '@cashscript/utils';
7
8
  export * as utils from '@cashscript/utils';
8
- export { Utxo, Recipient, SignatureAlgorithm, HashType, Network, isSignableUtxo, } from './interfaces.js';
9
+ export { Utxo, Recipient, SignatureAlgorithm, HashType, Network, isUtxoP2PKH, } from './interfaces.js';
9
10
  export * from './Errors.js';
10
11
  export { NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, } from './network/index.js';
package/dist/index.js CHANGED
@@ -2,9 +2,10 @@ import SignatureTemplate from './SignatureTemplate.js';
2
2
  export { SignatureTemplate };
3
3
  export { Contract } from './Contract.js';
4
4
  export { Transaction } from './Transaction.js';
5
+ export { TransactionBuilder } from './TransactionBuilder.js';
5
6
  export { encodeArgument } from './Argument.js';
6
7
  export * as utils from '@cashscript/utils';
7
- export { SignatureAlgorithm, HashType, Network, isSignableUtxo, } from './interfaces.js';
8
+ export { SignatureAlgorithm, HashType, Network, isUtxoP2PKH, } from './interfaces.js';
8
9
  export * from './Errors.js';
9
10
  export { BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, } from './network/index.js';
10
11
  //# sourceMappingURL=index.js.map
@@ -7,10 +7,27 @@ export interface Utxo {
7
7
  satoshis: bigint;
8
8
  token?: TokenDetails;
9
9
  }
10
- export interface SignableUtxo extends Utxo {
10
+ export interface UnlockableUtxo extends Utxo {
11
+ unlocker: Unlocker;
12
+ options?: InputOptions;
13
+ }
14
+ export declare function isUnlockableUtxo(utxo: Utxo): utxo is UnlockableUtxo;
15
+ export interface InputOptions {
16
+ sequence?: number;
17
+ }
18
+ export interface GenerateUnlockingBytecodeOptions {
19
+ transaction: Transaction;
20
+ sourceOutputs: LibauthOutput[];
21
+ inputIndex: number;
22
+ }
23
+ export interface Unlocker {
24
+ generateLockingBytecode: () => Uint8Array;
25
+ generateUnlockingBytecode: (options: GenerateUnlockingBytecodeOptions) => Uint8Array;
26
+ }
27
+ export interface UtxoP2PKH extends Utxo {
11
28
  template: SignatureTemplate;
12
29
  }
13
- export declare function isSignableUtxo(utxo: Utxo): utxo is SignableUtxo;
30
+ export declare function isUtxoP2PKH(utxo: Utxo): utxo is UtxoP2PKH;
14
31
  export interface Recipient {
15
32
  to: string;
16
33
  amount: bigint;
@@ -1,4 +1,7 @@
1
- export function isSignableUtxo(utxo) {
1
+ export function isUnlockableUtxo(utxo) {
2
+ return 'unlocker' in utxo;
3
+ }
4
+ export function isUtxoP2PKH(utxo) {
2
5
  return 'template' in utxo;
3
6
  }
4
7
  export var SignatureAlgorithm;
package/dist/utils.d.ts CHANGED
@@ -14,7 +14,7 @@ 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;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashscript",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
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.8.2",
47
+ "@cashscript/utils": "^0.9.1",
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": "3a9c17952da919b523ff5b922a4724cb370fc83d"
62
+ "gitHead": "0043ea317d554436cf2c8927e82a8a8a04bee615"
63
63
  }