cashscript 0.11.4 → 0.12.0
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 +18 -6
- package/dist/Argument.js +12 -8
- package/dist/Contract.d.ts +3 -9
- package/dist/Contract.js +4 -31
- package/dist/Errors.d.ts +2 -2
- package/dist/SignatureTemplate.d.ts +1 -0
- package/dist/SignatureTemplate.js +13 -4
- package/dist/TransactionBuilder.d.ts +8 -4
- package/dist/TransactionBuilder.js +74 -22
- package/dist/debugging.js +53 -20
- package/dist/index.d.ts +1 -2
- package/dist/index.js +0 -1
- package/dist/interfaces.d.ts +10 -6
- package/dist/interfaces.js +6 -3
- package/dist/libauth-template/LibauthTemplate.d.ts +6 -0
- package/dist/libauth-template/LibauthTemplate.js +445 -0
- package/dist/libauth-template/utils.d.ts +27 -0
- package/dist/libauth-template/utils.js +89 -0
- package/dist/network/MockNetworkProvider.d.ts +4 -3
- package/dist/network/MockNetworkProvider.js +4 -14
- package/dist/test/JestExtensions.js +5 -26
- package/dist/types/type-inference.d.ts +8 -6
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +3 -23
- package/package.json +4 -7
- package/dist/LibauthTemplate.d.ts +0 -32
- package/dist/LibauthTemplate.js +0 -324
- package/dist/Transaction.d.ts +0 -45
- package/dist/Transaction.js +0 -385
- package/dist/advanced/LibauthTemplate.d.ts +0 -45
- package/dist/advanced/LibauthTemplate.js +0 -426
|
@@ -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;
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { binToBase64, binToHex, utf8ToBin, } from '@bitauth/libauth';
|
|
2
|
+
import { encodeFunctionArguments } from '../Argument.js';
|
|
3
|
+
import { debugTemplate } from '../debugging.js';
|
|
4
|
+
import { isContractUnlocker, isP2PKHUnlocker, isStandardUnlockableUtxo, isUnlockableUtxo, VmTarget, } from '../interfaces.js';
|
|
5
|
+
import SignatureTemplate from '../SignatureTemplate.js';
|
|
6
|
+
import { addressToLockScript, extendedStringify, zip } from '../utils.js';
|
|
7
|
+
import { deflate } from 'pako';
|
|
8
|
+
import MockNetworkProvider from '../network/MockNetworkProvider.js';
|
|
9
|
+
import { addHexPrefixExceptEmpty, formatBytecodeForDebugging, formatParametersForDebugging, getLockScriptName, getSignatureAndPubkeyFromP2PKHInput, getUnlockScriptName, lockingBytecodeIsSetToSlot, serialiseTokenDetails } from './utils.js';
|
|
10
|
+
// TODO: Add / improve descriptions throughout the template generation
|
|
11
|
+
export const getLibauthTemplate = (transactionBuilder) => {
|
|
12
|
+
if (transactionBuilder.inputs.some((input) => !isStandardUnlockableUtxo(input))) {
|
|
13
|
+
throw new Error('Cannot use debugging functionality with a transaction that contains custom unlockers');
|
|
14
|
+
}
|
|
15
|
+
const libauthTransaction = transactionBuilder.buildLibauthTransaction();
|
|
16
|
+
const vmTarget = transactionBuilder.provider instanceof MockNetworkProvider
|
|
17
|
+
? transactionBuilder.provider.vmTarget
|
|
18
|
+
: VmTarget.BCH_2025_05;
|
|
19
|
+
const template = {
|
|
20
|
+
$schema: 'https://ide.bitauth.com/authentication-template-v0.schema.json',
|
|
21
|
+
description: 'Imported from cashscript',
|
|
22
|
+
name: 'CashScript Generated Debugging Template',
|
|
23
|
+
supported: [vmTarget],
|
|
24
|
+
version: 0,
|
|
25
|
+
entities: generateAllTemplateEntities(transactionBuilder),
|
|
26
|
+
scripts: generateAllTemplateScripts(transactionBuilder),
|
|
27
|
+
scenarios: generateAllTemplateScenarios(libauthTransaction, transactionBuilder),
|
|
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
|
+
return template;
|
|
85
|
+
};
|
|
86
|
+
export const debugLibauthTemplate = (template, transaction) => {
|
|
87
|
+
const allArtifacts = transaction.inputs
|
|
88
|
+
.map(input => isContractUnlocker(input.unlocker) ? input.unlocker.contract : undefined)
|
|
89
|
+
.filter((contract) => Boolean(contract))
|
|
90
|
+
.map(contract => contract.artifact);
|
|
91
|
+
return debugTemplate(template, allArtifacts);
|
|
92
|
+
};
|
|
93
|
+
export const getBitauthUri = (template) => {
|
|
94
|
+
const base64toBase64Url = (base64) => base64.replace(/\+/g, '-').replace(/\//g, '_');
|
|
95
|
+
const payload = base64toBase64Url(binToBase64(deflate(utf8ToBin(extendedStringify(template)))));
|
|
96
|
+
return `https://ide.bitauth.com/import-template/${payload}`;
|
|
97
|
+
};
|
|
98
|
+
const generateAllTemplateEntities = (transactionBuilder) => {
|
|
99
|
+
const entities = transactionBuilder.inputs.map((input, inputIndex) => {
|
|
100
|
+
if (isP2PKHUnlocker(input.unlocker)) {
|
|
101
|
+
return generateTemplateEntitiesP2PKH(inputIndex);
|
|
102
|
+
}
|
|
103
|
+
if (isContractUnlocker(input.unlocker)) {
|
|
104
|
+
const encodedArgs = encodeFunctionArguments(input.unlocker.abiFunction, input.unlocker.params ?? []);
|
|
105
|
+
return generateTemplateEntitiesP2SH(input.unlocker.contract, input.unlocker.abiFunction, encodedArgs, inputIndex);
|
|
106
|
+
}
|
|
107
|
+
throw new Error('Unknown unlocker type');
|
|
108
|
+
});
|
|
109
|
+
return entities.reduce((acc, entity) => ({ ...acc, ...entity }), {});
|
|
110
|
+
};
|
|
111
|
+
const generateAllTemplateScripts = (transactionBuilder) => {
|
|
112
|
+
const scripts = transactionBuilder.inputs.map((input, inputIndex) => {
|
|
113
|
+
if (isP2PKHUnlocker(input.unlocker)) {
|
|
114
|
+
return generateTemplateScriptsP2PKH(inputIndex);
|
|
115
|
+
}
|
|
116
|
+
if (isContractUnlocker(input.unlocker)) {
|
|
117
|
+
const encodedArgs = encodeFunctionArguments(input.unlocker.abiFunction, input.unlocker.params ?? []);
|
|
118
|
+
return generateTemplateScriptsP2SH(input.unlocker.contract, input.unlocker.abiFunction, encodedArgs, input.unlocker.contract.encodedConstructorArgs, inputIndex);
|
|
119
|
+
}
|
|
120
|
+
throw new Error('Unknown unlocker type');
|
|
121
|
+
});
|
|
122
|
+
return scripts.reduce((acc, script) => ({ ...acc, ...script }), {});
|
|
123
|
+
};
|
|
124
|
+
const generateAllTemplateScenarios = (libauthTransaction, transactionBuilder) => {
|
|
125
|
+
const scenarios = transactionBuilder.inputs.map((input, inputIndex) => {
|
|
126
|
+
if (isP2PKHUnlocker(input.unlocker)) {
|
|
127
|
+
return generateTemplateScenariosP2PKH(libauthTransaction, transactionBuilder, inputIndex);
|
|
128
|
+
}
|
|
129
|
+
if (isContractUnlocker(input.unlocker)) {
|
|
130
|
+
const encodedArgs = encodeFunctionArguments(input.unlocker.abiFunction, input.unlocker.params ?? []);
|
|
131
|
+
return generateTemplateScenarios(input.unlocker.contract, libauthTransaction, transactionBuilder, input.unlocker.abiFunction, encodedArgs, inputIndex);
|
|
132
|
+
}
|
|
133
|
+
throw new Error('Unknown unlocker type');
|
|
134
|
+
});
|
|
135
|
+
return scenarios.reduce((acc, scenario) => ({ ...acc, ...scenario }), {});
|
|
136
|
+
};
|
|
137
|
+
const generateTemplateEntitiesP2PKH = (inputIndex) => {
|
|
138
|
+
const lockScriptName = `p2pkh_placeholder_lock_${inputIndex}`;
|
|
139
|
+
const unlockScriptName = `p2pkh_placeholder_unlock_${inputIndex}`;
|
|
140
|
+
return {
|
|
141
|
+
[`signer_${inputIndex}`]: {
|
|
142
|
+
scripts: [lockScriptName, unlockScriptName],
|
|
143
|
+
description: `P2PKH data for input ${inputIndex}`,
|
|
144
|
+
name: `P2PKH Signer (input #${inputIndex})`,
|
|
145
|
+
variables: {
|
|
146
|
+
[`signature_${inputIndex}`]: {
|
|
147
|
+
description: '',
|
|
148
|
+
name: `P2PKH Signature (input #${inputIndex})`,
|
|
149
|
+
type: 'WalletData',
|
|
150
|
+
},
|
|
151
|
+
[`public_key_${inputIndex}`]: {
|
|
152
|
+
description: '',
|
|
153
|
+
name: `P2PKH public key (input #${inputIndex})`,
|
|
154
|
+
type: 'WalletData',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
const generateTemplateEntitiesP2SH = (contract, abiFunction, encodedFunctionArgs, inputIndex) => {
|
|
161
|
+
const entities = {
|
|
162
|
+
[contract.artifact.contractName + '_input' + inputIndex + '_parameters']: {
|
|
163
|
+
description: 'Contract creation and function parameters',
|
|
164
|
+
name: `${contract.artifact.contractName} (input #${inputIndex})`,
|
|
165
|
+
scripts: [
|
|
166
|
+
getLockScriptName(contract),
|
|
167
|
+
getUnlockScriptName(contract, abiFunction, inputIndex),
|
|
168
|
+
],
|
|
169
|
+
variables: {
|
|
170
|
+
...createWalletTemplateVariables(contract.artifact, abiFunction, encodedFunctionArgs),
|
|
171
|
+
...generateFunctionIndexTemplateVariable(contract.artifact.abi),
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
return entities;
|
|
176
|
+
};
|
|
177
|
+
const createWalletTemplateVariables = (artifact, abiFunction, encodedFunctionArgs) => {
|
|
178
|
+
const functionParameters = Object.fromEntries(abiFunction.inputs.map((input, index) => ([
|
|
179
|
+
input.name,
|
|
180
|
+
{
|
|
181
|
+
description: `"${input.name}" parameter of function "${abiFunction.name}"`,
|
|
182
|
+
name: input.name,
|
|
183
|
+
type: encodedFunctionArgs[index] instanceof SignatureTemplate ? 'Key' : 'WalletData',
|
|
184
|
+
},
|
|
185
|
+
])));
|
|
186
|
+
const constructorParameters = Object.fromEntries(artifact.constructorInputs.map((input) => ([
|
|
187
|
+
input.name,
|
|
188
|
+
{
|
|
189
|
+
description: `"${input.name}" parameter of this contract`,
|
|
190
|
+
name: input.name,
|
|
191
|
+
type: 'WalletData',
|
|
192
|
+
},
|
|
193
|
+
])));
|
|
194
|
+
return { ...functionParameters, ...constructorParameters };
|
|
195
|
+
};
|
|
196
|
+
const generateTemplateScriptsP2PKH = (inputIndex) => {
|
|
197
|
+
const lockScriptName = `p2pkh_placeholder_lock_${inputIndex}`;
|
|
198
|
+
const unlockScriptName = `p2pkh_placeholder_unlock_${inputIndex}`;
|
|
199
|
+
const scripts = {
|
|
200
|
+
[unlockScriptName]: {
|
|
201
|
+
passes: [`P2PKH_spend_input${inputIndex}_evaluate`],
|
|
202
|
+
name: `P2PKH Unlock (input #${inputIndex})`,
|
|
203
|
+
script: `<signature_${inputIndex}>\n<public_key_${inputIndex}>`,
|
|
204
|
+
unlocks: lockScriptName,
|
|
205
|
+
},
|
|
206
|
+
[lockScriptName]: {
|
|
207
|
+
lockingType: 'standard',
|
|
208
|
+
name: `P2PKH Lock (input #${inputIndex})`,
|
|
209
|
+
script: `OP_DUP\nOP_HASH160 <$(<public_key_${inputIndex}> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG`,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
return scripts;
|
|
213
|
+
};
|
|
214
|
+
const generateTemplateScriptsP2SH = (contract, abiFunction, encodedFunctionArgs, encodedConstructorArgs, inputIndex) => {
|
|
215
|
+
const unlockingScriptName = getUnlockScriptName(contract, abiFunction, inputIndex);
|
|
216
|
+
const lockingScriptName = getLockScriptName(contract);
|
|
217
|
+
return {
|
|
218
|
+
[unlockingScriptName]: generateTemplateUnlockScript(contract, abiFunction, encodedFunctionArgs, inputIndex),
|
|
219
|
+
[lockingScriptName]: generateTemplateLockScript(contract, encodedConstructorArgs),
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
const generateTemplateLockScript = (contract, constructorArguments) => {
|
|
223
|
+
return {
|
|
224
|
+
lockingType: contract.addressType,
|
|
225
|
+
name: contract.artifact.contractName,
|
|
226
|
+
script: [
|
|
227
|
+
`// "${contract.artifact.contractName}" contract constructor parameters`,
|
|
228
|
+
formatParametersForDebugging(contract.artifact.constructorInputs, constructorArguments),
|
|
229
|
+
'',
|
|
230
|
+
'// bytecode',
|
|
231
|
+
formatBytecodeForDebugging(contract.artifact),
|
|
232
|
+
].join('\n'),
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
const generateTemplateUnlockScript = (contract, abiFunction, encodedFunctionArgs, inputIndex) => {
|
|
236
|
+
const scenarioIdentifier = `${contract.artifact.contractName}_${abiFunction.name}_input${inputIndex}_evaluate`;
|
|
237
|
+
const functionIndex = contract.artifact.abi.findIndex((func) => func.name === abiFunction.name);
|
|
238
|
+
const functionIndexString = contract.artifact.abi.length > 1
|
|
239
|
+
? ['// function index in contract', `<function_index> // int = <${functionIndex}>`, '']
|
|
240
|
+
: [];
|
|
241
|
+
return {
|
|
242
|
+
// this unlocking script must pass our only scenario
|
|
243
|
+
passes: [scenarioIdentifier],
|
|
244
|
+
name: `${abiFunction.name} (input #${inputIndex})`,
|
|
245
|
+
script: [
|
|
246
|
+
`// "${abiFunction.name}" function parameters`,
|
|
247
|
+
formatParametersForDebugging(abiFunction.inputs, encodedFunctionArgs),
|
|
248
|
+
'',
|
|
249
|
+
...functionIndexString,
|
|
250
|
+
].join('\n'),
|
|
251
|
+
unlocks: getLockScriptName(contract),
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
const generateTemplateScenarios = (contract, libauthTransaction, transactionBuilder, abiFunction, encodedFunctionArgs, inputIndex) => {
|
|
255
|
+
const artifact = contract.artifact;
|
|
256
|
+
const encodedConstructorArgs = contract.encodedConstructorArgs;
|
|
257
|
+
const scenarioIdentifier = `${artifact.contractName}_${abiFunction.name}_input${inputIndex}_evaluate`;
|
|
258
|
+
const scenarios = {
|
|
259
|
+
// single scenario to spend out transaction under test given the CashScript parameters provided
|
|
260
|
+
[scenarioIdentifier]: {
|
|
261
|
+
name: `Evaluate ${artifact.contractName} ${abiFunction.name} (input #${inputIndex})`,
|
|
262
|
+
description: 'An example evaluation where this script execution passes.',
|
|
263
|
+
data: {
|
|
264
|
+
// encode values for the variables defined above in `entities` property
|
|
265
|
+
bytecode: {
|
|
266
|
+
...generateTemplateScenarioParametersFunctionIndex(abiFunction, contract.artifact.abi),
|
|
267
|
+
...generateTemplateScenarioParametersValues(abiFunction.inputs, encodedFunctionArgs),
|
|
268
|
+
...generateTemplateScenarioParametersValues(artifact.constructorInputs, encodedConstructorArgs),
|
|
269
|
+
},
|
|
270
|
+
keys: {
|
|
271
|
+
privateKeys: generateTemplateScenarioKeys(abiFunction.inputs, encodedFunctionArgs),
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
transaction: generateTemplateScenarioTransaction(contract, libauthTransaction, transactionBuilder, inputIndex),
|
|
275
|
+
sourceOutputs: generateTemplateScenarioSourceOutputs(transactionBuilder, libauthTransaction, inputIndex),
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
return scenarios;
|
|
279
|
+
};
|
|
280
|
+
const generateTemplateScenariosP2PKH = (libauthTransaction, transactionBuilder, inputIndex) => {
|
|
281
|
+
const scenarioIdentifier = `P2PKH_spend_input${inputIndex}_evaluate`;
|
|
282
|
+
const { signature, publicKey } = getSignatureAndPubkeyFromP2PKHInput(libauthTransaction.inputs[inputIndex]);
|
|
283
|
+
const scenarios = {
|
|
284
|
+
// single scenario to spend out transaction under test given the CashScript parameters provided
|
|
285
|
+
[scenarioIdentifier]: {
|
|
286
|
+
name: `Evaluate P2PKH spend (input #${inputIndex})`,
|
|
287
|
+
description: 'An example evaluation where this script execution passes.',
|
|
288
|
+
data: {
|
|
289
|
+
// encode values for the variables defined above in `entities` property
|
|
290
|
+
bytecode: {
|
|
291
|
+
[`signature_${inputIndex}`]: `0x${binToHex(signature)}`,
|
|
292
|
+
[`public_key_${inputIndex}`]: `0x${binToHex(publicKey)}`,
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
transaction: generateTemplateScenarioTransaction(undefined, libauthTransaction, transactionBuilder, inputIndex),
|
|
296
|
+
sourceOutputs: generateTemplateScenarioSourceOutputs(transactionBuilder, libauthTransaction, inputIndex),
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
return scenarios;
|
|
300
|
+
};
|
|
301
|
+
const generateTemplateScenarioTransaction = (contract, libauthTransaction, transactionBuilder, slotIndex) => {
|
|
302
|
+
const zippedInputs = zip(transactionBuilder.inputs, libauthTransaction.inputs);
|
|
303
|
+
const inputs = zippedInputs.map(([csInput, libauthInput], inputIndex) => {
|
|
304
|
+
return {
|
|
305
|
+
outpointIndex: libauthInput.outpointIndex,
|
|
306
|
+
outpointTransactionHash: binToHex(libauthInput.outpointTransactionHash),
|
|
307
|
+
sequenceNumber: libauthInput.sequenceNumber,
|
|
308
|
+
unlockingBytecode: generateTemplateScenarioBytecode(csInput, libauthInput, inputIndex, 'p2pkh_placeholder_unlock', slotIndex === inputIndex),
|
|
309
|
+
};
|
|
310
|
+
});
|
|
311
|
+
const locktime = libauthTransaction.locktime;
|
|
312
|
+
const zippedOutputs = zip(transactionBuilder.outputs, libauthTransaction.outputs);
|
|
313
|
+
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
|
+
return {
|
|
322
|
+
lockingBytecode: `${binToHex(libauthOutput.lockingBytecode)}`,
|
|
323
|
+
token: serialiseTokenDetails(libauthOutput.token),
|
|
324
|
+
valueSatoshis: Number(libauthOutput.valueSatoshis),
|
|
325
|
+
};
|
|
326
|
+
});
|
|
327
|
+
const version = libauthTransaction.version;
|
|
328
|
+
return { inputs, locktime, outputs, version };
|
|
329
|
+
};
|
|
330
|
+
const generateTemplateScenarioSourceOutputs = (transactionBuilder, libauthTransaction, slotIndex) => {
|
|
331
|
+
const zippedInputs = zip(transactionBuilder.inputs, libauthTransaction.inputs);
|
|
332
|
+
return zippedInputs.map(([csInput, libauthInput], inputIndex) => {
|
|
333
|
+
return {
|
|
334
|
+
lockingBytecode: generateTemplateScenarioBytecode(csInput, libauthInput, inputIndex, 'p2pkh_placeholder_lock', inputIndex === slotIndex),
|
|
335
|
+
valueSatoshis: Number(csInput.satoshis),
|
|
336
|
+
token: serialiseTokenDetails(csInput.token),
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
const generateLockingScriptParams = (contract, { unlocker }, lockScriptName) => {
|
|
341
|
+
if (isP2PKHUnlocker(unlocker)) {
|
|
342
|
+
return {
|
|
343
|
+
script: lockScriptName,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const constructorParamsEntries = contract.artifact.constructorInputs
|
|
347
|
+
.map(({ name }, index) => [
|
|
348
|
+
name,
|
|
349
|
+
addHexPrefixExceptEmpty(binToHex(unlocker.contract.encodedConstructorArgs[index])),
|
|
350
|
+
]);
|
|
351
|
+
const constructorParams = Object.fromEntries(constructorParamsEntries);
|
|
352
|
+
return {
|
|
353
|
+
script: lockScriptName,
|
|
354
|
+
overrides: {
|
|
355
|
+
bytecode: { ...constructorParams },
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
};
|
|
359
|
+
const generateUnlockingScriptParams = (csInput, libauthInput, p2pkhScriptNameTemplate, inputIndex) => {
|
|
360
|
+
if (isP2PKHUnlocker(csInput.unlocker)) {
|
|
361
|
+
const { signature, publicKey } = getSignatureAndPubkeyFromP2PKHInput(libauthInput);
|
|
362
|
+
return {
|
|
363
|
+
script: `${p2pkhScriptNameTemplate}_${inputIndex}`,
|
|
364
|
+
overrides: {
|
|
365
|
+
bytecode: {
|
|
366
|
+
[`signature_${inputIndex}`]: `0x${binToHex(signature)}`,
|
|
367
|
+
[`public_key_${inputIndex}`]: `0x${binToHex(publicKey)}`,
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const abiFunction = csInput.unlocker.abiFunction;
|
|
373
|
+
const contract = csInput.unlocker.contract;
|
|
374
|
+
const encodedFunctionArgs = encodeFunctionArguments(abiFunction, csInput.unlocker.params);
|
|
375
|
+
return {
|
|
376
|
+
script: getUnlockScriptName(contract, abiFunction, inputIndex),
|
|
377
|
+
overrides: {
|
|
378
|
+
// encode values for the variables defined above in `entities` property
|
|
379
|
+
bytecode: {
|
|
380
|
+
...generateTemplateScenarioParametersFunctionIndex(abiFunction, contract.artifact.abi),
|
|
381
|
+
...generateTemplateScenarioParametersValues(abiFunction.inputs, encodedFunctionArgs),
|
|
382
|
+
...generateTemplateScenarioParametersValues(contract.artifact.constructorInputs, contract.encodedConstructorArgs),
|
|
383
|
+
},
|
|
384
|
+
keys: {
|
|
385
|
+
privateKeys: generateTemplateScenarioKeys(abiFunction.inputs, encodedFunctionArgs),
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
const generateTemplateScenarioParametersValues = (types, encodedArgs) => {
|
|
391
|
+
const typesAndArguments = zip(types, encodedArgs);
|
|
392
|
+
const entries = typesAndArguments
|
|
393
|
+
// SignatureTemplates are handled by the 'keys' object in the scenario
|
|
394
|
+
.filter(([, arg]) => !(arg instanceof SignatureTemplate))
|
|
395
|
+
.map(([input, arg]) => {
|
|
396
|
+
const encodedArgumentHex = binToHex(arg);
|
|
397
|
+
const prefixedEncodedArgument = addHexPrefixExceptEmpty(encodedArgumentHex);
|
|
398
|
+
return [input.name, prefixedEncodedArgument];
|
|
399
|
+
});
|
|
400
|
+
return Object.fromEntries(entries);
|
|
401
|
+
};
|
|
402
|
+
const generateTemplateScenarioKeys = (types, encodedArgs) => {
|
|
403
|
+
const typesAndArguments = zip(types, encodedArgs);
|
|
404
|
+
const entries = typesAndArguments
|
|
405
|
+
.filter(([, arg]) => arg instanceof SignatureTemplate)
|
|
406
|
+
.map(([input, arg]) => [input.name, binToHex(arg.privateKey)]);
|
|
407
|
+
return Object.fromEntries(entries);
|
|
408
|
+
};
|
|
409
|
+
// Used for generating the locking / unlocking bytecode for source outputs and inputs
|
|
410
|
+
const generateTemplateScenarioBytecode = (input, libauthInput, inputIndex, p2pkhScriptNameTemplate, insertSlot) => {
|
|
411
|
+
if (insertSlot)
|
|
412
|
+
return ['slot'];
|
|
413
|
+
if (isUnlockableUtxo(input) && isStandardUnlockableUtxo(input)) {
|
|
414
|
+
return generateUnlockingScriptParams(input, libauthInput, p2pkhScriptNameTemplate, inputIndex);
|
|
415
|
+
}
|
|
416
|
+
// 'slot' means that we are currently evaluating this specific input,
|
|
417
|
+
// {} means that it is the same script type, but not being evaluated
|
|
418
|
+
return {};
|
|
419
|
+
};
|
|
420
|
+
const generateTemplateScenarioTransactionOutputLockingBytecode = (csOutput, contract) => {
|
|
421
|
+
if (csOutput.to instanceof Uint8Array)
|
|
422
|
+
return binToHex(csOutput.to);
|
|
423
|
+
if ([contract.address, contract.tokenAddress].includes(csOutput.to))
|
|
424
|
+
return {};
|
|
425
|
+
return binToHex(addressToLockScript(csOutput.to));
|
|
426
|
+
};
|
|
427
|
+
const generateTemplateScenarioParametersFunctionIndex = (abiFunction, abi) => {
|
|
428
|
+
const functionIndex = abi.length > 1
|
|
429
|
+
? abi.findIndex((func) => func.name === abiFunction.name)
|
|
430
|
+
: undefined;
|
|
431
|
+
return functionIndex !== undefined ? { function_index: functionIndex.toString() } : {};
|
|
432
|
+
};
|
|
433
|
+
const generateFunctionIndexTemplateVariable = (abi) => {
|
|
434
|
+
if (abi.length > 1) {
|
|
435
|
+
return {
|
|
436
|
+
function_index: {
|
|
437
|
+
description: 'Script function index to execute',
|
|
438
|
+
name: 'function_index',
|
|
439
|
+
type: 'WalletData',
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
return {};
|
|
444
|
+
};
|
|
445
|
+
//# sourceMappingURL=LibauthTemplate.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AbiFunction, AbiInput, Artifact } from '@cashscript/utils';
|
|
2
|
+
import { HashType, LibauthTokenDetails, SignatureAlgorithm, TokenDetails } from '../interfaces.js';
|
|
3
|
+
import { type WalletTemplateScenarioBytecode, Input } from '@bitauth/libauth';
|
|
4
|
+
import { EncodedFunctionArgument } from '../Argument.js';
|
|
5
|
+
import { Contract } from '../Contract.js';
|
|
6
|
+
export declare const getLockScriptName: (contract: Contract) => string;
|
|
7
|
+
export declare const getUnlockScriptName: (contract: Contract, abiFunction: AbiFunction, inputIndex: number) => string;
|
|
8
|
+
export declare const getSignatureAlgorithmName: (signatureAlgorithm: SignatureAlgorithm) => string;
|
|
9
|
+
export declare const getHashTypeName: (hashType: HashType) => string;
|
|
10
|
+
export declare const addHexPrefixExceptEmpty: (value: string) => string;
|
|
11
|
+
export declare const formatParametersForDebugging: (types: readonly AbiInput[], args: EncodedFunctionArgument[]) => string;
|
|
12
|
+
export declare const formatBytecodeForDebugging: (artifact: Artifact) => string;
|
|
13
|
+
export declare const serialiseTokenDetails: (token?: TokenDetails | LibauthTokenDetails) => LibauthTemplateTokenDetails | undefined;
|
|
14
|
+
interface LibauthTemplateTokenDetails {
|
|
15
|
+
amount: string;
|
|
16
|
+
category: string;
|
|
17
|
+
nft?: {
|
|
18
|
+
capability: 'none' | 'mutable' | 'minting';
|
|
19
|
+
commitment: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare const lockingBytecodeIsSetToSlot: (lockingBytecode?: WalletTemplateScenarioBytecode | ["slot"]) => boolean;
|
|
23
|
+
export declare const getSignatureAndPubkeyFromP2PKHInput: (libauthInput: Input) => {
|
|
24
|
+
signature: Uint8Array;
|
|
25
|
+
publicKey: Uint8Array;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { bytecodeToScript, formatBitAuthScript } from '@cashscript/utils';
|
|
2
|
+
import { HashType, SignatureAlgorithm } from '../interfaces.js';
|
|
3
|
+
import { hexToBin, binToHex, isHex, decodeCashAddress, assertSuccess, decodeAuthenticationInstructions } from '@bitauth/libauth';
|
|
4
|
+
import { zip } from '../utils.js';
|
|
5
|
+
import SignatureTemplate from '../SignatureTemplate.js';
|
|
6
|
+
export const getLockScriptName = (contract) => {
|
|
7
|
+
const result = decodeCashAddress(contract.address);
|
|
8
|
+
if (typeof result === 'string')
|
|
9
|
+
throw new Error(result);
|
|
10
|
+
return `${contract.artifact.contractName}_${binToHex(result.payload)}_lock`;
|
|
11
|
+
};
|
|
12
|
+
export const getUnlockScriptName = (contract, abiFunction, inputIndex) => {
|
|
13
|
+
return `${contract.artifact.contractName}_${abiFunction.name}_input${inputIndex}_unlock`;
|
|
14
|
+
};
|
|
15
|
+
export const getSignatureAlgorithmName = (signatureAlgorithm) => {
|
|
16
|
+
const signatureAlgorithmNames = {
|
|
17
|
+
[SignatureAlgorithm.SCHNORR]: 'schnorr_signature',
|
|
18
|
+
[SignatureAlgorithm.ECDSA]: 'ecdsa_signature',
|
|
19
|
+
};
|
|
20
|
+
return signatureAlgorithmNames[signatureAlgorithm];
|
|
21
|
+
};
|
|
22
|
+
export const getHashTypeName = (hashType) => {
|
|
23
|
+
const hashtypeNames = {
|
|
24
|
+
[HashType.SIGHASH_ALL]: 'all_outputs',
|
|
25
|
+
[HashType.SIGHASH_ALL | HashType.SIGHASH_ANYONECANPAY]: 'all_outputs_single_input',
|
|
26
|
+
[HashType.SIGHASH_ALL | HashType.SIGHASH_UTXOS]: 'all_outputs_all_utxos',
|
|
27
|
+
[HashType.SIGHASH_ALL | HashType.SIGHASH_ANYONECANPAY | HashType.SIGHASH_UTXOS]: 'all_outputs_single_input_INVALID_all_utxos',
|
|
28
|
+
[HashType.SIGHASH_SINGLE]: 'corresponding_output',
|
|
29
|
+
[HashType.SIGHASH_SINGLE | HashType.SIGHASH_ANYONECANPAY]: 'corresponding_output_single_input',
|
|
30
|
+
[HashType.SIGHASH_SINGLE | HashType.SIGHASH_UTXOS]: 'corresponding_output_all_utxos',
|
|
31
|
+
[HashType.SIGHASH_SINGLE | HashType.SIGHASH_ANYONECANPAY | HashType.SIGHASH_UTXOS]: 'corresponding_output_single_input_INVALID_all_utxos',
|
|
32
|
+
[HashType.SIGHASH_NONE]: 'no_outputs',
|
|
33
|
+
[HashType.SIGHASH_NONE | HashType.SIGHASH_ANYONECANPAY]: 'no_outputs_single_input',
|
|
34
|
+
[HashType.SIGHASH_NONE | HashType.SIGHASH_UTXOS]: 'no_outputs_all_utxos',
|
|
35
|
+
[HashType.SIGHASH_NONE | HashType.SIGHASH_ANYONECANPAY | HashType.SIGHASH_UTXOS]: 'no_outputs_single_input_INVALID_all_utxos',
|
|
36
|
+
};
|
|
37
|
+
return hashtypeNames[hashType];
|
|
38
|
+
};
|
|
39
|
+
export const addHexPrefixExceptEmpty = (value) => {
|
|
40
|
+
return value.length > 0 ? `0x${value}` : '';
|
|
41
|
+
};
|
|
42
|
+
export const formatParametersForDebugging = (types, args) => {
|
|
43
|
+
if (types.length === 0)
|
|
44
|
+
return '// none';
|
|
45
|
+
// We reverse the arguments because the order of the arguments in the bytecode is reversed
|
|
46
|
+
const typesAndArguments = zip(types, args).reverse();
|
|
47
|
+
return typesAndArguments.map(([input, arg]) => {
|
|
48
|
+
if (arg instanceof SignatureTemplate) {
|
|
49
|
+
const signatureAlgorithmName = getSignatureAlgorithmName(arg.getSignatureAlgorithm());
|
|
50
|
+
const hashtypeName = getHashTypeName(arg.getHashType(false));
|
|
51
|
+
return `<${input.name}.${signatureAlgorithmName}.${hashtypeName}> // ${input.type}`;
|
|
52
|
+
}
|
|
53
|
+
const typeStr = input.type === 'bytes' ? `bytes${arg.length}` : input.type;
|
|
54
|
+
// we output these values as pushdata, comment will contain the type and the value of the variable
|
|
55
|
+
// e.g. <timeout> // int = <0xa08601>
|
|
56
|
+
return `<${input.name}> // ${typeStr} = <${`0x${binToHex(arg)}`}>`;
|
|
57
|
+
}).join('\n');
|
|
58
|
+
};
|
|
59
|
+
export const formatBytecodeForDebugging = (artifact) => {
|
|
60
|
+
if (!artifact.debug) {
|
|
61
|
+
return artifact.bytecode
|
|
62
|
+
.split(' ')
|
|
63
|
+
.map((asmElement) => (isHex(asmElement) ? `<0x${asmElement}>` : asmElement))
|
|
64
|
+
.join('\n');
|
|
65
|
+
}
|
|
66
|
+
return formatBitAuthScript(bytecodeToScript(hexToBin(artifact.debug.bytecode)), artifact.debug.sourceMap, artifact.source);
|
|
67
|
+
};
|
|
68
|
+
export const serialiseTokenDetails = (token) => {
|
|
69
|
+
if (!token)
|
|
70
|
+
return undefined;
|
|
71
|
+
return {
|
|
72
|
+
amount: token.amount.toString(),
|
|
73
|
+
category: token.category instanceof Uint8Array ? binToHex(token.category) : token.category,
|
|
74
|
+
nft: token.nft ? {
|
|
75
|
+
capability: token.nft.capability,
|
|
76
|
+
commitment: token.nft.commitment instanceof Uint8Array ? binToHex(token.nft.commitment) : token.nft.commitment,
|
|
77
|
+
} : undefined,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
export const lockingBytecodeIsSetToSlot = (lockingBytecode) => {
|
|
81
|
+
return Array.isArray(lockingBytecode) && lockingBytecode.length === 1 && lockingBytecode[0] === 'slot';
|
|
82
|
+
};
|
|
83
|
+
export const getSignatureAndPubkeyFromP2PKHInput = (libauthInput) => {
|
|
84
|
+
const inputData = (assertSuccess(decodeAuthenticationInstructions(libauthInput.unlockingBytecode)));
|
|
85
|
+
const signature = inputData[0].data;
|
|
86
|
+
const publicKey = inputData[1].data;
|
|
87
|
+
return { signature, publicKey };
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Utxo, Network } from '../interfaces.js';
|
|
1
|
+
import { Utxo, Network, VmTarget } from '../interfaces.js';
|
|
2
2
|
import NetworkProvider from './NetworkProvider.js';
|
|
3
|
-
interface MockNetworkProviderOptions {
|
|
3
|
+
export interface MockNetworkProviderOptions {
|
|
4
4
|
updateUtxoSet: boolean;
|
|
5
|
+
vmTarget?: VmTarget;
|
|
5
6
|
}
|
|
6
7
|
export default class MockNetworkProvider implements NetworkProvider {
|
|
7
8
|
private utxoSet;
|
|
@@ -9,6 +10,7 @@ export default class MockNetworkProvider implements NetworkProvider {
|
|
|
9
10
|
network: Network;
|
|
10
11
|
blockHeight: number;
|
|
11
12
|
options: MockNetworkProviderOptions;
|
|
13
|
+
vmTarget: VmTarget;
|
|
12
14
|
constructor(options?: Partial<MockNetworkProviderOptions>);
|
|
13
15
|
getUtxos(address: string): Promise<Utxo[]>;
|
|
14
16
|
setBlockHeight(newBlockHeight: number): void;
|
|
@@ -18,4 +20,3 @@ export default class MockNetworkProvider implements NetworkProvider {
|
|
|
18
20
|
addUtxo(addressOrLockingBytecode: string, utxo: Utxo): void;
|
|
19
21
|
reset(): void;
|
|
20
22
|
}
|
|
21
|
-
export {};
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { binToHex, decodeTransactionUnsafe, hexToBin, isHex } from '@bitauth/libauth';
|
|
2
2
|
import { sha256 } from '@cashscript/utils';
|
|
3
|
-
import { Network } from '../interfaces.js';
|
|
4
|
-
import { addressToLockScript, libauthTokenDetailsToCashScriptTokenDetails
|
|
5
|
-
// redeclare the addresses from vars.ts instead of importing them
|
|
6
|
-
const aliceAddress = 'bchtest:qpgjmwev3spwlwkgmyjrr2s2cvlkkzlewq62mzgjnp';
|
|
7
|
-
const bobAddress = 'bchtest:qz6q5gqnxdldkr07xpls5474mmzmlesd6qnux4skuc';
|
|
8
|
-
const carolAddress = 'bchtest:qqsr7nqwe6rq5crj63gy5gdqchpnwmguusmr7tfmsj';
|
|
9
|
-
// We are setting the default updateUtxoSet to 'false' so that it doesn't break the current behaviour
|
|
10
|
-
// TODO: in a future breaking release we want to set this to 'true' by default
|
|
3
|
+
import { Network, VmTarget } from '../interfaces.js';
|
|
4
|
+
import { addressToLockScript, libauthTokenDetailsToCashScriptTokenDetails } from '../utils.js';
|
|
11
5
|
export default class MockNetworkProvider {
|
|
12
6
|
constructor(options) {
|
|
13
7
|
// we use lockingBytecode hex as the key for utxoMap to make cash addresses and token addresses interchangeable
|
|
@@ -15,12 +9,8 @@ export default class MockNetworkProvider {
|
|
|
15
9
|
this.transactionMap = {};
|
|
16
10
|
this.network = Network.MOCKNET;
|
|
17
11
|
this.blockHeight = 133700;
|
|
18
|
-
this.options = { updateUtxoSet:
|
|
19
|
-
|
|
20
|
-
this.addUtxo(aliceAddress, randomUtxo());
|
|
21
|
-
this.addUtxo(bobAddress, randomUtxo());
|
|
22
|
-
this.addUtxo(carolAddress, randomUtxo());
|
|
23
|
-
}
|
|
12
|
+
this.options = { updateUtxoSet: true, ...options };
|
|
13
|
+
this.vmTarget = this.options.vmTarget ?? VmTarget.BCH_2025_05;
|
|
24
14
|
}
|
|
25
15
|
async getUtxos(address) {
|
|
26
16
|
const addressLockingBytecode = binToHex(addressToLockScript(address));
|