cashscript 0.11.0-next.4 → 0.11.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.
@@ -34,4 +34,3 @@ export declare class Contract<TArtifact extends Artifact = Artifact, TResolved e
34
34
  private createUnlocker;
35
35
  }
36
36
  export type ContractFunction = (...args: FunctionArgument[]) => Transaction;
37
- export type ContractUnlocker = (...args: FunctionArgument[]) => Unlocker;
package/dist/Errors.js CHANGED
@@ -31,7 +31,8 @@ export class NoDebugInformationInArtifactError extends Error {
31
31
  }
32
32
  export class FailedTransactionError extends Error {
33
33
  constructor(reason, bitauthUri) {
34
- super(`${reason}${bitauthUri ? `\n\nBitauth URI: ${bitauthUri}` : ''}`);
34
+ const warning = 'WARNING: it is unsafe to use this Bitauth URI when using real private keys as they are included in the transaction template';
35
+ super(`${reason}${bitauthUri ? `\n\n${warning}\n\nBitauth URI: ${bitauthUri}` : ''}`);
35
36
  this.reason = reason;
36
37
  this.bitauthUri = bitauthUri;
37
38
  }
@@ -53,14 +54,7 @@ export class FailedTransactionEvaluationError extends FailedTransactionError {
53
54
  }
54
55
  export class FailedRequireError extends FailedTransactionError {
55
56
  constructor(artifact, failingInstructionPointer, requireStatement, inputIndex, bitauthUri, libauthErrorMessage) {
56
- let { statement, lineNumber } = getLocationDataForInstructionPointer(artifact, failingInstructionPointer);
57
- if (!statement.includes('require')) {
58
- statement = requireStatement.message
59
- ? `require(${statement}, "${requireStatement.message}")`
60
- : `require(${statement})`;
61
- // Sometimes in reconstructed multiline require statements, we get double commas
62
- statement = statement.replace(/,,/g, ',');
63
- }
57
+ const { statement, lineNumber } = getLocationDataForInstructionPointer(artifact, failingInstructionPointer);
64
58
  const baseMessage = `${artifact.contractName}.cash:${lineNumber} Require statement failed at input ${inputIndex} in contract ${artifact.contractName}.cash at line ${lineNumber}`;
65
59
  const baseMessageWithRequireMessage = `${baseMessage} with the following message: ${requireStatement.message}`;
66
60
  const fullMessage = `${requireStatement.message ? baseMessageWithRequireMessage : baseMessage}.\nFailing statement: ${statement}`;
@@ -1,7 +1,7 @@
1
1
  import { bytecodeToScript, formatBitAuthScript, } from '@cashscript/utils';
2
2
  import { hexToBin, decodeTransaction, binToHex, binToBase64, utf8ToBin, isHex, } from '@bitauth/libauth';
3
3
  import { deflate } from 'pako';
4
- import { isUtxoP2PKH, SignatureAlgorithm, HashType, isUnlockableUtxo, } from './interfaces.js';
4
+ import { isUtxoP2PKH, SignatureAlgorithm, HashType, isUnlockableUtxo, isStandardUnlockableUtxo, } from './interfaces.js';
5
5
  import SignatureTemplate from './SignatureTemplate.js';
6
6
  import { addressToLockScript, extendedStringify, zip } from './utils.js';
7
7
  import { generateUnlockingScriptParams } from './advanced/LibauthTemplate.js';
@@ -195,21 +195,6 @@ export const generateTemplateScenarioTransactionOutputLockingBytecode = (csOutpu
195
195
  return {};
196
196
  return binToHex(addressToLockScript(csOutput.to));
197
197
  };
198
- /**
199
- * Generates source outputs for a BitAuth template scenario
200
- *
201
- * @param csTransaction - The CashScript transaction to generate source outputs for
202
- * @returns An array of BitAuth template scenario outputs with locking scripts and values
203
- *
204
- * For each input in the transaction:
205
- * - Generates appropriate locking bytecode (P2PKH or contract)
206
- * - Includes the input value in satoshis
207
- * - Includes any token details if present
208
- *
209
- * The slotIndex tracks which input is the contract input vs P2PKH inputs
210
- * to properly generate the locking scripts.
211
- */
212
- // TODO: This looks like it needs some refactor to work with the new stuff
213
198
  const generateTemplateScenarioSourceOutputs = (csTransaction) => {
214
199
  const slotIndex = csTransaction.inputs.findIndex((input) => !isUtxoP2PKH(input));
215
200
  return csTransaction.inputs.map((input, inputIndex) => {
@@ -239,7 +224,7 @@ export const generateTemplateScenarioBytecode = (input, inputIndex, p2pkhScriptN
239
224
  },
240
225
  };
241
226
  }
242
- if (isUnlockableUtxo(input)) {
227
+ if (isUnlockableUtxo(input) && isStandardUnlockableUtxo(input)) {
243
228
  return generateUnlockingScriptParams(input, p2pkhScriptNameTemplate, inputIndex);
244
229
  }
245
230
  // 'slot' means that we are currently evaluating this specific input,
@@ -1,4 +1,4 @@
1
- import { Unlocker, HashType, SignatureAlgorithm } from './interfaces.js';
1
+ import { HashType, SignatureAlgorithm, P2PKHUnlocker } from './interfaces.js';
2
2
  export default class SignatureTemplate {
3
3
  private hashtype;
4
4
  private signatureAlgorithm;
@@ -8,7 +8,7 @@ export default class SignatureTemplate {
8
8
  getHashType(bchForkId?: boolean): number;
9
9
  getSignatureAlgorithm(): SignatureAlgorithm;
10
10
  getPublicKey(): Uint8Array;
11
- unlockP2PKH(): Unlocker;
11
+ unlockP2PKH(): P2PKHUnlocker;
12
12
  }
13
13
  interface Keypair {
14
14
  toWIF(): string;
@@ -10,6 +10,7 @@ import { TransactionBuilder } from './TransactionBuilder.js';
10
10
  import { buildTemplate, getBitauthUri } from './LibauthTemplate.js';
11
11
  import { debugTemplate } from './debugging.js';
12
12
  import { FailedTransactionError } from './Errors.js';
13
+ import semver from 'semver';
13
14
  export class Transaction {
14
15
  constructor(contract, unlocker, abiFunction, encodedFunctionArgs, selector) {
15
16
  this.contract = contract;
@@ -113,13 +114,14 @@ export class Transaction {
113
114
  }
114
115
  // method to debug the transaction with libauth VM, throws upon evaluation error
115
116
  async debug() {
116
- if (!this.contract.artifact.debug) {
117
- console.warn('No debug information found in artifact. Recompile with cashc version 0.10.0 or newer to get better debugging information.');
117
+ if (!semver.satisfies(this.contract.artifact.compiler.version, '>=0.11.0')) {
118
+ console.warn('For the best debugging experience, please recompile your contract with cashc version 0.11.0 or newer.');
118
119
  }
119
120
  const template = await this.getLibauthTemplate();
120
121
  return debugTemplate(template, [this.contract.artifact]);
121
122
  }
122
123
  async bitauthUri() {
124
+ console.warn('WARNING: it is unsafe to use this Bitauth URI when using real private keys as they are included in the transaction template');
123
125
  const template = await this.getLibauthTemplate();
124
126
  return getBitauthUri(template);
125
127
  }
@@ -2,6 +2,8 @@ import { Transaction as LibauthTransaction, WalletTemplate } from '@bitauth/liba
2
2
  import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions } from './interfaces.js';
3
3
  import { NetworkProvider } from './network/index.js';
4
4
  import { DebugResults } from './debugging.js';
5
+ import { WcTransactionOptions } from './walletconnect-utils.js';
6
+ import { WcTransactionObject } from './walletconnect-utils.js';
5
7
  export interface TransactionBuilderOptions {
6
8
  provider: NetworkProvider;
7
9
  }
@@ -23,10 +25,11 @@ export declare class TransactionBuilder {
23
25
  private checkMaxFee;
24
26
  buildLibauthTransaction(): LibauthTransaction;
25
27
  build(): string;
26
- debug(): Promise<DebugResults>;
28
+ debug(): DebugResults;
27
29
  bitauthUri(): string;
28
30
  getLibauthTemplate(): WalletTemplate;
29
31
  send(): Promise<TransactionDetails>;
30
32
  send(raw: true): Promise<string>;
31
33
  private getTxDetails;
34
+ generateWcTransactionObject(options?: WcTransactionOptions): WcTransactionObject;
32
35
  }
@@ -1,10 +1,12 @@
1
- import { binToHex, decodeTransaction, encodeTransaction, hexToBin, } from '@bitauth/libauth';
1
+ import { binToHex, decodeTransaction, decodeTransactionUnsafe, encodeTransaction, hexToBin, } from '@bitauth/libauth';
2
2
  import delay from 'delay';
3
- import { isUnlockableUtxo, } from './interfaces.js';
4
- import { cashScriptOutputToLibauthOutput, createOpReturnOutput, validateInput, validateOutput, } from './utils.js';
3
+ import { isUnlockableUtxo, isStandardUnlockableUtxo, } from './interfaces.js';
4
+ import { cashScriptOutputToLibauthOutput, createOpReturnOutput, generateLibauthSourceOutputs, validateInput, validateOutput, } from './utils.js';
5
5
  import { FailedTransactionError } from './Errors.js';
6
6
  import { getBitauthUri } from './LibauthTemplate.js';
7
7
  import { debugLibauthTemplate, getLibauthTemplates } from './advanced/LibauthTemplate.js';
8
+ import { getWcContractInfo } from './walletconnect-utils.js';
9
+ import semver from 'semver';
8
10
  const DEFAULT_SEQUENCE = 0xfffffffe;
9
11
  export class TransactionBuilder {
10
12
  constructor(options) {
@@ -76,14 +78,7 @@ export class TransactionBuilder {
76
78
  version: 2,
77
79
  };
78
80
  // Generate source outputs from inputs (for signing with SIGHASH_UTXOS)
79
- const sourceOutputs = this.inputs.map((input) => {
80
- const sourceOutput = {
81
- amount: input.satoshis,
82
- to: input.unlocker.generateLockingBytecode(),
83
- token: input.token,
84
- };
85
- return cashScriptOutputToLibauthOutput(sourceOutput);
86
- });
81
+ const sourceOutputs = generateLibauthSourceOutputs(this.inputs);
87
82
  const inputScripts = this.inputs.map((input, inputIndex) => (input.unlocker.generateUnlockingBytecode({ transaction, sourceOutputs, inputIndex })));
88
83
  inputScripts.forEach((script, i) => {
89
84
  transaction.inputs[i].unlockingBytecode = script;
@@ -94,12 +89,21 @@ export class TransactionBuilder {
94
89
  const transaction = this.buildLibauthTransaction();
95
90
  return binToHex(encodeTransaction(transaction));
96
91
  }
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() {
92
+ debug() {
93
+ if (this.inputs.some((input) => !isStandardUnlockableUtxo(input))) {
94
+ throw new Error('Cannot debug a transaction with custom unlocker');
95
+ }
96
+ // We can typecast this because we check that all inputs are standard unlockable in the check above
97
+ const contractVersions = this.inputs
98
+ .map((input) => 'contract' in input.unlocker ? input.unlocker.contract.artifact.compiler.version : null)
99
+ .filter((version) => version !== null);
100
+ if (!contractVersions.every((version) => semver.satisfies(version, '>=0.11.0'))) {
101
+ console.warn('For the best debugging experience, please recompile your contract with cashc version 0.11.0 or newer.');
102
+ }
100
103
  return debugLibauthTemplate(this.getLibauthTemplate(), this);
101
104
  }
102
105
  bitauthUri() {
106
+ console.warn('WARNING: it is unsafe to use this Bitauth URI when using real private keys as they are included in the transaction template');
103
107
  return getBitauthUri(this.getLibauthTemplate());
104
108
  }
105
109
  getLibauthTemplate() {
@@ -107,8 +111,11 @@ export class TransactionBuilder {
107
111
  }
108
112
  async send(raw) {
109
113
  const tx = this.build();
110
- // Debug the transaction locally before sending so any errors are caught early
111
- await this.debug();
114
+ // If all inputs are standard unlockable, we can debug the transaction locally
115
+ // before sending so any errors are caught early
116
+ if (this.inputs.every((input) => isStandardUnlockableUtxo(input))) {
117
+ this.debug();
118
+ }
112
119
  try {
113
120
  const txid = await this.provider.sendRawTransaction(tx);
114
121
  return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
@@ -135,5 +142,22 @@ export class TransactionBuilder {
135
142
  // Should not happen
136
143
  throw new Error('Could not retrieve transaction details for over 10 minutes');
137
144
  }
145
+ generateWcTransactionObject(options) {
146
+ const inputs = this.inputs;
147
+ if (!inputs.every(input => isStandardUnlockableUtxo(input))) {
148
+ throw new Error('All inputs must be StandardUnlockableUtxos to generate the wcSourceOutputs');
149
+ }
150
+ const encodedTransaction = this.build();
151
+ const transaction = decodeTransactionUnsafe(hexToBin(encodedTransaction));
152
+ const libauthSourceOutputs = generateLibauthSourceOutputs(inputs);
153
+ const sourceOutputs = libauthSourceOutputs.map((sourceOutput, index) => {
154
+ return {
155
+ ...sourceOutput,
156
+ ...transaction.inputs[index],
157
+ ...getWcContractInfo(inputs[index]),
158
+ };
159
+ });
160
+ return { ...options, transaction, sourceOutputs };
161
+ }
138
162
  }
139
163
  //# sourceMappingURL=TransactionBuilder.js.map
@@ -1,9 +1,9 @@
1
1
  import { TransactionBch, WalletTemplate, WalletTemplateScenarioBytecode } from '@bitauth/libauth';
2
- import { AbiFunction, Artifact } from '@cashscript/utils';
2
+ import { AbiFunction } from '@cashscript/utils';
3
3
  import { EncodedConstructorArgument, EncodedFunctionArgument } from '../Argument.js';
4
4
  import { Contract } from '../Contract.js';
5
5
  import { DebugResults } from '../debugging.js';
6
- import { AddressType, UnlockableUtxo } from '../interfaces.js';
6
+ import { StandardUnlockableUtxo } from '../interfaces.js';
7
7
  import SignatureTemplate from '../SignatureTemplate.js';
8
8
  import { Transaction } from '../Transaction.js';
9
9
  import { TransactionBuilder } from '../TransactionBuilder.js';
@@ -22,7 +22,7 @@ export declare const generateTemplateEntitiesP2PKH: (inputIndex: number) => Wall
22
22
  * https://ide.bitauth.com/authentication-template-v0.schema.json
23
23
  *
24
24
  */
25
- export declare const generateTemplateEntitiesP2SH: (artifact: Artifact, abiFunction: AbiFunction, encodedFunctionArgs: EncodedFunctionArgument[], inputIndex: number) => WalletTemplate["entities"];
25
+ export declare const generateTemplateEntitiesP2SH: (contract: Contract, abiFunction: AbiFunction, encodedFunctionArgs: EncodedFunctionArgument[], inputIndex: number) => WalletTemplate["entities"];
26
26
  /**
27
27
  * Generates template scripts for P2PKH (Pay to Public Key Hash) placeholder scripts.
28
28
  *
@@ -38,8 +38,8 @@ export declare const generateTemplateScriptsP2PKH: (template: SignatureTemplate,
38
38
  * https://ide.bitauth.com/authentication-template-v0.schema.json
39
39
  *
40
40
  */
41
- export declare const generateTemplateScriptsP2SH: (artifact: Artifact, addressType: AddressType, abiFunction: AbiFunction, encodedFunctionArgs: EncodedFunctionArgument[], encodedConstructorArgs: EncodedConstructorArgument[], inputIndex: number) => WalletTemplate["scripts"];
41
+ export declare const generateTemplateScriptsP2SH: (contract: Contract, abiFunction: AbiFunction, encodedFunctionArgs: EncodedFunctionArgument[], encodedConstructorArgs: EncodedConstructorArgument[], inputIndex: number) => WalletTemplate["scripts"];
42
42
  export declare const generateTemplateScenarios: (contract: Contract, libauthTransaction: TransactionBch, csTransaction: Transaction, abiFunction: AbiFunction, encodedFunctionArgs: EncodedFunctionArgument[], inputIndex: number) => WalletTemplate["scenarios"];
43
43
  export declare const getLibauthTemplates: (txn: TransactionBuilder) => WalletTemplate;
44
44
  export declare const debugLibauthTemplate: (template: WalletTemplate, transaction: TransactionBuilder) => DebugResults;
45
- export declare const generateUnlockingScriptParams: (csInput: UnlockableUtxo, p2pkhScriptNameTemplate: string, inputIndex: number) => WalletTemplateScenarioBytecode;
45
+ export declare const generateUnlockingScriptParams: (csInput: StandardUnlockableUtxo, p2pkhScriptNameTemplate: string, inputIndex: number) => WalletTemplateScenarioBytecode;
@@ -1,6 +1,7 @@
1
- import { binToHex, } from '@bitauth/libauth';
1
+ import { binToHex, decodeCashAddress, } from '@bitauth/libauth';
2
2
  import { encodeFunctionArguments } from '../Argument.js';
3
3
  import { debugTemplate } from '../debugging.js';
4
+ import { isP2PKHUnlocker, isStandardUnlockableUtxo, } from '../interfaces.js';
4
5
  import { addHexPrefixExceptEmpty, formatBytecodeForDebugging, formatParametersForDebugging, generateTemplateScenarioBytecode, generateTemplateScenarioKeys, generateTemplateScenarioParametersFunctionIndex, generateTemplateScenarioParametersValues, generateTemplateScenarioTransactionOutputLockingBytecode, getHashTypeName, getSignatureAlgorithmName, serialiseTokenDetails, } from '../LibauthTemplate.js';
5
6
  import SignatureTemplate from '../SignatureTemplate.js';
6
7
  import { addressToLockScript } from '../utils.js';
@@ -36,21 +37,21 @@ export const generateTemplateEntitiesP2PKH = (inputIndex) => {
36
37
  * https://ide.bitauth.com/authentication-template-v0.schema.json
37
38
  *
38
39
  */
39
- export const generateTemplateEntitiesP2SH = (artifact, abiFunction, encodedFunctionArgs, inputIndex) => {
40
+ export const generateTemplateEntitiesP2SH = (contract, abiFunction, encodedFunctionArgs, inputIndex) => {
40
41
  const entities = {
41
- [artifact.contractName + '_input' + inputIndex + '_parameters']: {
42
+ [contract.artifact.contractName + '_input' + inputIndex + '_parameters']: {
42
43
  description: 'Contract creation and function parameters',
43
- name: `${artifact.contractName} (input #${inputIndex})`,
44
+ name: `${contract.artifact.contractName} (input #${inputIndex})`,
44
45
  scripts: [
45
- artifact.contractName + '_lock',
46
- artifact.contractName + '_' + abiFunction.name + '_input' + inputIndex + '_unlock',
46
+ getLockScriptName(contract),
47
+ getUnlockScriptName(contract, abiFunction, inputIndex),
47
48
  ],
48
- variables: createWalletTemplateVariables(artifact, abiFunction, encodedFunctionArgs),
49
+ variables: createWalletTemplateVariables(contract.artifact, abiFunction, encodedFunctionArgs),
49
50
  },
50
51
  };
51
52
  // function_index is a special variable that indicates the function to execute
52
- if (artifact.abi.length > 1) {
53
- entities[artifact.contractName + '_input' + inputIndex + '_parameters'].variables.function_index = {
53
+ if (contract.artifact.abi.length > 1) {
54
+ entities[contract.artifact.contractName + '_input' + inputIndex + '_parameters'].variables.function_index = {
54
55
  description: 'Script function index to execute',
55
56
  name: 'function_index',
56
57
  type: 'WalletData',
@@ -113,13 +114,13 @@ export const generateTemplateScriptsP2PKH = (template, inputIndex) => {
113
114
  * https://ide.bitauth.com/authentication-template-v0.schema.json
114
115
  *
115
116
  */
116
- export const generateTemplateScriptsP2SH = (artifact, addressType, abiFunction, encodedFunctionArgs, encodedConstructorArgs, inputIndex) => {
117
+ export const generateTemplateScriptsP2SH = (contract, abiFunction, encodedFunctionArgs, encodedConstructorArgs, inputIndex) => {
117
118
  // definition of locking scripts and unlocking scripts with their respective bytecode
118
- const unlockingScriptName = artifact.contractName + '_' + abiFunction.name + '_input' + inputIndex + '_unlock';
119
- const lockingScriptName = artifact.contractName + '_lock';
119
+ const unlockingScriptName = getUnlockScriptName(contract, abiFunction, inputIndex);
120
+ const lockingScriptName = getLockScriptName(contract);
120
121
  return {
121
- [unlockingScriptName]: generateTemplateUnlockScript(artifact, abiFunction, encodedFunctionArgs, inputIndex),
122
- [lockingScriptName]: generateTemplateLockScript(artifact, addressType, encodedConstructorArgs),
122
+ [unlockingScriptName]: generateTemplateUnlockScript(contract, abiFunction, encodedFunctionArgs, inputIndex),
123
+ [lockingScriptName]: generateTemplateLockScript(contract, encodedConstructorArgs),
123
124
  };
124
125
  };
125
126
  /**
@@ -129,16 +130,16 @@ export const generateTemplateScriptsP2SH = (artifact, addressType, abiFunction,
129
130
  * https://ide.bitauth.com/authentication-template-v0.schema.json
130
131
  *
131
132
  */
132
- const generateTemplateLockScript = (artifact, addressType, constructorArguments) => {
133
+ const generateTemplateLockScript = (contract, constructorArguments) => {
133
134
  return {
134
- lockingType: addressType,
135
- name: artifact.contractName,
135
+ lockingType: contract.addressType,
136
+ name: contract.artifact.contractName,
136
137
  script: [
137
- `// "${artifact.contractName}" contract constructor parameters`,
138
- formatParametersForDebugging(artifact.constructorInputs, constructorArguments),
138
+ `// "${contract.artifact.contractName}" contract constructor parameters`,
139
+ formatParametersForDebugging(contract.artifact.constructorInputs, constructorArguments),
139
140
  '',
140
141
  '// bytecode',
141
- formatBytecodeForDebugging(artifact),
142
+ formatBytecodeForDebugging(contract.artifact),
142
143
  ].join('\n'),
143
144
  };
144
145
  };
@@ -149,10 +150,10 @@ const generateTemplateLockScript = (artifact, addressType, constructorArguments)
149
150
  * https://ide.bitauth.com/authentication-template-v0.schema.json
150
151
  *
151
152
  */
152
- const generateTemplateUnlockScript = (artifact, abiFunction, encodedFunctionArgs, inputIndex) => {
153
- const scenarioIdentifier = `${artifact.contractName}_${abiFunction.name}_input${inputIndex}_evaluate`;
154
- const functionIndex = artifact.abi.findIndex((func) => func.name === abiFunction.name);
155
- const functionIndexString = artifact.abi.length > 1
153
+ const generateTemplateUnlockScript = (contract, abiFunction, encodedFunctionArgs, inputIndex) => {
154
+ const scenarioIdentifier = `${contract.artifact.contractName}_${abiFunction.name}_input${inputIndex}_evaluate`;
155
+ const functionIndex = contract.artifact.abi.findIndex((func) => func.name === abiFunction.name);
156
+ const functionIndexString = contract.artifact.abi.length > 1
156
157
  ? ['// function index in contract', `<function_index> // int = <${functionIndex}>`, '']
157
158
  : [];
158
159
  return {
@@ -165,7 +166,7 @@ const generateTemplateUnlockScript = (artifact, abiFunction, encodedFunctionArgs
165
166
  '',
166
167
  ...functionIndexString,
167
168
  ].join('\n'),
168
- unlocks: artifact.contractName + '_lock',
169
+ unlocks: getLockScriptName(contract),
169
170
  };
170
171
  };
171
172
  export const generateTemplateScenarios = (contract, libauthTransaction, csTransaction, abiFunction, encodedFunctionArgs, inputIndex) => {
@@ -247,6 +248,9 @@ const createCSTransaction = (txn) => {
247
248
  return csTransaction;
248
249
  };
249
250
  export const getLibauthTemplates = (txn) => {
251
+ if (txn.inputs.some((input) => !isStandardUnlockableUtxo(input))) {
252
+ throw new Error('Cannot use debugging functionality with a transaction that contains custom unlockers');
253
+ }
250
254
  const libauthTransaction = txn.buildLibauthTransaction();
251
255
  const csTransaction = createCSTransaction(txn);
252
256
  const baseTemplate = {
@@ -269,10 +273,11 @@ export const getLibauthTemplates = (txn) => {
269
273
  // Initialize bytecode mappings, these will be used to map the locking and unlocking scripts and naming the scripts
270
274
  const unlockingBytecodeToLockingBytecodeParams = {};
271
275
  const lockingBytecodeToLockingBytecodeParams = {};
276
+ // We can typecast this because we check that all inputs are standard unlockable at the top of this function
272
277
  for (const [inputIndex, input] of txn.inputs.entries()) {
273
278
  // If template exists on the input, it indicates this is a P2PKH (Pay to Public Key Hash) input
274
279
  if ('template' in input.unlocker) {
275
- // @ts-ignore TODO: Remove UtxoP2PKH type and only use UnlockableUtxo in Libaith Template generation
280
+ // @ts-ignore TODO: Remove UtxoP2PKH type and only use UnlockableUtxo in Libauth Template generation
276
281
  input.template = input.unlocker?.template; // Added to support P2PKH inputs in buildTemplate
277
282
  Object.assign(p2pkhEntities, generateTemplateEntitiesP2PKH(inputIndex));
278
283
  Object.assign(p2pkhScripts, generateTemplateScriptsP2PKH(input.unlocker.template, inputIndex));
@@ -290,9 +295,9 @@ export const getLibauthTemplates = (txn) => {
290
295
  // Generate a scenario object for this contract input
291
296
  Object.assign(scenarios, generateTemplateScenarios(contract, libauthTransaction, csTransaction, abiFunction, encodedArgs, inputIndex));
292
297
  // Generate entities for this contract input
293
- const entity = generateTemplateEntitiesP2SH(contract.artifact, abiFunction, encodedArgs, inputIndex);
298
+ const entity = generateTemplateEntitiesP2SH(contract, abiFunction, encodedArgs, inputIndex);
294
299
  // Generate scripts for this contract input
295
- const script = generateTemplateScriptsP2SH(contract.artifact, contract.addressType, abiFunction, encodedArgs, contract.encodedConstructorArgs, inputIndex);
300
+ const script = generateTemplateScriptsP2SH(contract, abiFunction, encodedArgs, contract.encodedConstructorArgs, inputIndex);
296
301
  // Find the lock script name for this contract input
297
302
  const lockScriptName = Object.keys(script).find(scriptName => scriptName.includes('_lock'));
298
303
  if (lockScriptName) {
@@ -359,8 +364,8 @@ export const debugLibauthTemplate = (template, transaction) => {
359
364
  .map(contract => contract.artifact);
360
365
  return debugTemplate(template, allArtifacts);
361
366
  };
362
- const generateLockingScriptParams = (contract, csInput, lockScriptName) => {
363
- if (('template' in csInput.unlocker)) {
367
+ const generateLockingScriptParams = (contract, { unlocker }, lockScriptName) => {
368
+ if (isP2PKHUnlocker(unlocker)) {
364
369
  return {
365
370
  script: lockScriptName,
366
371
  };
@@ -368,8 +373,7 @@ const generateLockingScriptParams = (contract, csInput, lockScriptName) => {
368
373
  const constructorParamsEntries = contract.artifact.constructorInputs
369
374
  .map(({ name }, index) => [
370
375
  name,
371
- // TODO: For some reason, typescript forgets that the unlocker is a ContractUnlocker
372
- addHexPrefixExceptEmpty(binToHex(csInput.unlocker.contract.encodedConstructorArgs[index])),
376
+ addHexPrefixExceptEmpty(binToHex(unlocker.contract.encodedConstructorArgs[index])),
373
377
  ]);
374
378
  const constructorParams = Object.fromEntries(constructorParamsEntries);
375
379
  return {
@@ -380,7 +384,7 @@ const generateLockingScriptParams = (contract, csInput, lockScriptName) => {
380
384
  };
381
385
  };
382
386
  export const generateUnlockingScriptParams = (csInput, p2pkhScriptNameTemplate, inputIndex) => {
383
- if (('template' in csInput.unlocker)) {
387
+ if (isP2PKHUnlocker(csInput.unlocker)) {
384
388
  return {
385
389
  script: `${p2pkhScriptNameTemplate}_${inputIndex}`,
386
390
  overrides: {
@@ -396,7 +400,7 @@ export const generateUnlockingScriptParams = (csInput, p2pkhScriptNameTemplate,
396
400
  const contract = csInput.unlocker.contract;
397
401
  const encodedFunctionArgs = encodeFunctionArguments(abiFunction, csInput.unlocker.params);
398
402
  return {
399
- script: `${csInput.unlocker.contract.name}_${abiFunction.name}_input${inputIndex}_unlock`,
403
+ script: getUnlockScriptName(contract, abiFunction, inputIndex),
400
404
  overrides: {
401
405
  // encode values for the variables defined above in `entities` property
402
406
  bytecode: {
@@ -410,4 +414,13 @@ export const generateUnlockingScriptParams = (csInput, p2pkhScriptNameTemplate,
410
414
  },
411
415
  };
412
416
  };
417
+ const getLockScriptName = (contract) => {
418
+ const result = decodeCashAddress(contract.address);
419
+ if (typeof result === 'string')
420
+ throw new Error(result);
421
+ return `${contract.artifact.contractName}_${binToHex(result.payload)}_lock`;
422
+ };
423
+ const getUnlockScriptName = (contract, abiFunction, inputIndex) => {
424
+ return `${contract.artifact.contractName}_${abiFunction.name}_input${inputIndex}_unlock`;
425
+ };
413
426
  //# sourceMappingURL=LibauthTemplate.js.map
package/dist/debugging.js CHANGED
@@ -5,6 +5,11 @@ import { FailedRequireError, FailedTransactionError, FailedTransactionEvaluation
5
5
  import { getBitauthUri } from './LibauthTemplate.js';
6
6
  // debugs the template, optionally logging the execution data
7
7
  export const debugTemplate = (template, artifacts) => {
8
+ // If a contract has the same name, but a different bytecode, then it is considered a name collision
9
+ const hasArtifactNameCollision = artifacts.some((artifact) => (artifacts.some((other) => other.contractName === artifact.contractName && other.bytecode !== artifact.bytecode)));
10
+ if (hasArtifactNameCollision) {
11
+ throw new Error('There are multiple artifacts with the same contractName. Please make sure that all artifacts have unique names.');
12
+ }
8
13
  const results = {};
9
14
  const unlockingScriptIds = Object.keys(template.scripts).filter((key) => 'unlocks' in template.scripts[key]);
10
15
  for (const unlockingScriptId of unlockingScriptIds) {
@@ -173,7 +178,10 @@ const calculateCleanupSize = (instructions) => {
173
178
  // OP_NIP (or OP_DROP/OP_2DROP in optimised bytecode) is used for cleanup at the end of a function,
174
179
  // OP_ENDIF and OP_ELSE are the end of branches. We need to remove all of these to get to the actual last
175
180
  // executed instruction of a function
176
- // TODO: What about OP_1??
181
+ // Note that in the case where we re-add OP_1 because we cannot optimise the final explicit VERIFY into an implicit one
182
+ // (like when dealing with if-statements, or with CHECKLOCKTIMEVERIFY), it is impossible to run into an implicit final
183
+ // verify failure. That is why OP_1 does not need to be included in the cleanup opcodes.
184
+ // TODO: Perhaps we also do not need to add OP_DROP/OP_2DROP either, because they only occur together with OP_1
177
185
  const cleanupOpcodes = [Op.OP_ENDIF, Op.OP_NIP, Op.OP_ELSE, Op.OP_DROP, Op.OP_2DROP];
178
186
  let cleanupSize = 0;
179
187
  for (const instruction of [...instructions].reverse()) {
package/dist/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export * from './interfaces.js';
9
9
  export * from './Errors.js';
10
10
  export { type NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
11
11
  export { randomUtxo, randomToken, randomNFT } from './utils.js';
12
+ export * from './walletconnect-utils.js';
package/dist/index.js CHANGED
@@ -8,4 +8,5 @@ export * from './interfaces.js';
8
8
  export * from './Errors.js';
9
9
  export { BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
10
10
  export { randomUtxo, randomToken, randomNFT } from './utils.js';
11
+ export * from './walletconnect-utils.js';
11
12
  //# sourceMappingURL=index.js.map
@@ -14,7 +14,11 @@ export interface UnlockableUtxo extends Utxo {
14
14
  unlocker: Unlocker;
15
15
  options?: InputOptions;
16
16
  }
17
+ export interface StandardUnlockableUtxo extends UnlockableUtxo {
18
+ unlocker: StandardUnlocker;
19
+ }
17
20
  export declare function isUnlockableUtxo(utxo: Utxo): utxo is UnlockableUtxo;
21
+ export declare function isStandardUnlockableUtxo(utxo: UnlockableUtxo): utxo is StandardUnlockableUtxo;
18
22
  export interface InputOptions {
19
23
  sequence?: number;
20
24
  }
@@ -23,19 +27,22 @@ export interface GenerateUnlockingBytecodeOptions {
23
27
  sourceOutputs: LibauthOutput[];
24
28
  inputIndex: number;
25
29
  }
26
- export interface BaseUnlocker {
30
+ export interface Unlocker {
27
31
  generateLockingBytecode: () => Uint8Array;
28
32
  generateUnlockingBytecode: (options: GenerateUnlockingBytecodeOptions) => Uint8Array;
29
33
  }
30
- export interface ContractUnlocker extends BaseUnlocker {
34
+ export interface ContractUnlocker extends Unlocker {
31
35
  contract: Contract;
32
36
  params: FunctionArgument[];
33
37
  abiFunction: AbiFunction;
34
38
  }
35
- export interface P2PKHUnlocker extends BaseUnlocker {
39
+ export interface P2PKHUnlocker extends Unlocker {
36
40
  template: SignatureTemplate;
37
41
  }
38
- export type Unlocker = ContractUnlocker | P2PKHUnlocker;
42
+ export type StandardUnlocker = ContractUnlocker | P2PKHUnlocker;
43
+ export declare function isContractUnlocker(unlocker: Unlocker): unlocker is ContractUnlocker;
44
+ export declare function isP2PKHUnlocker(unlocker: Unlocker): unlocker is P2PKHUnlocker;
45
+ export declare function isStandardUnlocker(unlocker: Unlocker): unlocker is StandardUnlocker;
39
46
  export interface UtxoP2PKH extends Utxo {
40
47
  template: SignatureTemplate;
41
48
  }
@@ -1,6 +1,18 @@
1
1
  export function isUnlockableUtxo(utxo) {
2
2
  return 'unlocker' in utxo;
3
3
  }
4
+ export function isStandardUnlockableUtxo(utxo) {
5
+ return isStandardUnlocker(utxo.unlocker);
6
+ }
7
+ export function isContractUnlocker(unlocker) {
8
+ return 'contract' in unlocker;
9
+ }
10
+ export function isP2PKHUnlocker(unlocker) {
11
+ return 'template' in unlocker;
12
+ }
13
+ export function isStandardUnlocker(unlocker) {
14
+ return isContractUnlocker(unlocker) || isP2PKHUnlocker(unlocker);
15
+ }
4
16
  export function isUtxoP2PKH(utxo) {
5
17
  return 'template' in utxo;
6
18
  }
@@ -4,8 +4,10 @@ export default class MockNetworkProvider implements NetworkProvider {
4
4
  private utxoMap;
5
5
  private transactionMap;
6
6
  network: Network;
7
+ blockHeight: number;
7
8
  constructor();
8
9
  getUtxos(address: string): Promise<Utxo[]>;
10
+ setBlockHeight(newBlockHeight: number): void;
9
11
  getBlockHeight(): Promise<number>;
10
12
  getRawTransaction(txid: string): Promise<string>;
11
13
  sendRawTransaction(txHex: string): Promise<string>;
@@ -11,6 +11,7 @@ export default class MockNetworkProvider {
11
11
  this.utxoMap = {};
12
12
  this.transactionMap = {};
13
13
  this.network = Network.MOCKNET;
14
+ this.blockHeight = 133700;
14
15
  for (let i = 0; i < 3; i += 1) {
15
16
  this.addUtxo(aliceAddress, randomUtxo());
16
17
  this.addUtxo(bobAddress, randomUtxo());
@@ -21,8 +22,11 @@ export default class MockNetworkProvider {
21
22
  const lockingBytecode = binToHex(addressToLockScript(address));
22
23
  return this.utxoMap[lockingBytecode] ?? [];
23
24
  }
25
+ setBlockHeight(newBlockHeight) {
26
+ this.blockHeight = newBlockHeight;
27
+ }
24
28
  async getBlockHeight() {
25
- return 133700;
29
+ return this.blockHeight;
26
30
  }
27
31
  async getRawTransaction(txid) {
28
32
  return this.transactionMap[txid];
@@ -1,14 +1,17 @@
1
1
  expect.extend({
2
- async toLog(transaction, match) {
2
+ toLog(transaction, match) {
3
3
  const loggerSpy = jest.spyOn(console, 'log');
4
4
  // Clear any previous calls (if spy reused accidentally)
5
5
  loggerSpy.mockClear();
6
6
  // silence actual stdout output
7
7
  loggerSpy.mockImplementation(() => { });
8
8
  try {
9
- await transaction.debug();
9
+ executeDebug(transaction);
10
+ }
11
+ catch (error) {
12
+ if (error instanceof OldTransactionBuilderError)
13
+ throw error;
10
14
  }
11
- catch { }
12
15
  // We concatenate all the logs into a single string - if no logs are present, we set received to undefined
13
16
  const receivedBase = loggerSpy.mock.calls.reduce((acc, [log]) => `${acc}\n${log}`, '').trim();
14
17
  const received = receivedBase === '' ? undefined : receivedBase;
@@ -17,25 +20,37 @@ expect.extend({
17
20
  const receivedText = `Received: ${this.utils.printReceived(received)}`;
18
21
  const message = () => `${matcherHint}\n\n${expectedText}\n${receivedText}`;
19
22
  try {
20
- expect(loggerSpy).toBeCalledWith(match ? expect.stringMatching(match) : expect.anything());
23
+ // We first check if the expected string is present in any of the individual console.log calls
24
+ expect(loggerSpy).toHaveBeenCalledWith(match ? expect.stringMatching(match) : expect.anything());
21
25
  }
22
- catch (e) {
23
- return { message, pass: false };
26
+ catch {
27
+ try {
28
+ // We add this extra check to allow expect().toLog() to check multiple console.log calls in a single test
29
+ // (e.g. for log ordering), which would fail the first check because that compares the individual console.log calls
30
+ expect(receivedBase).toMatch(match ? match : expect.anything());
31
+ }
32
+ catch {
33
+ return { message, pass: false };
34
+ }
35
+ }
36
+ finally {
37
+ // Restore the original console.log implementation
38
+ loggerSpy.mockRestore();
24
39
  }
25
- // Restore the original console.log implementation
26
- loggerSpy.mockRestore();
27
40
  return { message, pass: true };
28
41
  },
29
42
  });
30
43
  expect.extend({
31
- async toFailRequireWith(transaction, match) {
44
+ toFailRequireWith(transaction, match) {
32
45
  try {
33
- await transaction.debug();
46
+ executeDebug(transaction);
34
47
  const matcherHint = this.utils.matcherHint('.toFailRequireWith', undefined, match.toString(), { isNot: this.isNot });
35
48
  const message = () => `${matcherHint}\n\nContract function did not fail a require statement.`;
36
49
  return { message, pass: false };
37
50
  }
38
51
  catch (transactionError) {
52
+ if (transactionError instanceof OldTransactionBuilderError)
53
+ throw transactionError;
39
54
  const matcherHint = this.utils.matcherHint('toFailRequireWith', 'received', 'expected', { isNot: this.isNot });
40
55
  const expectedText = `Expected pattern: ${this.isNot ? 'not ' : ''}${this.utils.printExpected(match)}`;
41
56
  const receivedText = `Received string: ${this.utils.printReceived(transactionError?.message ?? '')}`;
@@ -49,18 +64,35 @@ expect.extend({
49
64
  }
50
65
  }
51
66
  },
52
- async toFailRequire(transaction) {
67
+ toFailRequire(transaction) {
53
68
  try {
54
- await transaction.debug();
69
+ executeDebug(transaction);
55
70
  const message = () => 'Contract function did not fail a require statement.';
56
71
  return { message, pass: false };
57
72
  }
58
73
  catch (transactionError) {
74
+ if (transactionError instanceof OldTransactionBuilderError)
75
+ throw transactionError;
59
76
  const receivedText = `Received string: ${this.utils.printReceived(transactionError?.message ?? '')}`;
60
77
  const message = () => `Contract function failed a require statement.\n${receivedText}`;
61
78
  return { message, pass: true };
62
79
  }
63
80
  },
64
81
  });
82
+ // Wrapper function with custom error in case people use it with the old transaction builder
83
+ // This is a temporary solution until we fully remove the old transaction builder from the SDK
84
+ const executeDebug = (transaction) => {
85
+ const debugResults = transaction.debug();
86
+ if (debugResults instanceof Promise) {
87
+ debugResults.catch(() => { });
88
+ throw new OldTransactionBuilderError();
89
+ }
90
+ };
91
+ class OldTransactionBuilderError extends Error {
92
+ constructor() {
93
+ super('The CashScript JestExtensions do not support the old transaction builder since v0.11.0. Please use the new TransactionBuilder class.');
94
+ this.name = 'OldTransactionBuilderError';
95
+ }
96
+ }
65
97
  export {};
66
98
  //# sourceMappingURL=JestExtensions.js.map
package/dist/utils.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Transaction } from '@bitauth/libauth';
2
2
  import { Script } from '@cashscript/utils';
3
- import { Utxo, Output, LibauthOutput, TokenDetails, AddressType } from './interfaces.js';
3
+ import { Utxo, Output, LibauthOutput, TokenDetails, AddressType, UnlockableUtxo } from './interfaces.js';
4
4
  export declare function validateInput(utxo: Utxo): void;
5
5
  export declare function validateOutput(output: Output): void;
6
6
  export declare function calculateDust(output: Output): number;
@@ -8,6 +8,7 @@ export declare function getOutputSize(output: Output): number;
8
8
  export declare function encodeOutput(output: Output): Uint8Array;
9
9
  export declare function cashScriptOutputToLibauthOutput(output: Output): LibauthOutput;
10
10
  export declare function libauthOutputToCashScriptOutput(output: LibauthOutput): Output;
11
+ export declare function generateLibauthSourceOutputs(inputs: UnlockableUtxo[]): LibauthOutput[];
11
12
  export declare function getInputSize(inputScript: Uint8Array): number;
12
13
  export declare function getTxSizeWithoutInputs(outputs: Output[]): number;
13
14
  export declare function createInputScript(redeemScript: Script, encodedArgs: Uint8Array[], selector?: number): Uint8Array;
package/dist/utils.js CHANGED
@@ -74,6 +74,17 @@ export function libauthOutputToCashScriptOutput(output) {
74
74
  },
75
75
  };
76
76
  }
77
+ export function generateLibauthSourceOutputs(inputs) {
78
+ const sourceOutputs = inputs.map((input) => {
79
+ const sourceOutput = {
80
+ amount: input.satoshis,
81
+ to: input.unlocker.generateLockingBytecode(),
82
+ token: input.token,
83
+ };
84
+ return cashScriptOutputToLibauthOutput(sourceOutput);
85
+ });
86
+ return sourceOutputs;
87
+ }
77
88
  function isTokenAddress(address) {
78
89
  const result = decodeCashAddress(address);
79
90
  if (typeof result === 'string')
@@ -0,0 +1,25 @@
1
+ import type { StandardUnlockableUtxo, LibauthOutput, Unlocker } from './interfaces.js';
2
+ import { type AbiFunction, type Artifact } from '@cashscript/utils';
3
+ import { type Input, type TransactionCommon } from '@bitauth/libauth';
4
+ export interface WcTransactionOptions {
5
+ broadcast?: boolean;
6
+ userPrompt?: string;
7
+ }
8
+ export interface WcTransactionObject {
9
+ transaction: TransactionCommon | string;
10
+ sourceOutputs: WcSourceOutput[];
11
+ broadcast?: boolean;
12
+ userPrompt?: string;
13
+ }
14
+ export type WcSourceOutput = Input & LibauthOutput & WcContractInfo;
15
+ export interface WcContractInfo {
16
+ contract?: {
17
+ abiFunction: AbiFunction;
18
+ redeemScript: Uint8Array;
19
+ artifact: Partial<Artifact>;
20
+ };
21
+ }
22
+ export declare function getWcContractInfo(input: StandardUnlockableUtxo): WcContractInfo | {};
23
+ export declare const placeholderSignature: () => Uint8Array;
24
+ export declare const placeholderPublicKey: () => Uint8Array;
25
+ export declare const placeholderP2PKHUnlocker: (userAddress: string) => Unlocker;
@@ -0,0 +1,35 @@
1
+ import { scriptToBytecode } from '@cashscript/utils';
2
+ import { cashAddressToLockingBytecode } from '@bitauth/libauth';
3
+ export function getWcContractInfo(input) {
4
+ // If the input does not have a contract unlocker, return an empty object
5
+ if (!('contract' in input.unlocker))
6
+ return {};
7
+ const contract = input.unlocker.contract;
8
+ const abiFunctionName = input.unlocker.abiFunction?.name;
9
+ const abiFunction = contract.artifact.abi.find(abi => abi.name === abiFunctionName);
10
+ if (!abiFunction) {
11
+ throw new Error(`ABI function ${abiFunctionName} not found in contract artifact`);
12
+ }
13
+ const wcContractObj = {
14
+ contract: {
15
+ abiFunction: abiFunction,
16
+ redeemScript: scriptToBytecode(contract.redeemScript),
17
+ artifact: contract.artifact,
18
+ },
19
+ };
20
+ return wcContractObj;
21
+ }
22
+ export const placeholderSignature = () => Uint8Array.from(Array(65));
23
+ export const placeholderPublicKey = () => Uint8Array.from(Array(33));
24
+ export const placeholderP2PKHUnlocker = (userAddress) => {
25
+ const decodeAddressResult = cashAddressToLockingBytecode(userAddress);
26
+ if (typeof decodeAddressResult === 'string') {
27
+ throw new Error(`Invalid address: ${decodeAddressResult}`);
28
+ }
29
+ const lockingBytecode = decodeAddressResult.bytecode;
30
+ return {
31
+ generateLockingBytecode: () => lockingBytecode,
32
+ generateUnlockingBytecode: () => Uint8Array.from(Array(0)),
33
+ };
34
+ };
35
+ //# sourceMappingURL=walletconnect-utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashscript",
3
- "version": "0.11.0-next.4",
3
+ "version": "0.11.1",
4
4
  "description": "Easily write and interact with Bitcoin Cash contracts",
5
5
  "keywords": [
6
6
  "bitcoin cash",
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@bitauth/libauth": "^3.1.0-next.2",
49
- "@cashscript/utils": "^0.11.0-next.4",
49
+ "@cashscript/utils": "^0.11.1",
50
50
  "@electrum-cash/network": "^4.1.1",
51
51
  "@mr-zwets/bchn-api-wrapper": "^1.0.1",
52
52
  "delay": "^6.0.0",
@@ -63,5 +63,5 @@
63
63
  "jest": "^29.7.0",
64
64
  "typescript": "^5.7.3"
65
65
  },
66
- "gitHead": "68cdf30db455e3eef448a4feacfff0f5f26a36a7"
66
+ "gitHead": "ecd880401ed92fa7740ccd23d405f10b2fb61a29"
67
67
  }