cashscript 0.10.5 → 0.11.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/Contract.js +8 -11
- package/dist/Errors.d.ts +6 -0
- package/dist/Errors.js +10 -0
- package/dist/LibauthTemplate.d.ts +15 -1
- package/dist/LibauthTemplate.js +45 -28
- package/dist/SignatureTemplate.js +1 -0
- package/dist/Transaction.d.ts +2 -2
- package/dist/Transaction.js +6 -14
- package/dist/TransactionBuilder.d.ts +8 -2
- package/dist/TransactionBuilder.js +24 -4
- package/dist/advanced/LibauthTemplate.d.ts +44 -0
- package/dist/advanced/LibauthTemplate.js +396 -0
- package/dist/debugging.d.ts +2 -2
- package/dist/debugging.js +35 -20
- package/dist/interfaces.d.ts +7 -0
- package/dist/network/ElectrumNetworkProvider.js +1 -1
- package/dist/utils.d.ts +6 -3
- package/dist/utils.js +33 -16
- package/package.json +13 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CashScript
|
|
2
2
|
|
|
3
|
-

|
|
3
|
+
[](https://github.com/CashScript/cashscript/actions/workflows/github-actions.yml)
|
|
4
4
|
[](https://codecov.io/gh/CashScript/cashscript/)
|
|
5
5
|
[](https://www.npmjs.com/package/cashscript)
|
|
6
6
|
[](https://www.npmjs.com/package/cashscript)
|
package/dist/Contract.js
CHANGED
|
@@ -5,16 +5,20 @@ import { encodeFunctionArgument, encodeConstructorArguments, encodeFunctionArgum
|
|
|
5
5
|
import { addressToLockScript, createInputScript, createSighashPreimage, scriptToAddress, } from './utils.js';
|
|
6
6
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
7
7
|
import { ElectrumNetworkProvider } from './network/index.js';
|
|
8
|
+
import semver from 'semver';
|
|
8
9
|
export class Contract {
|
|
9
10
|
constructor(artifact, constructorArgs, options) {
|
|
10
11
|
this.artifact = artifact;
|
|
11
12
|
this.options = options;
|
|
12
13
|
this.provider = this.options?.provider ?? new ElectrumNetworkProvider();
|
|
13
14
|
this.addressType = this.options?.addressType ?? 'p2sh32';
|
|
14
|
-
const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName'];
|
|
15
|
+
const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName', 'compiler'];
|
|
15
16
|
if (!expectedProperties.every((property) => property in artifact)) {
|
|
16
17
|
throw new Error('Invalid or incomplete artifact provided');
|
|
17
18
|
}
|
|
19
|
+
if (!semver.satisfies(artifact.compiler.version, '>=0.7.0', { includePrerelease: true })) {
|
|
20
|
+
throw new Error(`Artifact compiled with unsupported compiler version: ${artifact.compiler.version}, required >=0.7.0`);
|
|
21
|
+
}
|
|
18
22
|
if (artifact.constructorInputs.length !== constructorArgs.length) {
|
|
19
23
|
throw new Error(`Incorrect number of arguments passed to ${artifact.contractName} constructor. Expected ${artifact.constructorInputs.length} arguments (${artifact.constructorInputs.map((input) => input.type)}) but got ${constructorArgs.length}`);
|
|
20
24
|
}
|
|
@@ -83,26 +87,19 @@ export class Contract {
|
|
|
83
87
|
const encodedArgs = args
|
|
84
88
|
.map((arg, i) => encodeFunctionArgument(arg, abiFunction.inputs[i].type));
|
|
85
89
|
const generateUnlockingBytecode = ({ transaction, sourceOutputs, inputIndex }) => {
|
|
86
|
-
// TODO: Remove old-style covenant code for v1.0 release
|
|
87
|
-
let covenantHashType = -1;
|
|
88
90
|
const completeArgs = encodedArgs.map((arg) => {
|
|
89
91
|
if (!(arg instanceof SignatureTemplate))
|
|
90
92
|
return arg;
|
|
91
|
-
//
|
|
92
|
-
if (covenantHashType < 0)
|
|
93
|
-
covenantHashType = arg.getHashType();
|
|
93
|
+
// Generate transaction signature from SignatureTemplate
|
|
94
94
|
const preimage = createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, arg.getHashType());
|
|
95
95
|
const sighash = hash256(preimage);
|
|
96
96
|
return arg.generateSignature(sighash);
|
|
97
97
|
});
|
|
98
|
-
const
|
|
99
|
-
? createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, covenantHashType)
|
|
100
|
-
: undefined;
|
|
101
|
-
const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector, preimage);
|
|
98
|
+
const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector);
|
|
102
99
|
return unlockingBytecode;
|
|
103
100
|
};
|
|
104
101
|
const generateLockingBytecode = () => addressToLockScript(this.address);
|
|
105
|
-
return { generateUnlockingBytecode, generateLockingBytecode };
|
|
102
|
+
return { generateUnlockingBytecode, generateLockingBytecode, contract: this, params: args, abiFunction };
|
|
106
103
|
};
|
|
107
104
|
}
|
|
108
105
|
}
|
package/dist/Errors.d.ts
CHANGED
|
@@ -2,9 +2,15 @@ import { Artifact, RequireStatement, Type } from '@cashscript/utils';
|
|
|
2
2
|
export declare class TypeError extends Error {
|
|
3
3
|
constructor(actual: string, expected: Type);
|
|
4
4
|
}
|
|
5
|
+
export declare class UndefinedInputError extends Error {
|
|
6
|
+
constructor();
|
|
7
|
+
}
|
|
5
8
|
export declare class OutputSatoshisTooSmallError extends Error {
|
|
6
9
|
constructor(satoshis: bigint, minimumAmount: bigint);
|
|
7
10
|
}
|
|
11
|
+
export declare class OutputTokenAmountTooSmallError extends Error {
|
|
12
|
+
constructor(amount: bigint);
|
|
13
|
+
}
|
|
8
14
|
export declare class TokensToNonTokenAddressError extends Error {
|
|
9
15
|
constructor(address: string);
|
|
10
16
|
}
|
package/dist/Errors.js
CHANGED
|
@@ -4,11 +4,21 @@ export class TypeError extends Error {
|
|
|
4
4
|
super(`Found type '${actual}' where type '${expected.toString()}' was expected`);
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
|
+
export class UndefinedInputError extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super('Input is undefined');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
7
12
|
export class OutputSatoshisTooSmallError extends Error {
|
|
8
13
|
constructor(satoshis, minimumAmount) {
|
|
9
14
|
super(`Tried to add an output with ${satoshis} satoshis, which is less than the required minimum for this output-type (${minimumAmount})`);
|
|
10
15
|
}
|
|
11
16
|
}
|
|
17
|
+
export class OutputTokenAmountTooSmallError extends Error {
|
|
18
|
+
constructor(amount) {
|
|
19
|
+
super(`Tried to add an output with ${amount} tokens, which is invalid`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
12
22
|
export class TokensToNonTokenAddressError extends Error {
|
|
13
23
|
constructor(address) {
|
|
14
24
|
super(`Tried to send tokens to an address without token support, ${address}.`);
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbiInput, Artifact } from '@cashscript/utils';
|
|
2
|
+
import { WalletTemplate, WalletTemplateScenarioBytecode } from '@bitauth/libauth';
|
|
3
|
+
import { Utxo, TokenDetails, LibauthTokenDetails, Output, SignatureAlgorithm, HashType } from './interfaces.js';
|
|
2
4
|
import { Transaction } from './Transaction.js';
|
|
5
|
+
import { EncodedFunctionArgument } from './Argument.js';
|
|
6
|
+
import { Contract } from './Contract.js';
|
|
3
7
|
interface BuildTemplateOptions {
|
|
4
8
|
transaction: Transaction;
|
|
5
9
|
transactionHex?: string;
|
|
6
10
|
}
|
|
7
11
|
export declare const buildTemplate: ({ transaction, transactionHex, }: BuildTemplateOptions) => Promise<WalletTemplate>;
|
|
8
12
|
export declare const getBitauthUri: (template: WalletTemplate) => string;
|
|
13
|
+
export declare const generateTemplateScenarioTransactionOutputLockingBytecode: (csOutput: Output, contract: Contract) => string | {};
|
|
14
|
+
export declare const generateTemplateScenarioBytecode: (input: Utxo, p2pkhScriptName: string, placeholderKeyName: string, insertSlot?: boolean) => WalletTemplateScenarioBytecode | ["slot"];
|
|
15
|
+
export declare const generateTemplateScenarioParametersValues: (types: readonly AbiInput[], encodedArgs: EncodedFunctionArgument[]) => Record<string, string>;
|
|
16
|
+
export declare const addHexPrefixExceptEmpty: (value: string) => string;
|
|
17
|
+
export declare const generateTemplateScenarioKeys: (types: readonly AbiInput[], encodedArgs: EncodedFunctionArgument[]) => Record<string, string>;
|
|
18
|
+
export declare const formatParametersForDebugging: (types: readonly AbiInput[], args: EncodedFunctionArgument[]) => string;
|
|
19
|
+
export declare const getSignatureAlgorithmName: (signatureAlgorithm: SignatureAlgorithm) => string;
|
|
20
|
+
export declare const getHashTypeName: (hashType: HashType) => string;
|
|
21
|
+
export declare const formatBytecodeForDebugging: (artifact: Artifact) => string;
|
|
22
|
+
export declare const serialiseTokenDetails: (token?: TokenDetails | LibauthTokenDetails) => LibauthTemplateTokenDetails | undefined;
|
|
9
23
|
export interface LibauthTemplateTokenDetails {
|
|
10
24
|
amount: string;
|
|
11
25
|
category: string;
|
package/dist/LibauthTemplate.js
CHANGED
|
@@ -11,7 +11,7 @@ export const buildTemplate = async ({ transaction, transactionHex = undefined, /
|
|
|
11
11
|
const template = {
|
|
12
12
|
$schema: 'https://ide.bitauth.com/authentication-template-v0.schema.json',
|
|
13
13
|
description: 'Imported from cashscript',
|
|
14
|
-
name:
|
|
14
|
+
name: 'CashScript Generated Debugging Template',
|
|
15
15
|
supported: ['BCH_2023_05'],
|
|
16
16
|
version: 0,
|
|
17
17
|
entities: generateTemplateEntities(contract.artifact, transaction.abiFunction, transaction.encodedFunctionArgs),
|
|
@@ -28,9 +28,9 @@ export const buildTemplate = async ({ transaction, transactionHex = undefined, /
|
|
|
28
28
|
const signatureAlgorithmName = getSignatureAlgorithmName(input.template.getSignatureAlgorithm());
|
|
29
29
|
const hashtypeName = getHashTypeName(input.template.getHashType(false));
|
|
30
30
|
const signatureString = `${placeholderKeyName}.${signatureAlgorithmName}.${hashtypeName}`;
|
|
31
|
-
template.entities.
|
|
32
|
-
template.entities.
|
|
33
|
-
...template.entities.
|
|
31
|
+
template.entities[snakeCase(contract.name + 'Parameters')].scripts.push(lockScriptName, unlockScriptName);
|
|
32
|
+
template.entities[snakeCase(contract.name + 'Parameters')].variables = {
|
|
33
|
+
...template.entities[snakeCase(contract.name + 'Parameters')].variables,
|
|
34
34
|
[placeholderKeyName]: {
|
|
35
35
|
description: placeholderKeyName,
|
|
36
36
|
name: placeholderKeyName,
|
|
@@ -75,12 +75,12 @@ const generateTemplateEntities = (artifact, abiFunction, encodedFunctionArgs) =>
|
|
|
75
75
|
},
|
|
76
76
|
])));
|
|
77
77
|
const entities = {
|
|
78
|
-
|
|
78
|
+
[snakeCase(artifact.contractName + 'Parameters')]: {
|
|
79
79
|
description: 'Contract creation and function parameters',
|
|
80
|
-
name: '
|
|
80
|
+
name: snakeCase(artifact.contractName + 'Parameters'),
|
|
81
81
|
scripts: [
|
|
82
|
-
'
|
|
83
|
-
'
|
|
82
|
+
snakeCase(artifact.contractName + '_lock'),
|
|
83
|
+
snakeCase(artifact.contractName + '_unlock'),
|
|
84
84
|
],
|
|
85
85
|
variables: {
|
|
86
86
|
...functionParameters,
|
|
@@ -90,7 +90,7 @@ const generateTemplateEntities = (artifact, abiFunction, encodedFunctionArgs) =>
|
|
|
90
90
|
};
|
|
91
91
|
// function_index is a special variable that indicates the function to execute
|
|
92
92
|
if (artifact.abi.length > 1) {
|
|
93
|
-
entities.
|
|
93
|
+
entities[snakeCase(artifact.contractName + 'Parameters')].variables.function_index = {
|
|
94
94
|
description: 'Script function index to execute',
|
|
95
95
|
name: 'function_index',
|
|
96
96
|
type: 'WalletData',
|
|
@@ -101,14 +101,14 @@ const generateTemplateEntities = (artifact, abiFunction, encodedFunctionArgs) =>
|
|
|
101
101
|
const generateTemplateScripts = (artifact, addressType, abiFunction, encodedFunctionArgs, encodedConstructorArgs) => {
|
|
102
102
|
// definition of locking scripts and unlocking scripts with their respective bytecode
|
|
103
103
|
return {
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
[snakeCase(artifact.contractName + '_unlock')]: generateTemplateUnlockScript(artifact, abiFunction, encodedFunctionArgs),
|
|
105
|
+
[snakeCase(artifact.contractName + '_lock')]: generateTemplateLockScript(artifact, addressType, encodedConstructorArgs),
|
|
106
106
|
};
|
|
107
107
|
};
|
|
108
108
|
const generateTemplateLockScript = (artifact, addressType, constructorArguments) => {
|
|
109
109
|
return {
|
|
110
110
|
lockingType: addressType,
|
|
111
|
-
name: '
|
|
111
|
+
name: snakeCase(artifact.contractName + '_lock'),
|
|
112
112
|
script: [
|
|
113
113
|
`// "${artifact.contractName}" contract constructor parameters`,
|
|
114
114
|
formatParametersForDebugging(artifact.constructorInputs, constructorArguments),
|
|
@@ -125,15 +125,15 @@ const generateTemplateUnlockScript = (artifact, abiFunction, encodedFunctionArgs
|
|
|
125
125
|
: [];
|
|
126
126
|
return {
|
|
127
127
|
// this unlocking script must pass our only scenario
|
|
128
|
-
passes: ['
|
|
129
|
-
name: '
|
|
128
|
+
passes: [snakeCase(artifact.contractName + 'Evaluate')],
|
|
129
|
+
name: snakeCase(artifact.contractName + '_unlock'),
|
|
130
130
|
script: [
|
|
131
131
|
`// "${abiFunction.name}" function parameters`,
|
|
132
132
|
formatParametersForDebugging(abiFunction.inputs, encodedFunctionArgs),
|
|
133
133
|
'',
|
|
134
134
|
...functionIndexString,
|
|
135
135
|
].join('\n'),
|
|
136
|
-
unlocks: '
|
|
136
|
+
unlocks: snakeCase(artifact.contractName + '_lock'),
|
|
137
137
|
};
|
|
138
138
|
};
|
|
139
139
|
const generateTemplateScenarios = (contract, transaction, transactionHex, artifact, abiFunction, encodedFunctionArgs, encodedConstructorArgs) => {
|
|
@@ -142,8 +142,8 @@ const generateTemplateScenarios = (contract, transaction, transactionHex, artifa
|
|
|
142
142
|
throw Error(libauthTransaction);
|
|
143
143
|
const scenarios = {
|
|
144
144
|
// single scenario to spend out transaction under test given the CashScript parameters provided
|
|
145
|
-
|
|
146
|
-
name: 'Evaluate',
|
|
145
|
+
[snakeCase(artifact.contractName + 'Evaluate')]: {
|
|
146
|
+
name: snakeCase(artifact.contractName + 'Evaluate'),
|
|
147
147
|
description: 'An example evaluation where this script execution passes.',
|
|
148
148
|
data: {
|
|
149
149
|
// encode values for the variables defined above in `entities` property
|
|
@@ -163,7 +163,7 @@ const generateTemplateScenarios = (contract, transaction, transactionHex, artifa
|
|
|
163
163
|
};
|
|
164
164
|
if (artifact.abi.length > 1) {
|
|
165
165
|
const functionIndex = artifact.abi.findIndex((func) => func.name === transaction.abiFunction.name);
|
|
166
|
-
scenarios.
|
|
166
|
+
scenarios[snakeCase(artifact.contractName + 'Evaluate')].data.bytecode.function_index = functionIndex.toString();
|
|
167
167
|
}
|
|
168
168
|
return scenarios;
|
|
169
169
|
};
|
|
@@ -190,13 +190,27 @@ const generateTemplateScenarioTransaction = (contract, libauthTransaction, csTra
|
|
|
190
190
|
const version = libauthTransaction.version;
|
|
191
191
|
return { inputs, locktime, outputs, version };
|
|
192
192
|
};
|
|
193
|
-
const generateTemplateScenarioTransactionOutputLockingBytecode = (csOutput, contract) => {
|
|
193
|
+
export const generateTemplateScenarioTransactionOutputLockingBytecode = (csOutput, contract) => {
|
|
194
194
|
if (csOutput.to instanceof Uint8Array)
|
|
195
195
|
return binToHex(csOutput.to);
|
|
196
196
|
if ([contract.address, contract.tokenAddress].includes(csOutput.to))
|
|
197
197
|
return {};
|
|
198
198
|
return binToHex(addressToLockScript(csOutput.to));
|
|
199
199
|
};
|
|
200
|
+
/**
|
|
201
|
+
* Generates source outputs for a BitAuth template scenario
|
|
202
|
+
*
|
|
203
|
+
* @param csTransaction - The CashScript transaction to generate source outputs for
|
|
204
|
+
* @returns An array of BitAuth template scenario outputs with locking scripts and values
|
|
205
|
+
*
|
|
206
|
+
* For each input in the transaction:
|
|
207
|
+
* - Generates appropriate locking bytecode (P2PKH or contract)
|
|
208
|
+
* - Includes the input value in satoshis
|
|
209
|
+
* - Includes any token details if present
|
|
210
|
+
*
|
|
211
|
+
* The slotIndex tracks which input is the contract input vs P2PKH inputs
|
|
212
|
+
* to properly generate the locking scripts.
|
|
213
|
+
*/
|
|
200
214
|
const generateTemplateScenarioSourceOutputs = (csTransaction) => {
|
|
201
215
|
const slotIndex = csTransaction.inputs.findIndex((input) => !isUtxoP2PKH(input));
|
|
202
216
|
return csTransaction.inputs.map((input, index) => {
|
|
@@ -208,7 +222,7 @@ const generateTemplateScenarioSourceOutputs = (csTransaction) => {
|
|
|
208
222
|
});
|
|
209
223
|
};
|
|
210
224
|
// Used for generating the locking / unlocking bytecode for source outputs and inputs
|
|
211
|
-
const generateTemplateScenarioBytecode = (input, p2pkhScriptName, placeholderKeyName, insertSlot) => {
|
|
225
|
+
export const generateTemplateScenarioBytecode = (input, p2pkhScriptName, placeholderKeyName, insertSlot) => {
|
|
212
226
|
if (isUtxoP2PKH(input)) {
|
|
213
227
|
return {
|
|
214
228
|
script: p2pkhScriptName,
|
|
@@ -223,26 +237,29 @@ const generateTemplateScenarioBytecode = (input, p2pkhScriptName, placeholderKey
|
|
|
223
237
|
}
|
|
224
238
|
return insertSlot ? ['slot'] : {};
|
|
225
239
|
};
|
|
226
|
-
const generateTemplateScenarioParametersValues = (types, encodedArgs) => {
|
|
240
|
+
export const generateTemplateScenarioParametersValues = (types, encodedArgs) => {
|
|
227
241
|
const typesAndArguments = zip(types, encodedArgs);
|
|
228
242
|
const entries = typesAndArguments
|
|
229
243
|
// SignatureTemplates are handled by the 'keys' object in the scenario
|
|
230
244
|
.filter(([, arg]) => !(arg instanceof SignatureTemplate))
|
|
231
245
|
.map(([input, arg]) => {
|
|
232
246
|
const encodedArgumentHex = binToHex(arg);
|
|
233
|
-
const prefixedEncodedArgument = encodedArgumentHex
|
|
247
|
+
const prefixedEncodedArgument = addHexPrefixExceptEmpty(encodedArgumentHex);
|
|
234
248
|
return [snakeCase(input.name), prefixedEncodedArgument];
|
|
235
249
|
});
|
|
236
250
|
return Object.fromEntries(entries);
|
|
237
251
|
};
|
|
238
|
-
const
|
|
252
|
+
export const addHexPrefixExceptEmpty = (value) => {
|
|
253
|
+
return value.length > 0 ? `0x${value}` : '';
|
|
254
|
+
};
|
|
255
|
+
export const generateTemplateScenarioKeys = (types, encodedArgs) => {
|
|
239
256
|
const typesAndArguments = zip(types, encodedArgs);
|
|
240
257
|
const entries = typesAndArguments
|
|
241
258
|
.filter(([, arg]) => arg instanceof SignatureTemplate)
|
|
242
259
|
.map(([input, arg]) => [snakeCase(input.name), binToHex(arg.privateKey)]);
|
|
243
260
|
return Object.fromEntries(entries);
|
|
244
261
|
};
|
|
245
|
-
const formatParametersForDebugging = (types, args) => {
|
|
262
|
+
export const formatParametersForDebugging = (types, args) => {
|
|
246
263
|
if (types.length === 0)
|
|
247
264
|
return '// none';
|
|
248
265
|
// We reverse the arguments because the order of the arguments in the bytecode is reversed
|
|
@@ -259,14 +276,14 @@ const formatParametersForDebugging = (types, args) => {
|
|
|
259
276
|
return `<${snakeCase(input.name)}> // ${typeStr} = <${`0x${binToHex(arg)}`}>`;
|
|
260
277
|
}).join('\n');
|
|
261
278
|
};
|
|
262
|
-
const getSignatureAlgorithmName = (signatureAlgorithm) => {
|
|
279
|
+
export const getSignatureAlgorithmName = (signatureAlgorithm) => {
|
|
263
280
|
const signatureAlgorithmNames = {
|
|
264
281
|
[SignatureAlgorithm.SCHNORR]: 'schnorr_signature',
|
|
265
282
|
[SignatureAlgorithm.ECDSA]: 'ecdsa_signature',
|
|
266
283
|
};
|
|
267
284
|
return signatureAlgorithmNames[signatureAlgorithm];
|
|
268
285
|
};
|
|
269
|
-
const getHashTypeName = (hashType) => {
|
|
286
|
+
export const getHashTypeName = (hashType) => {
|
|
270
287
|
const hashtypeNames = {
|
|
271
288
|
[HashType.SIGHASH_ALL]: 'all_outputs',
|
|
272
289
|
[HashType.SIGHASH_ALL | HashType.SIGHASH_ANYONECANPAY]: 'all_outputs_single_input',
|
|
@@ -283,7 +300,7 @@ const getHashTypeName = (hashType) => {
|
|
|
283
300
|
};
|
|
284
301
|
return hashtypeNames[hashType];
|
|
285
302
|
};
|
|
286
|
-
const formatBytecodeForDebugging = (artifact) => {
|
|
303
|
+
export const formatBytecodeForDebugging = (artifact) => {
|
|
287
304
|
if (!artifact.debug) {
|
|
288
305
|
return artifact.bytecode
|
|
289
306
|
.split(' ')
|
|
@@ -292,7 +309,7 @@ const formatBytecodeForDebugging = (artifact) => {
|
|
|
292
309
|
}
|
|
293
310
|
return formatBitAuthScript(bytecodeToScript(hexToBin(artifact.debug.bytecode)), artifact.debug.sourceMap, artifact.source);
|
|
294
311
|
};
|
|
295
|
-
const serialiseTokenDetails = (token) => {
|
|
312
|
+
export const serialiseTokenDetails = (token) => {
|
|
296
313
|
if (!token)
|
|
297
314
|
return undefined;
|
|
298
315
|
return {
|
package/dist/Transaction.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { AbiFunction } from '@cashscript/utils';
|
|
|
3
3
|
import { Utxo, Output, Recipient, TokenDetails, TransactionDetails, Unlocker } from './interfaces.js';
|
|
4
4
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
5
5
|
import { Contract } from './Contract.js';
|
|
6
|
-
import {
|
|
6
|
+
import { DebugResults } from './debugging.js';
|
|
7
7
|
import { EncodedFunctionArgument } from './Argument.js';
|
|
8
8
|
export declare class Transaction {
|
|
9
9
|
contract: Contract;
|
|
@@ -37,7 +37,7 @@ export declare class Transaction {
|
|
|
37
37
|
build(): Promise<string>;
|
|
38
38
|
send(): Promise<TransactionDetails>;
|
|
39
39
|
send(raw: true): Promise<string>;
|
|
40
|
-
debug(): Promise<
|
|
40
|
+
debug(): Promise<DebugResults>;
|
|
41
41
|
bitauthUri(): Promise<string>;
|
|
42
42
|
getLibauthTemplate(): Promise<WalletTemplate>;
|
|
43
43
|
private getTxDetails;
|
package/dist/Transaction.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { hexToBin, decodeTransaction, } from '@bitauth/libauth';
|
|
2
2
|
import delay from 'delay';
|
|
3
|
-
import { encodeBip68, placeholder,
|
|
3
|
+
import { encodeBip68, placeholder, } from '@cashscript/utils';
|
|
4
4
|
import deepEqual from 'fast-deep-equal';
|
|
5
5
|
import { isUtxoP2PKH, SignatureAlgorithm, } from './interfaces.js';
|
|
6
|
-
import { createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs,
|
|
6
|
+
import { createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, validateOutput, utxoComparator, calculateDust, getOutputSize, utxoTokenComparator, } from './utils.js';
|
|
7
7
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
8
8
|
import { P2PKH_INPUT_SIZE } from './constants.js';
|
|
9
9
|
import { TransactionBuilder } from './TransactionBuilder.js';
|
|
@@ -101,10 +101,7 @@ export class Transaction {
|
|
|
101
101
|
async send(raw) {
|
|
102
102
|
const tx = await this.build();
|
|
103
103
|
// Debug the transaction locally before sending so any errors are caught early
|
|
104
|
-
|
|
105
|
-
if (!this.abiFunction.covenant) {
|
|
106
|
-
await this.debug();
|
|
107
|
-
}
|
|
104
|
+
await this.debug();
|
|
108
105
|
try {
|
|
109
106
|
const txid = await this.contract.provider.sendRawTransaction(tx);
|
|
110
107
|
return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
|
|
@@ -120,7 +117,7 @@ export class Transaction {
|
|
|
120
117
|
console.warn('No debug information found in artifact. Recompile with cashc version 0.10.0 or newer to get better debugging information.');
|
|
121
118
|
}
|
|
122
119
|
const template = await this.getLibauthTemplate();
|
|
123
|
-
return debugTemplate(template, this.contract.artifact);
|
|
120
|
+
return debugTemplate(template, [this.contract.artifact]);
|
|
124
121
|
}
|
|
125
122
|
async bitauthUri() {
|
|
126
123
|
const template = await this.getLibauthTemplate();
|
|
@@ -259,13 +256,8 @@ export class Transaction {
|
|
|
259
256
|
// (see https://transactionfee.info/charts/bitcoin-script-ecdsa-length/)
|
|
260
257
|
return placeholder(73);
|
|
261
258
|
});
|
|
262
|
-
// Create a placeholder
|
|
263
|
-
const
|
|
264
|
-
? placeholder(getPreimageSize(scriptToBytecode(this.contract.redeemScript)))
|
|
265
|
-
: undefined;
|
|
266
|
-
// Create a placeholder input script for size calculation using the placeholder
|
|
267
|
-
// arguments and correctly sized placeholder preimage
|
|
268
|
-
const placeholderScript = createInputScript(this.contract.redeemScript, placeholderArgs, this.selector, placeholderPreimage);
|
|
259
|
+
// Create a placeholder input script for size calculation using the placeholder arguments
|
|
260
|
+
const placeholderScript = createInputScript(this.contract.redeemScript, placeholderArgs, this.selector);
|
|
269
261
|
// Add one extra byte per input to over-estimate tx-in count
|
|
270
262
|
const contractInputSize = getInputSize(placeholderScript) + 1;
|
|
271
263
|
// Note that we use the addPrecision function to add "decimal points" to BigInt numbers
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { Transaction as LibauthTransaction, WalletTemplate } from '@bitauth/libauth';
|
|
1
2
|
import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions } from './interfaces.js';
|
|
2
3
|
import { NetworkProvider } from './network/index.js';
|
|
4
|
+
import { DebugResults } from './debugging.js';
|
|
3
5
|
export interface TransactionBuilderOptions {
|
|
4
6
|
provider: NetworkProvider;
|
|
5
7
|
}
|
|
@@ -7,8 +9,8 @@ export declare class TransactionBuilder {
|
|
|
7
9
|
provider: NetworkProvider;
|
|
8
10
|
inputs: UnlockableUtxo[];
|
|
9
11
|
outputs: Output[];
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
locktime: number;
|
|
13
|
+
maxFee?: bigint;
|
|
12
14
|
constructor(options: TransactionBuilderOptions);
|
|
13
15
|
addInput(utxo: Utxo, unlocker: Unlocker, options?: InputOptions): this;
|
|
14
16
|
addInputs(utxos: Utxo[], unlocker: Unlocker, options?: InputOptions): this;
|
|
@@ -19,7 +21,11 @@ export declare class TransactionBuilder {
|
|
|
19
21
|
setLocktime(locktime: number): this;
|
|
20
22
|
setMaxFee(maxFee: bigint): this;
|
|
21
23
|
private checkMaxFee;
|
|
24
|
+
buildLibauthTransaction(): LibauthTransaction;
|
|
22
25
|
build(): string;
|
|
26
|
+
debug(): Promise<DebugResults>;
|
|
27
|
+
bitauthUri(): string;
|
|
28
|
+
getLibauthTemplate(): WalletTemplate;
|
|
23
29
|
send(): Promise<TransactionDetails>;
|
|
24
30
|
send(raw: true): Promise<string>;
|
|
25
31
|
private getTxDetails;
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { binToHex, decodeTransaction, encodeTransaction, hexToBin, } from '@bitauth/libauth';
|
|
2
2
|
import delay from 'delay';
|
|
3
3
|
import { isUnlockableUtxo, } from './interfaces.js';
|
|
4
|
-
import { cashScriptOutputToLibauthOutput, createOpReturnOutput, validateOutput, } from './utils.js';
|
|
4
|
+
import { cashScriptOutputToLibauthOutput, createOpReturnOutput, validateInput, validateOutput, } from './utils.js';
|
|
5
5
|
import { FailedTransactionError } from './Errors.js';
|
|
6
|
+
import { getBitauthUri } from './LibauthTemplate.js';
|
|
7
|
+
import { debugLibauthTemplate, getLibauthTemplates } from './advanced/LibauthTemplate.js';
|
|
6
8
|
const DEFAULT_SEQUENCE = 0xfffffffe;
|
|
7
9
|
export class TransactionBuilder {
|
|
8
10
|
constructor(options) {
|
|
9
11
|
this.inputs = [];
|
|
10
12
|
this.outputs = [];
|
|
13
|
+
this.locktime = 0;
|
|
11
14
|
this.provider = options.provider;
|
|
12
15
|
}
|
|
13
16
|
addInput(utxo, unlocker, options) {
|
|
14
|
-
this.
|
|
15
|
-
return this;
|
|
17
|
+
return this.addInputs([utxo], unlocker, options);
|
|
16
18
|
}
|
|
17
19
|
addInputs(utxos, unlocker, options) {
|
|
20
|
+
utxos.forEach(validateInput);
|
|
18
21
|
if ((!unlocker && utxos.some((utxo) => !isUnlockableUtxo(utxo)))
|
|
19
22
|
|| (unlocker && utxos.some((utxo) => isUnlockableUtxo(utxo)))) {
|
|
20
23
|
throw new Error('Either all UTXOs must have an individual unlocker specified, or no UTXOs must have an individual unlocker specified and a shared unlocker must be provided');
|
|
@@ -57,7 +60,7 @@ export class TransactionBuilder {
|
|
|
57
60
|
throw new Error(`Transaction fee of ${fee} is higher than max fee of ${this.maxFee}`);
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
|
-
|
|
63
|
+
buildLibauthTransaction() {
|
|
61
64
|
this.checkMaxFee();
|
|
62
65
|
const inputs = this.inputs.map((utxo) => ({
|
|
63
66
|
outpointIndex: utxo.vout,
|
|
@@ -85,10 +88,27 @@ export class TransactionBuilder {
|
|
|
85
88
|
inputScripts.forEach((script, i) => {
|
|
86
89
|
transaction.inputs[i].unlockingBytecode = script;
|
|
87
90
|
});
|
|
91
|
+
return transaction;
|
|
92
|
+
}
|
|
93
|
+
build() {
|
|
94
|
+
const transaction = this.buildLibauthTransaction();
|
|
88
95
|
return binToHex(encodeTransaction(transaction));
|
|
89
96
|
}
|
|
97
|
+
// method to debug the transaction with libauth VM, throws upon evaluation error
|
|
98
|
+
// TODO: Remove the async in the future (this currently breaks our debugging tests)
|
|
99
|
+
async debug() {
|
|
100
|
+
return debugLibauthTemplate(this.getLibauthTemplate(), this);
|
|
101
|
+
}
|
|
102
|
+
bitauthUri() {
|
|
103
|
+
return getBitauthUri(this.getLibauthTemplate());
|
|
104
|
+
}
|
|
105
|
+
getLibauthTemplate() {
|
|
106
|
+
return getLibauthTemplates(this);
|
|
107
|
+
}
|
|
90
108
|
async send(raw) {
|
|
91
109
|
const tx = this.build();
|
|
110
|
+
// Debug the transaction locally before sending so any errors are caught early
|
|
111
|
+
await this.debug();
|
|
92
112
|
try {
|
|
93
113
|
const txid = await this.provider.sendRawTransaction(tx);
|
|
94
114
|
return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { TransactionBch, WalletTemplate } from '@bitauth/libauth';
|
|
2
|
+
import { AbiFunction, Artifact } from '@cashscript/utils';
|
|
3
|
+
import { EncodedConstructorArgument, EncodedFunctionArgument } from '../Argument.js';
|
|
4
|
+
import { Contract } from '../Contract.js';
|
|
5
|
+
import { DebugResults } from '../debugging.js';
|
|
6
|
+
import { AddressType } from '../interfaces.js';
|
|
7
|
+
import SignatureTemplate from '../SignatureTemplate.js';
|
|
8
|
+
import { Transaction } from '../Transaction.js';
|
|
9
|
+
import { TransactionBuilder } from '../TransactionBuilder.js';
|
|
10
|
+
/**
|
|
11
|
+
* Generates template entities for P2PKH (Pay to Public Key Hash) placeholder scripts.
|
|
12
|
+
*
|
|
13
|
+
* Follows the WalletTemplateEntity specification from:
|
|
14
|
+
* https://ide.bitauth.com/authentication-template-v0.schema.json
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
export declare const generateTemplateEntitiesP2PKH: (index: number) => WalletTemplate["entities"];
|
|
18
|
+
/**
|
|
19
|
+
* Generates template entities for P2SH (Pay to Script Hash) placeholder scripts.
|
|
20
|
+
*
|
|
21
|
+
* Follows the WalletTemplateEntity specification from:
|
|
22
|
+
* https://ide.bitauth.com/authentication-template-v0.schema.json
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
export declare const generateTemplateEntitiesP2SH: (artifact: Artifact, abiFunction: AbiFunction, encodedFunctionArgs: EncodedFunctionArgument[]) => WalletTemplate["entities"];
|
|
26
|
+
/**
|
|
27
|
+
* Generates template scripts for P2PKH (Pay to Public Key Hash) placeholder scripts.
|
|
28
|
+
*
|
|
29
|
+
* Follows the WalletTemplateScript specification from:
|
|
30
|
+
* https://ide.bitauth.com/authentication-template-v0.schema.json
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
export declare const generateTemplateScriptsP2PKH: (template: SignatureTemplate, index: number) => WalletTemplate["scripts"];
|
|
34
|
+
/**
|
|
35
|
+
* Generates template scripts for P2SH (Pay to Script Hash) placeholder scripts.
|
|
36
|
+
*
|
|
37
|
+
* Follows the WalletTemplateScript specification from:
|
|
38
|
+
* https://ide.bitauth.com/authentication-template-v0.schema.json
|
|
39
|
+
*
|
|
40
|
+
*/
|
|
41
|
+
export declare const generateTemplateScriptsP2SH: (artifact: Artifact, addressType: AddressType, abiFunction: AbiFunction, encodedFunctionArgs: EncodedFunctionArgument[], encodedConstructorArgs: EncodedConstructorArgument[], scenarioIds: string[]) => WalletTemplate["scripts"];
|
|
42
|
+
export declare const generateTemplateScenarios: (scenarioIdentifier: string, contract: Contract, libauthTransaction: TransactionBch, csTransaction: Transaction, abiFunction: AbiFunction, encodedFunctionArgs: EncodedFunctionArgument[], slotIndex: number) => WalletTemplate["scenarios"];
|
|
43
|
+
export declare const getLibauthTemplates: (txn: TransactionBuilder) => WalletTemplate;
|
|
44
|
+
export declare const debugLibauthTemplate: (template: WalletTemplate, transaction: TransactionBuilder) => DebugResults;
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { binToHex, } from '@bitauth/libauth';
|
|
2
|
+
import { encodeFunctionArguments } from '../Argument.js';
|
|
3
|
+
import { debugTemplate } from '../debugging.js';
|
|
4
|
+
import { addHexPrefixExceptEmpty, formatBytecodeForDebugging, formatParametersForDebugging, generateTemplateScenarioBytecode, generateTemplateScenarioKeys, generateTemplateScenarioParametersValues, generateTemplateScenarioTransactionOutputLockingBytecode, getHashTypeName, getSignatureAlgorithmName, serialiseTokenDetails, } from '../LibauthTemplate.js';
|
|
5
|
+
import SignatureTemplate from '../SignatureTemplate.js';
|
|
6
|
+
import { addressToLockScript, snakeCase, titleCase } from '../utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Generates template entities for P2PKH (Pay to Public Key Hash) placeholder scripts.
|
|
9
|
+
*
|
|
10
|
+
* Follows the WalletTemplateEntity specification from:
|
|
11
|
+
* https://ide.bitauth.com/authentication-template-v0.schema.json
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
export const generateTemplateEntitiesP2PKH = (index) => {
|
|
15
|
+
const lockScriptName = `p2pkh_placeholder_lock_${index}`;
|
|
16
|
+
const unlockScriptName = `p2pkh_placeholder_unlock_${index}`;
|
|
17
|
+
return {
|
|
18
|
+
[`signer_${index}`]: {
|
|
19
|
+
scripts: [lockScriptName, unlockScriptName],
|
|
20
|
+
description: `placeholder_key_${index}`,
|
|
21
|
+
name: `Signer ${index}`,
|
|
22
|
+
variables: {
|
|
23
|
+
[`placeholder_key_${index}`]: {
|
|
24
|
+
description: '',
|
|
25
|
+
name: `Placeholder Key ${index}`,
|
|
26
|
+
type: 'Key',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Generates template entities for P2SH (Pay to Script Hash) placeholder scripts.
|
|
34
|
+
*
|
|
35
|
+
* Follows the WalletTemplateEntity specification from:
|
|
36
|
+
* https://ide.bitauth.com/authentication-template-v0.schema.json
|
|
37
|
+
*
|
|
38
|
+
*/
|
|
39
|
+
export const generateTemplateEntitiesP2SH = (artifact, abiFunction, encodedFunctionArgs) => {
|
|
40
|
+
const functionParameters = Object.fromEntries(abiFunction.inputs.map((input, index) => ([
|
|
41
|
+
snakeCase(input.name),
|
|
42
|
+
{
|
|
43
|
+
description: `"${input.name}" parameter of function "${abiFunction.name}"`,
|
|
44
|
+
name: titleCase(input.name),
|
|
45
|
+
type: encodedFunctionArgs[index] instanceof SignatureTemplate ? 'Key' : 'WalletData',
|
|
46
|
+
},
|
|
47
|
+
])));
|
|
48
|
+
const constructorParameters = Object.fromEntries(artifact.constructorInputs.map((input) => ([
|
|
49
|
+
snakeCase(input.name),
|
|
50
|
+
{
|
|
51
|
+
description: `"${input.name}" parameter of this contract`,
|
|
52
|
+
name: titleCase(input.name),
|
|
53
|
+
type: 'WalletData',
|
|
54
|
+
},
|
|
55
|
+
])));
|
|
56
|
+
const entities = {
|
|
57
|
+
[snakeCase(artifact.contractName + 'Parameters')]: {
|
|
58
|
+
description: 'Contract creation and function parameters',
|
|
59
|
+
name: titleCase(artifact.contractName),
|
|
60
|
+
scripts: [
|
|
61
|
+
snakeCase(artifact.contractName + '_lock'),
|
|
62
|
+
snakeCase(artifact.contractName + '_' + abiFunction.name + '_unlock'),
|
|
63
|
+
],
|
|
64
|
+
variables: {
|
|
65
|
+
...functionParameters,
|
|
66
|
+
...constructorParameters,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
// function_index is a special variable that indicates the function to execute
|
|
71
|
+
if (artifact.abi.length > 1) {
|
|
72
|
+
entities[snakeCase(artifact.contractName + 'Parameters')].variables.function_index = {
|
|
73
|
+
description: 'Script function index to execute',
|
|
74
|
+
name: titleCase('function_index'),
|
|
75
|
+
type: 'WalletData',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return entities;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Generates template scripts for P2PKH (Pay to Public Key Hash) placeholder scripts.
|
|
82
|
+
*
|
|
83
|
+
* Follows the WalletTemplateScript specification from:
|
|
84
|
+
* https://ide.bitauth.com/authentication-template-v0.schema.json
|
|
85
|
+
*
|
|
86
|
+
*/
|
|
87
|
+
export const generateTemplateScriptsP2PKH = (template, index) => {
|
|
88
|
+
const scripts = {};
|
|
89
|
+
const lockScriptName = `p2pkh_placeholder_lock_${index}`;
|
|
90
|
+
const unlockScriptName = `p2pkh_placeholder_unlock_${index}`;
|
|
91
|
+
const placeholderKeyName = `placeholder_key_${index}`;
|
|
92
|
+
const signatureAlgorithmName = getSignatureAlgorithmName(template.getSignatureAlgorithm());
|
|
93
|
+
const hashtypeName = getHashTypeName(template.getHashType(false));
|
|
94
|
+
const signatureString = `${placeholderKeyName}.${signatureAlgorithmName}.${hashtypeName}`;
|
|
95
|
+
// add extra unlocking and locking script for P2PKH inputs spent alongside our contract
|
|
96
|
+
// this is needed for correct cross-references in the template
|
|
97
|
+
scripts[unlockScriptName] = {
|
|
98
|
+
name: unlockScriptName,
|
|
99
|
+
script: `<${signatureString}>\n<${placeholderKeyName}.public_key>`,
|
|
100
|
+
unlocks: lockScriptName,
|
|
101
|
+
};
|
|
102
|
+
scripts[lockScriptName] = {
|
|
103
|
+
lockingType: 'standard',
|
|
104
|
+
name: lockScriptName,
|
|
105
|
+
script: `OP_DUP\nOP_HASH160 <$(<${placeholderKeyName}.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG`,
|
|
106
|
+
};
|
|
107
|
+
return scripts;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Generates template scripts for P2SH (Pay to Script Hash) placeholder scripts.
|
|
111
|
+
*
|
|
112
|
+
* Follows the WalletTemplateScript specification from:
|
|
113
|
+
* https://ide.bitauth.com/authentication-template-v0.schema.json
|
|
114
|
+
*
|
|
115
|
+
*/
|
|
116
|
+
export const generateTemplateScriptsP2SH = (artifact, addressType, abiFunction, encodedFunctionArgs, encodedConstructorArgs, scenarioIds) => {
|
|
117
|
+
// definition of locking scripts and unlocking scripts with their respective bytecode
|
|
118
|
+
return {
|
|
119
|
+
[snakeCase(artifact.contractName + '_' + abiFunction.name + '_unlock')]: generateTemplateUnlockScript(artifact, abiFunction, encodedFunctionArgs, scenarioIds),
|
|
120
|
+
[snakeCase(artifact.contractName + '_lock')]: generateTemplateLockScript(artifact, addressType, encodedConstructorArgs),
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Generates a template lock script for a P2SH (Pay to Script Hash) placeholder script.
|
|
125
|
+
*
|
|
126
|
+
* Follows the WalletTemplateScriptLocking specification from:
|
|
127
|
+
* https://ide.bitauth.com/authentication-template-v0.schema.json
|
|
128
|
+
*
|
|
129
|
+
*/
|
|
130
|
+
const generateTemplateLockScript = (artifact, addressType, constructorArguments) => {
|
|
131
|
+
return {
|
|
132
|
+
lockingType: addressType,
|
|
133
|
+
name: artifact.contractName,
|
|
134
|
+
script: [
|
|
135
|
+
`// "${artifact.contractName}" contract constructor parameters`,
|
|
136
|
+
formatParametersForDebugging(artifact.constructorInputs, constructorArguments),
|
|
137
|
+
'',
|
|
138
|
+
'// bytecode',
|
|
139
|
+
formatBytecodeForDebugging(artifact),
|
|
140
|
+
].join('\n'),
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Generates a template unlock script for a P2SH (Pay to Script Hash) placeholder script.
|
|
145
|
+
*
|
|
146
|
+
* Follows the WalletTemplateScriptUnlocking specification from:
|
|
147
|
+
* https://ide.bitauth.com/authentication-template-v0.schema.json
|
|
148
|
+
*
|
|
149
|
+
*/
|
|
150
|
+
const generateTemplateUnlockScript = (artifact, abiFunction, encodedFunctionArgs, scenarioIds) => {
|
|
151
|
+
const functionIndex = artifact.abi.findIndex((func) => func.name === abiFunction.name);
|
|
152
|
+
const functionIndexString = artifact.abi.length > 1
|
|
153
|
+
? ['// function index in contract', `<function_index> // int = <${functionIndex}>`, '']
|
|
154
|
+
: [];
|
|
155
|
+
return {
|
|
156
|
+
// this unlocking script must pass our only scenario
|
|
157
|
+
passes: scenarioIds,
|
|
158
|
+
name: abiFunction.name,
|
|
159
|
+
script: [
|
|
160
|
+
`// "${abiFunction.name}" function parameters`,
|
|
161
|
+
formatParametersForDebugging(abiFunction.inputs, encodedFunctionArgs),
|
|
162
|
+
'',
|
|
163
|
+
...functionIndexString,
|
|
164
|
+
].join('\n'),
|
|
165
|
+
unlocks: snakeCase(artifact.contractName + '_lock'),
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
export const generateTemplateScenarios = (scenarioIdentifier, contract, libauthTransaction, csTransaction, abiFunction, encodedFunctionArgs, slotIndex) => {
|
|
169
|
+
const artifact = contract.artifact;
|
|
170
|
+
const encodedConstructorArgs = contract.encodedConstructorArgs;
|
|
171
|
+
const scenarios = {
|
|
172
|
+
// single scenario to spend out transaction under test given the CashScript parameters provided
|
|
173
|
+
[scenarioIdentifier]: {
|
|
174
|
+
name: snakeCase(artifact.contractName + '_' + abiFunction.name + 'Evaluate'),
|
|
175
|
+
description: 'An example evaluation where this script execution passes.',
|
|
176
|
+
data: {
|
|
177
|
+
// encode values for the variables defined above in `entities` property
|
|
178
|
+
bytecode: {
|
|
179
|
+
...generateTemplateScenarioParametersValues(abiFunction.inputs, encodedFunctionArgs),
|
|
180
|
+
...generateTemplateScenarioParametersValues(artifact.constructorInputs, encodedConstructorArgs),
|
|
181
|
+
},
|
|
182
|
+
currentBlockHeight: 2,
|
|
183
|
+
currentBlockTime: Math.round(+new Date() / 1000),
|
|
184
|
+
keys: {
|
|
185
|
+
privateKeys: generateTemplateScenarioKeys(abiFunction.inputs, encodedFunctionArgs),
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
transaction: generateTemplateScenarioTransaction(contract, libauthTransaction, csTransaction, slotIndex),
|
|
189
|
+
sourceOutputs: generateTemplateScenarioSourceOutputs(csTransaction, slotIndex),
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
if (artifact.abi.length > 1) {
|
|
193
|
+
const functionIndex = artifact.abi.findIndex((func) => func.name === abiFunction.name);
|
|
194
|
+
scenarios[scenarioIdentifier].data.bytecode.function_index = functionIndex.toString();
|
|
195
|
+
}
|
|
196
|
+
return scenarios;
|
|
197
|
+
};
|
|
198
|
+
const generateTemplateScenarioTransaction = (contract, libauthTransaction, csTransaction, slotIndex) => {
|
|
199
|
+
const inputs = libauthTransaction.inputs.map((input, index) => {
|
|
200
|
+
const csInput = csTransaction.inputs[index];
|
|
201
|
+
return {
|
|
202
|
+
outpointIndex: input.outpointIndex,
|
|
203
|
+
outpointTransactionHash: binToHex(input.outpointTransactionHash),
|
|
204
|
+
sequenceNumber: input.sequenceNumber,
|
|
205
|
+
unlockingBytecode: generateTemplateScenarioBytecode(csInput, `p2pkh_placeholder_unlock_${index}`, `placeholder_key_${index}`, slotIndex === index),
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
const locktime = libauthTransaction.locktime;
|
|
209
|
+
const outputs = libauthTransaction.outputs.map((output, index) => {
|
|
210
|
+
const csOutput = csTransaction.outputs[index];
|
|
211
|
+
return {
|
|
212
|
+
lockingBytecode: generateTemplateScenarioTransactionOutputLockingBytecode(csOutput, contract),
|
|
213
|
+
token: serialiseTokenDetails(output.token),
|
|
214
|
+
valueSatoshis: Number(output.valueSatoshis),
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
const version = libauthTransaction.version;
|
|
218
|
+
return { inputs, locktime, outputs, version };
|
|
219
|
+
};
|
|
220
|
+
const generateTemplateScenarioSourceOutputs = (csTransaction, slotIndex) => {
|
|
221
|
+
return csTransaction.inputs.map((input, index) => {
|
|
222
|
+
return {
|
|
223
|
+
lockingBytecode: generateTemplateScenarioBytecode(input, `p2pkh_placeholder_lock_${index}`, `placeholder_key_${index}`, index === slotIndex),
|
|
224
|
+
valueSatoshis: Number(input.satoshis),
|
|
225
|
+
token: serialiseTokenDetails(input.token),
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Creates a transaction object from a TransactionBuilder instance
|
|
231
|
+
*
|
|
232
|
+
* @param txn - The TransactionBuilder instance to convert
|
|
233
|
+
* @returns A transaction object containing inputs, outputs, locktime and version
|
|
234
|
+
*/
|
|
235
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
236
|
+
const createCSTransaction = (txn) => {
|
|
237
|
+
const csTransaction = {
|
|
238
|
+
inputs: txn.inputs,
|
|
239
|
+
locktime: txn.locktime,
|
|
240
|
+
outputs: txn.outputs,
|
|
241
|
+
version: 2,
|
|
242
|
+
};
|
|
243
|
+
return csTransaction;
|
|
244
|
+
};
|
|
245
|
+
export const getLibauthTemplates = (txn) => {
|
|
246
|
+
const libauthTransaction = txn.buildLibauthTransaction();
|
|
247
|
+
const csTransaction = createCSTransaction(txn);
|
|
248
|
+
const baseTemplate = {
|
|
249
|
+
$schema: 'https://ide.bitauth.com/authentication-template-v0.schema.json',
|
|
250
|
+
description: 'Imported from cashscript',
|
|
251
|
+
name: 'CashScript Generated Debugging Template',
|
|
252
|
+
supported: ['BCH_2023_05'],
|
|
253
|
+
version: 0,
|
|
254
|
+
entities: {},
|
|
255
|
+
scripts: {},
|
|
256
|
+
scenarios: {},
|
|
257
|
+
};
|
|
258
|
+
// Initialize collections for entities, scripts, and scenarios
|
|
259
|
+
const entities = {};
|
|
260
|
+
const scripts = {};
|
|
261
|
+
const scenarios = {};
|
|
262
|
+
// Initialize collections for P2PKH entities and scripts
|
|
263
|
+
const p2pkhEntities = {};
|
|
264
|
+
const p2pkhScripts = {};
|
|
265
|
+
// Initialize bytecode mappings, these will be used to map the locking and unlocking scripts and naming the scripts
|
|
266
|
+
const unlockingBytecodeToLockingBytecodeParams = {};
|
|
267
|
+
const lockingBytecodeToLockingBytecodeParams = {};
|
|
268
|
+
for (const [idx, input] of txn.inputs.entries()) {
|
|
269
|
+
// If template exists on the input, it indicates this is a P2PKH (Pay to Public Key Hash) input
|
|
270
|
+
if (input.unlocker?.template) {
|
|
271
|
+
// @ts-ignore TODO: Remove UtxoP2PKH type and only use UnlockableUtxo in Libaith Template generation
|
|
272
|
+
input.template = input.unlocker?.template; // Added to support P2PKH inputs in buildTemplate
|
|
273
|
+
Object.assign(p2pkhEntities, generateTemplateEntitiesP2PKH(idx));
|
|
274
|
+
Object.assign(p2pkhScripts, generateTemplateScriptsP2PKH(input.unlocker.template, idx));
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
// If contract exists on the input, it indicates this is a contract input
|
|
278
|
+
if (input.unlocker?.contract) {
|
|
279
|
+
const contract = input.unlocker?.contract;
|
|
280
|
+
const abiFunction = input.unlocker?.abiFunction;
|
|
281
|
+
if (!abiFunction) {
|
|
282
|
+
throw new Error('No ABI function found in unlocker');
|
|
283
|
+
}
|
|
284
|
+
// Find matching function and index from contract.unlock Object, this uses Function Reference Comparison.
|
|
285
|
+
// Generate unique scenario identifier by combining contract name, function name and counter
|
|
286
|
+
const baseIdentifier = `${contract.artifact.contractName}_${abiFunction.name}Evaluate`;
|
|
287
|
+
let scenarioIdentifier = baseIdentifier;
|
|
288
|
+
let counter = 0;
|
|
289
|
+
const scenarioIds = [snakeCase(scenarioIdentifier)];
|
|
290
|
+
// Find first available unique identifier by incrementing counter
|
|
291
|
+
while (scenarios[snakeCase(scenarioIdentifier)]) {
|
|
292
|
+
counter++;
|
|
293
|
+
scenarioIdentifier = `${baseIdentifier}${counter}`;
|
|
294
|
+
scenarioIds.push(snakeCase(scenarioIdentifier));
|
|
295
|
+
}
|
|
296
|
+
scenarioIdentifier = snakeCase(scenarioIdentifier);
|
|
297
|
+
// Encode the function arguments for this contract input
|
|
298
|
+
const encodedArgs = encodeFunctionArguments(abiFunction, input.unlocker.params ?? []);
|
|
299
|
+
// Generate a scenario object for this contract input
|
|
300
|
+
Object.assign(scenarios, generateTemplateScenarios(scenarioIdentifier, contract, libauthTransaction, csTransaction, abiFunction, encodedArgs, idx));
|
|
301
|
+
// Generate entities for this contract input
|
|
302
|
+
const entity = generateTemplateEntitiesP2SH(contract.artifact, abiFunction, encodedArgs);
|
|
303
|
+
// Generate scripts for this contract input
|
|
304
|
+
const script = generateTemplateScriptsP2SH(contract.artifact, contract.addressType, abiFunction, encodedArgs, contract.encodedConstructorArgs, scenarioIds);
|
|
305
|
+
// Find the lock script name for this contract input
|
|
306
|
+
const lockScriptName = Object.keys(script).find(scriptName => scriptName.includes('_lock'));
|
|
307
|
+
if (lockScriptName) {
|
|
308
|
+
// Generate bytecodes for this contract input
|
|
309
|
+
const csInput = csTransaction.inputs[idx];
|
|
310
|
+
const unlockingBytecode = binToHex(libauthTransaction.inputs[idx].unlockingBytecode);
|
|
311
|
+
const lockingScriptParams = generateLockingScriptParams(csInput.unlocker.contract, csInput, lockScriptName);
|
|
312
|
+
// Assign a name to the unlocking bytecode so later it can be used to replace the bytecode/slot in scenarios
|
|
313
|
+
unlockingBytecodeToLockingBytecodeParams[unlockingBytecode] = lockingScriptParams;
|
|
314
|
+
// Assign a name to the locking bytecode so later it can be used to replace with bytecode/slot in scenarios
|
|
315
|
+
lockingBytecodeToLockingBytecodeParams[binToHex(addressToLockScript(contract.address))] = lockingScriptParams;
|
|
316
|
+
}
|
|
317
|
+
// Add entities and scripts to the base template and repeat the process for the next input
|
|
318
|
+
Object.assign(entities, entity);
|
|
319
|
+
Object.assign(scripts, script);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Merge P2PKH scripts
|
|
323
|
+
for (const entity of Object.values(p2pkhEntities)) {
|
|
324
|
+
if (entity.scripts) {
|
|
325
|
+
entity.scripts = [...Object.keys(scripts), ...entity.scripts];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
Object.assign(entities, p2pkhEntities);
|
|
329
|
+
Object.assign(scripts, p2pkhScripts);
|
|
330
|
+
const finalTemplate = { ...baseTemplate, entities, scripts, scenarios };
|
|
331
|
+
// Loop through all scenarios and map the locking and unlocking scripts to the scenarios
|
|
332
|
+
// Replace the script tag with the identifiers we created earlier
|
|
333
|
+
// For Inputs
|
|
334
|
+
for (const scenario of Object.values(scenarios)) {
|
|
335
|
+
for (const [idx, input] of libauthTransaction.inputs.entries()) {
|
|
336
|
+
const unlockingBytecode = binToHex(input.unlockingBytecode);
|
|
337
|
+
// If false then it stays lockingBytecode: {}
|
|
338
|
+
if (unlockingBytecodeToLockingBytecodeParams[unlockingBytecode]) {
|
|
339
|
+
// ['slot'] this identifies the source output in which the locking script under test will be placed
|
|
340
|
+
if (Array.isArray(scenario?.sourceOutputs?.[idx]?.lockingBytecode))
|
|
341
|
+
continue;
|
|
342
|
+
// If true then assign a name to the locking bytecode script.
|
|
343
|
+
if (scenario.sourceOutputs && scenario.sourceOutputs[idx]) {
|
|
344
|
+
scenario.sourceOutputs[idx] = {
|
|
345
|
+
...scenario.sourceOutputs[idx],
|
|
346
|
+
lockingBytecode: unlockingBytecodeToLockingBytecodeParams[unlockingBytecode],
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// For Outputs
|
|
352
|
+
for (const [idx, output] of libauthTransaction.outputs.entries()) {
|
|
353
|
+
const lockingBytecode = binToHex(output.lockingBytecode);
|
|
354
|
+
// If false then it stays lockingBytecode: {}
|
|
355
|
+
if (lockingBytecodeToLockingBytecodeParams[lockingBytecode]) {
|
|
356
|
+
// ['slot'] this identifies the source output in which the locking script under test will be placed
|
|
357
|
+
if (Array.isArray(scenario?.transaction?.outputs?.[idx]?.lockingBytecode))
|
|
358
|
+
continue;
|
|
359
|
+
// If true then assign a name to the locking bytecode script.
|
|
360
|
+
if (scenario?.transaction && scenario?.transaction?.outputs && scenario?.transaction?.outputs[idx]) {
|
|
361
|
+
scenario.transaction.outputs[idx] = {
|
|
362
|
+
...scenario.transaction.outputs[idx],
|
|
363
|
+
lockingBytecode: lockingBytecodeToLockingBytecodeParams[lockingBytecode],
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return finalTemplate;
|
|
370
|
+
};
|
|
371
|
+
export const debugLibauthTemplate = (template, transaction) => {
|
|
372
|
+
const allArtifacts = transaction.inputs
|
|
373
|
+
.map(input => input.unlocker?.contract)
|
|
374
|
+
.filter((contract) => !!contract)
|
|
375
|
+
.map(contract => contract.artifact);
|
|
376
|
+
return debugTemplate(template, allArtifacts);
|
|
377
|
+
};
|
|
378
|
+
const generateLockingScriptParams = (contract, csInput, lockScriptName) => {
|
|
379
|
+
if (!csInput.unlocker?.contract)
|
|
380
|
+
return {
|
|
381
|
+
script: lockScriptName,
|
|
382
|
+
};
|
|
383
|
+
const constructorParamsEntries = contract.artifact.constructorInputs
|
|
384
|
+
.map(({ name }, index) => [
|
|
385
|
+
name,
|
|
386
|
+
addHexPrefixExceptEmpty(binToHex(csInput.unlocker.contract.encodedConstructorArgs[index])),
|
|
387
|
+
]);
|
|
388
|
+
const constructorParams = Object.fromEntries(constructorParamsEntries);
|
|
389
|
+
return {
|
|
390
|
+
script: lockScriptName,
|
|
391
|
+
overrides: {
|
|
392
|
+
bytecode: { ...constructorParams },
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
//# sourceMappingURL=LibauthTemplate.js.map
|
package/dist/debugging.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AuthenticationProgramStateCommon, WalletTemplate } from '@bitauth/libauth';
|
|
2
2
|
import { Artifact } from '@cashscript/utils';
|
|
3
|
-
export declare const evaluateTemplate: (template: WalletTemplate) => boolean;
|
|
4
3
|
export type DebugResult = AuthenticationProgramStateCommon[];
|
|
5
|
-
export
|
|
4
|
+
export type DebugResults = Record<string, DebugResult>;
|
|
5
|
+
export declare const debugTemplate: (template: WalletTemplate, artifacts: Artifact[]) => DebugResults;
|
package/dist/debugging.js
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
|
-
import { AuthenticationErrorCommon, binToHex, createCompiler,
|
|
1
|
+
import { AuthenticationErrorCommon, binToHex, createCompiler, createVirtualMachineBch2025, encodeAuthenticationInstruction, walletTemplateToCompilerConfiguration } from '@bitauth/libauth';
|
|
2
2
|
import { Op, PrimitiveType, bytecodeToAsm, decodeBool, decodeInt, decodeString } from '@cashscript/utils';
|
|
3
|
-
import { findLastIndex, toRegExp } from './utils.js';
|
|
3
|
+
import { findLastIndex, snakeCase, toRegExp } from './utils.js';
|
|
4
4
|
import { FailedRequireError, FailedTransactionError, FailedTransactionEvaluationError } from './Errors.js';
|
|
5
5
|
import { getBitauthUri } from './LibauthTemplate.js';
|
|
6
|
-
//
|
|
7
|
-
export const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
// debugs the template, optionally logging the execution data
|
|
7
|
+
export const debugTemplate = (template, artifacts) => {
|
|
8
|
+
const results = {};
|
|
9
|
+
const unlockingScriptIds = Object.keys(template.scripts).filter((key) => 'unlocks' in template.scripts[key]);
|
|
10
|
+
for (const unlockingScriptId of unlockingScriptIds) {
|
|
11
|
+
const scenarioIds = template.scripts[unlockingScriptId].passes ?? [];
|
|
12
|
+
// There are no scenarios defined for P2PKH placeholder scripts, so we skip them
|
|
13
|
+
if (scenarioIds.length === 0)
|
|
14
|
+
continue;
|
|
15
|
+
const matchingArtifact = artifacts.find((artifact) => unlockingScriptId.startsWith(snakeCase(artifact.contractName)));
|
|
16
|
+
if (!matchingArtifact) {
|
|
17
|
+
throw new Error(`No artifact found for unlocking script ${unlockingScriptId}`);
|
|
18
|
+
}
|
|
19
|
+
for (const scenarioId of scenarioIds) {
|
|
20
|
+
results[`${unlockingScriptId}.${scenarioId}`] = debugSingleScenario(template, matchingArtifact, unlockingScriptId, scenarioId);
|
|
21
|
+
}
|
|
12
22
|
}
|
|
13
|
-
return
|
|
23
|
+
return results;
|
|
14
24
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const { vm, program } = createProgram(template);
|
|
25
|
+
const debugSingleScenario = (template, artifact, unlockingScriptId, scenarioId) => {
|
|
26
|
+
const { vm, program } = createProgram(template, unlockingScriptId, scenarioId);
|
|
18
27
|
const fullDebugSteps = vm.debug(program);
|
|
19
28
|
// P2SH executions have 3 phases, we only want the last one (locking script execution)
|
|
20
29
|
// https://libauth.org/types/AuthenticationVirtualMachine.html#__type.debug
|
|
@@ -39,7 +48,7 @@ export const debugTemplate = (template, artifact) => {
|
|
|
39
48
|
// preceding OP_CHECKSIG opcode. The error message is registered in the next instruction, so we need to increment
|
|
40
49
|
// the instruction pointer to get the correct error message from the require messages in the artifact.
|
|
41
50
|
// Note that we do NOT use this adjusted IP when passing the failing IP into the FailedRequireError
|
|
42
|
-
const isNullFail = lastExecutedDebugStep.error
|
|
51
|
+
const isNullFail = lastExecutedDebugStep.error.includes(AuthenticationErrorCommon.nonNullSignatureFailure);
|
|
43
52
|
const requireStatementIp = failingIp + (isNullFail ? 1 : 0);
|
|
44
53
|
const requireStatement = (artifact.debug?.requires ?? [])
|
|
45
54
|
.find((statement) => statement.ip === requireStatementIp);
|
|
@@ -72,15 +81,20 @@ export const debugTemplate = (template, artifact) => {
|
|
|
72
81
|
return fullDebugSteps;
|
|
73
82
|
};
|
|
74
83
|
// internal util. instantiates the virtual machine and compiles the template into a program
|
|
75
|
-
const createProgram = (template) => {
|
|
84
|
+
const createProgram = (template, unlockingScriptId, scenarioId) => {
|
|
76
85
|
const configuration = walletTemplateToCompilerConfiguration(template);
|
|
77
|
-
const vm =
|
|
86
|
+
const vm = createVirtualMachineBch2025();
|
|
78
87
|
const compiler = createCompiler(configuration);
|
|
88
|
+
if (!template.scripts[unlockingScriptId]) {
|
|
89
|
+
throw new Error(`No unlock script found in template for ID ${unlockingScriptId}`);
|
|
90
|
+
}
|
|
91
|
+
if (!template.scenarios?.[scenarioId]) {
|
|
92
|
+
throw new Error(`No scenario found in template for ID ${scenarioId}`);
|
|
93
|
+
}
|
|
79
94
|
const scenarioGeneration = compiler.generateScenario({
|
|
80
95
|
debug: true,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
scenarioId: 'evaluate_function',
|
|
96
|
+
unlockingScriptId,
|
|
97
|
+
scenarioId,
|
|
84
98
|
});
|
|
85
99
|
if (typeof scenarioGeneration === 'string') {
|
|
86
100
|
throw new FailedTransactionError(scenarioGeneration, getBitauthUri(template));
|
|
@@ -121,8 +135,9 @@ const failedFinalVerify = (evaluationResult) => {
|
|
|
121
135
|
// If any of the following errors occurred, then the final verify failed - any other messages
|
|
122
136
|
// indicate other kinds of failures
|
|
123
137
|
return toRegExp([
|
|
124
|
-
|
|
125
|
-
AuthenticationErrorCommon.
|
|
138
|
+
// TODO: Ask Jason to put these back into an enum and replace with the enum value
|
|
139
|
+
'The CashAssembly internal evaluation completed with an unexpected number of items on the stack (must be exactly 1).', // AuthenticationErrorCommon.requiresCleanStack,
|
|
140
|
+
'The CashAssembly internal evaluation completed with a non-empty control stack.', // AuthenticationErrorCommon.nonEmptyControlStack,
|
|
126
141
|
AuthenticationErrorCommon.unsuccessfulEvaluation,
|
|
127
142
|
]).test(evaluationResult);
|
|
128
143
|
};
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type Transaction } from '@bitauth/libauth';
|
|
2
2
|
import type { NetworkProvider } from './network/index.js';
|
|
3
3
|
import type SignatureTemplate from './SignatureTemplate.js';
|
|
4
|
+
import { Contract } from './Contract.js';
|
|
5
|
+
import { AbiFunction } from '@cashscript/utils';
|
|
6
|
+
import { FunctionArgument } from './Argument.js';
|
|
4
7
|
export interface Utxo {
|
|
5
8
|
txid: string;
|
|
6
9
|
vout: number;
|
|
@@ -23,6 +26,10 @@ export interface GenerateUnlockingBytecodeOptions {
|
|
|
23
26
|
export interface Unlocker {
|
|
24
27
|
generateLockingBytecode: () => Uint8Array;
|
|
25
28
|
generateUnlockingBytecode: (options: GenerateUnlockingBytecodeOptions) => Uint8Array;
|
|
29
|
+
contract?: Contract;
|
|
30
|
+
params?: FunctionArgument[];
|
|
31
|
+
abiFunction?: AbiFunction;
|
|
32
|
+
template?: SignatureTemplate;
|
|
26
33
|
}
|
|
27
34
|
export interface UtxoP2PKH extends Utxo {
|
|
28
35
|
template: SignatureTemplate;
|
|
@@ -38,7 +38,7 @@ export default class ElectrumNetworkProvider {
|
|
|
38
38
|
}
|
|
39
39
|
else if (network === Network.CHIPNET) {
|
|
40
40
|
this.electrum = new ElectrumCluster('CashScript Application', '1.4.1', 1, 1, ClusterOrder.PRIORITY);
|
|
41
|
-
this.electrum.addServer('chipnet.
|
|
41
|
+
this.electrum.addServer('chipnet.bch.ninja', 50004, ElectrumTransport.WSS.Scheme, false);
|
|
42
42
|
}
|
|
43
43
|
else {
|
|
44
44
|
throw new Error(`Tried to instantiate an ElectrumNetworkProvider for unsupported network ${network}`);
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Transaction } from '@bitauth/libauth';
|
|
2
2
|
import { Script } from '@cashscript/utils';
|
|
3
3
|
import { Utxo, Output, LibauthOutput, TokenDetails, AddressType } from './interfaces.js';
|
|
4
|
+
export { snakeCase } from 'change-case';
|
|
5
|
+
export declare function validateInput(utxo: Utxo): void;
|
|
4
6
|
export declare function validateOutput(output: Output): void;
|
|
5
7
|
export declare function calculateDust(output: Output): number;
|
|
6
8
|
export declare function getOutputSize(output: Output): number;
|
|
@@ -8,9 +10,8 @@ export declare function encodeOutput(output: Output): Uint8Array;
|
|
|
8
10
|
export declare function cashScriptOutputToLibauthOutput(output: Output): LibauthOutput;
|
|
9
11
|
export declare function libauthOutputToCashScriptOutput(output: LibauthOutput): Output;
|
|
10
12
|
export declare function getInputSize(inputScript: Uint8Array): number;
|
|
11
|
-
export declare function getPreimageSize(script: Uint8Array): number;
|
|
12
13
|
export declare function getTxSizeWithoutInputs(outputs: Output[]): number;
|
|
13
|
-
export declare function createInputScript(redeemScript: Script, encodedArgs: Uint8Array[], selector?: number
|
|
14
|
+
export declare function createInputScript(redeemScript: Script, encodedArgs: Uint8Array[], selector?: number): Uint8Array;
|
|
14
15
|
export declare function createOpReturnOutput(opReturnData: string[]): Output;
|
|
15
16
|
export declare function createSighashPreimage(transaction: Transaction, sourceOutputs: LibauthOutput[], inputIndex: number, coveredBytecode: Uint8Array, hashtype: number): Uint8Array;
|
|
16
17
|
export declare function toRegExp(reasons: string[]): RegExp;
|
|
@@ -32,6 +33,8 @@ export declare const randomUtxo: (defaults?: Partial<Utxo>) => Utxo;
|
|
|
32
33
|
export declare const randomToken: (defaults?: Partial<TokenDetails>) => TokenDetails;
|
|
33
34
|
export declare const randomNFT: (defaults?: Partial<TokenDetails>) => TokenDetails;
|
|
34
35
|
export declare function findLastIndex<T>(array: Array<T>, predicate: (value: T, index: number, obj: T[]) => boolean): number;
|
|
35
|
-
export declare const
|
|
36
|
+
export declare const titleCase: (str: string) => string;
|
|
36
37
|
export declare const extendedStringify: (obj: any, spaces?: number) => string;
|
|
37
38
|
export declare const zip: <T, U>(a: readonly T[], b: readonly U[]) => [T, U][];
|
|
39
|
+
export declare const isFungibleTokenUtxo: (utxo: Utxo) => boolean;
|
|
40
|
+
export declare const isNonTokenUtxo: (utxo: Utxo) => boolean;
|
package/dist/utils.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex,
|
|
1
|
+
import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex, generateSigningSerializationBch, utf8ToBin, hexToBin, flattenBinArray, LockingBytecodeType, encodeTransactionOutput, isHex, bigIntToCompactUint, NonFungibleTokenCapability, bigIntToVmNumber, } from '@bitauth/libauth';
|
|
2
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
|
-
import { OutputSatoshisTooSmallError, TokensToNonTokenAddressError, } from './Errors.js';
|
|
5
|
+
import { OutputSatoshisTooSmallError, OutputTokenAmountTooSmallError, TokensToNonTokenAddressError, UndefinedInputError, } from './Errors.js';
|
|
6
|
+
export { snakeCase } from 'change-case';
|
|
6
7
|
// ////////// PARAMETER VALIDATION ////////////////////////////////////////////
|
|
8
|
+
export function validateInput(utxo) {
|
|
9
|
+
if (!utxo) {
|
|
10
|
+
throw new UndefinedInputError();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
7
13
|
export function validateOutput(output) {
|
|
8
14
|
if (typeof output.to !== 'string')
|
|
9
15
|
return;
|
|
@@ -15,6 +21,9 @@ export function validateOutput(output) {
|
|
|
15
21
|
if (!isTokenAddress(output.to)) {
|
|
16
22
|
throw new TokensToNonTokenAddressError(output.to);
|
|
17
23
|
}
|
|
24
|
+
if (output.token.amount < 0n) {
|
|
25
|
+
throw new OutputTokenAmountTooSmallError(output.token.amount);
|
|
26
|
+
}
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
export function calculateDust(output) {
|
|
@@ -79,11 +88,6 @@ export function getInputSize(inputScript) {
|
|
|
79
88
|
const varIntSize = scriptSize > 252 ? 3 : 1;
|
|
80
89
|
return 32 + 4 + varIntSize + scriptSize + 4;
|
|
81
90
|
}
|
|
82
|
-
export function getPreimageSize(script) {
|
|
83
|
-
const scriptSize = script.byteLength;
|
|
84
|
-
const varIntSize = scriptSize > 252 ? 3 : 1;
|
|
85
|
-
return 4 + 32 + 32 + 36 + varIntSize + scriptSize + 8 + 4 + 32 + 4 + 4;
|
|
86
|
-
}
|
|
87
91
|
export function getTxSizeWithoutInputs(outputs) {
|
|
88
92
|
// Transaction format:
|
|
89
93
|
// Version (4 Bytes)
|
|
@@ -106,11 +110,9 @@ export function getTxSizeWithoutInputs(outputs) {
|
|
|
106
110
|
return size;
|
|
107
111
|
}
|
|
108
112
|
// ////////// BUILD OBJECTS ///////////////////////////////////////////////////
|
|
109
|
-
export function createInputScript(redeemScript, encodedArgs, selector
|
|
110
|
-
// Create unlock script / redeemScriptSig (add potential
|
|
113
|
+
export function createInputScript(redeemScript, encodedArgs, selector) {
|
|
114
|
+
// Create unlock script / redeemScriptSig (add potential selector)
|
|
111
115
|
const unlockScript = [...encodedArgs].reverse();
|
|
112
|
-
if (preimage !== undefined)
|
|
113
|
-
unlockScript.push(preimage);
|
|
114
116
|
if (selector !== undefined)
|
|
115
117
|
unlockScript.push(encodeInt(BigInt(selector)));
|
|
116
118
|
// Create input script and compile it to bytecode
|
|
@@ -132,7 +134,7 @@ function toBin(output) {
|
|
|
132
134
|
export function createSighashPreimage(transaction, sourceOutputs, inputIndex, coveredBytecode, hashtype) {
|
|
133
135
|
const context = { inputIndex, sourceOutputs, transaction };
|
|
134
136
|
const signingSerializationType = new Uint8Array([hashtype]);
|
|
135
|
-
const sighashPreimage =
|
|
137
|
+
const sighashPreimage = generateSigningSerializationBch(context, { coveredBytecode, signingSerializationType });
|
|
136
138
|
return sighashPreimage;
|
|
137
139
|
}
|
|
138
140
|
export function toRegExp(reasons) {
|
|
@@ -262,11 +264,24 @@ export function findLastIndex(array, predicate) {
|
|
|
262
264
|
}
|
|
263
265
|
return -1;
|
|
264
266
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
// TODO: Somehow, this turns P2PKHLock into p2pkh_lock, but P2PKH_Lock into p2_pkh_lock
|
|
268
|
+
// export const snakeCase = (str: string): string => (
|
|
269
|
+
// str
|
|
270
|
+
// && str
|
|
271
|
+
// .match(
|
|
272
|
+
// /([A-Z]+\d*[A-Z]*(?=[A-Z][a-z])|[A-Z]?[a-z]+\d*[a-z]+|[A-Z]?[a-z]+\d*|[A-Z]+\d*|\d+)/g,
|
|
273
|
+
// )!
|
|
274
|
+
// .map((s) => s.toLowerCase())
|
|
275
|
+
// .join('_')
|
|
276
|
+
// );
|
|
277
|
+
export const titleCase = (str) => {
|
|
278
|
+
if (!str)
|
|
279
|
+
return '';
|
|
280
|
+
return str
|
|
267
281
|
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
|
|
268
|
-
.map((s) => s.toLowerCase())
|
|
269
|
-
.join('
|
|
282
|
+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())
|
|
283
|
+
.join(' ');
|
|
284
|
+
};
|
|
270
285
|
// JSON.stringify version that can serialize otherwise unsupported types (bigint and Uint8Array)
|
|
271
286
|
export const extendedStringify = (obj, spaces) => JSON.stringify(obj, (_, v) => {
|
|
272
287
|
if (typeof v === 'bigint') {
|
|
@@ -278,4 +293,6 @@ export const extendedStringify = (obj, spaces) => JSON.stringify(obj, (_, v) =>
|
|
|
278
293
|
return v;
|
|
279
294
|
}, spaces);
|
|
280
295
|
export const zip = (a, b) => (Array.from(Array(Math.max(b.length, a.length)), (_, i) => [a[i], b[i]]));
|
|
296
|
+
export const isFungibleTokenUtxo = (utxo) => (utxo.token !== undefined && utxo.token.amount > 0n && utxo.token.nft === undefined);
|
|
297
|
+
export const isNonTokenUtxo = (utxo) => utxo.token === undefined;
|
|
281
298
|
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cashscript",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0-next.1",
|
|
4
4
|
"description": "Easily write and interact with Bitcoin Cash contracts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin cash",
|
|
@@ -43,21 +43,25 @@
|
|
|
43
43
|
"test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@bitauth/libauth": "^3.0.
|
|
47
|
-
"@cashscript/utils": "^0.
|
|
46
|
+
"@bitauth/libauth": "^3.1.0-next.2",
|
|
47
|
+
"@cashscript/utils": "^0.11.0-next.1",
|
|
48
48
|
"@mr-zwets/bchn-api-wrapper": "^1.0.1",
|
|
49
|
-
"
|
|
49
|
+
"change-case": "^5.4.4",
|
|
50
|
+
"delay": "^6.0.0",
|
|
50
51
|
"electrum-cash": "^2.0.10",
|
|
51
52
|
"fast-deep-equal": "^3.1.3",
|
|
52
|
-
"pako": "^2.1.0"
|
|
53
|
+
"pako": "^2.1.0",
|
|
54
|
+
"semver": "^7.6.3"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
|
-
"@jest/globals": "^29.
|
|
57
|
+
"@jest/globals": "^29.7.0",
|
|
56
58
|
"@psf/bch-js": "^6.8.0",
|
|
59
|
+
"@types/change-case": "^2.3.5",
|
|
57
60
|
"@types/pako": "^2.0.3",
|
|
61
|
+
"@types/semver": "^7.5.8",
|
|
58
62
|
"eslint": "^8.54.0",
|
|
59
|
-
"jest": "^29.
|
|
60
|
-
"typescript": "^5.
|
|
63
|
+
"jest": "^29.7.0",
|
|
64
|
+
"typescript": "^5.7.3"
|
|
61
65
|
},
|
|
62
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "41f969f460606048595bb108e753044b9673e748"
|
|
63
67
|
}
|