cashscript 0.11.5 → 0.12.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.
package/README.md CHANGED
@@ -37,17 +37,29 @@ Using the CashScript SDK, you can import contract artifact files, create new ins
37
37
  const provider = new ElectrumNetworkProvider('mainnet');
38
38
 
39
39
  // Create a new P2PKH contract with constructor arguments: { pkh: pkh }
40
- const contract = new Contract(P2PKH, [pkh], provider);
40
+ const contract = new Contract(P2PKH, [pkh], { provider });
41
41
 
42
42
  // Get contract balance & output address + balance
43
43
  console.log('contract address:', contract.address);
44
44
  console.log('contract balance:', await contract.getBalance());
45
45
 
46
- // Call the spend function with the owner's signature
47
- // And use it to send 0. 000 100 00 BCH back to the contract's address
48
- const txDetails = await contract.functions
49
- .spend(pk, new SignatureTemplate(keypair))
50
- .to(contract.address, 10000)
46
+ const transactionBuilder = new TransactionBuilder({ provider });
47
+ const contractUtxos = await contract.getUtxos();
48
+
49
+ const sendAmount = 10_000n;
50
+ const destinationAddress = '... some address ...';
51
+
52
+ // Calculate the change amount, accounting for a miner fee of 1000 satoshis
53
+ const changeAmount = contractUtxos[0].satoshis - sendAmount - 1000n;
54
+
55
+ // Construct a transaction with the transaction builder
56
+ const txDetails = await transactionBuilder
57
+ // Add a contract input that spends from the contract using the 'spend' function
58
+ .addInput(contractUtxos[0], contract.unlock.spend(pk, new SignatureTemplate(keypair)))
59
+ // Add an output that sends 0. 000 100 00 BCH back to the destination address
60
+ .addOutput({ to: destinationAddress, amount: sendAmount })
61
+ // Add a change output that sends the change back to the contract's address
62
+ .addOutput({ to: contract.address, amount: changeAmount })
51
63
  .send();
52
64
 
53
65
  console.log(txDetails);
package/dist/Argument.js CHANGED
@@ -34,15 +34,19 @@ export function encodeFunctionArgument(argument, typeStr) {
34
34
  if (!(argument instanceof Uint8Array)) {
35
35
  throw Error(`Value for type ${type} should be a Uint8Array or hex string`);
36
36
  }
37
- // Redefine SIG as a bytes65 so it is included in the size checks below
38
- // Note that ONLY Schnorr signatures are accepted
39
- if (type === PrimitiveType.SIG && argument.byteLength !== 0) {
40
- type = new BytesType(65);
37
+ // Redefine SIG as a bytes65 (Schnorr) or bytes71, bytes72, bytes73 (ECDSA) or bytes0 (for NULLFAIL)
38
+ if (type === PrimitiveType.SIG) {
39
+ if (![0, 65, 71, 72, 73].includes(argument.byteLength)) {
40
+ throw new TypeError(`bytes${argument.byteLength}`, type);
41
+ }
42
+ type = new BytesType(argument.byteLength);
41
43
  }
42
- // Redefine DATASIG as a bytes64 so it is included in the size checks below
43
- // Note that ONLY Schnorr signatures are accepted
44
- if (type === PrimitiveType.DATASIG && argument.byteLength !== 0) {
45
- type = new BytesType(64);
44
+ // Redefine DATASIG as a bytes64 (Schnorr) or bytes70, bytes71, bytes72 (ECDSA) or bytes0 (for NULLFAIL)
45
+ if (type === PrimitiveType.DATASIG) {
46
+ if (![0, 64, 70, 71, 72].includes(argument.byteLength)) {
47
+ throw new TypeError(`bytes${argument.byteLength}`, type);
48
+ }
49
+ type = new BytesType(argument.byteLength);
46
50
  }
47
51
  // Bounded bytes types require a correctly sized argument
48
52
  if (type instanceof BytesType && type.bound && argument.byteLength !== type.bound) {
@@ -1,36 +1,30 @@
1
1
  import { Artifact, Script } from '@cashscript/utils';
2
- import { Transaction } from './Transaction.js';
3
- import { ConstructorArgument, FunctionArgument } from './Argument.js';
2
+ import { ConstructorArgument } from './Argument.js';
4
3
  import { Unlocker, ContractOptions, Utxo, AddressType } from './interfaces.js';
5
4
  import NetworkProvider from './network/NetworkProvider.js';
6
5
  import { ParamsToTuple, AbiToFunctionMap } from './types/type-inference.js';
7
6
  export declare class Contract<TArtifact extends Artifact = Artifact, TResolved extends {
8
7
  constructorInputs: ConstructorArgument[];
9
- functions: Record<string, any>;
10
8
  unlock: Record<string, any>;
11
9
  } = {
12
10
  constructorInputs: ParamsToTuple<TArtifact['constructorInputs']>;
13
- functions: AbiToFunctionMap<TArtifact['abi'], Transaction>;
14
11
  unlock: AbiToFunctionMap<TArtifact['abi'], Unlocker>;
15
12
  }> {
16
13
  artifact: TArtifact;
17
- private options?;
14
+ private options;
18
15
  name: string;
19
16
  address: string;
20
17
  tokenAddress: string;
21
18
  bytecode: string;
22
19
  bytesize: number;
23
20
  opcount: number;
24
- functions: TResolved['functions'];
25
21
  unlock: TResolved['unlock'];
26
22
  redeemScript: Script;
27
23
  provider: NetworkProvider;
28
24
  addressType: AddressType;
29
25
  encodedConstructorArgs: Uint8Array[];
30
- constructor(artifact: TArtifact, constructorArgs: TResolved['constructorInputs'], options?: ContractOptions | undefined);
26
+ constructor(artifact: TArtifact, constructorArgs: TResolved['constructorInputs'], options: ContractOptions);
31
27
  getBalance(): Promise<bigint>;
32
28
  getUtxos(): Promise<Utxo[]>;
33
- private createFunction;
34
29
  private createUnlocker;
35
30
  }
36
- export type ContractFunction = (...args: FunctionArgument[]) => Transaction;
package/dist/Contract.js CHANGED
@@ -1,17 +1,15 @@
1
1
  import { binToHex } from '@bitauth/libauth';
2
2
  import { asmToScript, calculateBytesize, countOpcodes, generateRedeemScript, hash256, scriptToBytecode, } from '@cashscript/utils';
3
- import { Transaction } from './Transaction.js';
4
- import { encodeFunctionArgument, encodeConstructorArguments, encodeFunctionArguments, } from './Argument.js';
3
+ import { encodeFunctionArgument, encodeConstructorArguments, } from './Argument.js';
5
4
  import { addressToLockScript, createInputScript, createSighashPreimage, scriptToAddress, } from './utils.js';
6
5
  import SignatureTemplate from './SignatureTemplate.js';
7
- import { ElectrumNetworkProvider } from './network/index.js';
8
6
  import semver from 'semver';
9
7
  export class Contract {
10
8
  constructor(artifact, constructorArgs, options) {
11
9
  this.artifact = artifact;
12
10
  this.options = options;
13
- this.provider = this.options?.provider ?? new ElectrumNetworkProvider();
14
- this.addressType = this.options?.addressType ?? 'p2sh32';
11
+ this.provider = this.options.provider;
12
+ this.addressType = this.options.addressType ?? 'p2sh32';
15
13
  const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName', 'compiler'];
16
14
  if (!expectedProperties.every((property) => property in artifact)) {
17
15
  throw new Error('Invalid or incomplete artifact provided');
@@ -25,21 +23,7 @@ export class Contract {
25
23
  // Encode arguments (this also performs type checking)
26
24
  this.encodedConstructorArgs = encodeConstructorArguments(artifact, constructorArgs);
27
25
  this.redeemScript = generateRedeemScript(asmToScript(this.artifact.bytecode), this.encodedConstructorArgs);
28
- // Populate the functions object with the contract's functions
29
- // (with a special case for single function, which has no "function selector")
30
- this.functions = {};
31
- if (artifact.abi.length === 1) {
32
- const f = artifact.abi[0];
33
- // @ts-ignore TODO: see if we can use generics to make TypeScript happy
34
- this.functions[f.name] = this.createFunction(f);
35
- }
36
- else {
37
- artifact.abi.forEach((f, i) => {
38
- // @ts-ignore TODO: see if we can use generics to make TypeScript happy
39
- this.functions[f.name] = this.createFunction(f, i);
40
- });
41
- }
42
- // Populate the functions object with the contract's functions
26
+ // Populate the 'unlock' object with the contract's functions
43
27
  // (with a special case for single function, which has no "function selector")
44
28
  this.unlock = {};
45
29
  if (artifact.abi.length === 1) {
@@ -67,17 +51,6 @@ export class Contract {
67
51
  async getUtxos() {
68
52
  return this.provider.getUtxos(this.address);
69
53
  }
70
- createFunction(abiFunction, selector) {
71
- return (...args) => {
72
- if (abiFunction.inputs.length !== 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}`);
74
- }
75
- // Encode passed args (this also performs type checking)
76
- const encodedArgs = encodeFunctionArguments(abiFunction, args);
77
- const unlocker = this.createUnlocker(abiFunction, selector)(...args);
78
- return new Transaction(this, unlocker, abiFunction, encodedArgs, selector);
79
- };
80
- }
81
54
  createUnlocker(abiFunction, selector) {
82
55
  return (...args) => {
83
56
  if (abiFunction.inputs.length !== args.length) {
package/dist/Errors.d.ts CHANGED
@@ -19,8 +19,8 @@ export declare class NoDebugInformationInArtifactError extends Error {
19
19
  }
20
20
  export declare class FailedTransactionError extends Error {
21
21
  reason: string;
22
- bitauthUri?: string | undefined;
23
- constructor(reason: string, bitauthUri?: string | undefined);
22
+ bitauthUri: string;
23
+ constructor(reason: string, bitauthUri: string);
24
24
  }
25
25
  export declare class FailedTransactionEvaluationError extends FailedTransactionError {
26
26
  artifact: Artifact;
@@ -5,6 +5,7 @@ export default class SignatureTemplate {
5
5
  privateKey: Uint8Array;
6
6
  constructor(signer: Keypair | Uint8Array | string, hashtype?: HashType, signatureAlgorithm?: SignatureAlgorithm);
7
7
  generateSignature(payload: Uint8Array, bchForkId?: boolean): Uint8Array;
8
+ signMessageHash(payload: Uint8Array): Uint8Array;
8
9
  getHashType(bchForkId?: boolean): number;
9
10
  getSignatureAlgorithm(): SignatureAlgorithm;
10
11
  getPublicKey(): Uint8Array;
@@ -1,4 +1,4 @@
1
- import { decodePrivateKeyWif, secp256k1, SigningSerializationFlag } from '@bitauth/libauth';
1
+ import { decodePrivateKeyWif, hexToBin, isHex, secp256k1, SigningSerializationFlag } from '@bitauth/libauth';
2
2
  import { hash256, scriptToBytecode } from '@cashscript/utils';
3
3
  import { HashType, SignatureAlgorithm, } from './interfaces.js';
4
4
  import { createSighashPreimage, publicKeyToP2PKHLockingBytecode } from './utils.js';
@@ -11,18 +11,27 @@ export default class SignatureTemplate {
11
11
  this.privateKey = decodeWif(wif);
12
12
  }
13
13
  else if (typeof signer === 'string') {
14
- this.privateKey = decodeWif(signer);
14
+ const maybeHexString = signer.startsWith('0x') ? signer.slice(2) : signer;
15
+ if (isHex(maybeHexString)) {
16
+ this.privateKey = hexToBin(maybeHexString);
17
+ }
18
+ else {
19
+ this.privateKey = decodeWif(maybeHexString);
20
+ }
15
21
  }
16
22
  else {
17
23
  this.privateKey = signer;
18
24
  }
19
25
  }
20
- // TODO: Allow signing of non-transaction messages (i.e. don't add the hashtype)
21
26
  generateSignature(payload, bchForkId) {
27
+ const signature = this.signMessageHash(payload);
28
+ return Uint8Array.from([...signature, this.getHashType(bchForkId)]);
29
+ }
30
+ signMessageHash(payload) {
22
31
  const signature = this.signatureAlgorithm === SignatureAlgorithm.SCHNORR
23
32
  ? secp256k1.signMessageHashSchnorr(this.privateKey, payload)
24
33
  : secp256k1.signMessageHashDER(this.privateKey, payload);
25
- return Uint8Array.from([...signature, this.getHashType(bchForkId)]);
34
+ return signature;
26
35
  }
27
36
  getHashType(bchForkId = true) {
28
37
  return bchForkId ? (this.hashtype | SigningSerializationFlag.forkId) : this.hashtype;
@@ -1,18 +1,21 @@
1
1
  import { Transaction as LibauthTransaction, WalletTemplate } from '@bitauth/libauth';
2
- import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions } from './interfaces.js';
2
+ import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions, VmResourceUsage } from './interfaces.js';
3
3
  import { NetworkProvider } from './network/index.js';
4
4
  import { DebugResults } from './debugging.js';
5
5
  import { WcTransactionOptions } from './walletconnect-utils.js';
6
6
  import { WcTransactionObject } from './walletconnect-utils.js';
7
7
  export interface TransactionBuilderOptions {
8
8
  provider: NetworkProvider;
9
+ maximumFeeSatoshis?: bigint;
10
+ maximumFeeSatsPerByte?: number;
11
+ allowImplicitFungibleTokenBurn?: boolean;
9
12
  }
10
13
  export declare class TransactionBuilder {
11
14
  provider: NetworkProvider;
12
15
  inputs: UnlockableUtxo[];
13
16
  outputs: Output[];
14
17
  locktime: number;
15
- maxFee?: bigint;
18
+ options: TransactionBuilderOptions;
16
19
  constructor(options: TransactionBuilderOptions);
17
20
  addInput(utxo: Utxo, unlocker: Unlocker, options?: InputOptions): this;
18
21
  addInputs(utxos: Utxo[], unlocker: Unlocker, options?: InputOptions): this;
@@ -21,12 +24,13 @@ export declare class TransactionBuilder {
21
24
  addOutputs(outputs: Output[]): this;
22
25
  addOpReturnOutput(chunks: string[]): this;
23
26
  setLocktime(locktime: number): this;
24
- setMaxFee(maxFee: bigint): this;
25
27
  private checkMaxFee;
28
+ private checkFungibleTokenBurn;
26
29
  buildLibauthTransaction(): LibauthTransaction;
27
30
  build(): string;
28
31
  debug(): DebugResults;
29
- bitauthUri(): string;
32
+ getVmResourceUsage(verbose?: boolean): Array<VmResourceUsage>;
33
+ getBitauthUri(): string;
30
34
  getLibauthTemplate(): WalletTemplate;
31
35
  send(): Promise<TransactionDetails>;
32
36
  send(raw: true): Promise<string>;
@@ -1,9 +1,8 @@
1
1
  import { binToHex, decodeTransaction, decodeTransactionUnsafe, encodeTransaction, hexToBin, } from '@bitauth/libauth';
2
- import { isUnlockableUtxo, isStandardUnlockableUtxo, isP2PKHUnlocker, } from './interfaces.js';
2
+ import { isUnlockableUtxo, isStandardUnlockableUtxo, isContractUnlocker, } from './interfaces.js';
3
3
  import { cashScriptOutputToLibauthOutput, createOpReturnOutput, delay, generateLibauthSourceOutputs, validateInput, validateOutput, } from './utils.js';
4
4
  import { FailedTransactionError } from './Errors.js';
5
- import { getBitauthUri } from './LibauthTemplate.js';
6
- import { debugLibauthTemplate, getLibauthTemplates } from './advanced/LibauthTemplate.js';
5
+ import { debugLibauthTemplate, getLibauthTemplate, getBitauthUri } from './libauth-template/LibauthTemplate.js';
7
6
  import { getWcContractInfo } from './walletconnect-utils.js';
8
7
  import semver from 'semver';
9
8
  const DEFAULT_SEQUENCE = 0xfffffffe;
@@ -13,6 +12,10 @@ export class TransactionBuilder {
13
12
  this.outputs = [];
14
13
  this.locktime = 0;
15
14
  this.provider = options.provider;
15
+ this.options = {
16
+ allowImplicitFungibleTokenBurn: options.allowImplicitFungibleTokenBurn ?? false,
17
+ ...options,
18
+ };
16
19
  }
17
20
  addInput(utxo, unlocker, options) {
18
21
  return this.addInputs([utxo], unlocker, options);
@@ -47,22 +50,45 @@ export class TransactionBuilder {
47
50
  this.locktime = locktime;
48
51
  return this;
49
52
  }
50
- setMaxFee(maxFee) {
51
- this.maxFee = maxFee;
52
- return this;
53
- }
54
- checkMaxFee() {
55
- if (!this.maxFee)
56
- return;
53
+ checkMaxFee(transaction) {
57
54
  const totalInputAmount = this.inputs.reduce((total, input) => total + input.satoshis, 0n);
58
55
  const totalOutputAmount = this.outputs.reduce((total, output) => total + output.amount, 0n);
59
56
  const fee = totalInputAmount - totalOutputAmount;
60
- if (fee > this.maxFee) {
61
- throw new Error(`Transaction fee of ${fee} is higher than max fee of ${this.maxFee}`);
57
+ if (this.options.maximumFeeSatoshis && fee > this.options.maximumFeeSatoshis) {
58
+ throw new Error(`Transaction fee of ${fee} is higher than max fee of ${this.options.maximumFeeSatoshis}`);
59
+ }
60
+ if (this.options.maximumFeeSatsPerByte) {
61
+ const transactionSize = encodeTransaction(transaction).byteLength;
62
+ const feePerByte = Number((Number(fee) / transactionSize).toFixed(2));
63
+ if (feePerByte > this.options.maximumFeeSatsPerByte) {
64
+ throw new Error(`Transaction fee per byte of ${feePerByte} is higher than max fee per byte of ${this.options.maximumFeeSatsPerByte}`);
65
+ }
66
+ }
67
+ }
68
+ checkFungibleTokenBurn() {
69
+ if (this.options.allowImplicitFungibleTokenBurn)
70
+ return;
71
+ const tokenInputAmounts = {};
72
+ const tokenOutputAmounts = {};
73
+ for (const input of this.inputs) {
74
+ if (input.token?.amount) {
75
+ tokenInputAmounts[input.token.category] = (tokenInputAmounts[input.token.category] || 0n) + input.token.amount;
76
+ }
77
+ }
78
+ for (const output of this.outputs) {
79
+ if (output.token?.amount) {
80
+ tokenOutputAmounts[output.token.category] = (tokenOutputAmounts[output.token.category] || 0n) + output.token.amount;
81
+ }
82
+ }
83
+ for (const [category, inputAmount] of Object.entries(tokenInputAmounts)) {
84
+ const outputAmount = tokenOutputAmounts[category] || 0n;
85
+ if (outputAmount < inputAmount) {
86
+ throw new Error(`Implicit burning of fungible tokens for category ${category} is not allowed (input amount: ${inputAmount}, output amount: ${outputAmount}). If this is intended, set allowImplicitFungibleTokenBurn to true.`);
87
+ }
62
88
  }
63
89
  }
64
90
  buildLibauthTransaction() {
65
- this.checkMaxFee();
91
+ this.checkFungibleTokenBurn();
66
92
  const inputs = this.inputs.map((utxo) => ({
67
93
  outpointIndex: utxo.vout,
68
94
  outpointTransactionHash: hexToBin(utxo.txid),
@@ -82,6 +108,7 @@ export class TransactionBuilder {
82
108
  inputScripts.forEach((script, i) => {
83
109
  transaction.inputs[i].unlockingBytecode = script;
84
110
  });
111
+ this.checkMaxFee(transaction);
85
112
  return transaction;
86
113
  }
87
114
  build() {
@@ -89,10 +116,6 @@ export class TransactionBuilder {
89
116
  return binToHex(encodeTransaction(transaction));
90
117
  }
91
118
  debug() {
92
- // do not debug a pure P2PKH-spend transaction
93
- if (this.inputs.every((input) => isP2PKHUnlocker(input.unlocker))) {
94
- return {};
95
- }
96
119
  if (this.inputs.some((input) => !isStandardUnlockableUtxo(input))) {
97
120
  throw new Error('Cannot debug a transaction with custom unlocker');
98
121
  }
@@ -105,12 +128,42 @@ export class TransactionBuilder {
105
128
  }
106
129
  return debugLibauthTemplate(this.getLibauthTemplate(), this);
107
130
  }
108
- bitauthUri() {
131
+ getVmResourceUsage(verbose = false) {
132
+ // Note that only StandardUnlockableUtxo inputs are supported for debugging, so any transaction with custom unlockers
133
+ // cannot be debugged (and therefore cannot return VM resource usage)
134
+ const results = this.debug();
135
+ const vmResourceUsage = [];
136
+ const tableData = [];
137
+ const formatMetric = (value, total, withPercentage = false) => `${formatNumber(value)} / ${formatNumber(total)}${withPercentage ? ` (${(value / total * 100).toFixed(0)}%)` : ''}`;
138
+ const formatNumber = (value) => value.toLocaleString('en');
139
+ const resultEntries = Object.entries(results);
140
+ for (const [index, input] of this.inputs.entries()) {
141
+ const [, result] = resultEntries.find(([entryKey]) => entryKey.includes(`input${index}`)) ?? [];
142
+ const metrics = result?.at(-1)?.metrics;
143
+ // Should not happen
144
+ if (!metrics)
145
+ throw new Error('VM resource could not be calculated');
146
+ vmResourceUsage.push(metrics);
147
+ tableData.push({
148
+ 'Contract - Function': isContractUnlocker(input.unlocker) ? `${input.unlocker.contract.name} - ${input.unlocker.abiFunction.name}` : 'P2PKH Input',
149
+ Ops: metrics.evaluatedInstructionCount,
150
+ 'Op Cost Budget Usage': formatMetric(metrics.operationCost, metrics.maximumOperationCost, true),
151
+ SigChecks: formatMetric(metrics.signatureCheckCount, metrics.maximumSignatureCheckCount),
152
+ Hashes: formatMetric(metrics.hashDigestIterations, metrics.maximumHashDigestIterations),
153
+ });
154
+ }
155
+ if (verbose) {
156
+ console.log('VM Resource usage by inputs:');
157
+ console.table(tableData);
158
+ }
159
+ return vmResourceUsage;
160
+ }
161
+ getBitauthUri() {
109
162
  console.warn('WARNING: it is unsafe to use this Bitauth URI when using real private keys as they are included in the transaction template');
110
163
  return getBitauthUri(this.getLibauthTemplate());
111
164
  }
112
165
  getLibauthTemplate() {
113
- return getLibauthTemplates(this);
166
+ return getLibauthTemplate(this);
114
167
  }
115
168
  async send(raw) {
116
169
  const tx = this.build();
@@ -125,7 +178,7 @@ export class TransactionBuilder {
125
178
  }
126
179
  catch (e) {
127
180
  const reason = e.error ?? e.message;
128
- throw new FailedTransactionError(reason);
181
+ throw new FailedTransactionError(reason, this.getBitauthUri());
129
182
  }
130
183
  }
131
184
  async getTxDetails(txid, raw) {
package/dist/debugging.js CHANGED
@@ -1,8 +1,24 @@
1
- import { AuthenticationErrorCommon, binToHex, createCompiler, createVirtualMachineBch2025, decodeAuthenticationInstructions, encodeAuthenticationInstruction, walletTemplateToCompilerConfiguration } from '@bitauth/libauth';
1
+ import { AuthenticationErrorCommon, binToHex, createCompiler, createVirtualMachineBch2023, createVirtualMachineBch2025, createVirtualMachineBch2026, createVirtualMachineBchSpec, decodeAuthenticationInstructions, encodeAuthenticationInstruction, walletTemplateToCompilerConfiguration } from '@bitauth/libauth';
2
2
  import { Op, PrimitiveType, asmToBytecode, bytecodeToAsm, decodeBool, decodeInt, decodeString } from '@cashscript/utils';
3
3
  import { findLastIndex, toRegExp } from './utils.js';
4
4
  import { FailedRequireError, FailedTransactionError, FailedTransactionEvaluationError } from './Errors.js';
5
- import { getBitauthUri } from './LibauthTemplate.js';
5
+ import { getBitauthUri } from './libauth-template/LibauthTemplate.js';
6
+ /* eslint-enable @typescript-eslint/indent */
7
+ const createVirtualMachine = (vmTarget) => {
8
+ switch (vmTarget) {
9
+ case 'BCH_2023_05':
10
+ return createVirtualMachineBch2023();
11
+ case 'BCH_2025_05':
12
+ return createVirtualMachineBch2025();
13
+ case 'BCH_2026_05':
14
+ return createVirtualMachineBch2026();
15
+ case 'BCH_SPEC':
16
+ // TODO: This typecast is shitty, but it's hard to fix
17
+ return createVirtualMachineBchSpec();
18
+ default:
19
+ throw new Error(`Debugging is not supported for the ${vmTarget} virtual machine.`);
20
+ }
21
+ };
6
22
  // debugs the template, optionally logging the execution data
7
23
  export const debugTemplate = (template, artifacts) => {
8
24
  // If a contract has the same name, but a different bytecode, then it is considered a name collision
@@ -14,13 +30,7 @@ export const debugTemplate = (template, artifacts) => {
14
30
  const unlockingScriptIds = Object.keys(template.scripts).filter((key) => 'unlocks' in template.scripts[key]);
15
31
  for (const unlockingScriptId of unlockingScriptIds) {
16
32
  const scenarioIds = template.scripts[unlockingScriptId].passes ?? [];
17
- // There are no scenarios defined for P2PKH placeholder scripts, so we skip them
18
- if (scenarioIds.length === 0)
19
- continue;
20
33
  const matchingArtifact = artifacts.find((artifact) => unlockingScriptId.startsWith(artifact.contractName));
21
- if (!matchingArtifact) {
22
- throw new Error(`No artifact found for unlocking script ${unlockingScriptId}`);
23
- }
24
34
  for (const scenarioId of scenarioIds) {
25
35
  results[`${unlockingScriptId}.${scenarioId}`] = debugSingleScenario(template, matchingArtifact, unlockingScriptId, scenarioId);
26
36
  }
@@ -38,11 +48,14 @@ const debugSingleScenario = (template, artifact, unlockingScriptId, scenarioId)
38
48
  // https://libauth.org/types/AuthenticationProgramStateControlStack.html
39
49
  const executedDebugSteps = lockingScriptDebugResult
40
50
  .filter((debugStep) => debugStep.controlStack.every(item => item === true));
41
- const executedLogs = (artifact.debug?.logs ?? [])
42
- .filter((log) => executedDebugSteps.some((debugStep) => log.ip === debugStep.ip));
43
- for (const log of executedLogs) {
44
- const inputIndex = extractInputIndexFromScenario(scenarioId);
45
- logConsoleLogStatement(log, executedDebugSteps, artifact, inputIndex);
51
+ // P2PKH inputs do not have an artifact, so we skip the console.log handling
52
+ if (artifact) {
53
+ const executedLogs = (artifact.debug?.logs ?? [])
54
+ .filter((log) => executedDebugSteps.some((debugStep) => log.ip === debugStep.ip));
55
+ for (const log of executedLogs) {
56
+ const inputIndex = extractInputIndexFromScenario(scenarioId);
57
+ logConsoleLogStatement(log, executedDebugSteps, artifact, inputIndex, vm);
58
+ }
46
59
  }
47
60
  const lastExecutedDebugStep = executedDebugSteps[executedDebugSteps.length - 1];
48
61
  // If an error is present in the last step, that means a require statement in the middle of the function failed
@@ -60,9 +73,15 @@ const debugSingleScenario = (template, artifact, unlockingScriptId, scenarioId)
60
73
  // Note that we do NOT use this adjusted IP when passing the failing IP into the FailedRequireError.
61
74
  const isNullFail = lastExecutedDebugStep.error.includes(AuthenticationErrorCommon.nonNullSignatureFailure);
62
75
  const requireStatementIp = failingIp + (isNullFail && isSignatureCheckWithoutVerify(failingInstruction) ? 1 : 0);
76
+ const { program: { inputIndex }, error } = lastExecutedDebugStep;
77
+ // If there is no artifact, this is a P2PKH debug error, error can occur when final CHECKSIG fails with NULLFAIL or when
78
+ // public key does not match pkh in EQUALVERIFY
79
+ // Note: due to P2PKHUnlocker implementation, the CHECKSIG cannot fail in practice, only the EQUALVERIFY can fail
80
+ if (!artifact) {
81
+ throw new FailedTransactionError(error, getBitauthUri(template));
82
+ }
63
83
  const requireStatement = (artifact.debug?.requires ?? [])
64
84
  .find((statement) => statement.ip === requireStatementIp);
65
- const { program: { inputIndex }, error } = lastExecutedDebugStep;
66
85
  if (requireStatement) {
67
86
  // Note that we use failingIp here rather than requireStatementIp, see comment above
68
87
  throw new FailedRequireError(artifact, failingIp, requireStatement, inputIndex, getBitauthUri(template), error);
@@ -82,9 +101,14 @@ const debugSingleScenario = (template, artifact, unlockingScriptId, scenarioId)
82
101
  // logDebugSteps(executedDebugSteps, lastExecutedDebugStep.instructions);
83
102
  // console.warn('message', finalExecutedVerifyIp);
84
103
  // console.warn(artifact.debug?.requires);
104
+ const { program: { inputIndex } } = lastExecutedDebugStep;
105
+ // If there is no artifact, this is a P2PKH debug error, final verify can only occur when final CHECKSIG failed
106
+ // Note: due to P2PKHUnlocker implementation, this cannot happen in practice
107
+ if (!artifact) {
108
+ throw new FailedTransactionError(evaluationResult, getBitauthUri(template));
109
+ }
85
110
  const requireStatement = (artifact.debug?.requires ?? [])
86
111
  .find((message) => message.ip === finalExecutedVerifyIp);
87
- const { program: { inputIndex } } = lastExecutedDebugStep;
88
112
  if (requireStatement) {
89
113
  throw new FailedRequireError(artifact, sourcemapInstructionPointer, requireStatement, inputIndex, getBitauthUri(template));
90
114
  }
@@ -102,7 +126,7 @@ const extractInputIndexFromScenario = (scenarioId) => {
102
126
  // internal util. instantiates the virtual machine and compiles the template into a program
103
127
  const createProgram = (template, unlockingScriptId, scenarioId) => {
104
128
  const configuration = walletTemplateToCompilerConfiguration(template);
105
- const vm = createVirtualMachineBch2025();
129
+ const vm = createVirtualMachine(template.supported[0]);
106
130
  const compiler = createCompiler(configuration);
107
131
  if (!template.scripts[unlockingScriptId]) {
108
132
  throw new Error(`No unlock script found in template for ID ${unlockingScriptId}`);
@@ -123,18 +147,18 @@ const createProgram = (template, unlockingScriptId, scenarioId) => {
123
147
  }
124
148
  return { vm, program: scenarioGeneration.scenario.program };
125
149
  };
126
- const logConsoleLogStatement = (log, debugSteps, artifact, inputIndex) => {
150
+ const logConsoleLogStatement = (log, debugSteps, artifact, inputIndex, vm) => {
127
151
  let line = `${artifact.contractName}.cash:${log.line}`;
128
152
  const decodedData = log.data.map((element) => {
129
153
  if (typeof element === 'string')
130
154
  return element;
131
155
  const debugStep = debugSteps.find((step) => step.ip === element.ip);
132
- const transformedDebugStep = applyStackItemTransformations(element, debugStep);
156
+ const transformedDebugStep = applyStackItemTransformations(element, debugStep, vm);
133
157
  return decodeStackItem(element, transformedDebugStep.stack);
134
158
  });
135
159
  console.log(`[Input #${inputIndex}] ${line} ${decodedData.join(' ')}`);
136
160
  };
137
- const applyStackItemTransformations = (element, debugStep) => {
161
+ const applyStackItemTransformations = (element, debugStep, vm) => {
138
162
  if (!element.transformations)
139
163
  return debugStep;
140
164
  const transformationsBytecode = asmToBytecode(element.transformations);
@@ -150,8 +174,9 @@ const applyStackItemTransformations = (element, debugStep) => {
150
174
  instructions: transformationsAuthenticationInstructions,
151
175
  signedMessages: [],
152
176
  program: { ...debugStep.program },
177
+ functionTable: debugStep.functionTable ?? {},
178
+ functionCount: debugStep.functionCount ?? 0,
153
179
  };
154
- const vm = createVirtualMachineBch2025();
155
180
  const transformationsEndState = vm.stateEvaluate(transformationsStartState);
156
181
  return transformationsEndState;
157
182
  };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export { default as SignatureTemplate } from './SignatureTemplate.js';
2
- export { Contract, type ContractFunction } from './Contract.js';
3
- export { Transaction } from './Transaction.js';
2
+ export { Contract } from './Contract.js';
4
3
  export { TransactionBuilder } from './TransactionBuilder.js';
5
4
  export { type ConstructorArgument, type FunctionArgument, type EncodedConstructorArgument, type EncodedFunctionArgument, encodeFunctionArgument, } from './Argument.js';
6
5
  export type { Artifact, AbiFunction, AbiInput } from '@cashscript/utils';
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  export { default as SignatureTemplate } from './SignatureTemplate.js';
2
2
  export { Contract } from './Contract.js';
3
- export { Transaction } from './Transaction.js';
4
3
  export { TransactionBuilder } from './TransactionBuilder.js';
5
4
  export { encodeFunctionArgument, } from './Argument.js';
6
5
  export * as utils from '@cashscript/utils';
@@ -1,4 +1,4 @@
1
- import { type Transaction } from '@bitauth/libauth';
1
+ import { AuthenticationProgramStateResourceLimits, type Transaction } from '@bitauth/libauth';
2
2
  import type { NetworkProvider } from './network/index.js';
3
3
  import type SignatureTemplate from './SignatureTemplate.js';
4
4
  import { Contract } from './Contract.js';
@@ -47,10 +47,6 @@ export declare function isContractUnlocker(unlocker: Unlocker): unlocker is Cont
47
47
  export declare function isP2PKHUnlocker(unlocker: Unlocker): unlocker is P2PKHUnlocker;
48
48
  export declare function isStandardUnlocker(unlocker: Unlocker): unlocker is StandardUnlocker;
49
49
  export declare function isPlaceholderUnlocker(unlocker: Unlocker): unlocker is PlaceholderP2PKHUnlocker;
50
- export interface UtxoP2PKH extends Utxo {
51
- template: SignatureTemplate;
52
- }
53
- export declare function isUtxoP2PKH(utxo: Utxo): utxo is UtxoP2PKH;
54
50
  export interface Recipient {
55
51
  to: string;
56
52
  amount: bigint;
@@ -107,12 +103,20 @@ export declare const Network: {
107
103
  REGTEST: "regtest";
108
104
  };
109
105
  export type Network = (typeof Network)[keyof typeof Network];
106
+ export declare const VmTarget: {
107
+ BCH_2023_05: "BCH_2023_05";
108
+ BCH_2025_05: "BCH_2025_05";
109
+ BCH_2026_05: "BCH_2026_05";
110
+ BCH_SPEC: "BCH_SPEC";
111
+ };
112
+ export type VmTarget = (typeof VmTarget)[keyof typeof VmTarget];
110
113
  export interface TransactionDetails extends Transaction {
111
114
  txid: string;
112
115
  hex: string;
113
116
  }
114
117
  export interface ContractOptions {
115
- provider?: NetworkProvider;
118
+ provider: NetworkProvider;
116
119
  addressType?: AddressType;
117
120
  }
118
121
  export type AddressType = 'p2sh20' | 'p2sh32';
122
+ export type VmResourceUsage = AuthenticationProgramStateResourceLimits['metrics'];
@@ -16,9 +16,6 @@ export function isStandardUnlocker(unlocker) {
16
16
  export function isPlaceholderUnlocker(unlocker) {
17
17
  return 'placeholder' in unlocker;
18
18
  }
19
- export function isUtxoP2PKH(utxo) {
20
- return 'template' in utxo;
21
- }
22
19
  export var SignatureAlgorithm;
23
20
  (function (SignatureAlgorithm) {
24
21
  SignatureAlgorithm[SignatureAlgorithm["ECDSA"] = 0] = "ECDSA";
@@ -43,4 +40,10 @@ export const Network = {
43
40
  MOCKNET: literal('mocknet'),
44
41
  REGTEST: literal('regtest'),
45
42
  };
43
+ export const VmTarget = {
44
+ BCH_2023_05: literal('BCH_2023_05'),
45
+ BCH_2025_05: literal('BCH_2025_05'),
46
+ BCH_2026_05: literal('BCH_2026_05'),
47
+ BCH_SPEC: literal('BCH_SPEC'),
48
+ };
46
49
  //# sourceMappingURL=interfaces.js.map
@@ -0,0 +1,6 @@
1
+ import { WalletTemplate } from '@bitauth/libauth';
2
+ import { DebugResults } from '../debugging.js';
3
+ import { TransactionBuilder } from '../TransactionBuilder.js';
4
+ export declare const getLibauthTemplate: (transactionBuilder: TransactionBuilder) => WalletTemplate;
5
+ export declare const debugLibauthTemplate: (template: WalletTemplate, transaction: TransactionBuilder) => DebugResults;
6
+ export declare const getBitauthUri: (template: WalletTemplate) => string;