cashscript 0.11.0-next.0 → 0.11.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # CashScript
2
2
 
3
- ![Build Status](https://github.com/CashScript/cashscript/actions/workflows/github-actions.yml/badge.svg)
3
+ [![Build Status](https://github.com/CashScript/cashscript/actions/workflows/github-actions.yml/badge.svg)](https://github.com/CashScript/cashscript/actions/workflows/github-actions.yml)
4
4
  [![Coverage Status](https://img.shields.io/codecov/c/github/CashScript/cashscript.svg)](https://codecov.io/gh/CashScript/cashscript/)
5
5
  [![NPM Version](https://img.shields.io/npm/v/cashscript.svg)](https://www.npmjs.com/package/cashscript)
6
6
  [![NPM Monthly Downloads](https://img.shields.io/npm/dm/cashscript.svg)](https://www.npmjs.com/package/cashscript)
@@ -14,7 +14,7 @@ See the [GitHub repository](https://github.com/CashScript/cashscript) and the [C
14
14
  CashScript is a high-level language that allows you to write Bitcoin Cash smart contracts in a straightforward and familiar way. Its syntax is inspired by Ethereum's Solidity language, but its functionality is different since the underlying systems have very different fundamentals. See the [language documentation](https://cashscript.org/docs/language/) for a full reference of the language.
15
15
 
16
16
  ## The CashScript SDK
17
- The main way to interact with CashScript contracts and integrate them into applications is using the CashScript SDK. This SDK allows you to import `.json` artifact files that were compiled using the `cashc` compiler and convert them to `Contract` objects. These objects are used to create new contract instances. These instances are used to interact with the contracts using the functions that were implemented in the `.cash` file. For more information on the CashScript SDK, refer to the [SDK documentation](https://cashscript.org/docs/sdk/).
17
+ The main way to interact with CashScript contracts and integrate them into applications is using the CashScript SDK. This SDK allows you to import `.json` (or `.ts`) artifact files that were compiled using the `cashc` compiler and convert them to `Contract` objects. These objects are used to create new contract instances. These instances are used to interact with the contracts using the functions that were implemented in the `.cash` file. For more information on the CashScript SDK, refer to the [SDK documentation](https://cashscript.org/docs/sdk/).
18
18
 
19
19
  ### Installation
20
20
  ```bash
@@ -3,8 +3,17 @@ import { Transaction } from './Transaction.js';
3
3
  import { ConstructorArgument, FunctionArgument } from './Argument.js';
4
4
  import { Unlocker, ContractOptions, Utxo, AddressType } from './interfaces.js';
5
5
  import NetworkProvider from './network/NetworkProvider.js';
6
- export declare class Contract {
7
- artifact: Artifact;
6
+ import { ParamsToTuple, AbiToFunctionMap } from './types/type-inference.js';
7
+ export declare class Contract<TArtifact extends Artifact = Artifact, TResolved extends {
8
+ constructorInputs: ConstructorArgument[];
9
+ functions: Record<string, any>;
10
+ unlock: Record<string, any>;
11
+ } = {
12
+ constructorInputs: ParamsToTuple<TArtifact['constructorInputs']>;
13
+ functions: AbiToFunctionMap<TArtifact['abi'], Transaction>;
14
+ unlock: AbiToFunctionMap<TArtifact['abi'], Unlocker>;
15
+ }> {
16
+ artifact: TArtifact;
8
17
  private options?;
9
18
  name: string;
10
19
  address: string;
@@ -12,13 +21,13 @@ export declare class Contract {
12
21
  bytecode: string;
13
22
  bytesize: number;
14
23
  opcount: number;
15
- functions: Record<string, ContractFunction>;
16
- unlock: Record<string, ContractUnlocker>;
24
+ functions: TResolved['functions'];
25
+ unlock: TResolved['unlock'];
17
26
  redeemScript: Script;
18
27
  provider: NetworkProvider;
19
28
  addressType: AddressType;
20
29
  encodedConstructorArgs: Uint8Array[];
21
- constructor(artifact: Artifact, constructorArgs: ConstructorArgument[], options?: ContractOptions | undefined);
30
+ constructor(artifact: TArtifact, constructorArgs: TResolved['constructorInputs'], options?: ContractOptions | undefined);
22
31
  getBalance(): Promise<bigint>;
23
32
  getUtxos(): Promise<Utxo[]>;
24
33
  private createFunction;
package/dist/Contract.js CHANGED
@@ -1,22 +1,26 @@
1
1
  import { binToHex } from '@bitauth/libauth';
2
2
  import { asmToScript, calculateBytesize, countOpcodes, generateRedeemScript, hash256, scriptToBytecode, } from '@cashscript/utils';
3
3
  import { Transaction } from './Transaction.js';
4
- import { encodeFunctionArgument, encodeConstructorArguments, encodeFunctionArguments } from './Argument.js';
4
+ import { encodeFunctionArgument, encodeConstructorArguments, encodeFunctionArguments, } from './Argument.js';
5
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
+ import semver from 'semver';
8
9
  export class Contract {
9
10
  constructor(artifact, constructorArgs, options) {
10
11
  this.artifact = artifact;
11
12
  this.options = options;
12
13
  this.provider = this.options?.provider ?? new ElectrumNetworkProvider();
13
14
  this.addressType = this.options?.addressType ?? 'p2sh32';
14
- const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName'];
15
+ const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName', 'compiler'];
15
16
  if (!expectedProperties.every((property) => property in artifact)) {
16
17
  throw new Error('Invalid or incomplete artifact provided');
17
18
  }
19
+ if (!semver.satisfies(artifact.compiler.version, '>=0.7.0', { includePrerelease: true })) {
20
+ throw new Error(`Artifact compiled with unsupported compiler version: ${artifact.compiler.version}, required >=0.7.0`);
21
+ }
18
22
  if (artifact.constructorInputs.length !== constructorArgs.length) {
19
- throw new Error(`Incorrect number of arguments passed to ${artifact.contractName} constructor. Expected ${artifact.constructorInputs.length} arguments (${artifact.constructorInputs.map(input => input.type)}) but got ${constructorArgs.length}`);
23
+ throw new Error(`Incorrect number of arguments passed to ${artifact.contractName} constructor. Expected ${artifact.constructorInputs.length} arguments (${artifact.constructorInputs.map((input) => input.type)}) but got ${constructorArgs.length}`);
20
24
  }
21
25
  // Encode arguments (this also performs type checking)
22
26
  this.encodedConstructorArgs = encodeConstructorArguments(artifact, constructorArgs);
@@ -26,10 +30,12 @@ export class Contract {
26
30
  this.functions = {};
27
31
  if (artifact.abi.length === 1) {
28
32
  const f = artifact.abi[0];
33
+ // @ts-ignore TODO: see if we can use generics to make TypeScript happy
29
34
  this.functions[f.name] = this.createFunction(f);
30
35
  }
31
36
  else {
32
37
  artifact.abi.forEach((f, i) => {
38
+ // @ts-ignore TODO: see if we can use generics to make TypeScript happy
33
39
  this.functions[f.name] = this.createFunction(f, i);
34
40
  });
35
41
  }
@@ -38,10 +44,12 @@ export class Contract {
38
44
  this.unlock = {};
39
45
  if (artifact.abi.length === 1) {
40
46
  const f = artifact.abi[0];
47
+ // @ts-ignore TODO: see if we can use generics to make TypeScript happy
41
48
  this.unlock[f.name] = this.createUnlocker(f);
42
49
  }
43
50
  else {
44
51
  artifact.abi.forEach((f, i) => {
52
+ // @ts-ignore TODO: see if we can use generics to make TypeScript happy
45
53
  this.unlock[f.name] = this.createUnlocker(f, i);
46
54
  });
47
55
  }
@@ -62,7 +70,7 @@ export class Contract {
62
70
  createFunction(abiFunction, selector) {
63
71
  return (...args) => {
64
72
  if (abiFunction.inputs.length !== args.length) {
65
- throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map(input => input.type)}) but got ${args.length}`);
73
+ throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map((input) => input.type)}) but got ${args.length}`);
66
74
  }
67
75
  // Encode passed args (this also performs type checking)
68
76
  const encodedArgs = encodeFunctionArguments(abiFunction, args);
@@ -73,32 +81,25 @@ export class Contract {
73
81
  createUnlocker(abiFunction, selector) {
74
82
  return (...args) => {
75
83
  if (abiFunction.inputs.length !== args.length) {
76
- throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map(input => input.type)}) but got ${args.length}`);
84
+ throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map((input) => input.type)}) but got ${args.length}`);
77
85
  }
78
86
  const bytecode = scriptToBytecode(this.redeemScript);
79
87
  const encodedArgs = args
80
88
  .map((arg, i) => encodeFunctionArgument(arg, abiFunction.inputs[i].type));
81
89
  const generateUnlockingBytecode = ({ transaction, sourceOutputs, inputIndex }) => {
82
- // TODO: Remove old-style covenant code for v1.0 release
83
- let covenantHashType = -1;
84
90
  const completeArgs = encodedArgs.map((arg) => {
85
91
  if (!(arg instanceof SignatureTemplate))
86
92
  return arg;
87
- // First signature is used for sighash preimage (maybe not the best way)
88
- if (covenantHashType < 0)
89
- covenantHashType = arg.getHashType();
93
+ // Generate transaction signature from SignatureTemplate
90
94
  const preimage = createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, arg.getHashType());
91
95
  const sighash = hash256(preimage);
92
96
  return arg.generateSignature(sighash);
93
97
  });
94
- const preimage = abiFunction.covenant
95
- ? createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, covenantHashType)
96
- : undefined;
97
- const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector, preimage);
98
+ const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector);
98
99
  return unlockingBytecode;
99
100
  };
100
101
  const generateLockingBytecode = () => addressToLockScript(this.address);
101
- return { generateUnlockingBytecode, generateLockingBytecode };
102
+ return { generateUnlockingBytecode, generateLockingBytecode, contract: this, params: args, abiFunction };
102
103
  };
103
104
  }
104
105
  }
package/dist/Errors.d.ts CHANGED
@@ -2,9 +2,15 @@ import { Artifact, RequireStatement, Type } from '@cashscript/utils';
2
2
  export declare class TypeError extends Error {
3
3
  constructor(actual: string, expected: Type);
4
4
  }
5
+ export declare class UndefinedInputError extends Error {
6
+ constructor();
7
+ }
5
8
  export declare class OutputSatoshisTooSmallError extends Error {
6
9
  constructor(satoshis: bigint, minimumAmount: bigint);
7
10
  }
11
+ export declare class OutputTokenAmountTooSmallError extends Error {
12
+ constructor(amount: bigint);
13
+ }
8
14
  export declare class TokensToNonTokenAddressError extends Error {
9
15
  constructor(address: string);
10
16
  }
package/dist/Errors.js CHANGED
@@ -4,11 +4,21 @@ export class TypeError extends Error {
4
4
  super(`Found type '${actual}' where type '${expected.toString()}' was expected`);
5
5
  }
6
6
  }
7
+ export class UndefinedInputError extends Error {
8
+ constructor() {
9
+ super('Input is undefined');
10
+ }
11
+ }
7
12
  export class OutputSatoshisTooSmallError extends Error {
8
13
  constructor(satoshis, minimumAmount) {
9
14
  super(`Tried to add an output with ${satoshis} satoshis, which is less than the required minimum for this output-type (${minimumAmount})`);
10
15
  }
11
16
  }
17
+ export class OutputTokenAmountTooSmallError extends Error {
18
+ constructor(amount) {
19
+ super(`Tried to add an output with ${amount} tokens, which is invalid`);
20
+ }
21
+ }
12
22
  export class TokensToNonTokenAddressError extends Error {
13
23
  constructor(address) {
14
24
  super(`Tried to send tokens to an address without token support, ${address}.`);
@@ -1,11 +1,25 @@
1
- import { WalletTemplate } from '@bitauth/libauth';
1
+ import { AbiInput, Artifact } from '@cashscript/utils';
2
+ import { WalletTemplate, WalletTemplateScenarioBytecode } from '@bitauth/libauth';
3
+ import { Utxo, TokenDetails, LibauthTokenDetails, Output, SignatureAlgorithm, HashType } from './interfaces.js';
2
4
  import { Transaction } from './Transaction.js';
5
+ import { EncodedFunctionArgument } from './Argument.js';
6
+ import { Contract } from './Contract.js';
3
7
  interface BuildTemplateOptions {
4
8
  transaction: Transaction;
5
9
  transactionHex?: string;
6
10
  }
7
11
  export declare const buildTemplate: ({ transaction, transactionHex, }: BuildTemplateOptions) => Promise<WalletTemplate>;
8
12
  export declare const getBitauthUri: (template: WalletTemplate) => string;
13
+ export declare const generateTemplateScenarioTransactionOutputLockingBytecode: (csOutput: Output, contract: Contract) => string | {};
14
+ export declare const generateTemplateScenarioBytecode: (input: Utxo, p2pkhScriptName: string, placeholderKeyName: string, insertSlot?: boolean) => WalletTemplateScenarioBytecode | ["slot"];
15
+ export declare const generateTemplateScenarioParametersValues: (types: readonly AbiInput[], encodedArgs: EncodedFunctionArgument[]) => Record<string, string>;
16
+ export declare const addHexPrefixExceptEmpty: (value: string) => string;
17
+ export declare const generateTemplateScenarioKeys: (types: readonly AbiInput[], encodedArgs: EncodedFunctionArgument[]) => Record<string, string>;
18
+ export declare const formatParametersForDebugging: (types: readonly AbiInput[], args: EncodedFunctionArgument[]) => string;
19
+ export declare const getSignatureAlgorithmName: (signatureAlgorithm: SignatureAlgorithm) => string;
20
+ export declare const getHashTypeName: (hashType: HashType) => string;
21
+ export declare const formatBytecodeForDebugging: (artifact: Artifact) => string;
22
+ export declare const serialiseTokenDetails: (token?: TokenDetails | LibauthTokenDetails) => LibauthTemplateTokenDetails | undefined;
9
23
  export interface LibauthTemplateTokenDetails {
10
24
  amount: string;
11
25
  category: string;
@@ -11,8 +11,8 @@ export const buildTemplate = async ({ transaction, transactionHex = undefined, /
11
11
  const template = {
12
12
  $schema: 'https://ide.bitauth.com/authentication-template-v0.schema.json',
13
13
  description: 'Imported from cashscript',
14
- name: contract.artifact.contractName,
15
- supported: ['BCH_2023_05'],
14
+ name: 'CashScript Generated Debugging Template',
15
+ supported: ['BCH_2025_05'],
16
16
  version: 0,
17
17
  entities: generateTemplateEntities(contract.artifact, transaction.abiFunction, transaction.encodedFunctionArgs),
18
18
  scripts: generateTemplateScripts(contract.artifact, contract.addressType, transaction.abiFunction, transaction.encodedFunctionArgs, contract.encodedConstructorArgs),
@@ -28,9 +28,9 @@ export const buildTemplate = async ({ transaction, transactionHex = undefined, /
28
28
  const signatureAlgorithmName = getSignatureAlgorithmName(input.template.getSignatureAlgorithm());
29
29
  const hashtypeName = getHashTypeName(input.template.getHashType(false));
30
30
  const signatureString = `${placeholderKeyName}.${signatureAlgorithmName}.${hashtypeName}`;
31
- template.entities.parameters.scripts.push(lockScriptName, unlockScriptName);
32
- template.entities.parameters.variables = {
33
- ...template.entities.parameters.variables,
31
+ template.entities[snakeCase(contract.name + 'Parameters')].scripts.push(lockScriptName, unlockScriptName);
32
+ template.entities[snakeCase(contract.name + 'Parameters')].variables = {
33
+ ...template.entities[snakeCase(contract.name + 'Parameters')].variables,
34
34
  [placeholderKeyName]: {
35
35
  description: placeholderKeyName,
36
36
  name: placeholderKeyName,
@@ -75,12 +75,12 @@ const generateTemplateEntities = (artifact, abiFunction, encodedFunctionArgs) =>
75
75
  },
76
76
  ])));
77
77
  const entities = {
78
- parameters: {
78
+ [snakeCase(artifact.contractName + 'Parameters')]: {
79
79
  description: 'Contract creation and function parameters',
80
- name: 'parameters',
80
+ name: snakeCase(artifact.contractName + 'Parameters'),
81
81
  scripts: [
82
- 'lock',
83
- 'unlock_lock',
82
+ snakeCase(artifact.contractName + '_lock'),
83
+ snakeCase(artifact.contractName + '_unlock'),
84
84
  ],
85
85
  variables: {
86
86
  ...functionParameters,
@@ -90,7 +90,7 @@ const generateTemplateEntities = (artifact, abiFunction, encodedFunctionArgs) =>
90
90
  };
91
91
  // function_index is a special variable that indicates the function to execute
92
92
  if (artifact.abi.length > 1) {
93
- entities.parameters.variables.function_index = {
93
+ entities[snakeCase(artifact.contractName + 'Parameters')].variables.function_index = {
94
94
  description: 'Script function index to execute',
95
95
  name: 'function_index',
96
96
  type: 'WalletData',
@@ -101,14 +101,14 @@ const generateTemplateEntities = (artifact, abiFunction, encodedFunctionArgs) =>
101
101
  const generateTemplateScripts = (artifact, addressType, abiFunction, encodedFunctionArgs, encodedConstructorArgs) => {
102
102
  // definition of locking scripts and unlocking scripts with their respective bytecode
103
103
  return {
104
- unlock_lock: generateTemplateUnlockScript(artifact, abiFunction, encodedFunctionArgs),
105
- lock: generateTemplateLockScript(artifact, addressType, encodedConstructorArgs),
104
+ [snakeCase(artifact.contractName + '_unlock')]: generateTemplateUnlockScript(artifact, abiFunction, encodedFunctionArgs),
105
+ [snakeCase(artifact.contractName + '_lock')]: generateTemplateLockScript(artifact, addressType, encodedConstructorArgs),
106
106
  };
107
107
  };
108
108
  const generateTemplateLockScript = (artifact, addressType, constructorArguments) => {
109
109
  return {
110
110
  lockingType: addressType,
111
- name: 'lock',
111
+ name: snakeCase(artifact.contractName + '_lock'),
112
112
  script: [
113
113
  `// "${artifact.contractName}" contract constructor parameters`,
114
114
  formatParametersForDebugging(artifact.constructorInputs, constructorArguments),
@@ -125,15 +125,15 @@ const generateTemplateUnlockScript = (artifact, abiFunction, encodedFunctionArgs
125
125
  : [];
126
126
  return {
127
127
  // this unlocking script must pass our only scenario
128
- passes: ['evaluate_function'],
129
- name: 'unlock',
128
+ passes: [snakeCase(artifact.contractName + 'Evaluate')],
129
+ name: snakeCase(artifact.contractName + '_unlock'),
130
130
  script: [
131
131
  `// "${abiFunction.name}" function parameters`,
132
132
  formatParametersForDebugging(abiFunction.inputs, encodedFunctionArgs),
133
133
  '',
134
134
  ...functionIndexString,
135
135
  ].join('\n'),
136
- unlocks: 'lock',
136
+ unlocks: snakeCase(artifact.contractName + '_lock'),
137
137
  };
138
138
  };
139
139
  const generateTemplateScenarios = (contract, transaction, transactionHex, artifact, abiFunction, encodedFunctionArgs, encodedConstructorArgs) => {
@@ -142,8 +142,8 @@ const generateTemplateScenarios = (contract, transaction, transactionHex, artifa
142
142
  throw Error(libauthTransaction);
143
143
  const scenarios = {
144
144
  // single scenario to spend out transaction under test given the CashScript parameters provided
145
- evaluate_function: {
146
- name: 'Evaluate',
145
+ [snakeCase(artifact.contractName + 'Evaluate')]: {
146
+ name: snakeCase(artifact.contractName + 'Evaluate'),
147
147
  description: 'An example evaluation where this script execution passes.',
148
148
  data: {
149
149
  // encode values for the variables defined above in `entities` property
@@ -163,7 +163,7 @@ const generateTemplateScenarios = (contract, transaction, transactionHex, artifa
163
163
  };
164
164
  if (artifact.abi.length > 1) {
165
165
  const functionIndex = artifact.abi.findIndex((func) => func.name === transaction.abiFunction.name);
166
- scenarios.evaluate_function.data.bytecode.function_index = functionIndex.toString();
166
+ scenarios[snakeCase(artifact.contractName + 'Evaluate')].data.bytecode.function_index = functionIndex.toString();
167
167
  }
168
168
  return scenarios;
169
169
  };
@@ -190,13 +190,27 @@ const generateTemplateScenarioTransaction = (contract, libauthTransaction, csTra
190
190
  const version = libauthTransaction.version;
191
191
  return { inputs, locktime, outputs, version };
192
192
  };
193
- const generateTemplateScenarioTransactionOutputLockingBytecode = (csOutput, contract) => {
193
+ export const generateTemplateScenarioTransactionOutputLockingBytecode = (csOutput, contract) => {
194
194
  if (csOutput.to instanceof Uint8Array)
195
195
  return binToHex(csOutput.to);
196
196
  if ([contract.address, contract.tokenAddress].includes(csOutput.to))
197
197
  return {};
198
198
  return binToHex(addressToLockScript(csOutput.to));
199
199
  };
200
+ /**
201
+ * Generates source outputs for a BitAuth template scenario
202
+ *
203
+ * @param csTransaction - The CashScript transaction to generate source outputs for
204
+ * @returns An array of BitAuth template scenario outputs with locking scripts and values
205
+ *
206
+ * For each input in the transaction:
207
+ * - Generates appropriate locking bytecode (P2PKH or contract)
208
+ * - Includes the input value in satoshis
209
+ * - Includes any token details if present
210
+ *
211
+ * The slotIndex tracks which input is the contract input vs P2PKH inputs
212
+ * to properly generate the locking scripts.
213
+ */
200
214
  const generateTemplateScenarioSourceOutputs = (csTransaction) => {
201
215
  const slotIndex = csTransaction.inputs.findIndex((input) => !isUtxoP2PKH(input));
202
216
  return csTransaction.inputs.map((input, index) => {
@@ -208,7 +222,7 @@ const generateTemplateScenarioSourceOutputs = (csTransaction) => {
208
222
  });
209
223
  };
210
224
  // Used for generating the locking / unlocking bytecode for source outputs and inputs
211
- const generateTemplateScenarioBytecode = (input, p2pkhScriptName, placeholderKeyName, insertSlot) => {
225
+ export const generateTemplateScenarioBytecode = (input, p2pkhScriptName, placeholderKeyName, insertSlot) => {
212
226
  if (isUtxoP2PKH(input)) {
213
227
  return {
214
228
  script: p2pkhScriptName,
@@ -223,26 +237,29 @@ const generateTemplateScenarioBytecode = (input, p2pkhScriptName, placeholderKey
223
237
  }
224
238
  return insertSlot ? ['slot'] : {};
225
239
  };
226
- const generateTemplateScenarioParametersValues = (types, encodedArgs) => {
240
+ export const generateTemplateScenarioParametersValues = (types, encodedArgs) => {
227
241
  const typesAndArguments = zip(types, encodedArgs);
228
242
  const entries = typesAndArguments
229
243
  // SignatureTemplates are handled by the 'keys' object in the scenario
230
244
  .filter(([, arg]) => !(arg instanceof SignatureTemplate))
231
245
  .map(([input, arg]) => {
232
246
  const encodedArgumentHex = binToHex(arg);
233
- const prefixedEncodedArgument = encodedArgumentHex.length > 0 ? `0x${encodedArgumentHex}` : '';
247
+ const prefixedEncodedArgument = addHexPrefixExceptEmpty(encodedArgumentHex);
234
248
  return [snakeCase(input.name), prefixedEncodedArgument];
235
249
  });
236
250
  return Object.fromEntries(entries);
237
251
  };
238
- const generateTemplateScenarioKeys = (types, encodedArgs) => {
252
+ export const addHexPrefixExceptEmpty = (value) => {
253
+ return value.length > 0 ? `0x${value}` : '';
254
+ };
255
+ export const generateTemplateScenarioKeys = (types, encodedArgs) => {
239
256
  const typesAndArguments = zip(types, encodedArgs);
240
257
  const entries = typesAndArguments
241
258
  .filter(([, arg]) => arg instanceof SignatureTemplate)
242
259
  .map(([input, arg]) => [snakeCase(input.name), binToHex(arg.privateKey)]);
243
260
  return Object.fromEntries(entries);
244
261
  };
245
- const formatParametersForDebugging = (types, args) => {
262
+ export const formatParametersForDebugging = (types, args) => {
246
263
  if (types.length === 0)
247
264
  return '// none';
248
265
  // We reverse the arguments because the order of the arguments in the bytecode is reversed
@@ -259,14 +276,14 @@ const formatParametersForDebugging = (types, args) => {
259
276
  return `<${snakeCase(input.name)}> // ${typeStr} = <${`0x${binToHex(arg)}`}>`;
260
277
  }).join('\n');
261
278
  };
262
- const getSignatureAlgorithmName = (signatureAlgorithm) => {
279
+ export const getSignatureAlgorithmName = (signatureAlgorithm) => {
263
280
  const signatureAlgorithmNames = {
264
281
  [SignatureAlgorithm.SCHNORR]: 'schnorr_signature',
265
282
  [SignatureAlgorithm.ECDSA]: 'ecdsa_signature',
266
283
  };
267
284
  return signatureAlgorithmNames[signatureAlgorithm];
268
285
  };
269
- const getHashTypeName = (hashType) => {
286
+ export const getHashTypeName = (hashType) => {
270
287
  const hashtypeNames = {
271
288
  [HashType.SIGHASH_ALL]: 'all_outputs',
272
289
  [HashType.SIGHASH_ALL | HashType.SIGHASH_ANYONECANPAY]: 'all_outputs_single_input',
@@ -283,7 +300,7 @@ const getHashTypeName = (hashType) => {
283
300
  };
284
301
  return hashtypeNames[hashType];
285
302
  };
286
- const formatBytecodeForDebugging = (artifact) => {
303
+ export const formatBytecodeForDebugging = (artifact) => {
287
304
  if (!artifact.debug) {
288
305
  return artifact.bytecode
289
306
  .split(' ')
@@ -292,7 +309,7 @@ const formatBytecodeForDebugging = (artifact) => {
292
309
  }
293
310
  return formatBitAuthScript(bytecodeToScript(hexToBin(artifact.debug.bytecode)), artifact.debug.sourceMap, artifact.source);
294
311
  };
295
- const serialiseTokenDetails = (token) => {
312
+ export const serialiseTokenDetails = (token) => {
296
313
  if (!token)
297
314
  return undefined;
298
315
  return {
@@ -46,6 +46,7 @@ export default class SignatureTemplate {
46
46
  const unlockingBytecode = scriptToBytecode([signature, publicKey]);
47
47
  return unlockingBytecode;
48
48
  },
49
+ template: this,
49
50
  };
50
51
  }
51
52
  }
@@ -3,7 +3,7 @@ import { AbiFunction } from '@cashscript/utils';
3
3
  import { Utxo, Output, Recipient, TokenDetails, TransactionDetails, Unlocker } from './interfaces.js';
4
4
  import SignatureTemplate from './SignatureTemplate.js';
5
5
  import { Contract } from './Contract.js';
6
- import { DebugResult } from './debugging.js';
6
+ import { DebugResults } from './debugging.js';
7
7
  import { EncodedFunctionArgument } from './Argument.js';
8
8
  export declare class Transaction {
9
9
  contract: Contract;
@@ -37,7 +37,7 @@ export declare class Transaction {
37
37
  build(): Promise<string>;
38
38
  send(): Promise<TransactionDetails>;
39
39
  send(raw: true): Promise<string>;
40
- debug(): Promise<DebugResult>;
40
+ debug(): Promise<DebugResults>;
41
41
  bitauthUri(): Promise<string>;
42
42
  getLibauthTemplate(): Promise<WalletTemplate>;
43
43
  private getTxDetails;
@@ -1,9 +1,9 @@
1
1
  import { hexToBin, decodeTransaction, } from '@bitauth/libauth';
2
2
  import delay from 'delay';
3
- import { encodeBip68, placeholder, scriptToBytecode, } from '@cashscript/utils';
3
+ import { encodeBip68, placeholder, } from '@cashscript/utils';
4
4
  import deepEqual from 'fast-deep-equal';
5
5
  import { isUtxoP2PKH, SignatureAlgorithm, } from './interfaces.js';
6
- import { createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, validateOutput, utxoComparator, calculateDust, getOutputSize, utxoTokenComparator, } from './utils.js';
6
+ import { createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, validateOutput, utxoComparator, calculateDust, getOutputSize, utxoTokenComparator, } from './utils.js';
7
7
  import SignatureTemplate from './SignatureTemplate.js';
8
8
  import { P2PKH_INPUT_SIZE } from './constants.js';
9
9
  import { TransactionBuilder } from './TransactionBuilder.js';
@@ -101,10 +101,7 @@ export class Transaction {
101
101
  async send(raw) {
102
102
  const tx = await this.build();
103
103
  // Debug the transaction locally before sending so any errors are caught early
104
- // Libauth debugging does not work with old-style covenants
105
- if (!this.abiFunction.covenant) {
106
- await this.debug();
107
- }
104
+ await this.debug();
108
105
  try {
109
106
  const txid = await this.contract.provider.sendRawTransaction(tx);
110
107
  return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
@@ -120,7 +117,7 @@ export class Transaction {
120
117
  console.warn('No debug information found in artifact. Recompile with cashc version 0.10.0 or newer to get better debugging information.');
121
118
  }
122
119
  const template = await this.getLibauthTemplate();
123
- return debugTemplate(template, this.contract.artifact);
120
+ return debugTemplate(template, [this.contract.artifact]);
124
121
  }
125
122
  async bitauthUri() {
126
123
  const template = await this.getLibauthTemplate();
@@ -259,13 +256,8 @@ export class Transaction {
259
256
  // (see https://transactionfee.info/charts/bitcoin-script-ecdsa-length/)
260
257
  return placeholder(73);
261
258
  });
262
- // Create a placeholder preimage of the correct size
263
- const placeholderPreimage = this.abiFunction.covenant
264
- ? placeholder(getPreimageSize(scriptToBytecode(this.contract.redeemScript)))
265
- : undefined;
266
- // Create a placeholder input script for size calculation using the placeholder
267
- // arguments and correctly sized placeholder preimage
268
- const placeholderScript = createInputScript(this.contract.redeemScript, placeholderArgs, this.selector, placeholderPreimage);
259
+ // Create a placeholder input script for size calculation using the placeholder arguments
260
+ const placeholderScript = createInputScript(this.contract.redeemScript, placeholderArgs, this.selector);
269
261
  // Add one extra byte per input to over-estimate tx-in count
270
262
  const contractInputSize = getInputSize(placeholderScript) + 1;
271
263
  // Note that we use the addPrecision function to add "decimal points" to BigInt numbers
@@ -1,5 +1,7 @@
1
+ import { Transaction as LibauthTransaction, WalletTemplate } from '@bitauth/libauth';
1
2
  import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions } from './interfaces.js';
2
3
  import { NetworkProvider } from './network/index.js';
4
+ import { DebugResults } from './debugging.js';
3
5
  export interface TransactionBuilderOptions {
4
6
  provider: NetworkProvider;
5
7
  }
@@ -7,8 +9,8 @@ export declare class TransactionBuilder {
7
9
  provider: NetworkProvider;
8
10
  inputs: UnlockableUtxo[];
9
11
  outputs: Output[];
10
- private locktime;
11
- private maxFee?;
12
+ locktime: number;
13
+ maxFee?: bigint;
12
14
  constructor(options: TransactionBuilderOptions);
13
15
  addInput(utxo: Utxo, unlocker: Unlocker, options?: InputOptions): this;
14
16
  addInputs(utxos: Utxo[], unlocker: Unlocker, options?: InputOptions): this;
@@ -19,7 +21,11 @@ export declare class TransactionBuilder {
19
21
  setLocktime(locktime: number): this;
20
22
  setMaxFee(maxFee: bigint): this;
21
23
  private checkMaxFee;
24
+ buildLibauthTransaction(): LibauthTransaction;
22
25
  build(): string;
26
+ debug(): Promise<DebugResults>;
27
+ bitauthUri(): string;
28
+ getLibauthTemplate(): WalletTemplate;
23
29
  send(): Promise<TransactionDetails>;
24
30
  send(raw: true): Promise<string>;
25
31
  private getTxDetails;
@@ -1,20 +1,23 @@
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 { cashScriptOutputToLibauthOutput, createOpReturnOutput, validateOutput, } from './utils.js';
4
+ import { cashScriptOutputToLibauthOutput, createOpReturnOutput, validateInput, validateOutput, } from './utils.js';
5
5
  import { FailedTransactionError } from './Errors.js';
6
+ import { getBitauthUri } from './LibauthTemplate.js';
7
+ import { debugLibauthTemplate, getLibauthTemplates } from './advanced/LibauthTemplate.js';
6
8
  const DEFAULT_SEQUENCE = 0xfffffffe;
7
9
  export class TransactionBuilder {
8
10
  constructor(options) {
9
11
  this.inputs = [];
10
12
  this.outputs = [];
13
+ this.locktime = 0;
11
14
  this.provider = options.provider;
12
15
  }
13
16
  addInput(utxo, unlocker, options) {
14
- this.inputs.push({ ...utxo, unlocker, options });
15
- return this;
17
+ return this.addInputs([utxo], unlocker, options);
16
18
  }
17
19
  addInputs(utxos, unlocker, options) {
20
+ utxos.forEach(validateInput);
18
21
  if ((!unlocker && utxos.some((utxo) => !isUnlockableUtxo(utxo)))
19
22
  || (unlocker && utxos.some((utxo) => isUnlockableUtxo(utxo)))) {
20
23
  throw new Error('Either all UTXOs must have an individual unlocker specified, or no UTXOs must have an individual unlocker specified and a shared unlocker must be provided');
@@ -57,7 +60,7 @@ export class TransactionBuilder {
57
60
  throw new Error(`Transaction fee of ${fee} is higher than max fee of ${this.maxFee}`);
58
61
  }
59
62
  }
60
- build() {
63
+ buildLibauthTransaction() {
61
64
  this.checkMaxFee();
62
65
  const inputs = this.inputs.map((utxo) => ({
63
66
  outpointIndex: utxo.vout,
@@ -85,10 +88,27 @@ export class TransactionBuilder {
85
88
  inputScripts.forEach((script, i) => {
86
89
  transaction.inputs[i].unlockingBytecode = script;
87
90
  });
91
+ return transaction;
92
+ }
93
+ build() {
94
+ const transaction = this.buildLibauthTransaction();
88
95
  return binToHex(encodeTransaction(transaction));
89
96
  }
97
+ // method to debug the transaction with libauth VM, throws upon evaluation error
98
+ // TODO: Remove the async in the future (this currently breaks our debugging tests)
99
+ async debug() {
100
+ return debugLibauthTemplate(this.getLibauthTemplate(), this);
101
+ }
102
+ bitauthUri() {
103
+ return getBitauthUri(this.getLibauthTemplate());
104
+ }
105
+ getLibauthTemplate() {
106
+ return getLibauthTemplates(this);
107
+ }
90
108
  async send(raw) {
91
109
  const tx = this.build();
110
+ // Debug the transaction locally before sending so any errors are caught early
111
+ await this.debug();
92
112
  try {
93
113
  const txid = await this.provider.sendRawTransaction(tx);
94
114
  return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);