cashscript 0.11.5 → 0.12.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 +18 -6
- package/dist/Argument.js +12 -8
- package/dist/Contract.d.ts +3 -9
- package/dist/Contract.js +4 -31
- package/dist/Errors.d.ts +2 -2
- package/dist/SignatureTemplate.d.ts +1 -0
- package/dist/SignatureTemplate.js +13 -4
- package/dist/TransactionBuilder.d.ts +8 -4
- package/dist/TransactionBuilder.js +73 -20
- package/dist/debugging.js +45 -20
- package/dist/index.d.ts +1 -2
- package/dist/index.js +0 -1
- package/dist/interfaces.d.ts +10 -6
- package/dist/interfaces.js +6 -3
- package/dist/libauth-template/LibauthTemplate.d.ts +6 -0
- package/dist/libauth-template/LibauthTemplate.js +445 -0
- package/dist/libauth-template/utils.d.ts +27 -0
- package/dist/libauth-template/utils.js +89 -0
- package/dist/network/MockNetworkProvider.d.ts +4 -3
- package/dist/network/MockNetworkProvider.js +4 -14
- package/dist/test/{JestExtensions.js → TestExtensions.js} +20 -41
- package/jest/package.json +2 -2
- package/package.json +8 -13
- package/vitest/package.json +5 -0
- package/dist/LibauthTemplate.d.ts +0 -32
- package/dist/LibauthTemplate.js +0 -324
- package/dist/Transaction.d.ts +0 -45
- package/dist/Transaction.js +0 -384
- package/dist/advanced/LibauthTemplate.d.ts +0 -45
- package/dist/advanced/LibauthTemplate.js +0 -426
- package/dist/test/{JestExtensions.d.ts → TestExtensions.d.ts} +1 -1
package/README.md
CHANGED
|
@@ -37,17 +37,29 @@ Using the CashScript SDK, you can import contract artifact files, create new ins
|
|
|
37
37
|
const provider = new ElectrumNetworkProvider('mainnet');
|
|
38
38
|
|
|
39
39
|
// Create a new P2PKH contract with constructor arguments: { pkh: pkh }
|
|
40
|
-
const contract = new Contract(P2PKH, [pkh], provider);
|
|
40
|
+
const contract = new Contract(P2PKH, [pkh], { provider });
|
|
41
41
|
|
|
42
42
|
// Get contract balance & output address + balance
|
|
43
43
|
console.log('contract address:', contract.address);
|
|
44
44
|
console.log('contract balance:', await contract.getBalance());
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const transactionBuilder = new TransactionBuilder({ provider });
|
|
47
|
+
const contractUtxos = await contract.getUtxos();
|
|
48
|
+
|
|
49
|
+
const sendAmount = 10_000n;
|
|
50
|
+
const destinationAddress = '... some address ...';
|
|
51
|
+
|
|
52
|
+
// Calculate the change amount, accounting for a miner fee of 1000 satoshis
|
|
53
|
+
const changeAmount = contractUtxos[0].satoshis - sendAmount - 1000n;
|
|
54
|
+
|
|
55
|
+
// Construct a transaction with the transaction builder
|
|
56
|
+
const txDetails = await transactionBuilder
|
|
57
|
+
// Add a contract input that spends from the contract using the 'spend' function
|
|
58
|
+
.addInput(contractUtxos[0], contract.unlock.spend(pk, new SignatureTemplate(keypair)))
|
|
59
|
+
// Add an output that sends 0. 000 100 00 BCH back to the destination address
|
|
60
|
+
.addOutput({ to: destinationAddress, amount: sendAmount })
|
|
61
|
+
// Add a change output that sends the change back to the contract's address
|
|
62
|
+
.addOutput({ to: contract.address, amount: changeAmount })
|
|
51
63
|
.send();
|
|
52
64
|
|
|
53
65
|
console.log(txDetails);
|
package/dist/Argument.js
CHANGED
|
@@ -34,15 +34,19 @@ export function encodeFunctionArgument(argument, typeStr) {
|
|
|
34
34
|
if (!(argument instanceof Uint8Array)) {
|
|
35
35
|
throw Error(`Value for type ${type} should be a Uint8Array or hex string`);
|
|
36
36
|
}
|
|
37
|
-
// Redefine SIG as a bytes65
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
// Redefine SIG as a bytes65 (Schnorr) or bytes71, bytes72, bytes73 (ECDSA) or bytes0 (for NULLFAIL)
|
|
38
|
+
if (type === PrimitiveType.SIG) {
|
|
39
|
+
if (![0, 65, 71, 72, 73].includes(argument.byteLength)) {
|
|
40
|
+
throw new TypeError(`bytes${argument.byteLength}`, type);
|
|
41
|
+
}
|
|
42
|
+
type = new BytesType(argument.byteLength);
|
|
41
43
|
}
|
|
42
|
-
// Redefine DATASIG as a bytes64
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// Redefine DATASIG as a bytes64 (Schnorr) or bytes70, bytes71, bytes72 (ECDSA) or bytes0 (for NULLFAIL)
|
|
45
|
+
if (type === PrimitiveType.DATASIG) {
|
|
46
|
+
if (![0, 64, 70, 71, 72].includes(argument.byteLength)) {
|
|
47
|
+
throw new TypeError(`bytes${argument.byteLength}`, type);
|
|
48
|
+
}
|
|
49
|
+
type = new BytesType(argument.byteLength);
|
|
46
50
|
}
|
|
47
51
|
// Bounded bytes types require a correctly sized argument
|
|
48
52
|
if (type instanceof BytesType && type.bound && argument.byteLength !== type.bound) {
|
package/dist/Contract.d.ts
CHANGED
|
@@ -1,36 +1,30 @@
|
|
|
1
1
|
import { Artifact, Script } from '@cashscript/utils';
|
|
2
|
-
import {
|
|
3
|
-
import { ConstructorArgument, FunctionArgument } from './Argument.js';
|
|
2
|
+
import { ConstructorArgument } from './Argument.js';
|
|
4
3
|
import { Unlocker, ContractOptions, Utxo, AddressType } from './interfaces.js';
|
|
5
4
|
import NetworkProvider from './network/NetworkProvider.js';
|
|
6
5
|
import { ParamsToTuple, AbiToFunctionMap } from './types/type-inference.js';
|
|
7
6
|
export declare class Contract<TArtifact extends Artifact = Artifact, TResolved extends {
|
|
8
7
|
constructorInputs: ConstructorArgument[];
|
|
9
|
-
functions: Record<string, any>;
|
|
10
8
|
unlock: Record<string, any>;
|
|
11
9
|
} = {
|
|
12
10
|
constructorInputs: ParamsToTuple<TArtifact['constructorInputs']>;
|
|
13
|
-
functions: AbiToFunctionMap<TArtifact['abi'], Transaction>;
|
|
14
11
|
unlock: AbiToFunctionMap<TArtifact['abi'], Unlocker>;
|
|
15
12
|
}> {
|
|
16
13
|
artifact: TArtifact;
|
|
17
|
-
private options
|
|
14
|
+
private options;
|
|
18
15
|
name: string;
|
|
19
16
|
address: string;
|
|
20
17
|
tokenAddress: string;
|
|
21
18
|
bytecode: string;
|
|
22
19
|
bytesize: number;
|
|
23
20
|
opcount: number;
|
|
24
|
-
functions: TResolved['functions'];
|
|
25
21
|
unlock: TResolved['unlock'];
|
|
26
22
|
redeemScript: Script;
|
|
27
23
|
provider: NetworkProvider;
|
|
28
24
|
addressType: AddressType;
|
|
29
25
|
encodedConstructorArgs: Uint8Array[];
|
|
30
|
-
constructor(artifact: TArtifact, constructorArgs: TResolved['constructorInputs'], options
|
|
26
|
+
constructor(artifact: TArtifact, constructorArgs: TResolved['constructorInputs'], options: ContractOptions);
|
|
31
27
|
getBalance(): Promise<bigint>;
|
|
32
28
|
getUtxos(): Promise<Utxo[]>;
|
|
33
|
-
private createFunction;
|
|
34
29
|
private createUnlocker;
|
|
35
30
|
}
|
|
36
|
-
export type ContractFunction = (...args: FunctionArgument[]) => Transaction;
|
package/dist/Contract.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { binToHex } from '@bitauth/libauth';
|
|
2
2
|
import { asmToScript, calculateBytesize, countOpcodes, generateRedeemScript, hash256, scriptToBytecode, } from '@cashscript/utils';
|
|
3
|
-
import {
|
|
4
|
-
import { encodeFunctionArgument, encodeConstructorArguments, encodeFunctionArguments, } from './Argument.js';
|
|
3
|
+
import { encodeFunctionArgument, encodeConstructorArguments, } from './Argument.js';
|
|
5
4
|
import { addressToLockScript, createInputScript, createSighashPreimage, scriptToAddress, } from './utils.js';
|
|
6
5
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
7
|
-
import { ElectrumNetworkProvider } from './network/index.js';
|
|
8
6
|
import semver from 'semver';
|
|
9
7
|
export class Contract {
|
|
10
8
|
constructor(artifact, constructorArgs, options) {
|
|
11
9
|
this.artifact = artifact;
|
|
12
10
|
this.options = options;
|
|
13
|
-
this.provider = this.options
|
|
14
|
-
this.addressType = this.options
|
|
11
|
+
this.provider = this.options.provider;
|
|
12
|
+
this.addressType = this.options.addressType ?? 'p2sh32';
|
|
15
13
|
const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName', 'compiler'];
|
|
16
14
|
if (!expectedProperties.every((property) => property in artifact)) {
|
|
17
15
|
throw new Error('Invalid or incomplete artifact provided');
|
|
@@ -25,21 +23,7 @@ export class Contract {
|
|
|
25
23
|
// Encode arguments (this also performs type checking)
|
|
26
24
|
this.encodedConstructorArgs = encodeConstructorArguments(artifact, constructorArgs);
|
|
27
25
|
this.redeemScript = generateRedeemScript(asmToScript(this.artifact.bytecode), this.encodedConstructorArgs);
|
|
28
|
-
// Populate the
|
|
29
|
-
// (with a special case for single function, which has no "function selector")
|
|
30
|
-
this.functions = {};
|
|
31
|
-
if (artifact.abi.length === 1) {
|
|
32
|
-
const f = artifact.abi[0];
|
|
33
|
-
// @ts-ignore TODO: see if we can use generics to make TypeScript happy
|
|
34
|
-
this.functions[f.name] = this.createFunction(f);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
artifact.abi.forEach((f, i) => {
|
|
38
|
-
// @ts-ignore TODO: see if we can use generics to make TypeScript happy
|
|
39
|
-
this.functions[f.name] = this.createFunction(f, i);
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
// Populate the functions object with the contract's functions
|
|
26
|
+
// Populate the 'unlock' object with the contract's functions
|
|
43
27
|
// (with a special case for single function, which has no "function selector")
|
|
44
28
|
this.unlock = {};
|
|
45
29
|
if (artifact.abi.length === 1) {
|
|
@@ -67,17 +51,6 @@ export class Contract {
|
|
|
67
51
|
async getUtxos() {
|
|
68
52
|
return this.provider.getUtxos(this.address);
|
|
69
53
|
}
|
|
70
|
-
createFunction(abiFunction, selector) {
|
|
71
|
-
return (...args) => {
|
|
72
|
-
if (abiFunction.inputs.length !== 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}`);
|
|
74
|
-
}
|
|
75
|
-
// Encode passed args (this also performs type checking)
|
|
76
|
-
const encodedArgs = encodeFunctionArguments(abiFunction, args);
|
|
77
|
-
const unlocker = this.createUnlocker(abiFunction, selector)(...args);
|
|
78
|
-
return new Transaction(this, unlocker, abiFunction, encodedArgs, selector);
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
54
|
createUnlocker(abiFunction, selector) {
|
|
82
55
|
return (...args) => {
|
|
83
56
|
if (abiFunction.inputs.length !== args.length) {
|
package/dist/Errors.d.ts
CHANGED
|
@@ -19,8 +19,8 @@ export declare class NoDebugInformationInArtifactError extends Error {
|
|
|
19
19
|
}
|
|
20
20
|
export declare class FailedTransactionError extends Error {
|
|
21
21
|
reason: string;
|
|
22
|
-
bitauthUri
|
|
23
|
-
constructor(reason: string, bitauthUri
|
|
22
|
+
bitauthUri: string;
|
|
23
|
+
constructor(reason: string, bitauthUri: string);
|
|
24
24
|
}
|
|
25
25
|
export declare class FailedTransactionEvaluationError extends FailedTransactionError {
|
|
26
26
|
artifact: Artifact;
|
|
@@ -5,6 +5,7 @@ export default class SignatureTemplate {
|
|
|
5
5
|
privateKey: Uint8Array;
|
|
6
6
|
constructor(signer: Keypair | Uint8Array | string, hashtype?: HashType, signatureAlgorithm?: SignatureAlgorithm);
|
|
7
7
|
generateSignature(payload: Uint8Array, bchForkId?: boolean): Uint8Array;
|
|
8
|
+
signMessageHash(payload: Uint8Array): Uint8Array;
|
|
8
9
|
getHashType(bchForkId?: boolean): number;
|
|
9
10
|
getSignatureAlgorithm(): SignatureAlgorithm;
|
|
10
11
|
getPublicKey(): Uint8Array;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodePrivateKeyWif, secp256k1, SigningSerializationFlag } from '@bitauth/libauth';
|
|
1
|
+
import { decodePrivateKeyWif, hexToBin, isHex, secp256k1, SigningSerializationFlag } from '@bitauth/libauth';
|
|
2
2
|
import { hash256, scriptToBytecode } from '@cashscript/utils';
|
|
3
3
|
import { HashType, SignatureAlgorithm, } from './interfaces.js';
|
|
4
4
|
import { createSighashPreimage, publicKeyToP2PKHLockingBytecode } from './utils.js';
|
|
@@ -11,18 +11,27 @@ export default class SignatureTemplate {
|
|
|
11
11
|
this.privateKey = decodeWif(wif);
|
|
12
12
|
}
|
|
13
13
|
else if (typeof signer === 'string') {
|
|
14
|
-
|
|
14
|
+
const maybeHexString = signer.startsWith('0x') ? signer.slice(2) : signer;
|
|
15
|
+
if (isHex(maybeHexString)) {
|
|
16
|
+
this.privateKey = hexToBin(maybeHexString);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
this.privateKey = decodeWif(maybeHexString);
|
|
20
|
+
}
|
|
15
21
|
}
|
|
16
22
|
else {
|
|
17
23
|
this.privateKey = signer;
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
|
-
// TODO: Allow signing of non-transaction messages (i.e. don't add the hashtype)
|
|
21
26
|
generateSignature(payload, bchForkId) {
|
|
27
|
+
const signature = this.signMessageHash(payload);
|
|
28
|
+
return Uint8Array.from([...signature, this.getHashType(bchForkId)]);
|
|
29
|
+
}
|
|
30
|
+
signMessageHash(payload) {
|
|
22
31
|
const signature = this.signatureAlgorithm === SignatureAlgorithm.SCHNORR
|
|
23
32
|
? secp256k1.signMessageHashSchnorr(this.privateKey, payload)
|
|
24
33
|
: secp256k1.signMessageHashDER(this.privateKey, payload);
|
|
25
|
-
return
|
|
34
|
+
return signature;
|
|
26
35
|
}
|
|
27
36
|
getHashType(bchForkId = true) {
|
|
28
37
|
return bchForkId ? (this.hashtype | SigningSerializationFlag.forkId) : this.hashtype;
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { Transaction as LibauthTransaction, WalletTemplate } from '@bitauth/libauth';
|
|
2
|
-
import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions } from './interfaces.js';
|
|
2
|
+
import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions, VmResourceUsage } from './interfaces.js';
|
|
3
3
|
import { NetworkProvider } from './network/index.js';
|
|
4
4
|
import { DebugResults } from './debugging.js';
|
|
5
5
|
import { WcTransactionOptions } from './walletconnect-utils.js';
|
|
6
6
|
import { WcTransactionObject } from './walletconnect-utils.js';
|
|
7
7
|
export interface TransactionBuilderOptions {
|
|
8
8
|
provider: NetworkProvider;
|
|
9
|
+
maximumFeeSatoshis?: bigint;
|
|
10
|
+
maximumFeeSatsPerByte?: number;
|
|
11
|
+
allowImplicitFungibleTokenBurn?: boolean;
|
|
9
12
|
}
|
|
10
13
|
export declare class TransactionBuilder {
|
|
11
14
|
provider: NetworkProvider;
|
|
12
15
|
inputs: UnlockableUtxo[];
|
|
13
16
|
outputs: Output[];
|
|
14
17
|
locktime: number;
|
|
15
|
-
|
|
18
|
+
options: TransactionBuilderOptions;
|
|
16
19
|
constructor(options: TransactionBuilderOptions);
|
|
17
20
|
addInput(utxo: Utxo, unlocker: Unlocker, options?: InputOptions): this;
|
|
18
21
|
addInputs(utxos: Utxo[], unlocker: Unlocker, options?: InputOptions): this;
|
|
@@ -21,12 +24,13 @@ export declare class TransactionBuilder {
|
|
|
21
24
|
addOutputs(outputs: Output[]): this;
|
|
22
25
|
addOpReturnOutput(chunks: string[]): this;
|
|
23
26
|
setLocktime(locktime: number): this;
|
|
24
|
-
setMaxFee(maxFee: bigint): this;
|
|
25
27
|
private checkMaxFee;
|
|
28
|
+
private checkFungibleTokenBurn;
|
|
26
29
|
buildLibauthTransaction(): LibauthTransaction;
|
|
27
30
|
build(): string;
|
|
28
31
|
debug(): DebugResults;
|
|
29
|
-
|
|
32
|
+
getVmResourceUsage(verbose?: boolean): Array<VmResourceUsage>;
|
|
33
|
+
getBitauthUri(): string;
|
|
30
34
|
getLibauthTemplate(): WalletTemplate;
|
|
31
35
|
send(): Promise<TransactionDetails>;
|
|
32
36
|
send(raw: true): Promise<string>;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { binToHex, decodeTransaction, decodeTransactionUnsafe, encodeTransaction, hexToBin, } from '@bitauth/libauth';
|
|
2
|
-
import { isUnlockableUtxo, isStandardUnlockableUtxo,
|
|
2
|
+
import { isUnlockableUtxo, isStandardUnlockableUtxo, isContractUnlocker, } from './interfaces.js';
|
|
3
3
|
import { cashScriptOutputToLibauthOutput, createOpReturnOutput, delay, generateLibauthSourceOutputs, validateInput, validateOutput, } from './utils.js';
|
|
4
4
|
import { FailedTransactionError } from './Errors.js';
|
|
5
|
-
import { getBitauthUri } from './LibauthTemplate.js';
|
|
6
|
-
import { debugLibauthTemplate, getLibauthTemplates } from './advanced/LibauthTemplate.js';
|
|
5
|
+
import { debugLibauthTemplate, getLibauthTemplate, getBitauthUri } from './libauth-template/LibauthTemplate.js';
|
|
7
6
|
import { getWcContractInfo } from './walletconnect-utils.js';
|
|
8
7
|
import semver from 'semver';
|
|
9
8
|
const DEFAULT_SEQUENCE = 0xfffffffe;
|
|
@@ -13,6 +12,10 @@ export class TransactionBuilder {
|
|
|
13
12
|
this.outputs = [];
|
|
14
13
|
this.locktime = 0;
|
|
15
14
|
this.provider = options.provider;
|
|
15
|
+
this.options = {
|
|
16
|
+
allowImplicitFungibleTokenBurn: options.allowImplicitFungibleTokenBurn ?? false,
|
|
17
|
+
...options,
|
|
18
|
+
};
|
|
16
19
|
}
|
|
17
20
|
addInput(utxo, unlocker, options) {
|
|
18
21
|
return this.addInputs([utxo], unlocker, options);
|
|
@@ -47,22 +50,45 @@ export class TransactionBuilder {
|
|
|
47
50
|
this.locktime = locktime;
|
|
48
51
|
return this;
|
|
49
52
|
}
|
|
50
|
-
|
|
51
|
-
this.maxFee = maxFee;
|
|
52
|
-
return this;
|
|
53
|
-
}
|
|
54
|
-
checkMaxFee() {
|
|
55
|
-
if (!this.maxFee)
|
|
56
|
-
return;
|
|
53
|
+
checkMaxFee(transaction) {
|
|
57
54
|
const totalInputAmount = this.inputs.reduce((total, input) => total + input.satoshis, 0n);
|
|
58
55
|
const totalOutputAmount = this.outputs.reduce((total, output) => total + output.amount, 0n);
|
|
59
56
|
const fee = totalInputAmount - totalOutputAmount;
|
|
60
|
-
if (fee > this.
|
|
61
|
-
throw new Error(`Transaction fee of ${fee} is higher than max fee of ${this.
|
|
57
|
+
if (this.options.maximumFeeSatoshis && fee > this.options.maximumFeeSatoshis) {
|
|
58
|
+
throw new Error(`Transaction fee of ${fee} is higher than max fee of ${this.options.maximumFeeSatoshis}`);
|
|
59
|
+
}
|
|
60
|
+
if (this.options.maximumFeeSatsPerByte) {
|
|
61
|
+
const transactionSize = encodeTransaction(transaction).byteLength;
|
|
62
|
+
const feePerByte = Number((Number(fee) / transactionSize).toFixed(2));
|
|
63
|
+
if (feePerByte > this.options.maximumFeeSatsPerByte) {
|
|
64
|
+
throw new Error(`Transaction fee per byte of ${feePerByte} is higher than max fee per byte of ${this.options.maximumFeeSatsPerByte}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
checkFungibleTokenBurn() {
|
|
69
|
+
if (this.options.allowImplicitFungibleTokenBurn)
|
|
70
|
+
return;
|
|
71
|
+
const tokenInputAmounts = {};
|
|
72
|
+
const tokenOutputAmounts = {};
|
|
73
|
+
for (const input of this.inputs) {
|
|
74
|
+
if (input.token?.amount) {
|
|
75
|
+
tokenInputAmounts[input.token.category] = (tokenInputAmounts[input.token.category] || 0n) + input.token.amount;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const output of this.outputs) {
|
|
79
|
+
if (output.token?.amount) {
|
|
80
|
+
tokenOutputAmounts[output.token.category] = (tokenOutputAmounts[output.token.category] || 0n) + output.token.amount;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const [category, inputAmount] of Object.entries(tokenInputAmounts)) {
|
|
84
|
+
const outputAmount = tokenOutputAmounts[category] || 0n;
|
|
85
|
+
if (outputAmount < inputAmount) {
|
|
86
|
+
throw new Error(`Implicit burning of fungible tokens for category ${category} is not allowed (input amount: ${inputAmount}, output amount: ${outputAmount}). If this is intended, set allowImplicitFungibleTokenBurn to true.`);
|
|
87
|
+
}
|
|
62
88
|
}
|
|
63
89
|
}
|
|
64
90
|
buildLibauthTransaction() {
|
|
65
|
-
this.
|
|
91
|
+
this.checkFungibleTokenBurn();
|
|
66
92
|
const inputs = this.inputs.map((utxo) => ({
|
|
67
93
|
outpointIndex: utxo.vout,
|
|
68
94
|
outpointTransactionHash: hexToBin(utxo.txid),
|
|
@@ -82,6 +108,7 @@ export class TransactionBuilder {
|
|
|
82
108
|
inputScripts.forEach((script, i) => {
|
|
83
109
|
transaction.inputs[i].unlockingBytecode = script;
|
|
84
110
|
});
|
|
111
|
+
this.checkMaxFee(transaction);
|
|
85
112
|
return transaction;
|
|
86
113
|
}
|
|
87
114
|
build() {
|
|
@@ -89,10 +116,6 @@ export class TransactionBuilder {
|
|
|
89
116
|
return binToHex(encodeTransaction(transaction));
|
|
90
117
|
}
|
|
91
118
|
debug() {
|
|
92
|
-
// do not debug a pure P2PKH-spend transaction
|
|
93
|
-
if (this.inputs.every((input) => isP2PKHUnlocker(input.unlocker))) {
|
|
94
|
-
return {};
|
|
95
|
-
}
|
|
96
119
|
if (this.inputs.some((input) => !isStandardUnlockableUtxo(input))) {
|
|
97
120
|
throw new Error('Cannot debug a transaction with custom unlocker');
|
|
98
121
|
}
|
|
@@ -105,12 +128,42 @@ export class TransactionBuilder {
|
|
|
105
128
|
}
|
|
106
129
|
return debugLibauthTemplate(this.getLibauthTemplate(), this);
|
|
107
130
|
}
|
|
108
|
-
|
|
131
|
+
getVmResourceUsage(verbose = false) {
|
|
132
|
+
// Note that only StandardUnlockableUtxo inputs are supported for debugging, so any transaction with custom unlockers
|
|
133
|
+
// cannot be debugged (and therefore cannot return VM resource usage)
|
|
134
|
+
const results = this.debug();
|
|
135
|
+
const vmResourceUsage = [];
|
|
136
|
+
const tableData = [];
|
|
137
|
+
const formatMetric = (value, total, withPercentage = false) => `${formatNumber(value)} / ${formatNumber(total)}${withPercentage ? ` (${(value / total * 100).toFixed(0)}%)` : ''}`;
|
|
138
|
+
const formatNumber = (value) => value.toLocaleString('en');
|
|
139
|
+
const resultEntries = Object.entries(results);
|
|
140
|
+
for (const [index, input] of this.inputs.entries()) {
|
|
141
|
+
const [, result] = resultEntries.find(([entryKey]) => entryKey.includes(`input${index}`)) ?? [];
|
|
142
|
+
const metrics = result?.at(-1)?.metrics;
|
|
143
|
+
// Should not happen
|
|
144
|
+
if (!metrics)
|
|
145
|
+
throw new Error('VM resource could not be calculated');
|
|
146
|
+
vmResourceUsage.push(metrics);
|
|
147
|
+
tableData.push({
|
|
148
|
+
'Contract - Function': isContractUnlocker(input.unlocker) ? `${input.unlocker.contract.name} - ${input.unlocker.abiFunction.name}` : 'P2PKH Input',
|
|
149
|
+
Ops: metrics.evaluatedInstructionCount,
|
|
150
|
+
'Op Cost Budget Usage': formatMetric(metrics.operationCost, metrics.maximumOperationCost, true),
|
|
151
|
+
SigChecks: formatMetric(metrics.signatureCheckCount, metrics.maximumSignatureCheckCount),
|
|
152
|
+
Hashes: formatMetric(metrics.hashDigestIterations, metrics.maximumHashDigestIterations),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (verbose) {
|
|
156
|
+
console.log('VM Resource usage by inputs:');
|
|
157
|
+
console.table(tableData);
|
|
158
|
+
}
|
|
159
|
+
return vmResourceUsage;
|
|
160
|
+
}
|
|
161
|
+
getBitauthUri() {
|
|
109
162
|
console.warn('WARNING: it is unsafe to use this Bitauth URI when using real private keys as they are included in the transaction template');
|
|
110
163
|
return getBitauthUri(this.getLibauthTemplate());
|
|
111
164
|
}
|
|
112
165
|
getLibauthTemplate() {
|
|
113
|
-
return
|
|
166
|
+
return getLibauthTemplate(this);
|
|
114
167
|
}
|
|
115
168
|
async send(raw) {
|
|
116
169
|
const tx = this.build();
|
|
@@ -125,7 +178,7 @@ export class TransactionBuilder {
|
|
|
125
178
|
}
|
|
126
179
|
catch (e) {
|
|
127
180
|
const reason = e.error ?? e.message;
|
|
128
|
-
throw new FailedTransactionError(reason);
|
|
181
|
+
throw new FailedTransactionError(reason, this.getBitauthUri());
|
|
129
182
|
}
|
|
130
183
|
}
|
|
131
184
|
async getTxDetails(txid, raw) {
|
package/dist/debugging.js
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
|
-
import { AuthenticationErrorCommon, binToHex, createCompiler, createVirtualMachineBch2025, decodeAuthenticationInstructions, encodeAuthenticationInstruction, walletTemplateToCompilerConfiguration } from '@bitauth/libauth';
|
|
1
|
+
import { AuthenticationErrorCommon, binToHex, createCompiler, createVirtualMachineBch2023, createVirtualMachineBch2025, createVirtualMachineBch2026, createVirtualMachineBchSpec, decodeAuthenticationInstructions, encodeAuthenticationInstruction, walletTemplateToCompilerConfiguration } from '@bitauth/libauth';
|
|
2
2
|
import { Op, PrimitiveType, asmToBytecode, bytecodeToAsm, decodeBool, decodeInt, decodeString } from '@cashscript/utils';
|
|
3
3
|
import { findLastIndex, toRegExp } from './utils.js';
|
|
4
4
|
import { FailedRequireError, FailedTransactionError, FailedTransactionEvaluationError } from './Errors.js';
|
|
5
|
-
import { getBitauthUri } from './LibauthTemplate.js';
|
|
5
|
+
import { getBitauthUri } from './libauth-template/LibauthTemplate.js';
|
|
6
|
+
/* eslint-enable @typescript-eslint/indent */
|
|
7
|
+
const createVirtualMachine = (vmTarget) => {
|
|
8
|
+
switch (vmTarget) {
|
|
9
|
+
case 'BCH_2023_05':
|
|
10
|
+
return createVirtualMachineBch2023();
|
|
11
|
+
case 'BCH_2025_05':
|
|
12
|
+
return createVirtualMachineBch2025();
|
|
13
|
+
case 'BCH_2026_05':
|
|
14
|
+
return createVirtualMachineBch2026();
|
|
15
|
+
case 'BCH_SPEC':
|
|
16
|
+
// TODO: This typecast is shitty, but it's hard to fix
|
|
17
|
+
return createVirtualMachineBchSpec();
|
|
18
|
+
default:
|
|
19
|
+
throw new Error(`Debugging is not supported for the ${vmTarget} virtual machine.`);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
6
22
|
// debugs the template, optionally logging the execution data
|
|
7
23
|
export const debugTemplate = (template, artifacts) => {
|
|
8
24
|
// If a contract has the same name, but a different bytecode, then it is considered a name collision
|
|
@@ -14,13 +30,7 @@ export const debugTemplate = (template, artifacts) => {
|
|
|
14
30
|
const unlockingScriptIds = Object.keys(template.scripts).filter((key) => 'unlocks' in template.scripts[key]);
|
|
15
31
|
for (const unlockingScriptId of unlockingScriptIds) {
|
|
16
32
|
const scenarioIds = template.scripts[unlockingScriptId].passes ?? [];
|
|
17
|
-
// There are no scenarios defined for P2PKH placeholder scripts, so we skip them
|
|
18
|
-
if (scenarioIds.length === 0)
|
|
19
|
-
continue;
|
|
20
33
|
const matchingArtifact = artifacts.find((artifact) => unlockingScriptId.startsWith(artifact.contractName));
|
|
21
|
-
if (!matchingArtifact) {
|
|
22
|
-
throw new Error(`No artifact found for unlocking script ${unlockingScriptId}`);
|
|
23
|
-
}
|
|
24
34
|
for (const scenarioId of scenarioIds) {
|
|
25
35
|
results[`${unlockingScriptId}.${scenarioId}`] = debugSingleScenario(template, matchingArtifact, unlockingScriptId, scenarioId);
|
|
26
36
|
}
|
|
@@ -38,11 +48,14 @@ const debugSingleScenario = (template, artifact, unlockingScriptId, scenarioId)
|
|
|
38
48
|
// https://libauth.org/types/AuthenticationProgramStateControlStack.html
|
|
39
49
|
const executedDebugSteps = lockingScriptDebugResult
|
|
40
50
|
.filter((debugStep) => debugStep.controlStack.every(item => item === true));
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
// P2PKH inputs do not have an artifact, so we skip the console.log handling
|
|
52
|
+
if (artifact) {
|
|
53
|
+
const executedLogs = (artifact.debug?.logs ?? [])
|
|
54
|
+
.filter((log) => executedDebugSteps.some((debugStep) => log.ip === debugStep.ip));
|
|
55
|
+
for (const log of executedLogs) {
|
|
56
|
+
const inputIndex = extractInputIndexFromScenario(scenarioId);
|
|
57
|
+
logConsoleLogStatement(log, executedDebugSteps, artifact, inputIndex, vm);
|
|
58
|
+
}
|
|
46
59
|
}
|
|
47
60
|
const lastExecutedDebugStep = executedDebugSteps[executedDebugSteps.length - 1];
|
|
48
61
|
// If an error is present in the last step, that means a require statement in the middle of the function failed
|
|
@@ -60,9 +73,15 @@ const debugSingleScenario = (template, artifact, unlockingScriptId, scenarioId)
|
|
|
60
73
|
// Note that we do NOT use this adjusted IP when passing the failing IP into the FailedRequireError.
|
|
61
74
|
const isNullFail = lastExecutedDebugStep.error.includes(AuthenticationErrorCommon.nonNullSignatureFailure);
|
|
62
75
|
const requireStatementIp = failingIp + (isNullFail && isSignatureCheckWithoutVerify(failingInstruction) ? 1 : 0);
|
|
76
|
+
const { program: { inputIndex }, error } = lastExecutedDebugStep;
|
|
77
|
+
// If there is no artifact, this is a P2PKH debug error, error can occur when final CHECKSIG fails with NULLFAIL or when
|
|
78
|
+
// public key does not match pkh in EQUALVERIFY
|
|
79
|
+
// Note: due to P2PKHUnlocker implementation, the CHECKSIG cannot fail in practice, only the EQUALVERIFY can fail
|
|
80
|
+
if (!artifact) {
|
|
81
|
+
throw new FailedTransactionError(error, getBitauthUri(template));
|
|
82
|
+
}
|
|
63
83
|
const requireStatement = (artifact.debug?.requires ?? [])
|
|
64
84
|
.find((statement) => statement.ip === requireStatementIp);
|
|
65
|
-
const { program: { inputIndex }, error } = lastExecutedDebugStep;
|
|
66
85
|
if (requireStatement) {
|
|
67
86
|
// Note that we use failingIp here rather than requireStatementIp, see comment above
|
|
68
87
|
throw new FailedRequireError(artifact, failingIp, requireStatement, inputIndex, getBitauthUri(template), error);
|
|
@@ -82,9 +101,14 @@ const debugSingleScenario = (template, artifact, unlockingScriptId, scenarioId)
|
|
|
82
101
|
// logDebugSteps(executedDebugSteps, lastExecutedDebugStep.instructions);
|
|
83
102
|
// console.warn('message', finalExecutedVerifyIp);
|
|
84
103
|
// console.warn(artifact.debug?.requires);
|
|
104
|
+
const { program: { inputIndex } } = lastExecutedDebugStep;
|
|
105
|
+
// If there is no artifact, this is a P2PKH debug error, final verify can only occur when final CHECKSIG failed
|
|
106
|
+
// Note: due to P2PKHUnlocker implementation, this cannot happen in practice
|
|
107
|
+
if (!artifact) {
|
|
108
|
+
throw new FailedTransactionError(evaluationResult, getBitauthUri(template));
|
|
109
|
+
}
|
|
85
110
|
const requireStatement = (artifact.debug?.requires ?? [])
|
|
86
111
|
.find((message) => message.ip === finalExecutedVerifyIp);
|
|
87
|
-
const { program: { inputIndex } } = lastExecutedDebugStep;
|
|
88
112
|
if (requireStatement) {
|
|
89
113
|
throw new FailedRequireError(artifact, sourcemapInstructionPointer, requireStatement, inputIndex, getBitauthUri(template));
|
|
90
114
|
}
|
|
@@ -102,7 +126,7 @@ const extractInputIndexFromScenario = (scenarioId) => {
|
|
|
102
126
|
// internal util. instantiates the virtual machine and compiles the template into a program
|
|
103
127
|
const createProgram = (template, unlockingScriptId, scenarioId) => {
|
|
104
128
|
const configuration = walletTemplateToCompilerConfiguration(template);
|
|
105
|
-
const vm =
|
|
129
|
+
const vm = createVirtualMachine(template.supported[0]);
|
|
106
130
|
const compiler = createCompiler(configuration);
|
|
107
131
|
if (!template.scripts[unlockingScriptId]) {
|
|
108
132
|
throw new Error(`No unlock script found in template for ID ${unlockingScriptId}`);
|
|
@@ -123,18 +147,18 @@ const createProgram = (template, unlockingScriptId, scenarioId) => {
|
|
|
123
147
|
}
|
|
124
148
|
return { vm, program: scenarioGeneration.scenario.program };
|
|
125
149
|
};
|
|
126
|
-
const logConsoleLogStatement = (log, debugSteps, artifact, inputIndex) => {
|
|
150
|
+
const logConsoleLogStatement = (log, debugSteps, artifact, inputIndex, vm) => {
|
|
127
151
|
let line = `${artifact.contractName}.cash:${log.line}`;
|
|
128
152
|
const decodedData = log.data.map((element) => {
|
|
129
153
|
if (typeof element === 'string')
|
|
130
154
|
return element;
|
|
131
155
|
const debugStep = debugSteps.find((step) => step.ip === element.ip);
|
|
132
|
-
const transformedDebugStep = applyStackItemTransformations(element, debugStep);
|
|
156
|
+
const transformedDebugStep = applyStackItemTransformations(element, debugStep, vm);
|
|
133
157
|
return decodeStackItem(element, transformedDebugStep.stack);
|
|
134
158
|
});
|
|
135
159
|
console.log(`[Input #${inputIndex}] ${line} ${decodedData.join(' ')}`);
|
|
136
160
|
};
|
|
137
|
-
const applyStackItemTransformations = (element, debugStep) => {
|
|
161
|
+
const applyStackItemTransformations = (element, debugStep, vm) => {
|
|
138
162
|
if (!element.transformations)
|
|
139
163
|
return debugStep;
|
|
140
164
|
const transformationsBytecode = asmToBytecode(element.transformations);
|
|
@@ -150,8 +174,9 @@ const applyStackItemTransformations = (element, debugStep) => {
|
|
|
150
174
|
instructions: transformationsAuthenticationInstructions,
|
|
151
175
|
signedMessages: [],
|
|
152
176
|
program: { ...debugStep.program },
|
|
177
|
+
functionTable: debugStep.functionTable ?? {},
|
|
178
|
+
functionCount: debugStep.functionCount ?? 0,
|
|
153
179
|
};
|
|
154
|
-
const vm = createVirtualMachineBch2025();
|
|
155
180
|
const transformationsEndState = vm.stateEvaluate(transformationsStartState);
|
|
156
181
|
return transformationsEndState;
|
|
157
182
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { default as SignatureTemplate } from './SignatureTemplate.js';
|
|
2
|
-
export { Contract
|
|
3
|
-
export { Transaction } from './Transaction.js';
|
|
2
|
+
export { Contract } from './Contract.js';
|
|
4
3
|
export { TransactionBuilder } from './TransactionBuilder.js';
|
|
5
4
|
export { type ConstructorArgument, type FunctionArgument, type EncodedConstructorArgument, type EncodedFunctionArgument, encodeFunctionArgument, } from './Argument.js';
|
|
6
5
|
export type { Artifact, AbiFunction, AbiInput } from '@cashscript/utils';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { default as SignatureTemplate } from './SignatureTemplate.js';
|
|
2
2
|
export { Contract } from './Contract.js';
|
|
3
|
-
export { Transaction } from './Transaction.js';
|
|
4
3
|
export { TransactionBuilder } from './TransactionBuilder.js';
|
|
5
4
|
export { encodeFunctionArgument, } from './Argument.js';
|
|
6
5
|
export * as utils from '@cashscript/utils';
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Transaction } from '@bitauth/libauth';
|
|
1
|
+
import { AuthenticationProgramStateResourceLimits, type Transaction } from '@bitauth/libauth';
|
|
2
2
|
import type { NetworkProvider } from './network/index.js';
|
|
3
3
|
import type SignatureTemplate from './SignatureTemplate.js';
|
|
4
4
|
import { Contract } from './Contract.js';
|
|
@@ -47,10 +47,6 @@ export declare function isContractUnlocker(unlocker: Unlocker): unlocker is Cont
|
|
|
47
47
|
export declare function isP2PKHUnlocker(unlocker: Unlocker): unlocker is P2PKHUnlocker;
|
|
48
48
|
export declare function isStandardUnlocker(unlocker: Unlocker): unlocker is StandardUnlocker;
|
|
49
49
|
export declare function isPlaceholderUnlocker(unlocker: Unlocker): unlocker is PlaceholderP2PKHUnlocker;
|
|
50
|
-
export interface UtxoP2PKH extends Utxo {
|
|
51
|
-
template: SignatureTemplate;
|
|
52
|
-
}
|
|
53
|
-
export declare function isUtxoP2PKH(utxo: Utxo): utxo is UtxoP2PKH;
|
|
54
50
|
export interface Recipient {
|
|
55
51
|
to: string;
|
|
56
52
|
amount: bigint;
|
|
@@ -107,12 +103,20 @@ export declare const Network: {
|
|
|
107
103
|
REGTEST: "regtest";
|
|
108
104
|
};
|
|
109
105
|
export type Network = (typeof Network)[keyof typeof Network];
|
|
106
|
+
export declare const VmTarget: {
|
|
107
|
+
BCH_2023_05: "BCH_2023_05";
|
|
108
|
+
BCH_2025_05: "BCH_2025_05";
|
|
109
|
+
BCH_2026_05: "BCH_2026_05";
|
|
110
|
+
BCH_SPEC: "BCH_SPEC";
|
|
111
|
+
};
|
|
112
|
+
export type VmTarget = (typeof VmTarget)[keyof typeof VmTarget];
|
|
110
113
|
export interface TransactionDetails extends Transaction {
|
|
111
114
|
txid: string;
|
|
112
115
|
hex: string;
|
|
113
116
|
}
|
|
114
117
|
export interface ContractOptions {
|
|
115
|
-
provider
|
|
118
|
+
provider: NetworkProvider;
|
|
116
119
|
addressType?: AddressType;
|
|
117
120
|
}
|
|
118
121
|
export type AddressType = 'p2sh20' | 'p2sh32';
|
|
122
|
+
export type VmResourceUsage = AuthenticationProgramStateResourceLimits['metrics'];
|
package/dist/interfaces.js
CHANGED
|
@@ -16,9 +16,6 @@ export function isStandardUnlocker(unlocker) {
|
|
|
16
16
|
export function isPlaceholderUnlocker(unlocker) {
|
|
17
17
|
return 'placeholder' in unlocker;
|
|
18
18
|
}
|
|
19
|
-
export function isUtxoP2PKH(utxo) {
|
|
20
|
-
return 'template' in utxo;
|
|
21
|
-
}
|
|
22
19
|
export var SignatureAlgorithm;
|
|
23
20
|
(function (SignatureAlgorithm) {
|
|
24
21
|
SignatureAlgorithm[SignatureAlgorithm["ECDSA"] = 0] = "ECDSA";
|
|
@@ -43,4 +40,10 @@ export const Network = {
|
|
|
43
40
|
MOCKNET: literal('mocknet'),
|
|
44
41
|
REGTEST: literal('regtest'),
|
|
45
42
|
};
|
|
43
|
+
export const VmTarget = {
|
|
44
|
+
BCH_2023_05: literal('BCH_2023_05'),
|
|
45
|
+
BCH_2025_05: literal('BCH_2025_05'),
|
|
46
|
+
BCH_2026_05: literal('BCH_2026_05'),
|
|
47
|
+
BCH_SPEC: literal('BCH_SPEC'),
|
|
48
|
+
};
|
|
46
49
|
//# sourceMappingURL=interfaces.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { WalletTemplate } from '@bitauth/libauth';
|
|
2
|
+
import { DebugResults } from '../debugging.js';
|
|
3
|
+
import { TransactionBuilder } from '../TransactionBuilder.js';
|
|
4
|
+
export declare const getLibauthTemplate: (transactionBuilder: TransactionBuilder) => WalletTemplate;
|
|
5
|
+
export declare const debugLibauthTemplate: (template: WalletTemplate, transaction: TransactionBuilder) => DebugResults;
|
|
6
|
+
export declare const getBitauthUri: (template: WalletTemplate) => string;
|