cashscript 0.11.0-next.0 → 0.11.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/Contract.d.ts +14 -5
- package/dist/Contract.js +16 -15
- package/dist/Errors.d.ts +6 -0
- package/dist/Errors.js +10 -0
- package/dist/LibauthTemplate.d.ts +15 -1
- package/dist/LibauthTemplate.js +46 -29
- 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 +29 -15
- package/dist/interfaces.d.ts +7 -0
- package/dist/network/ElectrumNetworkProvider.d.ts +18 -5
- package/dist/network/ElectrumNetworkProvider.js +37 -50
- package/dist/types/type-inference.d.ts +32 -0
- package/dist/types/type-inference.js +2 -0
- package/dist/utils.d.ts +7 -4
- 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)
|
|
@@ -14,7 +14,7 @@ See the [GitHub repository](https://github.com/CashScript/cashscript) and the [C
|
|
|
14
14
|
CashScript is a high-level language that allows you to write Bitcoin Cash smart contracts in a straightforward and familiar way. Its syntax is inspired by Ethereum's Solidity language, but its functionality is different since the underlying systems have very different fundamentals. See the [language documentation](https://cashscript.org/docs/language/) for a full reference of the language.
|
|
15
15
|
|
|
16
16
|
## The CashScript SDK
|
|
17
|
-
The main way to interact with CashScript contracts and integrate them into applications is using the CashScript SDK. This SDK allows you to import `.json` artifact files that were compiled using the `cashc` compiler and convert them to `Contract` objects. These objects are used to create new contract instances. These instances are used to interact with the contracts using the functions that were implemented in the `.cash` file. For more information on the CashScript SDK, refer to the [SDK documentation](https://cashscript.org/docs/sdk/).
|
|
17
|
+
The main way to interact with CashScript contracts and integrate them into applications is using the CashScript SDK. This SDK allows you to import `.json` (or `.ts`) artifact files that were compiled using the `cashc` compiler and convert them to `Contract` objects. These objects are used to create new contract instances. These instances are used to interact with the contracts using the functions that were implemented in the `.cash` file. For more information on the CashScript SDK, refer to the [SDK documentation](https://cashscript.org/docs/sdk/).
|
|
18
18
|
|
|
19
19
|
### Installation
|
|
20
20
|
```bash
|
package/dist/Contract.d.ts
CHANGED
|
@@ -3,8 +3,17 @@ import { Transaction } from './Transaction.js';
|
|
|
3
3
|
import { ConstructorArgument, FunctionArgument } from './Argument.js';
|
|
4
4
|
import { Unlocker, ContractOptions, Utxo, AddressType } from './interfaces.js';
|
|
5
5
|
import NetworkProvider from './network/NetworkProvider.js';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { ParamsToTuple, AbiToFunctionMap } from './types/type-inference.js';
|
|
7
|
+
export declare class Contract<TArtifact extends Artifact = Artifact, TResolved extends {
|
|
8
|
+
constructorInputs: ConstructorArgument[];
|
|
9
|
+
functions: Record<string, any>;
|
|
10
|
+
unlock: Record<string, any>;
|
|
11
|
+
} = {
|
|
12
|
+
constructorInputs: ParamsToTuple<TArtifact['constructorInputs']>;
|
|
13
|
+
functions: AbiToFunctionMap<TArtifact['abi'], Transaction>;
|
|
14
|
+
unlock: AbiToFunctionMap<TArtifact['abi'], Unlocker>;
|
|
15
|
+
}> {
|
|
16
|
+
artifact: TArtifact;
|
|
8
17
|
private options?;
|
|
9
18
|
name: string;
|
|
10
19
|
address: string;
|
|
@@ -12,13 +21,13 @@ export declare class Contract {
|
|
|
12
21
|
bytecode: string;
|
|
13
22
|
bytesize: number;
|
|
14
23
|
opcount: number;
|
|
15
|
-
functions:
|
|
16
|
-
unlock:
|
|
24
|
+
functions: TResolved['functions'];
|
|
25
|
+
unlock: TResolved['unlock'];
|
|
17
26
|
redeemScript: Script;
|
|
18
27
|
provider: NetworkProvider;
|
|
19
28
|
addressType: AddressType;
|
|
20
29
|
encodedConstructorArgs: Uint8Array[];
|
|
21
|
-
constructor(artifact:
|
|
30
|
+
constructor(artifact: TArtifact, constructorArgs: TResolved['constructorInputs'], options?: ContractOptions | undefined);
|
|
22
31
|
getBalance(): Promise<bigint>;
|
|
23
32
|
getUtxos(): Promise<Utxo[]>;
|
|
24
33
|
private createFunction;
|
package/dist/Contract.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import { binToHex } from '@bitauth/libauth';
|
|
2
2
|
import { asmToScript, calculateBytesize, countOpcodes, generateRedeemScript, hash256, scriptToBytecode, } from '@cashscript/utils';
|
|
3
3
|
import { Transaction } from './Transaction.js';
|
|
4
|
-
import { encodeFunctionArgument, encodeConstructorArguments, encodeFunctionArguments } from './Argument.js';
|
|
4
|
+
import { encodeFunctionArgument, encodeConstructorArguments, encodeFunctionArguments, } from './Argument.js';
|
|
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
|
-
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}`);
|
|
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
|
}
|
|
21
25
|
// Encode arguments (this also performs type checking)
|
|
22
26
|
this.encodedConstructorArgs = encodeConstructorArguments(artifact, constructorArgs);
|
|
@@ -26,10 +30,12 @@ export class Contract {
|
|
|
26
30
|
this.functions = {};
|
|
27
31
|
if (artifact.abi.length === 1) {
|
|
28
32
|
const f = artifact.abi[0];
|
|
33
|
+
// @ts-ignore TODO: see if we can use generics to make TypeScript happy
|
|
29
34
|
this.functions[f.name] = this.createFunction(f);
|
|
30
35
|
}
|
|
31
36
|
else {
|
|
32
37
|
artifact.abi.forEach((f, i) => {
|
|
38
|
+
// @ts-ignore TODO: see if we can use generics to make TypeScript happy
|
|
33
39
|
this.functions[f.name] = this.createFunction(f, i);
|
|
34
40
|
});
|
|
35
41
|
}
|
|
@@ -38,10 +44,12 @@ export class Contract {
|
|
|
38
44
|
this.unlock = {};
|
|
39
45
|
if (artifact.abi.length === 1) {
|
|
40
46
|
const f = artifact.abi[0];
|
|
47
|
+
// @ts-ignore TODO: see if we can use generics to make TypeScript happy
|
|
41
48
|
this.unlock[f.name] = this.createUnlocker(f);
|
|
42
49
|
}
|
|
43
50
|
else {
|
|
44
51
|
artifact.abi.forEach((f, i) => {
|
|
52
|
+
// @ts-ignore TODO: see if we can use generics to make TypeScript happy
|
|
45
53
|
this.unlock[f.name] = this.createUnlocker(f, i);
|
|
46
54
|
});
|
|
47
55
|
}
|
|
@@ -62,7 +70,7 @@ export class Contract {
|
|
|
62
70
|
createFunction(abiFunction, selector) {
|
|
63
71
|
return (...args) => {
|
|
64
72
|
if (abiFunction.inputs.length !== args.length) {
|
|
65
|
-
throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map(input => input.type)}) but got ${args.length}`);
|
|
73
|
+
throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map((input) => input.type)}) but got ${args.length}`);
|
|
66
74
|
}
|
|
67
75
|
// Encode passed args (this also performs type checking)
|
|
68
76
|
const encodedArgs = encodeFunctionArguments(abiFunction, args);
|
|
@@ -73,32 +81,25 @@ export class Contract {
|
|
|
73
81
|
createUnlocker(abiFunction, selector) {
|
|
74
82
|
return (...args) => {
|
|
75
83
|
if (abiFunction.inputs.length !== args.length) {
|
|
76
|
-
throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map(input => input.type)}) but got ${args.length}`);
|
|
84
|
+
throw new Error(`Incorrect number of arguments passed to function ${abiFunction.name}. Expected ${abiFunction.inputs.length} arguments (${abiFunction.inputs.map((input) => input.type)}) but got ${args.length}`);
|
|
77
85
|
}
|
|
78
86
|
const bytecode = scriptToBytecode(this.redeemScript);
|
|
79
87
|
const encodedArgs = args
|
|
80
88
|
.map((arg, i) => encodeFunctionArgument(arg, abiFunction.inputs[i].type));
|
|
81
89
|
const generateUnlockingBytecode = ({ transaction, sourceOutputs, inputIndex }) => {
|
|
82
|
-
// TODO: Remove old-style covenant code for v1.0 release
|
|
83
|
-
let covenantHashType = -1;
|
|
84
90
|
const completeArgs = encodedArgs.map((arg) => {
|
|
85
91
|
if (!(arg instanceof SignatureTemplate))
|
|
86
92
|
return arg;
|
|
87
|
-
//
|
|
88
|
-
if (covenantHashType < 0)
|
|
89
|
-
covenantHashType = arg.getHashType();
|
|
93
|
+
// Generate transaction signature from SignatureTemplate
|
|
90
94
|
const preimage = createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, arg.getHashType());
|
|
91
95
|
const sighash = hash256(preimage);
|
|
92
96
|
return arg.generateSignature(sighash);
|
|
93
97
|
});
|
|
94
|
-
const
|
|
95
|
-
? createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, covenantHashType)
|
|
96
|
-
: undefined;
|
|
97
|
-
const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector, preimage);
|
|
98
|
+
const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector);
|
|
98
99
|
return unlockingBytecode;
|
|
99
100
|
};
|
|
100
101
|
const generateLockingBytecode = () => addressToLockScript(this.address);
|
|
101
|
-
return { generateUnlockingBytecode, generateLockingBytecode };
|
|
102
|
+
return { generateUnlockingBytecode, generateLockingBytecode, contract: this, params: args, abiFunction };
|
|
102
103
|
};
|
|
103
104
|
}
|
|
104
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,8 +11,8 @@ 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:
|
|
15
|
-
supported: ['
|
|
14
|
+
name: 'CashScript Generated Debugging Template',
|
|
15
|
+
supported: ['BCH_2025_05'],
|
|
16
16
|
version: 0,
|
|
17
17
|
entities: generateTemplateEntities(contract.artifact, transaction.abiFunction, transaction.encodedFunctionArgs),
|
|
18
18
|
scripts: generateTemplateScripts(contract.artifact, contract.addressType, transaction.abiFunction, transaction.encodedFunctionArgs, contract.encodedConstructorArgs),
|
|
@@ -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);
|