Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e2f8048
Change default of MockNetworkProvider updateUtxoSet to true
rkalis Sep 9, 2025
5b05cac
Make provider a required option in the Contract constructor
rkalis Sep 9, 2025
6b914d3
Remove old transaction builder and remove related occurrances in tests
rkalis Sep 9, 2025
9e30e35
Merge LibauthTemplate.ts and advanced/Libauthtemplate.ts and remove T…
rkalis Sep 9, 2025
ba8fa22
Move LibauthTemplate.ts from src/advanced/ to src/
rkalis Sep 9, 2025
37c91a9
Add removed deprecated transaction builder to release notes
rkalis Sep 9, 2025
a6726f4
Rework P2PKH template building (#357)
mainnet-pat Sep 18, 2025
7185ded
Make bitauthUri required on FailedTransactionError
rkalis Sep 18, 2025
0ef29ab
Remove currentBlockHeight and currentBlockTime from template scenarios
rkalis Sep 18, 2025
f44934e
Use zip() to loop over libauth & cs inputs/outputs
rkalis Sep 18, 2025
f41025d
Remove unnecessary exports from LibauthTemplate
rkalis Sep 18, 2025
8b54084
Address some TODOs in LibauthTemplate
rkalis Sep 18, 2025
3ad11e4
No longer seed MockNetworkProvider with any test UTXOs
rkalis Sep 18, 2025
a7a37e1
Update libauth, allow to set debugger's target VM version (#353)
mainnet-pat Sep 18, 2025
d349ebf
Better support for ECDSA signatures in ABI arguments (#319)
mainnet-pat Sep 23, 2025
fa4785a
Add vmResourceUsage method to TransactionBuilder (#354)
mainnet-pat Sep 23, 2025
328657c
Fix bug in SignatureTemplate where private key hex strings were not s…
rkalis Sep 25, 2025
65af68a
Add tests for contract unlockers and SignatureTemplate
rkalis Sep 25, 2025
5f03650
Add signMessageHash method to SignatureTemplate
rkalis Sep 30, 2025
b3cb273
Rework max fee options in TransactionBuilder & add max fee per byte
rkalis Sep 30, 2025
10fbe81
Update P2PKH-tokens tests to use new Transaction Builder
rkalis Sep 30, 2025
3229f5e
Add allowImplicitFungibleTokenBurn option to TransactionBuilder
rkalis Sep 30, 2025
ab75e5e
Update migration notes for v0.12
rkalis Sep 30, 2025
175954c
Refactor LibauthTemplate
rkalis Oct 2, 2025
20080f5
Bump version to 0.12.0
rkalis Oct 2, 2025
4eea331
Final changes before 0.12 release
rkalis Oct 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions examples/PriceOracle.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { padMinimallyEncodedVmNumber, flattenBinArray, secp256k1 } from '@bitauth/libauth';
import { padMinimallyEncodedVmNumber, flattenBinArray } from '@bitauth/libauth';
import { encodeInt, sha256 } from '@cashscript/utils';
import { SignatureAlgorithm, SignatureTemplate } from 'cashscript';

export class PriceOracle {
constructor(public privateKey: Uint8Array) {}
constructor(public privateKey: Uint8Array) { }

// Encode a blockHeight and bchUsdPrice into a byte sequence of 8 bytes (4 bytes per value)
createMessage(blockHeight: bigint, bchUsdPrice: bigint): Uint8Array {
Expand All @@ -12,9 +13,8 @@ export class PriceOracle {
return flattenBinArray([encodedBlockHeight, encodedBchUsdPrice]);
}

signMessage(message: Uint8Array): Uint8Array {
const signature = secp256k1.signMessageHashSchnorr(this.privateKey, sha256(message));
if (typeof signature === 'string') throw new Error();
return signature;
signMessage(message: Uint8Array, signatureAlgorithm: SignatureAlgorithm = SignatureAlgorithm.SCHNORR): Uint8Array {
const signatureTemplate = new SignatureTemplate(this.privateKey, undefined, signatureAlgorithm);
return signatureTemplate.signMessageHash(sha256(message));
}
}
2 changes: 1 addition & 1 deletion examples/announcement.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.11.0;
pragma cashscript ^0.12.0;

/* This is a contract showcasing covenants outside of regular transactional use.
* It enforces the contract to make an "announcement" on Memo.cash, and send the
Expand Down
2 changes: 1 addition & 1 deletion examples/hodl_vault.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.11.0;
pragma cashscript ^0.12.0;

// This contract forces HODLing until a certain price target has been reached
// A minimum block is provided to ensure that oracle price entries from before this block are disregarded
Expand Down
2 changes: 1 addition & 1 deletion examples/mecenas.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.11.0;
pragma cashscript ^0.12.0;

/* This is an unofficial CashScript port of Licho's Mecenas contract. It is
* not compatible with Licho's EC plugin, but rather meant as a demonstration
Expand Down
2 changes: 1 addition & 1 deletion examples/mecenas_locktime.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.11.0;
pragma cashscript ^0.12.0;

// This is an experimental contract for a more "streaming" Mecenas experience
// Completely untested, just a concept
Expand Down
2 changes: 1 addition & 1 deletion examples/p2pkh.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.11.0;
pragma cashscript ^0.12.0;

contract P2PKH(bytes20 pkh) {
// Require pk to match stored pkh and signature to match
Expand Down
8 changes: 4 additions & 4 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cashscript-examples",
"private": true,
"version": "0.11.5",
"version": "0.12.0",
"description": "Usage examples of the CashScript SDK",
"main": "p2pkh.js",
"type": "module",
Expand All @@ -11,10 +11,10 @@
"lint": "eslint . --ext .ts --ignore-path ../.eslintignore"
},
"dependencies": {
"@bitauth/libauth": "^3.1.0-next.2",
"@bitauth/libauth": "^3.1.0-next.8",
"@types/node": "^22.17.0",
"cashc": "^0.11.5",
"cashscript": "^0.11.5",
"cashc": "^0.12.0",
"cashscript": "^0.12.0",
"eslint": "^8.56.0",
"typescript": "^5.9.2"
}
Expand Down
4 changes: 2 additions & 2 deletions examples/testing-suite/artifacts/example.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
},
"compiler": {
"name": "cashc",
"version": "0.11.3"
"version": "0.12.0"
},
"updatedAt": "2025-08-05T08:32:16.100Z"
"updatedAt": "2025-10-02T09:56:11.510Z"
}
8 changes: 4 additions & 4 deletions examples/testing-suite/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "testing-suite",
"version": "0.11.5",
"version": "0.12.0",
"description": "Example project to develop and test CashScript contracts",
"main": "index.js",
"type": "module",
Expand All @@ -25,9 +25,9 @@
"test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"
},
"dependencies": {
"@bitauth/libauth": "^3.1.0-next.2",
"cashc": "^0.11.5",
"cashscript": "^0.11.5",
"@bitauth/libauth": "^3.1.0-next.8",
"cashc": "^0.12.0",
"cashscript": "^0.12.0",
"url-join": "^5.0.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion examples/transfer_with_timeout.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.11.0;
pragma cashscript ^0.12.0;

contract TransferWithTimeout(
pubkey sender,
Expand Down
6 changes: 3 additions & 3 deletions packages/cashc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cashc",
"version": "0.11.5",
"version": "0.12.0",
"description": "Compile Bitcoin Cash contracts to Bitcoin Cash Script or artifacts",
"keywords": [
"bitcoin",
Expand Down Expand Up @@ -51,8 +51,8 @@
"test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"
},
"dependencies": {
"@bitauth/libauth": "^3.1.0-next.2",
"@cashscript/utils": "^0.11.5",
"@bitauth/libauth": "^3.1.0-next.8",
"@cashscript/utils": "^0.12.0",
"antlr4": "^4.13.2",
"commander": "^14.0.0",
"semver": "^7.7.2"
Expand Down
2 changes: 1 addition & 1 deletion packages/cashc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export * from './Errors.js';
export * as utils from '@cashscript/utils';
export { compileFile, compileString } from './compiler.js';

export const version = '0.11.5';
export const version = '0.12.0';
24 changes: 18 additions & 6 deletions packages/cashscript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,29 @@ Using the CashScript SDK, you can import contract artifact files, create new ins
const provider = new ElectrumNetworkProvider('mainnet');

// Create a new P2PKH contract with constructor arguments: { pkh: pkh }
const contract = new Contract(P2PKH, [pkh], provider);
const contract = new Contract(P2PKH, [pkh], { provider });

// Get contract balance & output address + balance
console.log('contract address:', contract.address);
console.log('contract balance:', await contract.getBalance());

// Call the spend function with the owner's signature
// And use it to send 0. 000 100 00 BCH back to the contract's address
const txDetails = await contract.functions
.spend(pk, new SignatureTemplate(keypair))
.to(contract.address, 10000)
const transactionBuilder = new TransactionBuilder({ provider });
const contractUtxos = await contract.getUtxos();

const sendAmount = 10_000n;
const destinationAddress = '... some address ...';

// Calculate the change amount, accounting for a miner fee of 1000 satoshis
const changeAmount = contractUtxos[0].satoshis - sendAmount - 1000n;

// Construct a transaction with the transaction builder
const txDetails = await transactionBuilder
// Add a contract input that spends from the contract using the 'spend' function
.addInput(contractUtxos[0], contract.unlock.spend(pk, new SignatureTemplate(keypair)))
// Add an output that sends 0. 000 100 00 BCH back to the destination address
.addOutput({ to: destinationAddress, amount: sendAmount })
// Add a change output that sends the change back to the contract's address
.addOutput({ to: contract.address, amount: changeAmount })
.send();

console.log(txDetails);
Expand Down
7 changes: 3 additions & 4 deletions packages/cashscript/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cashscript",
"version": "0.11.5",
"version": "0.12.0",
"description": "Easily write and interact with Bitcoin Cash contracts",
"keywords": [
"bitcoin cash",
Expand Down Expand Up @@ -45,11 +45,10 @@
"test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"
},
"dependencies": {
"@bitauth/libauth": "^3.1.0-next.2",
"@cashscript/utils": "^0.11.5",
"@bitauth/libauth": "^3.1.0-next.8",
"@cashscript/utils": "^0.12.0",
"@electrum-cash/network": "^4.1.3",
"@mr-zwets/bchn-api-wrapper": "^1.0.1",
"fast-deep-equal": "^3.1.3",
"pako": "^2.1.0",
"semver": "^7.7.2"
},
Expand Down
20 changes: 12 additions & 8 deletions packages/cashscript/src/Argument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,20 @@ export function encodeFunctionArgument(argument: FunctionArgument, typeStr: stri
throw Error(`Value for type ${type} should be a Uint8Array or hex string`);
}

// Redefine SIG as a bytes65 so it is included in the size checks below
// Note that ONLY Schnorr signatures are accepted
if (type === PrimitiveType.SIG && argument.byteLength !== 0) {
type = new BytesType(65);
// Redefine SIG as a bytes65 (Schnorr) or bytes71, bytes72, bytes73 (ECDSA) or bytes0 (for NULLFAIL)
if (type === PrimitiveType.SIG) {
if (![0, 65, 71, 72, 73].includes(argument.byteLength)) {
throw new TypeError(`bytes${argument.byteLength}`, type);
}
type = new BytesType(argument.byteLength);
}

// Redefine DATASIG as a bytes64 so it is included in the size checks below
// Note that ONLY Schnorr signatures are accepted
if (type === PrimitiveType.DATASIG && argument.byteLength !== 0) {
type = new BytesType(64);
// Redefine DATASIG as a bytes64 (Schnorr) or bytes70, bytes71, bytes72 (ECDSA) or bytes0 (for NULLFAIL)
if (type === PrimitiveType.DATASIG) {
if (![0, 64, 70, 71, 72].includes(argument.byteLength)) {
throw new TypeError(`bytes${argument.byteLength}`, type);
}
type = new BytesType(argument.byteLength);
}

// Bounded bytes types require a correctly sized argument
Expand Down
53 changes: 5 additions & 48 deletions packages/cashscript/src/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import {
Script,
scriptToBytecode,
} from '@cashscript/utils';
import { Transaction } from './Transaction.js';
import {
ConstructorArgument, encodeFunctionArgument, encodeConstructorArguments, encodeFunctionArguments, FunctionArgument,
ConstructorArgument, encodeFunctionArgument, encodeConstructorArguments, FunctionArgument,
} from './Argument.js';
import {
Unlocker, ContractOptions, GenerateUnlockingBytecodeOptions, Utxo, AddressType, ContractUnlocker,
Expand All @@ -22,20 +21,17 @@ import {
addressToLockScript, createInputScript, createSighashPreimage, scriptToAddress,
} from './utils.js';
import SignatureTemplate from './SignatureTemplate.js';
import { ElectrumNetworkProvider } from './network/index.js';
import { ParamsToTuple, AbiToFunctionMap } from './types/type-inference.js';
import semver from 'semver';

export class Contract<
TArtifact extends Artifact = Artifact,
TResolved extends {
constructorInputs: ConstructorArgument[];
functions: Record<string, any>;
unlock: Record<string, any>;
}
= {
constructorInputs: ParamsToTuple<TArtifact['constructorInputs']>;
functions: AbiToFunctionMap<TArtifact['abi'], Transaction>;
unlock: AbiToFunctionMap<TArtifact['abi'], Unlocker>;
},
> {
Expand All @@ -45,10 +41,7 @@ export class Contract<
bytecode: string;
bytesize: number;
opcount: number;

functions: TResolved['functions'];
unlock: TResolved['unlock'];

redeemScript: Script;
public provider: NetworkProvider;
public addressType: AddressType;
Expand All @@ -57,10 +50,10 @@ export class Contract<
constructor(
public artifact: TArtifact,
constructorArgs: TResolved['constructorInputs'],
private options?: ContractOptions,
private options: ContractOptions,
) {
this.provider = this.options?.provider ?? new ElectrumNetworkProvider();
this.addressType = this.options?.addressType ?? 'p2sh32';
this.provider = this.options.provider;
this.addressType = this.options.addressType ?? 'p2sh32';

const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName', 'compiler'];
if (!expectedProperties.every((property) => property in artifact)) {
Expand All @@ -80,21 +73,7 @@ export class Contract<

this.redeemScript = generateRedeemScript(asmToScript(this.artifact.bytecode), this.encodedConstructorArgs);

// Populate the functions object with the contract's functions
// (with a special case for single function, which has no "function selector")
this.functions = {};
if (artifact.abi.length === 1) {
const f = artifact.abi[0];
// @ts-ignore TODO: see if we can use generics to make TypeScript happy
this.functions[f.name] = this.createFunction(f);
} else {
artifact.abi.forEach((f, i) => {
// @ts-ignore TODO: see if we can use generics to make TypeScript happy
this.functions[f.name] = this.createFunction(f, i);
});
}

// Populate the functions object with the contract's functions
// Populate the 'unlock' object with the contract's functions
// (with a special case for single function, which has no "function selector")
this.unlock = {};
if (artifact.abi.length === 1) {
Expand Down Expand Up @@ -125,27 +104,6 @@ export class Contract<
return this.provider.getUtxos(this.address);
}

private createFunction(abiFunction: AbiFunction, selector?: number): ContractFunction {
return (...args: FunctionArgument[]) => {
if (abiFunction.inputs.length !== args.length) {
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}`);
}

// Encode passed args (this also performs type checking)
const encodedArgs = encodeFunctionArguments(abiFunction, args);

const unlocker = this.createUnlocker(abiFunction, selector)(...args);

return new Transaction(
this,
unlocker,
abiFunction,
encodedArgs,
selector,
);
};
}

private createUnlocker(abiFunction: AbiFunction, selector?: number): ContractFunctionUnlocker {
return (...args: FunctionArgument[]) => {
if (abiFunction.inputs.length !== args.length) {
Expand Down Expand Up @@ -180,5 +138,4 @@ export class Contract<
}
}

export type ContractFunction = (...args: FunctionArgument[]) => Transaction;
type ContractFunctionUnlocker = (...args: FunctionArgument[]) => ContractUnlocker;
2 changes: 1 addition & 1 deletion packages/cashscript/src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class NoDebugInformationInArtifactError extends Error {
}

export class FailedTransactionError extends Error {
constructor(public reason: string, public bitauthUri?: string) {
constructor(public reason: string, public bitauthUri: string) {
const warning = 'WARNING: it is unsafe to use this Bitauth URI when using real private keys as they are included in the transaction template';
super(`${reason}${bitauthUri ? `\n\n${warning}\n\nBitauth URI: ${bitauthUri}` : ''}`);
}
Expand Down
Loading
Loading