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.
- package/dist/libauth-template/LibauthTemplate.js +44 -69
- package/dist/libauth-template/utils.d.ts +1 -2
- package/dist/libauth-template/utils.js +0 -3
- package/dist/test/{JestExtensions.js → TestExtensions.js} +16 -16
- package/jest/package.json +2 -2
- package/package.json +8 -13
- package/vitest/package.json +5 -0
- package/dist/test/{JestExtensions.d.ts → TestExtensions.d.ts} +1 -1
|
@@ -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 {
|
|
7
|
+
import { zlibSync } from 'fflate';
|
|
8
8
|
import MockNetworkProvider from '../network/MockNetworkProvider.js';
|
|
9
|
-
import { addHexPrefixExceptEmpty, DEFAULT_VM_TARGET, formatBytecodeForDebugging, formatParametersForDebugging, getLockScriptName, getSignatureAndPubkeyFromP2PKHInput, getUnlockScriptName,
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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=
|
|
77
|
+
//# sourceMappingURL=TestExtensions.js.map
|
package/jest/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cashscript",
|
|
3
|
-
"version": "0.13.0-next.
|
|
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
|
-
"
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
62
|
-
"
|
|
56
|
+
"typescript": "^5.9.2",
|
|
57
|
+
"vitest": "^4.0.15"
|
|
63
58
|
},
|
|
64
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "817391f108ce46c826dbab6f11ee4f1bb269c27f"
|
|
65
60
|
}
|