cashscript 0.13.0-next.1 → 0.13.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.
@@ -4,9 +4,9 @@ import { debugTemplate } from '../debugging.js';
4
4
  import { isContractUnlocker, isP2PKHUnlocker, isStandardUnlockableUtxo, isUnlockableUtxo, } from '../interfaces.js';
5
5
  import SignatureTemplate from '../SignatureTemplate.js';
6
6
  import { addressToLockScript, extendedStringify, zip } from '../utils.js';
7
- import { deflate } from 'pako';
7
+ import { zlibSync } from 'fflate';
8
8
  import MockNetworkProvider from '../network/MockNetworkProvider.js';
9
- import { addHexPrefixExceptEmpty, DEFAULT_VM_TARGET, formatBytecodeForDebugging, formatParametersForDebugging, getLockScriptName, getSignatureAndPubkeyFromP2PKHInput, getUnlockScriptName, lockingBytecodeIsSetToSlot, serialiseTokenDetails } from './utils.js';
9
+ import { addHexPrefixExceptEmpty, DEFAULT_VM_TARGET, formatBytecodeForDebugging, formatParametersForDebugging, getLockScriptName, getSignatureAndPubkeyFromP2PKHInput, getUnlockScriptName, serialiseTokenDetails } from './utils.js';
10
10
  // TODO: Add / improve descriptions throughout the template generation
11
11
  export const getLibauthTemplate = (transactionBuilder) => {
12
12
  if (transactionBuilder.inputs.some((input) => !isStandardUnlockableUtxo(input))) {
@@ -26,61 +26,6 @@ export const getLibauthTemplate = (transactionBuilder) => {
26
26
  scripts: generateAllTemplateScripts(transactionBuilder),
27
27
  scenarios: generateAllTemplateScenarios(libauthTransaction, transactionBuilder),
28
28
  };
29
- // TODO: Refactor the below code to not have deep reassignment of scenario.sourceOutputs and scenario.transaction.outputs
30
- // Initialize bytecode mappings, these will be used to map the locking and unlocking scripts and naming the scripts
31
- const unlockingBytecodeToLockingBytecodeParams = {};
32
- const lockingBytecodeToLockingBytecodeParams = {};
33
- // We can typecast this because we check that all inputs are standard unlockable at the top of this function
34
- for (const [inputIndex, input] of transactionBuilder.inputs.entries()) {
35
- if (isContractUnlocker(input.unlocker)) {
36
- const lockScriptName = getLockScriptName(input.unlocker.contract);
37
- if (!lockScriptName)
38
- continue;
39
- const lockingScriptParams = generateLockingScriptParams(input.unlocker.contract, input, lockScriptName);
40
- const unlockingBytecode = binToHex(libauthTransaction.inputs[inputIndex].unlockingBytecode);
41
- unlockingBytecodeToLockingBytecodeParams[unlockingBytecode] = lockingScriptParams;
42
- const lockingBytecode = binToHex(addressToLockScript(input.unlocker.contract.address));
43
- lockingBytecodeToLockingBytecodeParams[lockingBytecode] = lockingScriptParams;
44
- }
45
- }
46
- for (const scenario of Object.values(template.scenarios)) {
47
- // For Inputs
48
- for (const [idx, input] of libauthTransaction.inputs.entries()) {
49
- const unlockingBytecode = binToHex(input.unlockingBytecode);
50
- const lockingBytecodeParams = unlockingBytecodeToLockingBytecodeParams[unlockingBytecode];
51
- // If lockingBytecodeParams is unknown, then it stays at default: {}
52
- if (!lockingBytecodeParams)
53
- continue;
54
- // If locking bytecode is set to ['slot'] then this is being evaluated by the scenario, so we don't replace bytecode
55
- if (lockingBytecodeIsSetToSlot(scenario?.sourceOutputs?.[idx]?.lockingBytecode))
56
- continue;
57
- // If lockingBytecodeParams is known, and this input is not ['slot'] then assign a locking bytecode as source output
58
- if (scenario.sourceOutputs?.[idx]) {
59
- scenario.sourceOutputs[idx] = {
60
- ...scenario.sourceOutputs[idx],
61
- lockingBytecode: lockingBytecodeParams,
62
- };
63
- }
64
- }
65
- // For Outputs
66
- for (const [idx, output] of libauthTransaction.outputs.entries()) {
67
- const lockingBytecode = binToHex(output.lockingBytecode);
68
- const lockingBytecodeParams = lockingBytecodeToLockingBytecodeParams[lockingBytecode];
69
- // If lockingBytecodeParams is unknown, then it stays at default: {}
70
- if (!lockingBytecodeParams)
71
- continue;
72
- // If locking bytecode is set to ['slot'] then this is being evaluated by the scenario, so we don't replace bytecode
73
- if (lockingBytecodeIsSetToSlot(scenario?.transaction?.outputs?.[idx]?.lockingBytecode))
74
- continue;
75
- // If lockingBytecodeParams is known, and this input is not ['slot'] then assign a locking bytecode as source output
76
- if (scenario?.transaction?.outputs?.[idx]) {
77
- scenario.transaction.outputs[idx] = {
78
- ...scenario.transaction.outputs[idx],
79
- lockingBytecode: lockingBytecodeParams,
80
- };
81
- }
82
- }
83
- }
84
29
  return template;
85
30
  };
86
31
  export const debugLibauthTemplate = (template, transaction) => {
@@ -92,7 +37,7 @@ export const debugLibauthTemplate = (template, transaction) => {
92
37
  };
93
38
  export const getBitauthUri = (template) => {
94
39
  const base64toBase64Url = (base64) => base64.replace(/\+/g, '-').replace(/\//g, '_');
95
- const payload = base64toBase64Url(binToBase64(deflate(utf8ToBin(extendedStringify(template)))));
40
+ const payload = base64toBase64Url(binToBase64(zlibSync(utf8ToBin(extendedStringify(template)))));
96
41
  return `https://ide.bitauth.com/import-template/${payload}`;
97
42
  };
98
43
  const generateAllTemplateEntities = (transactionBuilder) => {
@@ -121,6 +66,20 @@ const generateAllTemplateScripts = (transactionBuilder) => {
121
66
  });
122
67
  return scripts.reduce((acc, script) => ({ ...acc, ...script }), {});
123
68
  };
69
+ const generateLockingBytecodeParamsMapping = (transactionBuilder) => {
70
+ // Initialize bytecode mapping, this will be used to map the locking bytecode to the locking bytecode params
71
+ const mapping = {};
72
+ // We can typecast this because we check that all inputs are standard unlockable at the top of this function
73
+ for (const input of transactionBuilder.inputs) {
74
+ if (isContractUnlocker(input.unlocker)) {
75
+ const lockScriptName = getLockScriptName(input.unlocker.contract);
76
+ const lockingScriptParams = generateLockingScriptParams(input.unlocker.contract, input, lockScriptName);
77
+ const lockingBytecode = binToHex(addressToLockScript(input.unlocker.contract.address));
78
+ mapping[lockingBytecode] = lockingScriptParams;
79
+ }
80
+ }
81
+ return mapping;
82
+ };
124
83
  const generateAllTemplateScenarios = (libauthTransaction, transactionBuilder) => {
125
84
  const scenarios = transactionBuilder.inputs.map((input, inputIndex) => {
126
85
  if (isP2PKHUnlocker(input.unlocker)) {
@@ -299,6 +258,7 @@ const generateTemplateScenariosP2PKH = (libauthTransaction, transactionBuilder,
299
258
  return scenarios;
300
259
  };
301
260
  const generateTemplateScenarioTransaction = (contract, libauthTransaction, transactionBuilder, slotIndex) => {
261
+ const lockingBytecodeParamsMapping = generateLockingBytecodeParamsMapping(transactionBuilder);
302
262
  const zippedInputs = zip(transactionBuilder.inputs, libauthTransaction.inputs);
303
263
  const inputs = zippedInputs.map(([csInput, libauthInput], inputIndex) => {
304
264
  return {
@@ -311,15 +271,8 @@ const generateTemplateScenarioTransaction = (contract, libauthTransaction, trans
311
271
  const locktime = libauthTransaction.locktime;
312
272
  const zippedOutputs = zip(transactionBuilder.outputs, libauthTransaction.outputs);
313
273
  const outputs = zippedOutputs.map(([csOutput, libauthOutput]) => {
314
- if (csOutput && contract) {
315
- return {
316
- lockingBytecode: generateTemplateScenarioTransactionOutputLockingBytecode(csOutput, contract),
317
- token: serialiseTokenDetails(libauthOutput.token),
318
- valueSatoshis: Number(libauthOutput.valueSatoshis),
319
- };
320
- }
321
274
  return {
322
- lockingBytecode: `${binToHex(libauthOutput.lockingBytecode)}`,
275
+ lockingBytecode: generateTemplateScenarioTransactionOutputLockingBytecode(csOutput, libauthOutput, contract, lockingBytecodeParamsMapping),
323
276
  token: serialiseTokenDetails(libauthOutput.token),
324
277
  valueSatoshis: Number(libauthOutput.valueSatoshis),
325
278
  };
@@ -331,7 +284,7 @@ const generateTemplateScenarioSourceOutputs = (transactionBuilder, libauthTransa
331
284
  const zippedInputs = zip(transactionBuilder.inputs, libauthTransaction.inputs);
332
285
  return zippedInputs.map(([csInput, libauthInput], inputIndex) => {
333
286
  return {
334
- lockingBytecode: generateTemplateScenarioBytecode(csInput, libauthInput, inputIndex, 'p2pkh_placeholder_lock', inputIndex === slotIndex),
287
+ lockingBytecode: generateTemplateScenarioBytecodeForSourceOutputs(csInput, libauthInput, inputIndex, 'p2pkh_placeholder_lock', inputIndex === slotIndex),
335
288
  valueSatoshis: Number(csInput.satoshis),
336
289
  token: serialiseTokenDetails(csInput.token),
337
290
  };
@@ -417,10 +370,32 @@ const generateTemplateScenarioBytecode = (input, libauthInput, inputIndex, p2pkh
417
370
  // {} means that it is the same script type, but not being evaluated
418
371
  return {};
419
372
  };
420
- const generateTemplateScenarioTransactionOutputLockingBytecode = (csOutput, contract) => {
373
+ const generateTemplateScenarioBytecodeForSourceOutputs = (input, libauthInput, inputIndex, p2pkhScriptNameTemplate, insertSlot) => {
374
+ if (insertSlot)
375
+ return ['slot'];
376
+ if (isUnlockableUtxo(input) && isStandardUnlockableUtxo(input)) {
377
+ // If the input is a contract unlocker, we need to generate the locking bytecode params for the source outputs
378
+ if (isContractUnlocker(input.unlocker)) {
379
+ const lockScriptName = getLockScriptName(input.unlocker.contract);
380
+ const lockingBytecodeParams = generateLockingScriptParams(input.unlocker.contract, input, lockScriptName);
381
+ return lockingBytecodeParams;
382
+ }
383
+ // For a P2PKH unlocker, the sourceOutputs "locking bytecode params" are the same as the unlocking bytecode params
384
+ return generateUnlockingScriptParams(input, libauthInput, p2pkhScriptNameTemplate, inputIndex);
385
+ }
386
+ // 'slot' means that we are currently evaluating this specific input,
387
+ // {} means that it is the same script type, but not being evaluated
388
+ return {};
389
+ };
390
+ const generateTemplateScenarioTransactionOutputLockingBytecode = (csOutput, libauthOutput, contract, lockingBytecodeParamsMapping) => {
391
+ // If lockingBytecodeParams is known from the mapping, return it
392
+ const lockingBytecode = binToHex(libauthOutput.lockingBytecode);
393
+ const lockingBytecodeParams = lockingBytecodeParamsMapping[lockingBytecode];
394
+ if (lockingBytecodeParams)
395
+ return lockingBytecodeParams;
421
396
  if (csOutput.to instanceof Uint8Array)
422
397
  return binToHex(csOutput.to);
423
- if ([contract.address, contract.tokenAddress].includes(csOutput.to))
398
+ if (contract && [contract.address, contract.tokenAddress].includes(csOutput.to))
424
399
  return {};
425
400
  return binToHex(addressToLockScript(csOutput.to));
426
401
  };
@@ -1,6 +1,6 @@
1
1
  import { AbiFunction, AbiInput, Artifact } from '@cashscript/utils';
2
2
  import { HashType, LibauthTokenDetails, SignatureAlgorithm, TokenDetails } from '../interfaces.js';
3
- import { type WalletTemplateScenarioBytecode, Input } from '@bitauth/libauth';
3
+ import { Input } from '@bitauth/libauth';
4
4
  import { EncodedFunctionArgument } from '../Argument.js';
5
5
  import { Contract } from '../Contract.js';
6
6
  export declare const DEFAULT_VM_TARGET: "BCH_2026_05";
@@ -20,7 +20,6 @@ interface LibauthTemplateTokenDetails {
20
20
  commitment: string;
21
21
  };
22
22
  }
23
- export declare const lockingBytecodeIsSetToSlot: (lockingBytecode?: WalletTemplateScenarioBytecode | ["slot"]) => boolean;
24
23
  export declare const getSignatureAndPubkeyFromP2PKHInput: (libauthInput: Input) => {
25
24
  signature: Uint8Array;
26
25
  publicKey: Uint8Array;
@@ -78,9 +78,6 @@ export const serialiseTokenDetails = (token) => {
78
78
  } : undefined,
79
79
  };
80
80
  };
81
- export const lockingBytecodeIsSetToSlot = (lockingBytecode) => {
82
- return Array.isArray(lockingBytecode) && lockingBytecode.length === 1 && lockingBytecode[0] === 'slot';
83
- };
84
81
  export const getSignatureAndPubkeyFromP2PKHInput = (libauthInput) => {
85
82
  const inputData = (assertSuccess(decodeAuthenticationInstructions(libauthInput.unlockingBytecode)));
86
83
  const signature = inputData[0].data;
@@ -1,6 +1,8 @@
1
+ const testFramework = globalThis.vi ?? globalThis.jest;
2
+ // Extend Vitest with the custom matchers, this file needs to be imported in the vitest.setup.ts file or the test file
1
3
  expect.extend({
2
4
  toLog(transaction, match) {
3
- const loggerSpy = jest.spyOn(console, 'log');
5
+ const loggerSpy = testFramework.spyOn(console, 'log');
4
6
  // Clear any previous calls (if spy reused accidentally)
5
7
  loggerSpy.mockClear();
6
8
  // silence actual stdout output
@@ -37,8 +39,18 @@ expect.extend({
37
39
  }
38
40
  return { message, pass: true };
39
41
  },
40
- });
41
- expect.extend({
42
+ toFailRequire(transaction) {
43
+ try {
44
+ transaction.debug();
45
+ const message = () => 'Contract function did not fail a require statement.';
46
+ return { message, pass: false };
47
+ }
48
+ catch (transactionError) {
49
+ const receivedText = `Received string: ${this.utils.printReceived(transactionError?.message ?? '')}`;
50
+ const message = () => `Contract function failed a require statement.\n${receivedText}`;
51
+ return { message, pass: true };
52
+ }
53
+ },
42
54
  toFailRequireWith(transaction, match) {
43
55
  try {
44
56
  transaction.debug();
@@ -60,18 +72,6 @@ expect.extend({
60
72
  }
61
73
  }
62
74
  },
63
- toFailRequire(transaction) {
64
- try {
65
- transaction.debug();
66
- const message = () => 'Contract function did not fail a require statement.';
67
- return { message, pass: false };
68
- }
69
- catch (transactionError) {
70
- const receivedText = `Received string: ${this.utils.printReceived(transactionError?.message ?? '')}`;
71
- const message = () => `Contract function failed a require statement.\n${receivedText}`;
72
- return { message, pass: true };
73
- }
74
- },
75
75
  });
76
76
  export {};
77
- //# sourceMappingURL=JestExtensions.js.map
77
+ //# sourceMappingURL=TestExtensions.js.map
package/jest/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "type": "module",
3
- "types": "../dist/test/JestExtensions.d.ts",
4
- "main": "../dist/test/JestExtensions.js"
3
+ "types": "../dist/test/TestExtensions.d.ts",
4
+ "main": "../dist/test/TestExtensions.js"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashscript",
3
- "version": "0.13.0-next.1",
3
+ "version": "0.13.0-next.2",
4
4
  "description": "Easily write and interact with Bitcoin Cash contracts",
5
5
  "keywords": [
6
6
  "bitcoin cash",
@@ -33,33 +33,28 @@
33
33
  },
34
34
  "scripts": {
35
35
  "build": "yarn clean && yarn compile",
36
- "build:test": "yarn clean:test && yarn compile:test",
37
36
  "clean": "rm -rf ./dist",
38
- "clean:test": "rm -rf ./dist-test",
39
37
  "compile": "tsc -p tsconfig.build.json",
40
- "compile:test": "tsc -p tsconfig.test.json",
41
38
  "lint": "eslint . --ext .ts --ignore-path ../../.eslintignore",
42
39
  "prepare": "yarn build",
43
40
  "prepublishOnly": "yarn test && yarn lint",
44
- "pretest": "yarn build:test",
45
- "test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"
41
+ "test": "vitest run"
46
42
  },
47
43
  "dependencies": {
48
44
  "@bitauth/libauth": "^3.1.0-next.8",
49
- "@cashscript/utils": "^0.13.0-next.1",
45
+ "@cashscript/utils": "^0.13.0-next.2",
50
46
  "@electrum-cash/network": "^4.1.3",
51
47
  "@mr-zwets/bchn-api-wrapper": "^1.0.1",
52
- "pako": "^2.1.0",
48
+ "fflate": "^0.8.2",
53
49
  "semver": "^7.7.2"
54
50
  },
55
51
  "devDependencies": {
56
- "@jest/globals": "^29.7.0",
57
52
  "@psf/bch-js": "^6.8.0",
58
- "@types/pako": "^2.0.3",
59
53
  "@types/semver": "^7.5.8",
54
+ "@vitest/coverage-v8": "^4.0.15",
60
55
  "eslint": "^8.54.0",
61
- "jest": "^29.7.0",
62
- "typescript": "^5.9.2"
56
+ "typescript": "^5.9.2",
57
+ "vitest": "^4.0.15"
63
58
  },
64
- "gitHead": "f3d3fd75400c93f4457276b56ce094b11c0986d7"
59
+ "gitHead": "817391f108ce46c826dbab6f11ee4f1bb269c27f"
65
60
  }
@@ -0,0 +1,5 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../dist/test/TestExtensions.d.ts",
4
+ "main": "../dist/test/TestExtensions.js"
5
+ }
@@ -1,4 +1,3 @@
1
- export {};
2
1
  declare global {
3
2
  namespace jest {
4
3
  interface Matchers<R> {
@@ -8,3 +7,4 @@ declare global {
8
7
  }
9
8
  }
10
9
  }
10
+ export {};