cashscript 0.9.2 → 0.10.0-next.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/dist/{Contract.d.ts → src/Contract.d.ts} +3 -2
- package/dist/{Contract.js → src/Contract.js} +1 -0
- package/dist/{Errors.d.ts → src/Errors.d.ts} +2 -2
- package/dist/{Errors.js → src/Errors.js} +3 -3
- package/dist/src/LibauthTemplate.d.ts +12 -0
- package/dist/src/LibauthTemplate.js +472 -0
- package/dist/{SignatureTemplate.d.ts → src/SignatureTemplate.d.ts} +1 -1
- package/dist/{Transaction.d.ts → src/Transaction.d.ts} +9 -7
- package/dist/{Transaction.js → src/Transaction.js} +47 -8
- package/dist/{index.d.ts → src/index.d.ts} +2 -1
- package/dist/{index.js → src/index.js} +2 -1
- package/dist/{interfaces.d.ts → src/interfaces.d.ts} +1 -1
- package/dist/src/network/MockNetworkProvider.d.ts +14 -0
- package/dist/src/network/MockNetworkProvider.js +46 -0
- package/dist/{network → src/network}/index.d.ts +1 -0
- package/dist/{network → src/network}/index.js +1 -0
- package/dist/{utils.d.ts → src/utils.d.ts} +6 -3
- package/dist/{utils.js → src/utils.js} +41 -14
- package/dist/test/JestExtensions.d.ts +9 -0
- package/dist/test/JestExtensions.js +66 -0
- package/package.json +8 -6
- /package/dist/{Argument.d.ts → src/Argument.d.ts} +0 -0
- /package/dist/{Argument.js → src/Argument.js} +0 -0
- /package/dist/{SignatureTemplate.js → src/SignatureTemplate.js} +0 -0
- /package/dist/{TransactionBuilder.d.ts → src/TransactionBuilder.d.ts} +0 -0
- /package/dist/{TransactionBuilder.js → src/TransactionBuilder.js} +0 -0
- /package/dist/{constants.d.ts → src/constants.d.ts} +0 -0
- /package/dist/{constants.js → src/constants.js} +0 -0
- /package/dist/{interfaces.js → src/interfaces.js} +0 -0
- /package/dist/{network → src/network}/BitcoinRpcNetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/BitcoinRpcNetworkProvider.js +0 -0
- /package/dist/{network → src/network}/ElectrumNetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/ElectrumNetworkProvider.js +0 -0
- /package/dist/{network → src/network}/FullStackNetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/FullStackNetworkProvider.js +0 -0
- /package/dist/{network → src/network}/NetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/NetworkProvider.js +0 -0
|
@@ -4,7 +4,8 @@ import { Argument } from './Argument.js';
|
|
|
4
4
|
import { Unlocker, ContractOptions, Utxo } from './interfaces.js';
|
|
5
5
|
import NetworkProvider from './network/NetworkProvider.js';
|
|
6
6
|
export declare class Contract {
|
|
7
|
-
|
|
7
|
+
artifact: Artifact;
|
|
8
|
+
constructorArgs: Argument[];
|
|
8
9
|
private options?;
|
|
9
10
|
name: string;
|
|
10
11
|
address: string;
|
|
@@ -16,7 +17,7 @@ export declare class Contract {
|
|
|
16
17
|
unlock: Record<string, ContractUnlocker>;
|
|
17
18
|
redeemScript: Script;
|
|
18
19
|
provider: NetworkProvider;
|
|
19
|
-
|
|
20
|
+
addressType: 'p2sh20' | 'p2sh32';
|
|
20
21
|
constructor(artifact: Artifact, constructorArgs: Argument[], options?: ContractOptions | undefined);
|
|
21
22
|
getBalance(): Promise<bigint>;
|
|
22
23
|
getUtxos(): Promise<Utxo[]>;
|
|
@@ -8,6 +8,7 @@ import { ElectrumNetworkProvider } from './network/index.js';
|
|
|
8
8
|
export class Contract {
|
|
9
9
|
constructor(artifact, constructorArgs, options) {
|
|
10
10
|
this.artifact = artifact;
|
|
11
|
+
this.constructorArgs = constructorArgs;
|
|
11
12
|
this.options = options;
|
|
12
13
|
this.provider = this.options?.provider ?? new ElectrumNetworkProvider();
|
|
13
14
|
this.addressType = this.options?.addressType ?? 'p2sh32';
|
|
@@ -10,8 +10,8 @@ export declare class TokensToNonTokenAddressError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
export declare class FailedTransactionError extends Error {
|
|
12
12
|
reason: string;
|
|
13
|
-
|
|
14
|
-
constructor(reason: string,
|
|
13
|
+
debugStr?: string | undefined;
|
|
14
|
+
constructor(reason: string, debugStr?: string | undefined);
|
|
15
15
|
}
|
|
16
16
|
export declare class FailedRequireError extends FailedTransactionError {
|
|
17
17
|
}
|
|
@@ -14,10 +14,10 @@ export class TokensToNonTokenAddressError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
export class FailedTransactionError extends Error {
|
|
17
|
-
constructor(reason,
|
|
18
|
-
super(`Transaction failed with reason: ${reason}${
|
|
17
|
+
constructor(reason, debugStr) {
|
|
18
|
+
super(`Transaction failed with reason: ${reason}${debugStr ? `\n\n${debugStr}` : ''}`);
|
|
19
19
|
this.reason = reason;
|
|
20
|
-
this.
|
|
20
|
+
this.debugStr = debugStr;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
export class FailedRequireError extends FailedTransactionError {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Artifact } from '@cashscript/utils';
|
|
2
|
+
import { AuthenticationTemplate, AuthenticationProgramStateBCHCHIPs } from '@bitauth/libauth';
|
|
3
|
+
import { Transaction } from './Transaction.js';
|
|
4
|
+
export declare const stringify: (any: any, spaces?: number | undefined) => string;
|
|
5
|
+
export declare const buildTemplate: ({ transaction, transactionHex, }: {
|
|
6
|
+
transaction: Transaction;
|
|
7
|
+
transactionHex?: string | undefined;
|
|
8
|
+
}) => Promise<AuthenticationTemplate>;
|
|
9
|
+
export declare const getBitauthUri: (template: AuthenticationTemplate) => string;
|
|
10
|
+
export declare const evaluateTemplate: (template: AuthenticationTemplate) => boolean;
|
|
11
|
+
export declare type DebugResult = AuthenticationProgramStateBCHCHIPs[];
|
|
12
|
+
export declare const debugTemplate: (template: AuthenticationTemplate, artifact: Artifact) => DebugResult;
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { Op, PrimitiveType, bytecodeToScript, decodeBool, decodeInt, decodeString, formatLibauthScript, } from '@cashscript/utils';
|
|
2
|
+
import { hash160, hexToBin, decodeTransaction, binToHex, authenticationTemplateToCompilerConfiguration, createCompiler, createVirtualMachineBCHCHIPs, binToBase64, utf8ToBin, isHex, AuthenticationErrorCommon, } from '@bitauth/libauth';
|
|
3
|
+
import { deflate } from 'pako';
|
|
4
|
+
import { isUtxoP2PKH, } from '../src/interfaces.js';
|
|
5
|
+
import { encodeArgument as csEncodeArgument } from './Argument.js';
|
|
6
|
+
import { toRegExp } from './utils.js';
|
|
7
|
+
// all bitauth variables must be in snake case
|
|
8
|
+
const snakeCase = (str) => (str
|
|
9
|
+
&& str
|
|
10
|
+
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
|
|
11
|
+
.map((s) => s.toLowerCase())
|
|
12
|
+
.join('_'));
|
|
13
|
+
const merge = (array) => array.reduce((prev, cur) => ({
|
|
14
|
+
...prev,
|
|
15
|
+
...{ [Object.keys(cur)[0]]: cur[Object.keys(cur)[0]] },
|
|
16
|
+
}), {});
|
|
17
|
+
const encodeArgument = (argument, typeStr) => {
|
|
18
|
+
if (typeStr === PrimitiveType.INT && argument === 0n) {
|
|
19
|
+
return Uint8Array.from([0]);
|
|
20
|
+
}
|
|
21
|
+
return csEncodeArgument(argument, typeStr);
|
|
22
|
+
};
|
|
23
|
+
// stringify version which can serialize otherwise unsupported types
|
|
24
|
+
export const stringify = (any, spaces) => JSON.stringify(any, (_, v) => {
|
|
25
|
+
if (typeof v === 'bigint') {
|
|
26
|
+
return `${v.toString()}`;
|
|
27
|
+
}
|
|
28
|
+
if (v instanceof Uint8Array) {
|
|
29
|
+
return `${binToHex(v)}`;
|
|
30
|
+
}
|
|
31
|
+
return v;
|
|
32
|
+
}, spaces);
|
|
33
|
+
const zip = (a, b) => Array.from(Array(Math.max(b.length, a.length)), (_, i) => [a[i], b[i]]);
|
|
34
|
+
const createScenarioTransaction = (libauthTransaction, csTransaction) => {
|
|
35
|
+
const contract = csTransaction.contract;
|
|
36
|
+
const result = {};
|
|
37
|
+
// only one 'slot' is allowed, otherwise {} must be used
|
|
38
|
+
let inputSlotInserted = false;
|
|
39
|
+
result.inputs = libauthTransaction.inputs.map((input, index) => {
|
|
40
|
+
const csInput = csTransaction.inputs[index];
|
|
41
|
+
const signable = isUtxoP2PKH(csInput);
|
|
42
|
+
let unlockingBytecode = {};
|
|
43
|
+
if (signable) {
|
|
44
|
+
unlockingBytecode = {
|
|
45
|
+
script: 'p2pkh_placeholder_unlock',
|
|
46
|
+
overrides: {
|
|
47
|
+
keys: {
|
|
48
|
+
privateKeys: {
|
|
49
|
+
placeholder_key: binToHex(csInput.template.privateKey),
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// assume it is our contract's input
|
|
57
|
+
// eslint-disable-next-line
|
|
58
|
+
if (!inputSlotInserted) {
|
|
59
|
+
unlockingBytecode = ['slot'];
|
|
60
|
+
inputSlotInserted = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
outpointIndex: input.outpointIndex,
|
|
65
|
+
outpointTransactionHash: input.outpointTransactionHash instanceof Uint8Array
|
|
66
|
+
? binToHex(input.outpointTransactionHash)
|
|
67
|
+
: input.outpointTransactionHash,
|
|
68
|
+
sequenceNumber: input.sequenceNumber,
|
|
69
|
+
unlockingBytecode,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
result.locktime = libauthTransaction.locktime;
|
|
73
|
+
result.outputs = libauthTransaction.outputs.map((output, index) => {
|
|
74
|
+
const csOutput = csTransaction.outputs[index];
|
|
75
|
+
let { lockingBytecode } = output;
|
|
76
|
+
if (typeof csOutput.to === 'string') {
|
|
77
|
+
if ([
|
|
78
|
+
contract.address,
|
|
79
|
+
contract.tokenAddress,
|
|
80
|
+
].includes(csOutput.to)) {
|
|
81
|
+
lockingBytecode = {};
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
for (const csInput of csTransaction.inputs) {
|
|
85
|
+
if (isUtxoP2PKH(csInput)) {
|
|
86
|
+
const inputPkh = hash160(csInput.template.getPublicKey());
|
|
87
|
+
if (binToHex(output.lockingBytecode).slice(6, 46)
|
|
88
|
+
=== binToHex(inputPkh)) {
|
|
89
|
+
lockingBytecode = {
|
|
90
|
+
script: 'p2pkh_placeholder_lock',
|
|
91
|
+
overrides: {
|
|
92
|
+
keys: {
|
|
93
|
+
privateKeys: {
|
|
94
|
+
placeholder_key: binToHex(csInput.template.privateKey),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
lockingBytecode: lockingBytecode instanceof Uint8Array
|
|
106
|
+
? binToHex(lockingBytecode)
|
|
107
|
+
: lockingBytecode,
|
|
108
|
+
token: output.token,
|
|
109
|
+
valueSatoshis: Number(output.valueSatoshis),
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
result.version = libauthTransaction.version;
|
|
113
|
+
return result;
|
|
114
|
+
};
|
|
115
|
+
const createScenarioSourceOutputs = (csTransaction) => {
|
|
116
|
+
// only one 'slot' is allowed, otherwise {} must be used
|
|
117
|
+
let inputSlotInserted = false;
|
|
118
|
+
return csTransaction.inputs.map((csInput) => {
|
|
119
|
+
const signable = isUtxoP2PKH(csInput);
|
|
120
|
+
let lockingBytecode = {};
|
|
121
|
+
if (signable) {
|
|
122
|
+
lockingBytecode = {
|
|
123
|
+
script: 'p2pkh_placeholder_lock',
|
|
124
|
+
overrides: {
|
|
125
|
+
keys: {
|
|
126
|
+
privateKeys: {
|
|
127
|
+
placeholder_key: binToHex(csInput.template.privateKey),
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// assume it is our contract's input
|
|
135
|
+
// eslint-disable-next-line
|
|
136
|
+
if (!inputSlotInserted) {
|
|
137
|
+
lockingBytecode = ['slot'];
|
|
138
|
+
inputSlotInserted = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const result = {
|
|
142
|
+
lockingBytecode: lockingBytecode,
|
|
143
|
+
valueSatoshis: Number(csInput.satoshis),
|
|
144
|
+
};
|
|
145
|
+
if (csInput.token) {
|
|
146
|
+
result.token = {
|
|
147
|
+
amount: csInput.token.amount.toString(),
|
|
148
|
+
category: csInput.token.category,
|
|
149
|
+
};
|
|
150
|
+
if (csInput.token.nft) {
|
|
151
|
+
result.token.nft = {
|
|
152
|
+
capability: csInput.token.nft.capability,
|
|
153
|
+
commitment: csInput.token.nft.commitment
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
export const buildTemplate = async ({ transaction, transactionHex = undefined, // set this argument to prevent unnecessary call `transaction.build()`
|
|
161
|
+
}) => {
|
|
162
|
+
const contract = transaction.contract;
|
|
163
|
+
const txHex = transactionHex ?? await transaction.build();
|
|
164
|
+
const libauthTransaction = decodeTransaction(hexToBin(txHex));
|
|
165
|
+
if (typeof libauthTransaction === 'string') {
|
|
166
|
+
throw Error(libauthTransaction);
|
|
167
|
+
}
|
|
168
|
+
const constructorInputs = contract.artifact.constructorInputs
|
|
169
|
+
.slice()
|
|
170
|
+
.reverse();
|
|
171
|
+
const contractParameters = contract.constructorArgs.slice().reverse();
|
|
172
|
+
const abiFunction = transaction.abiFunction;
|
|
173
|
+
const funcName = abiFunction.name;
|
|
174
|
+
const functionIndex = contract.artifact.abi.findIndex((func) => func.name === funcName);
|
|
175
|
+
const func = contract.artifact.abi[functionIndex];
|
|
176
|
+
const functionInputs = func.inputs.slice().reverse();
|
|
177
|
+
const args = transaction.args.slice().reverse();
|
|
178
|
+
const hasSignatureTemplates = transaction.inputs.filter((input) => isUtxoP2PKH(input)).length;
|
|
179
|
+
const formattedBytecode = contract.artifact.debug
|
|
180
|
+
? formatLibauthScript(bytecodeToScript(hexToBin(contract.artifact.debug.bytecode)), contract.artifact.debug.sourceMap, contract.artifact.source).split('\n')
|
|
181
|
+
: contract.artifact.bytecode.split(' ').map((asmElement) => {
|
|
182
|
+
if (isHex(asmElement)) {
|
|
183
|
+
return `<0x${asmElement}>`;
|
|
184
|
+
}
|
|
185
|
+
return asmElement;
|
|
186
|
+
});
|
|
187
|
+
const template = {
|
|
188
|
+
$schema: 'https://ide.bitauth.com/authentication-template-v0.schema.json',
|
|
189
|
+
description: `Imported from cashscript`,
|
|
190
|
+
name: contract.artifact.contractName,
|
|
191
|
+
supported: ['BCH_SPEC'],
|
|
192
|
+
version: 0,
|
|
193
|
+
};
|
|
194
|
+
// declaration of template variables and their types
|
|
195
|
+
template.entities = {
|
|
196
|
+
parameters: {
|
|
197
|
+
description: 'Contract creation and function parameters',
|
|
198
|
+
name: 'parameters',
|
|
199
|
+
scripts: [
|
|
200
|
+
'lock',
|
|
201
|
+
'unlock_lock',
|
|
202
|
+
],
|
|
203
|
+
variables: merge([
|
|
204
|
+
...functionInputs.map((input) => ({
|
|
205
|
+
[snakeCase(input.name)]: {
|
|
206
|
+
description: `"${input.name}" parameter of function "${func.name}"`,
|
|
207
|
+
name: input.name,
|
|
208
|
+
type: input.type === PrimitiveType.SIG ? 'Key' : 'WalletData',
|
|
209
|
+
},
|
|
210
|
+
})),
|
|
211
|
+
{
|
|
212
|
+
function_index: {
|
|
213
|
+
description: 'Script function index to execute',
|
|
214
|
+
name: 'function_index',
|
|
215
|
+
type: 'WalletData',
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
...constructorInputs.map((input) => ({
|
|
219
|
+
[snakeCase(input.name)]: {
|
|
220
|
+
description: `"${input.name}" parameter of this contract`,
|
|
221
|
+
name: input.name,
|
|
222
|
+
type: 'WalletData',
|
|
223
|
+
},
|
|
224
|
+
})),
|
|
225
|
+
]),
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
// add extra variables for the p2pkh utxos spent together with our contract
|
|
229
|
+
if (hasSignatureTemplates) {
|
|
230
|
+
template.entities.parameters.scripts.push('p2pkh_placeholder_lock', 'p2pkh_placeholder_unlock');
|
|
231
|
+
template.entities.parameters.variables = {
|
|
232
|
+
...template.entities.parameters.variables,
|
|
233
|
+
placeholder_key: {
|
|
234
|
+
description: 'placeholder_key',
|
|
235
|
+
name: 'placeholder_key',
|
|
236
|
+
type: 'Key',
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
template.scenarios = {
|
|
241
|
+
// single scenario to spend out transaction under test given the CashScript parameters provided
|
|
242
|
+
evaluate_function: {
|
|
243
|
+
name: 'Evaluate',
|
|
244
|
+
description: 'An example evaluation where this script execution passes.',
|
|
245
|
+
data: {
|
|
246
|
+
// encode values for the variables defined above in `entities` property
|
|
247
|
+
bytecode: merge([
|
|
248
|
+
...zip(functionInputs, args)
|
|
249
|
+
.filter(([input]) => input.type !== PrimitiveType.SIG)
|
|
250
|
+
.map(([input, arg]) => {
|
|
251
|
+
const hex = binToHex(arg);
|
|
252
|
+
const result = hex.length ? `0x${hex}` : hex;
|
|
253
|
+
return {
|
|
254
|
+
[snakeCase(input.name)]: result,
|
|
255
|
+
};
|
|
256
|
+
}),
|
|
257
|
+
{ function_index: functionIndex.toString() },
|
|
258
|
+
...constructorInputs.map((input, index) => {
|
|
259
|
+
const hex = binToHex(encodeArgument(contractParameters[index], constructorInputs[index].type));
|
|
260
|
+
const result = hex.length ? `0x${hex}` : hex;
|
|
261
|
+
return {
|
|
262
|
+
[snakeCase(input.name)]: result,
|
|
263
|
+
};
|
|
264
|
+
}),
|
|
265
|
+
]),
|
|
266
|
+
currentBlockHeight: 2,
|
|
267
|
+
currentBlockTime: Math.round(+new Date() / 1000),
|
|
268
|
+
keys: {
|
|
269
|
+
privateKeys: merge([
|
|
270
|
+
...zip(functionInputs, args)
|
|
271
|
+
.filter(([input]) => input.type === PrimitiveType.SIG)
|
|
272
|
+
.map(([input, arg]) => ({
|
|
273
|
+
[snakeCase(input.name)]: binToHex(arg.privateKey),
|
|
274
|
+
})),
|
|
275
|
+
...(hasSignatureTemplates
|
|
276
|
+
? [
|
|
277
|
+
{
|
|
278
|
+
// placeholder will be replaced by a key for each respective P2PKH input spent
|
|
279
|
+
placeholder_key: '<Uint8Array: 0x0000000000000000000000000000000000000000000000000000000000000000>',
|
|
280
|
+
},
|
|
281
|
+
]
|
|
282
|
+
: []),
|
|
283
|
+
]),
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
transaction: createScenarioTransaction(libauthTransaction, transaction),
|
|
287
|
+
sourceOutputs: createScenarioSourceOutputs(transaction),
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
// definition of locking scripts and unlocking scripts with their respective bytecode
|
|
291
|
+
template.scripts = {
|
|
292
|
+
unlock_lock: {
|
|
293
|
+
// this unlocking script must pass our only scenario
|
|
294
|
+
passes: ['evaluate_function'],
|
|
295
|
+
name: 'unlock',
|
|
296
|
+
// unlocking script contains the CashScript function parameters and function selector
|
|
297
|
+
// we output these values as pushdata, comment will contain the type and the value of the variable
|
|
298
|
+
// example: '<timeout> // int = <0xa08601>'
|
|
299
|
+
script: [
|
|
300
|
+
`// "${func.name}" function parameters`,
|
|
301
|
+
...(functionInputs.length
|
|
302
|
+
? zip(functionInputs, args).map(([input, arg]) => (input.type === PrimitiveType.SIG
|
|
303
|
+
? `<${snakeCase(input.name)}.schnorr_signature.all_outputs> // ${input.type}`
|
|
304
|
+
: `<${snakeCase(input.name)}> // ${input.type} = <${`0x${binToHex(arg)}`}>`))
|
|
305
|
+
: ['// none']),
|
|
306
|
+
'',
|
|
307
|
+
...(contract.artifact.abi.length > 1
|
|
308
|
+
? [
|
|
309
|
+
'// function index in contract',
|
|
310
|
+
`<function_index> // int = <${functionIndex.toString()}>`,
|
|
311
|
+
'',
|
|
312
|
+
]
|
|
313
|
+
: []),
|
|
314
|
+
].join('\n'),
|
|
315
|
+
unlocks: 'lock',
|
|
316
|
+
},
|
|
317
|
+
lock: {
|
|
318
|
+
lockingType: "p2sh20",
|
|
319
|
+
name: 'lock',
|
|
320
|
+
// locking script contains the CashScript contract parameters followed by the contract opcodes
|
|
321
|
+
// we output these values as pushdata, comment will contain the type and the value of the variable
|
|
322
|
+
// example: '<timeout> // int = <0xa08601>'
|
|
323
|
+
script: [
|
|
324
|
+
`// "${contract.artifact.contractName}" contract constructor parameters`,
|
|
325
|
+
...(constructorInputs.length
|
|
326
|
+
? constructorInputs.map((input, index) => {
|
|
327
|
+
const encoded = encodeArgument(contractParameters[index], constructorInputs[index].type);
|
|
328
|
+
return `<${snakeCase(input.name)}> // ${input.type === 'bytes' ? `bytes${encoded.length}` : input.type} = <${`0x${binToHex(encoded)}`}>`;
|
|
329
|
+
})
|
|
330
|
+
: ['// none']),
|
|
331
|
+
'',
|
|
332
|
+
'// bytecode',
|
|
333
|
+
...formattedBytecode,
|
|
334
|
+
].join('\n'),
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
// add extra unlocking and locking script for P2PKH inputs spent alongside our contract
|
|
338
|
+
// this is needed for correct cross-referrences in the template
|
|
339
|
+
if (hasSignatureTemplates) {
|
|
340
|
+
template.scripts.p2pkh_placeholder_unlock = {
|
|
341
|
+
name: 'p2pkh_placeholder_unlock',
|
|
342
|
+
script: '<placeholder_key.schnorr_signature.all_outputs>\n<placeholder_key.public_key>',
|
|
343
|
+
unlocks: 'p2pkh_placeholder_lock',
|
|
344
|
+
};
|
|
345
|
+
template.scripts.p2pkh_placeholder_lock = {
|
|
346
|
+
lockingType: 'standard',
|
|
347
|
+
name: 'p2pkh_placeholder_lock',
|
|
348
|
+
script: 'OP_DUP\nOP_HASH160 <$(<placeholder_key.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return template;
|
|
352
|
+
};
|
|
353
|
+
export const getBitauthUri = (template) => {
|
|
354
|
+
const base64toBase64Url = (base64) => base64.replace(/\+/g, '-').replace(/\//g, '_');
|
|
355
|
+
const payload = base64toBase64Url(binToBase64(deflate(utf8ToBin(stringify(template)))));
|
|
356
|
+
return `https://ide.bitauth.com/import-template/${payload}`;
|
|
357
|
+
};
|
|
358
|
+
// internal util. instantiates the virtual machine and compiles the template into a program
|
|
359
|
+
const createProgram = (template) => {
|
|
360
|
+
const configuration = authenticationTemplateToCompilerConfiguration(template);
|
|
361
|
+
const vm = createVirtualMachineBCHCHIPs();
|
|
362
|
+
const compiler = createCompiler(configuration);
|
|
363
|
+
const scenarioGeneration = compiler.generateScenario({
|
|
364
|
+
debug: true,
|
|
365
|
+
lockingScriptId: undefined,
|
|
366
|
+
unlockingScriptId: 'unlock_lock',
|
|
367
|
+
scenarioId: 'evaluate_function',
|
|
368
|
+
});
|
|
369
|
+
if (typeof scenarioGeneration === 'string'
|
|
370
|
+
|| typeof scenarioGeneration.scenario === 'string') {
|
|
371
|
+
// eslint-disable-next-line
|
|
372
|
+
throw scenarioGeneration;
|
|
373
|
+
}
|
|
374
|
+
return { vm, program: scenarioGeneration.scenario.program };
|
|
375
|
+
};
|
|
376
|
+
// evaluates the fully defined template, throws upon error
|
|
377
|
+
export const evaluateTemplate = (template) => {
|
|
378
|
+
const { vm, program } = createProgram(template);
|
|
379
|
+
const verifyResult = vm.verify(program);
|
|
380
|
+
if (typeof verifyResult === 'string') {
|
|
381
|
+
// eslint-disable-next-line
|
|
382
|
+
throw verifyResult;
|
|
383
|
+
}
|
|
384
|
+
return verifyResult;
|
|
385
|
+
};
|
|
386
|
+
// debugs the template, optionally logging the execution data
|
|
387
|
+
export const debugTemplate = (template, artifact) => {
|
|
388
|
+
const { vm, program } = createProgram(template);
|
|
389
|
+
const debugResult = vm.debug(program);
|
|
390
|
+
for (const log of artifact.debug?.logs ?? []) {
|
|
391
|
+
// there might be 2 elements with same instruction pointer, first from unllocking script, second from locking
|
|
392
|
+
const state = debugResult
|
|
393
|
+
.filter((debugState) => debugState.ip === log.ip)
|
|
394
|
+
.slice().reverse()[0];
|
|
395
|
+
if (!state) {
|
|
396
|
+
throw Error(`Instruction pointer ${log.ip} points to a non-existing state of the program`);
|
|
397
|
+
}
|
|
398
|
+
let line = `${artifact.contractName}.cash:${log.line}`;
|
|
399
|
+
log.data.forEach((element) => {
|
|
400
|
+
let value;
|
|
401
|
+
if (typeof element === 'string') {
|
|
402
|
+
value = element;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
const stackItem = state.stack.slice().reverse()[element.stackIndex];
|
|
406
|
+
if (!stackItem) {
|
|
407
|
+
throw Error(`Stack item at index ${element.stackIndex} not found at instruction pointer ${log.ip}`);
|
|
408
|
+
}
|
|
409
|
+
switch (element.type) {
|
|
410
|
+
case PrimitiveType.BOOL:
|
|
411
|
+
value = decodeBool(stackItem);
|
|
412
|
+
break;
|
|
413
|
+
case PrimitiveType.INT:
|
|
414
|
+
value = decodeInt(stackItem);
|
|
415
|
+
break;
|
|
416
|
+
case PrimitiveType.STRING:
|
|
417
|
+
value = decodeString(stackItem);
|
|
418
|
+
break;
|
|
419
|
+
default:
|
|
420
|
+
value = `0x${binToHex(stackItem)}`;
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
line += ` ${value}`;
|
|
425
|
+
});
|
|
426
|
+
// actual log, do not delete :)
|
|
427
|
+
console.log(line);
|
|
428
|
+
}
|
|
429
|
+
const lastState = debugResult[debugResult.length - 1];
|
|
430
|
+
if (lastState.error) {
|
|
431
|
+
const requireMessage = (artifact.debug?.requireMessages ?? []).filter((message) => message.ip === lastState.ip)[0];
|
|
432
|
+
if (requireMessage) {
|
|
433
|
+
// eslint-disable-next-line
|
|
434
|
+
throw `${artifact.contractName}.cash:${requireMessage.line} Error in evaluating input index ${lastState.program.inputIndex} with the following message: ${requireMessage.message}.
|
|
435
|
+
${lastState.error}`;
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
// eslint-disable-next-line
|
|
439
|
+
throw `Error in evaluating input index ${lastState.program.inputIndex}.
|
|
440
|
+
${lastState.error}`;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
// one last pass of verifications not covered by the above debugging
|
|
445
|
+
// checks for removed final verify
|
|
446
|
+
const evaluationResult = vm.verify(program);
|
|
447
|
+
if (typeof evaluationResult === 'string' && toRegExp([
|
|
448
|
+
AuthenticationErrorCommon.requiresCleanStack,
|
|
449
|
+
AuthenticationErrorCommon.nonEmptyControlStack,
|
|
450
|
+
AuthenticationErrorCommon.unsuccessfulEvaluation,
|
|
451
|
+
]).test(evaluationResult)) {
|
|
452
|
+
const stackContents = lastState.stack.map(item => `0x${binToHex(item)}`).join(', ');
|
|
453
|
+
const stackContentsMessage = `\nStack contents after evaluation: ${lastState.stack.length ? stackContents : 'empty'}`;
|
|
454
|
+
const lastMessage = artifact.debug?.requireMessages.sort((a, b) => b.ip - a.ip)[0];
|
|
455
|
+
if (!lastMessage) {
|
|
456
|
+
// eslint-disable-next-line
|
|
457
|
+
throw evaluationResult + stackContentsMessage;
|
|
458
|
+
}
|
|
459
|
+
const instructionsLeft = lastState.instructions.slice(lastMessage.ip);
|
|
460
|
+
if (instructionsLeft.length === 0
|
|
461
|
+
|| instructionsLeft.every(instruction => [Op.OP_NIP, Op.OP_ENDIF].includes(instruction.opcode))) {
|
|
462
|
+
// eslint-disable-next-line
|
|
463
|
+
throw `${artifact.contractName}.cash:${lastMessage.line} Error in evaluating input index ${lastState.program.inputIndex} with the following message: ${lastMessage.message}.
|
|
464
|
+
${evaluationResult.replace(/Error in evaluating input index \d: /, '')}` + stackContentsMessage;
|
|
465
|
+
}
|
|
466
|
+
// eslint-disable-next-line
|
|
467
|
+
throw evaluationResult + stackContentsMessage;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return debugResult;
|
|
471
|
+
};
|
|
472
|
+
//# sourceMappingURL=LibauthTemplate.js.map
|
|
@@ -2,7 +2,7 @@ import { Unlocker, HashType, SignatureAlgorithm } from './interfaces.js';
|
|
|
2
2
|
export default class SignatureTemplate {
|
|
3
3
|
private hashtype;
|
|
4
4
|
private signatureAlgorithm;
|
|
5
|
-
|
|
5
|
+
privateKey: Uint8Array;
|
|
6
6
|
constructor(signer: Keypair | Uint8Array | string, hashtype?: HashType, signatureAlgorithm?: SignatureAlgorithm);
|
|
7
7
|
generateSignature(payload: Uint8Array, bchForkId?: boolean): Uint8Array;
|
|
8
8
|
getHashType(bchForkId?: boolean): number;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { AbiFunction } from '@cashscript/utils';
|
|
2
|
-
import { Utxo, Recipient, TokenDetails, TransactionDetails, Unlocker } from './interfaces.js';
|
|
2
|
+
import { Utxo, Output, Recipient, TokenDetails, TransactionDetails, Unlocker } from './interfaces.js';
|
|
3
3
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
4
4
|
import { Contract } from './Contract.js';
|
|
5
|
+
import { DebugResult } from './LibauthTemplate.js';
|
|
5
6
|
export declare class Transaction {
|
|
6
|
-
|
|
7
|
+
contract: Contract;
|
|
7
8
|
private unlocker;
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
abiFunction: AbiFunction;
|
|
10
|
+
args: (Uint8Array | SignatureTemplate)[];
|
|
10
11
|
private selector?;
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
inputs: Utxo[];
|
|
13
|
+
outputs: Output[];
|
|
13
14
|
private sequence;
|
|
14
15
|
private locktime;
|
|
15
16
|
private feePerByte;
|
|
@@ -34,7 +35,8 @@ export declare class Transaction {
|
|
|
34
35
|
build(): Promise<string>;
|
|
35
36
|
send(): Promise<TransactionDetails>;
|
|
36
37
|
send(raw: true): Promise<string>;
|
|
38
|
+
debug(): Promise<DebugResult>;
|
|
39
|
+
bitauthUri(): Promise<string>;
|
|
37
40
|
private getTxDetails;
|
|
38
|
-
meep(): Promise<string>;
|
|
39
41
|
private setInputsAndOutputs;
|
|
40
42
|
}
|
|
@@ -4,10 +4,12 @@ import delay from 'delay';
|
|
|
4
4
|
import { placeholder, scriptToBytecode, } from '@cashscript/utils';
|
|
5
5
|
import deepEqual from 'fast-deep-equal';
|
|
6
6
|
import { isUtxoP2PKH, } from './interfaces.js';
|
|
7
|
-
import {
|
|
7
|
+
import { createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, buildError, validateOutput, utxoComparator, calculateDust, getOutputSize, utxoTokenComparator, } from './utils.js';
|
|
8
8
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
9
9
|
import { P2PKH_INPUT_SIZE } from './constants.js';
|
|
10
10
|
import { TransactionBuilder } from './TransactionBuilder.js';
|
|
11
|
+
import MockNetworkProvider from './network/MockNetworkProvider.js';
|
|
12
|
+
import { buildTemplate, debugTemplate, evaluateTemplate, getBitauthUri, } from './LibauthTemplate.js';
|
|
11
13
|
export class Transaction {
|
|
12
14
|
constructor(contract, unlocker, abiFunction, args, selector) {
|
|
13
15
|
this.contract = contract;
|
|
@@ -98,15 +100,56 @@ export class Transaction {
|
|
|
98
100
|
}
|
|
99
101
|
async send(raw) {
|
|
100
102
|
const tx = await this.build();
|
|
103
|
+
let template;
|
|
101
104
|
try {
|
|
105
|
+
if (this.contract.provider instanceof MockNetworkProvider) {
|
|
106
|
+
template = await buildTemplate({
|
|
107
|
+
transaction: this,
|
|
108
|
+
transactionHex: tx,
|
|
109
|
+
});
|
|
110
|
+
evaluateTemplate(template);
|
|
111
|
+
}
|
|
102
112
|
const txid = await this.contract.provider.sendRawTransaction(tx);
|
|
103
113
|
return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
|
|
104
114
|
}
|
|
105
|
-
catch (
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
catch (maybeNodeError) {
|
|
116
|
+
if (!template) {
|
|
117
|
+
template = await buildTemplate({
|
|
118
|
+
transaction: this,
|
|
119
|
+
transactionHex: tx,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const reason = maybeNodeError.error ?? maybeNodeError.message ?? maybeNodeError;
|
|
123
|
+
const bitauthUri = getBitauthUri(template);
|
|
124
|
+
try {
|
|
125
|
+
debugTemplate(template, this.contract.artifact);
|
|
126
|
+
}
|
|
127
|
+
catch (libauthError) {
|
|
128
|
+
if (this.contract.provider instanceof MockNetworkProvider) {
|
|
129
|
+
throw buildError(libauthError, bitauthUri);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const message = `${libauthError}\n\nUnderlying node error: ${reason}`;
|
|
133
|
+
throw buildError(message, bitauthUri);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// this must be unreachable
|
|
137
|
+
throw buildError(reason, bitauthUri);
|
|
108
138
|
}
|
|
109
139
|
}
|
|
140
|
+
// method to debug the transaction with libauth VM, throws upon evaluation error
|
|
141
|
+
async debug() {
|
|
142
|
+
const template = await buildTemplate({
|
|
143
|
+
transaction: this,
|
|
144
|
+
});
|
|
145
|
+
return debugTemplate(template, this.contract.artifact);
|
|
146
|
+
}
|
|
147
|
+
async bitauthUri() {
|
|
148
|
+
const template = await buildTemplate({
|
|
149
|
+
transaction: this,
|
|
150
|
+
});
|
|
151
|
+
return getBitauthUri(template);
|
|
152
|
+
}
|
|
110
153
|
async getTxDetails(txid, raw) {
|
|
111
154
|
for (let retries = 0; retries < 1200; retries += 1) {
|
|
112
155
|
await delay(500);
|
|
@@ -124,10 +167,6 @@ export class Transaction {
|
|
|
124
167
|
// Should not happen
|
|
125
168
|
throw new Error('Could not retrieve transaction details for over 10 minutes');
|
|
126
169
|
}
|
|
127
|
-
async meep() {
|
|
128
|
-
const tx = await this.build();
|
|
129
|
-
return meep(tx, this.inputs, this.contract.redeemScript);
|
|
130
|
-
}
|
|
131
170
|
async setInputsAndOutputs() {
|
|
132
171
|
if (this.outputs.length === 0) {
|
|
133
172
|
throw Error('Attempted to build a transaction without outputs');
|
|
@@ -8,4 +8,5 @@ export { Artifact, AbiFunction, AbiInput } from '@cashscript/utils';
|
|
|
8
8
|
export * as utils from '@cashscript/utils';
|
|
9
9
|
export * from './interfaces.js';
|
|
10
10
|
export * from './Errors.js';
|
|
11
|
-
export { NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, } from './network/index.js';
|
|
11
|
+
export { NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
|
|
12
|
+
export { randomUtxo, randomToken, randomNFT } from './utils.js';
|
|
@@ -7,5 +7,6 @@ export { encodeArgument } from './Argument.js';
|
|
|
7
7
|
export * as utils from '@cashscript/utils';
|
|
8
8
|
export * from './interfaces.js';
|
|
9
9
|
export * from './Errors.js';
|
|
10
|
-
export { BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, } from './network/index.js';
|
|
10
|
+
export { BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
|
|
11
|
+
export { randomUtxo, randomToken, randomNFT } from './utils.js';
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Utxo, Network } from '../interfaces.js';
|
|
2
|
+
import NetworkProvider from './NetworkProvider.js';
|
|
3
|
+
export default class MockNetworkProvider implements NetworkProvider {
|
|
4
|
+
private utxoMap;
|
|
5
|
+
private transactionMap;
|
|
6
|
+
network: Network;
|
|
7
|
+
constructor();
|
|
8
|
+
getUtxos(address: string): Promise<Utxo[]>;
|
|
9
|
+
getBlockHeight(): Promise<number>;
|
|
10
|
+
getRawTransaction(txid: string): Promise<string>;
|
|
11
|
+
sendRawTransaction(txHex: string): Promise<string>;
|
|
12
|
+
addUtxo(address: string, utxo: Utxo): void;
|
|
13
|
+
reset(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { binToHex, hexToBin } from '@bitauth/libauth';
|
|
2
|
+
import { sha256 } from '@cashscript/utils';
|
|
3
|
+
import { Network } from '../interfaces.js';
|
|
4
|
+
import { randomUtxo } from '../utils.js';
|
|
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
|
+
export default class MockNetworkProvider {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.utxoMap = {};
|
|
12
|
+
this.transactionMap = {};
|
|
13
|
+
this.network = Network.CHIPNET;
|
|
14
|
+
for (let i = 0; i < 3; i += 1) {
|
|
15
|
+
this.addUtxo(aliceAddress, randomUtxo());
|
|
16
|
+
this.addUtxo(bobAddress, randomUtxo());
|
|
17
|
+
this.addUtxo(carolAddress, randomUtxo());
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async getUtxos(address) {
|
|
21
|
+
return this.utxoMap[address];
|
|
22
|
+
}
|
|
23
|
+
async getBlockHeight() {
|
|
24
|
+
return 133700;
|
|
25
|
+
}
|
|
26
|
+
async getRawTransaction(txid) {
|
|
27
|
+
return this.transactionMap[txid];
|
|
28
|
+
}
|
|
29
|
+
async sendRawTransaction(txHex) {
|
|
30
|
+
const transactionBin = hexToBin(txHex);
|
|
31
|
+
const txid = binToHex(sha256(sha256(transactionBin)).reverse());
|
|
32
|
+
this.transactionMap[txid] = txHex;
|
|
33
|
+
return txid;
|
|
34
|
+
}
|
|
35
|
+
addUtxo(address, utxo) {
|
|
36
|
+
if (!this.utxoMap[address]) {
|
|
37
|
+
this.utxoMap[address] = [];
|
|
38
|
+
}
|
|
39
|
+
this.utxoMap[address].push(utxo);
|
|
40
|
+
}
|
|
41
|
+
reset() {
|
|
42
|
+
this.utxoMap = {};
|
|
43
|
+
this.transactionMap = {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=MockNetworkProvider.js.map
|
|
@@ -2,3 +2,4 @@ export { default as NetworkProvider } from './NetworkProvider.js';
|
|
|
2
2
|
export { default as BitcoinRpcNetworkProvider } from './BitcoinRpcNetworkProvider.js';
|
|
3
3
|
export { default as ElectrumNetworkProvider } from './ElectrumNetworkProvider.js';
|
|
4
4
|
export { default as FullStackNetworkProvider } from './FullStackNetworkProvider.js';
|
|
5
|
+
export { default as MockNetworkProvider } from './MockNetworkProvider.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { default as BitcoinRpcNetworkProvider } from './BitcoinRpcNetworkProvider.js';
|
|
2
2
|
export { default as ElectrumNetworkProvider } from './ElectrumNetworkProvider.js';
|
|
3
3
|
export { default as FullStackNetworkProvider } from './FullStackNetworkProvider.js';
|
|
4
|
+
export { default as MockNetworkProvider } from './MockNetworkProvider.js';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Transaction } from '@bitauth/libauth';
|
|
2
2
|
import { Script } from '@cashscript/utils';
|
|
3
|
-
import { Utxo, Output, LibauthOutput } from './interfaces.js';
|
|
3
|
+
import { Utxo, Output, LibauthOutput, TokenDetails } from './interfaces.js';
|
|
4
4
|
import { FailedTransactionError } from './Errors.js';
|
|
5
5
|
export declare function validateOutput(output: Output): void;
|
|
6
6
|
export declare function calculateDust(output: Output): number;
|
|
@@ -14,8 +14,8 @@ export declare function getTxSizeWithoutInputs(outputs: Output[]): number;
|
|
|
14
14
|
export declare function createInputScript(redeemScript: Script, encodedArgs: Uint8Array[], selector?: number, preimage?: Uint8Array): Uint8Array;
|
|
15
15
|
export declare function createOpReturnOutput(opReturnData: string[]): Output;
|
|
16
16
|
export declare function createSighashPreimage(transaction: Transaction, sourceOutputs: LibauthOutput[], inputIndex: number, coveredBytecode: Uint8Array, hashtype: number): Uint8Array;
|
|
17
|
-
export declare function buildError(reason: string,
|
|
18
|
-
export declare function
|
|
17
|
+
export declare function buildError(reason: string, debugStr?: string): FailedTransactionError;
|
|
18
|
+
export declare function toRegExp(reasons: string[]): RegExp;
|
|
19
19
|
export declare function scriptToAddress(script: Script, network: string, addressType: 'p2sh20' | 'p2sh32', tokenSupport: boolean): string;
|
|
20
20
|
export declare function scriptToLockingBytecode(script: Script, addressType: 'p2sh20' | 'p2sh32'): Uint8Array;
|
|
21
21
|
export declare function publicKeyToP2PKHLockingBytecode(publicKey: Uint8Array): Uint8Array;
|
|
@@ -30,3 +30,6 @@ export declare function utxoTokenComparator(a: Utxo, b: Utxo): number;
|
|
|
30
30
|
*/
|
|
31
31
|
export declare function addressToLockScript(address: string): Uint8Array;
|
|
32
32
|
export declare function getNetworkPrefix(network: string): 'bitcoincash' | 'bchtest' | 'bchreg';
|
|
33
|
+
export declare const randomUtxo: (defaults?: Partial<Utxo> | undefined) => Utxo;
|
|
34
|
+
export declare const randomToken: (defaults?: Partial<TokenDetails> | undefined) => TokenDetails;
|
|
35
|
+
export declare const randomNFT: (defaults?: Partial<TokenDetails> | undefined) => TokenDetails;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex, generateSigningSerializationBCH, utf8ToBin, hexToBin, flattenBinArray, LockingBytecodeType, encodeTransactionOutput, isHex, bigIntToCompactSize, } from '@bitauth/libauth';
|
|
2
|
-
import { encodeInt, hash160, hash256, Op, scriptToBytecode, } from '@cashscript/utils';
|
|
1
|
+
import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex, generateSigningSerializationBCH, utf8ToBin, hexToBin, flattenBinArray, LockingBytecodeType, encodeTransactionOutput, isHex, bigIntToCompactSize, AuthenticationErrorCommon, NonFungibleTokenCapability, bigIntToVmNumber, } from '@bitauth/libauth';
|
|
2
|
+
import { encodeInt, hash160, hash256, sha256, Op, scriptToBytecode, } from '@cashscript/utils';
|
|
3
3
|
import { Network, } from './interfaces.js';
|
|
4
4
|
import { VERSION_SIZE, LOCKTIME_SIZE } from './constants.js';
|
|
5
5
|
import { OutputSatoshisTooSmallError, TokensToNonTokenAddressError, Reason, FailedTransactionError, FailedRequireError, FailedTimeCheckError, FailedSigCheckError, } from './Errors.js';
|
|
@@ -135,35 +135,35 @@ export function createSighashPreimage(transaction, sourceOutputs, inputIndex, co
|
|
|
135
135
|
const sighashPreimage = generateSigningSerializationBCH(context, { coveredBytecode, signingSerializationType });
|
|
136
136
|
return sighashPreimage;
|
|
137
137
|
}
|
|
138
|
-
export function buildError(reason,
|
|
138
|
+
export function buildError(reason, debugStr) {
|
|
139
139
|
const require = [
|
|
140
140
|
Reason.EVAL_FALSE, Reason.VERIFY, Reason.EQUALVERIFY, Reason.CHECKMULTISIGVERIFY,
|
|
141
141
|
Reason.CHECKSIGVERIFY, Reason.CHECKDATASIGVERIFY, Reason.NUMEQUALVERIFY,
|
|
142
|
+
AuthenticationErrorCommon.failedVerify,
|
|
143
|
+
];
|
|
144
|
+
const timeCheck = [
|
|
145
|
+
Reason.NEGATIVE_LOCKTIME, Reason.UNSATISFIED_LOCKTIME,
|
|
146
|
+
AuthenticationErrorCommon.negativeLocktime, AuthenticationErrorCommon.unsatisfiedLocktime,
|
|
142
147
|
];
|
|
143
|
-
const timeCheck = [Reason.NEGATIVE_LOCKTIME, Reason.UNSATISFIED_LOCKTIME];
|
|
144
148
|
const sigCheck = [
|
|
145
149
|
Reason.SIG_COUNT, Reason.PUBKEY_COUNT, Reason.SIG_HASHTYPE, Reason.SIG_DER,
|
|
146
150
|
Reason.SIG_HIGH_S, Reason.SIG_NULLFAIL, Reason.SIG_BADLENGTH, Reason.SIG_NONSCHNORR,
|
|
151
|
+
AuthenticationErrorCommon.nonNullSignatureFailure,
|
|
147
152
|
];
|
|
148
153
|
if (toRegExp(require).test(reason)) {
|
|
149
|
-
return new FailedRequireError(reason,
|
|
154
|
+
return new FailedRequireError(reason, debugStr);
|
|
150
155
|
}
|
|
151
156
|
if (toRegExp(timeCheck).test(reason)) {
|
|
152
|
-
return new FailedTimeCheckError(reason,
|
|
157
|
+
return new FailedTimeCheckError(reason, debugStr);
|
|
153
158
|
}
|
|
154
159
|
if (toRegExp(sigCheck).test(reason)) {
|
|
155
|
-
return new FailedSigCheckError(reason,
|
|
160
|
+
return new FailedSigCheckError(reason, debugStr);
|
|
156
161
|
}
|
|
157
|
-
return new FailedTransactionError(reason,
|
|
162
|
+
return new FailedTransactionError(reason, debugStr);
|
|
158
163
|
}
|
|
159
|
-
function toRegExp(reasons) {
|
|
164
|
+
export function toRegExp(reasons) {
|
|
160
165
|
return new RegExp(reasons.join('|').replace(/\(/g, '\\(').replace(/\)/g, '\\)'));
|
|
161
166
|
}
|
|
162
|
-
// ////////// MISC ////////////////////////////////////////////////////////////
|
|
163
|
-
export function meep(tx, utxos, script) {
|
|
164
|
-
const scriptPubkey = binToHex(scriptToLockingBytecode(script, 'p2sh20'));
|
|
165
|
-
return `meep debug --tx=${tx} --idx=0 --amt=${utxos[0].satoshis} --pkscript=${scriptPubkey}`;
|
|
166
|
-
}
|
|
167
167
|
export function scriptToAddress(script, network, addressType, tokenSupport) {
|
|
168
168
|
const lockingBytecode = scriptToLockingBytecode(script, addressType);
|
|
169
169
|
const prefix = getNetworkPrefix(network);
|
|
@@ -249,4 +249,31 @@ function getPushDataOpcode(data) {
|
|
|
249
249
|
return Uint8Array.from([0x4c, byteLength]);
|
|
250
250
|
throw Error('Pushdata too large');
|
|
251
251
|
}
|
|
252
|
+
const randomInt = () => BigInt(Math.floor(Math.random() * 10000));
|
|
253
|
+
export const randomUtxo = (defaults) => ({
|
|
254
|
+
...{
|
|
255
|
+
txid: binToHex(sha256(bigIntToVmNumber(randomInt()))),
|
|
256
|
+
vout: Math.floor(Math.random() * 10),
|
|
257
|
+
satoshis: 20000n + randomInt(),
|
|
258
|
+
},
|
|
259
|
+
...defaults,
|
|
260
|
+
});
|
|
261
|
+
export const randomToken = (defaults) => ({
|
|
262
|
+
...{
|
|
263
|
+
category: binToHex(sha256(bigIntToVmNumber(randomInt()))),
|
|
264
|
+
amount: 10000n + randomInt(),
|
|
265
|
+
},
|
|
266
|
+
...defaults,
|
|
267
|
+
});
|
|
268
|
+
export const randomNFT = (defaults) => ({
|
|
269
|
+
...{
|
|
270
|
+
category: binToHex(sha256(bigIntToVmNumber(randomInt()))),
|
|
271
|
+
amount: 0n,
|
|
272
|
+
nft: {
|
|
273
|
+
commitment: binToHex(sha256(bigIntToVmNumber(randomInt()))).slice(0, 8),
|
|
274
|
+
capability: NonFungibleTokenCapability.none,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
...defaults,
|
|
278
|
+
});
|
|
252
279
|
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { printExpected, printReceived, matcherHint } from 'jest-matcher-utils';
|
|
2
|
+
expect.extend({
|
|
3
|
+
async toLog(transaction, match) {
|
|
4
|
+
const spyOnLoggerError = jest.spyOn(console, 'log');
|
|
5
|
+
// silence actual stdout output
|
|
6
|
+
spyOnLoggerError.mockImplementation(() => { });
|
|
7
|
+
try {
|
|
8
|
+
await transaction.debug();
|
|
9
|
+
}
|
|
10
|
+
catch { }
|
|
11
|
+
let message = '';
|
|
12
|
+
const failMessage = (received, expected) => () => `${matcherHint('.toLog', 'received', 'expected')}
|
|
13
|
+
|
|
14
|
+
Expected: ${printExpected(expected)}
|
|
15
|
+
Received: ${printReceived(received)}`;
|
|
16
|
+
try {
|
|
17
|
+
expect(spyOnLoggerError).toBeCalledWith(expect.stringMatching(match));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
message = error;
|
|
21
|
+
}
|
|
22
|
+
const received = spyOnLoggerError.mock.calls[0][0];
|
|
23
|
+
spyOnLoggerError.mockClear();
|
|
24
|
+
return {
|
|
25
|
+
message: failMessage(received, match),
|
|
26
|
+
pass: !message,
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
expect.extend({
|
|
31
|
+
async toFailRequireWith(transaction, match) {
|
|
32
|
+
let message = '';
|
|
33
|
+
let failMessage = () => { };
|
|
34
|
+
try {
|
|
35
|
+
await transaction.debug();
|
|
36
|
+
failMessage = () => () => `${matcherHint('.toFailRequireWith', undefined, '')}
|
|
37
|
+
|
|
38
|
+
Contract function did not fail a require statement`;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
message = error;
|
|
42
|
+
}
|
|
43
|
+
// should not have failed
|
|
44
|
+
if (this.isNot) {
|
|
45
|
+
return {
|
|
46
|
+
message: () => `${matcherHint('.toFailRequireWith', 'received', '', { isNot: true })}`,
|
|
47
|
+
pass: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (message) {
|
|
51
|
+
try {
|
|
52
|
+
expect(message).toMatch(match);
|
|
53
|
+
message = '';
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
message = error.message;
|
|
57
|
+
failMessage = () => () => message.replace('.toMatch', '.toFailRequireWith');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
message: failMessage(message, match),
|
|
62
|
+
pass: !message,
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=JestExtensions.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cashscript",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0-next.0",
|
|
4
4
|
"description": "Easily write and interact with Bitcoin Cash contracts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin cash",
|
|
@@ -38,26 +38,28 @@
|
|
|
38
38
|
"compile:test": "tsc -p tsconfig.test.json",
|
|
39
39
|
"lint": "eslint . --ext .ts --ignore-path ../../.eslintignore",
|
|
40
40
|
"prepare": "yarn build",
|
|
41
|
-
"prepublishOnly": "yarn test
|
|
41
|
+
"prepublishOnly": "yarn test",
|
|
42
42
|
"pretest": "yarn build:test",
|
|
43
43
|
"test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@bitauth/libauth": "^2.0.0-alpha.8",
|
|
47
|
-
"@cashscript/utils": "^0.
|
|
47
|
+
"@cashscript/utils": "^0.10.0-next.0",
|
|
48
48
|
"bip68": "^1.0.4",
|
|
49
49
|
"bitcoin-rpc-promise-retry": "^1.3.0",
|
|
50
50
|
"delay": "^5.0.0",
|
|
51
51
|
"electrum-cash": "^2.0.10",
|
|
52
|
-
"fast-deep-equal": "^3.1.3"
|
|
52
|
+
"fast-deep-equal": "^3.1.3",
|
|
53
|
+
"pako": "^2.1.0"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@jest/globals": "^29.4.1",
|
|
56
57
|
"@psf/bch-js": "^4.15.0",
|
|
58
|
+
"@types/pako": "^2.0.3",
|
|
57
59
|
"bip39": "^3.0.4",
|
|
58
|
-
"eslint": "^
|
|
60
|
+
"eslint": "^8.54.0",
|
|
59
61
|
"jest": "^29.4.1",
|
|
60
62
|
"typescript": "^4.1.5"
|
|
61
63
|
},
|
|
62
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "3514c438fa3c8069c36f45cc80730293ac85dbdf"
|
|
63
65
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|