Skip to content
Draft

v0.13 #370

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
bd51c76
Update default VM target to BCH_2026
rkalis Oct 9, 2025
c463ca7
Update script limits & docs
rkalis Oct 9, 2025
67b6ce0
Use MockNetworkProvider in examples
rkalis Oct 9, 2025
d16cbd6
Add do-while loop to grammar and AST builder
rkalis Oct 14, 2025
602eea2
Add checks for final require statement to loop
rkalis Oct 14, 2025
765735b
Add typechecking for do-while loop condition
rkalis Oct 14, 2025
aa627b9
Add TODO for OP_NOT OP_NOT <> '' optimisation
rkalis Oct 14, 2025
bcce083
Add compilation / code generation logic for do-while loops
rkalis Oct 14, 2025
0f685a6
Add number comparison negation optimisations + update release notes
rkalis Oct 16, 2025
ab8053e
Do not perform final-verify check inside loops
rkalis Oct 16, 2025
6f812d9
Add loops to docs
rkalis Oct 16, 2025
2a49196
Bump version to 0.13.0-next.0
rkalis Oct 16, 2025
ac2e5db
Fix loop scope not being closed bug
rkalis Nov 13, 2025
049e29e
Fix require statement detection inside loops and add tests for it
rkalis Nov 13, 2025
7ee176c
Rework console.log handling for loops + add tests
rkalis Nov 13, 2025
1e4e237
Update release notes
rkalis Nov 13, 2025
5aa5206
Bump version to 0.13.0-next.1
rkalis Nov 13, 2025
a25355a
Fix tests
rkalis Nov 13, 2025
f14bb9a
Fix spellcheck
rkalis Nov 13, 2025
0072f76
update next docs (#385)
mr-zwets Jan 6, 2026
7828d11
Refactor last remaining TODO in LibauthTemplate
rkalis Jan 6, 2026
c26146b
Replace pako with fflate
rkalis Jan 8, 2026
7065336
Add bitshift, arithmetic shift, and invert operators
rkalis Jan 8, 2026
6f36f73
fix spellcheck
mr-zwets Jan 9, 2026
ab17ece
small script-limits docs improvement
mr-zwets Jan 9, 2026
076ed8a
Update release notes for 0.13.0-next.2
rkalis Jan 13, 2026
f8e82bb
Bump version to 0.13.0-next.2
rkalis Jan 13, 2026
91701a8
Improve testing-suite
rkalis Jan 13, 2026
f574f7b
Remove url-join devDependency from cashc
rkalis Jan 13, 2026
3a7cb9d
Fix that int => bool casting now coerces the value to 1 or 0
rkalis Jan 15, 2026
f3fb73f
Remove "padding" type casting & add unsafe type casting
rkalis Jan 20, 2026
c64e482
Add toPaddedBytes(int, int) global function and tests
rkalis Jan 20, 2026
1d05680
Update fixture artifacts
rkalis Jan 20, 2026
43f16a2
Add small tests for unsafe_bool and unsafe_int casting
rkalis Jan 22, 2026
35a7b63
Update casting docs, release & migration notes and other related docs
rkalis Jan 22, 2026
d4e9ee2
Bump version to 0.13.0-next.3
rkalis Jan 22, 2026
88fdbe1
Treat booleans as numeric for compiled output
rkalis Feb 3, 2026
07dece8
Add type enforcing of bool and bytesX parameters at the start of func…
rkalis Feb 3, 2026
388f037
Add compiler flag to skip enforcing function parameter types
rkalis Feb 3, 2026
79078fd
Recompile fixture artifacts
rkalis Feb 3, 2026
d8a663b
Add generation/fixture for enforceFunctionParameterTypes=false and up…
rkalis Feb 3, 2026
c87fac6
Fix bug in bytesX type enforcing
rkalis Feb 3, 2026
e0a4c89
Update SDK fixtures and tests after compiler changes
rkalis Feb 3, 2026
5e97338
Update additional tests after bug fix
rkalis Feb 3, 2026
b9778e1
Support P2S in the CashScript SDK
rkalis Feb 13, 2026
59e0b32
Add type magic to differentiate between P2S and P2SH contracts
rkalis Feb 17, 2026
8f1522d
Update docs for P2S and other small changes
rkalis Feb 17, 2026
8bf234e
Update release and migration notes + small docs changes
rkalis Feb 19, 2026
a9b4062
Bump version to 0.13.0-next.4
rkalis Feb 19, 2026
9589673
Update MockNetworkProvider and its docs
rkalis Feb 24, 2026
124af72
Implement for/while loops step 1+2 (grammar + AST building)
rkalis Feb 24, 2026
e50b8ac
Add LoopLoweringTravelsal to lower while/for loops into do-while loops
rkalis Feb 26, 2026
2412182
Add (failing) tests for for/while loop compilation and console.log/re…
rkalis Feb 26, 2026
4ef8622
Fix issue where larger than bytes8 could not be casted to int
rkalis Mar 3, 2026
c53d0c8
Remove loop lowering setup in favour of custom compilation paths for …
rkalis Mar 5, 2026
3f528b4
Add sourceTags for labeling out-of-order opcodes
rkalis Mar 12, 2026
c52b4d8
Add AGENTS.md and CLAUDE.md
rkalis Mar 12, 2026
de5373f
Update docs for new loops release
rkalis Mar 12, 2026
672ddca
Bump version to 0.13.0-next.5
rkalis Mar 12, 2026
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
5 changes: 5 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
"locktimes",
"lokad",
"lshift",
"LSHIFTNUM",
"LSHIFTBIN",
"mecenas",
"meep",
"minimaldata",
Expand Down Expand Up @@ -154,6 +156,8 @@
"ripemd",
"rosco",
"rshift",
"RSHIFTBIN",
"RSHIFTNUM",
"sablier",
"satoshis",
"sats",
Expand Down Expand Up @@ -221,6 +225,7 @@
"cherian",
"CSCriptNum",
"docu",
"fflate",
"fundme",
"getblockcount",
"getrawtransaction",
Expand Down
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module.exports = {
'error',
{ allowShortCircuit: true }
],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], // Ignore unused variables that start with an underscore
'import/prefer-default-export': 0, // Useful when creating util files that may get expanded
'import/no-extraneous-dependencies': 0, // This gives weird errors
'no-bitwise': 0, // Need to use bitwise operators
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,5 @@ typings/
.dynamodb/

manual-test.ts

.claude/
93 changes: 93 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

CashScript is a high-level language for Bitcoin Cash smart contracts, consisting of a compiler (`cashc`), a TypeScript SDK (`cashscript`), and shared utilities (`@cashscript/utils`). It compiles `.cash` contract files into Bitcoin Cash bytecode artifacts.

## Common Commands

```bash
# Install all dependencies (also runs bootstrap + build)
yarn

# Build all packages (required after code changes to propagate across packages)
yarn build

# Run all tests (uses vitest)
yarn test

# Run tests for a single package
cd packages/cashc && yarn test
cd packages/utils && yarn test

# Run a specific test by name
yarn test -t 'test name from it/describe block'

# Run a single test file directly
yarn vitest run packages/utils/test/bitauth-script.test.ts

# Lint all packages
yarn lint

# Spellcheck
yarn spellcheck

# Regenerate ANTLR parser after grammar changes (from packages/cashc)
yarn antlr

# Run cashscript tests against real chipnet instead of mocknet
TESTS_USE_CHIPNET=true yarn test
```

## Architecture

### Monorepo Structure

Yarn workspaces + Lerna with three main packages:

- **`packages/cashc`** — Compiler: `.cash` source → artifact JSON
- **`packages/cashscript`** — SDK: load artifacts, build and send transactions
- **`packages/utils`** — Shared utilities used by both compiler and SDK

### Compiler Pipeline (`cashc`)

The compilation flow in `compiler.ts`:

1. **Lexing/Parsing** — ANTLR4 grammar (`src/grammar/CashScript.g4`) → parse tree
2. **AST Building** — `AstBuilder` converts parse tree → typed AST (`src/ast/`)
3. **Semantic Analysis** — Three traversals in order:
- `SymbolTableTraversal` — resolve identifiers and scopes
- `TypeCheckTraversal` — type checking
- `EnsureFinalRequireTraversal` — validate contract structure
4. **Code Generation** — `GenerateTargetTraversal` (`src/generation/`) emits bytecode opcodes, source maps, console logs, requires, and source tags
5. **Optimization** — `optimiseBytecode` (`utils/src/script.ts`) applies peephole optimizations, adjusting source maps and metadata indices
6. **Artifact Generation** — produces the final JSON artifact with bytecode, ABI, debug info

The compiler uses the **visitor pattern** throughout — AST nodes accept traversals defined in `AstVisitor`/`AstTraversal`.

### SDK (`cashscript`)

- `Contract` — loads compiled artifacts, instantiates with constructor args
- `TransactionBuilder` — builds and signs Bitcoin Cash transactions
- Network providers: `ElectrumNetworkProvider`, `BitcoinRpcNetworkProvider`, `MockNetworkProvider`, `FullStackNetworkProvider`
- `src/libauth-template/` — integrates with `@bitauth/libauth` for transaction evaluation and debugging

### Utils (`@cashscript/utils`)

Shared between compiler and SDK:
- `artifact.ts` — artifact type definitions, `DebugInformation` structure
- `script.ts` — bytecode optimization, opcode manipulation
- `source-map.ts` — source map encoding/decoding (format: `sl:sc:el:ec:h` per opcode, `;`-separated, with field inheritance compression)
- `bitauth-script.ts` — formats bytecode as human-readable BitAuth script (used in debugging)
- `types.ts` — shared types including `SourceTagKind`, `SourceTagEntry`
- `optimisations.ts` / `cashproof-optimisations.ts` — peephole optimization rules

## Code Conventions

- **ESM modules** — all packages use ES modules; file extensions required in imports (e.g., `./foo.js`)
- **Max line length**: 125 characters (strings and template literals exempt)
- **Explicit return types** on functions (expressions exempt)
- **Keep nested code to a minimum** — use helper functions to keep code DRY and readable
- Prefer writing code in a way that reads top-down, from the main entry point to the leaves.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,22 @@ npm install -g cashc
### Usage

```bash
Usage: cashc [options] [source_file]
Usage: cashc [options] <source_file>

Arguments:
source_file The source file to compile.

Options:
-V, --version Output the version number.
-o, --output <path> Specify a file to output the generated artifact.
-h, --hex Compile the contract to hex format rather than a full artifact.
-A, --asm Compile the contract to ASM format rather than a full artifact.
-c, --opcount Display the number of opcodes in the compiled bytecode.
-s, --size Display the size in bytes of the compiled bytecode.
-f, --format <format> Specify the format of the output. (choices: "json", "ts", default: "json")
-?, --help Display help
-V, --version Output the version number.
-o, --output <path> Specify a file to output the generated artifact.
-h, --hex Compile the contract to hex format rather than a full artifact.
-A, --asm Compile the contract to ASM format rather than a full artifact.
-c, --opcount Display the number of opcodes in the compiled bytecode.
-s, --size Display the size in bytes of the compiled bytecode.
-S, --skip-enforce-function-parameter-types Do not enforce function parameter types.
-f, --format <format> Specify the format of the output. (choices: "json", "ts", default:
"json")
-?, --help Display help
```

## The CashScript SDK
Expand Down
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.12.0;
pragma cashscript ^0.13.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
15 changes: 11 additions & 4 deletions examples/announcement.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { Contract, ElectrumNetworkProvider, Output, TransactionBuilder } from 'cashscript';
import { Contract, Output, randomUtxo, TransactionBuilder } from 'cashscript';
import { compileFile } from 'cashc';
import { stringify } from '@bitauth/libauth';
import { URL } from 'url';
import { MockNetworkProvider } from 'cashscript/dist';

// Compile the Announcement contract to an artifact object
const artifact = compileFile(new URL('announcement.cash', import.meta.url));

// Initialise a network provider for network operations on MAINNET
const addressType = 'p2sh20';
const provider = new ElectrumNetworkProvider();
const contractType = 'p2sh20';

// Once you're ready to send transactions on a real network (like chipnet or mainnet), use the ElectrumNetworkProvider
// const provider = new ElectrumNetworkProvider();
const provider = new MockNetworkProvider();

// Instantiate a new contract using the compiled artifact and network provider
// AND providing the constructor parameters (none)
const contract = new Contract(artifact, [], { provider, addressType });
const contract = new Contract(artifact, [], { provider, contractType });

// Add a mock UTXO to the mock network provider
provider.addUtxo(contract.address, randomUtxo());

// Get contract balance & output address + balance
console.log('contract address:', contract.address);
Expand Down
8 changes: 6 additions & 2 deletions examples/hodl_vault.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.12.0;
pragma cashscript ^0.13.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 All @@ -11,7 +11,11 @@ contract HodlVault(
int minBlock,
int priceTarget
) {
function spend(sig ownerSig, datasig oracleSig, bytes oracleMessage) {
function spend(
sig ownerSig,
datasig oracleSig,
bytes8 oracleMessage
) {
// message: { blockHeight, price }
bytes4 blockHeightBin, bytes4 priceBin = oracleMessage.split(4);
int blockHeight = int(blockHeightBin);
Expand Down
10 changes: 7 additions & 3 deletions examples/hodl_vault.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { stringify } from '@bitauth/libauth';
import { Contract, SignatureTemplate, ElectrumNetworkProvider, TransactionBuilder, Output } from 'cashscript';
import { Contract, SignatureTemplate, TransactionBuilder, Output, MockNetworkProvider, randomUtxo } from 'cashscript';
import { compileFile } from 'cashc';
import { URL } from 'url';

Expand All @@ -14,14 +14,18 @@ import {
// Compile the HodlVault contract to an artifact object
const artifact = compileFile(new URL('hodl_vault.cash', import.meta.url));

// Initialise a network provider for network operations on CHIPNET
const provider = new ElectrumNetworkProvider('chipnet');
// Once you're ready to send transactions on a real network (like chipnet or mainnet), use the ElectrumNetworkProvider
// const provider = new ElectrumNetworkProvider();
const provider = new MockNetworkProvider();

// Instantiate a new contract using the compiled artifact and network provider
// AND providing the constructor parameters
const parameters = [alicePub, oraclePub, 100000n, 30000n];
const contract = new Contract(artifact, parameters, { provider });

// Add a mock UTXO to the mock network provider
provider.addUtxo(contract.address, randomUtxo());

// Get contract balance & output address + balance
console.log('contract address:', contract.address);
const contractUtxos = await contract.getUtxos();
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.12.0;
pragma cashscript ^0.13.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
10 changes: 7 additions & 3 deletions examples/mecenas.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { stringify } from '@bitauth/libauth';
import { Contract, ElectrumNetworkProvider, Output, TransactionBuilder } from 'cashscript';
import { Contract, MockNetworkProvider, Output, randomUtxo, TransactionBuilder } from 'cashscript';
import { compileFile } from 'cashc';
import { URL } from 'url';

Expand All @@ -9,15 +9,19 @@ import { aliceAddress, alicePkh, bobPkh } from './common.js';
// Compile the Mecenas contract to an artifact object
const artifact = compileFile(new URL('mecenas.cash', import.meta.url));

// Initialise a network provider for network operations on CHIPNET
const provider = new ElectrumNetworkProvider('chipnet');
// Once you're ready to send transactions on a real network (like chipnet or mainnet), use the ElectrumNetworkProvider
// const provider = new ElectrumNetworkProvider();
const provider = new MockNetworkProvider();

// Instantiate a new contract using the compiled artifact and network provider
// AND providing the constructor parameters:
// (recipient: alicePkh, funder: bobPkh, pledge: 10000)
const pledgeAmount = 10_000n;
const contract = new Contract(artifact, [alicePkh, bobPkh, pledgeAmount], { provider });

// Add a mock UTXO to the mock network provider
provider.addUtxo(contract.address, randomUtxo());

// Get contract balance & output address + balance
console.log('contract address:', contract.address);
const contractUtxos = await contract.getUtxos();
Expand Down
4 changes: 2 additions & 2 deletions examples/mecenas_locktime.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.12.0;
pragma cashscript ^0.13.0;

// This is an experimental contract for a more "streaming" Mecenas experience
// Completely untested, just a concept
Expand Down Expand Up @@ -40,7 +40,7 @@ contract Mecenas(
// Insert new initialBlock (OP_PUSHBYTES_8 <tx.locktime>)
// Note that constructor parameters are added in reverse order,
// so initialBlock is the first statement in the contract bytecode.
bytes newContract = 0x08 + bytes8(tx.locktime) + this.activeBytecode.split(9)[1];
bytes newContract = 0x08 + toPaddedBytes(tx.locktime, 8) + this.activeBytecode.split(9)[1];

// Create the locking bytecode for the new contract and check that
// the change output sends to that contract
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.12.0;
pragma cashscript ^0.13.0;

contract P2PKH(bytes20 pkh) {
// Require pk to match stored pkh and signature to match
Expand Down
12 changes: 8 additions & 4 deletions examples/p2pkh.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { URL } from 'url';
import { compileFile } from 'cashc';
import { ElectrumNetworkProvider, Contract, SignatureTemplate, TransactionBuilder } from 'cashscript';
import { Contract, SignatureTemplate, TransactionBuilder, MockNetworkProvider, randomUtxo } from 'cashscript';
import { stringify } from '@bitauth/libauth';

// Import Alice's keys from common.ts
Expand All @@ -9,13 +9,17 @@ import { alicePkh, alicePriv, aliceAddress, alicePub } from './common.js';
// Compile the P2PKH contract to an artifact object
const artifact = compileFile(new URL('p2pkh.cash', import.meta.url));

// Initialise a network provider for network operations on CHIPNET
const provider = new ElectrumNetworkProvider('chipnet');
// Once you're ready to send transactions on a real network (like chipnet or mainnet), use the ElectrumNetworkProvider
// const provider = new ElectrumNetworkProvider();
const provider = new MockNetworkProvider();

// Instantiate a new contract using the compiled artifact and network provider
// AND providing the constructor parameters (pkh: alicePkh)
const contract = new Contract(artifact, [alicePkh], { provider });

// Add a mock UTXO to the mock network provider
provider.addUtxo(contract.address, randomUtxo());

// Get contract balance & output address + balance
console.log('contract address:', contract.address);
const contractUtxos = await contract.getUtxos();
Expand Down Expand Up @@ -52,4 +56,4 @@ if (changeAmount > 1000n) transactionBuilder.addOutput(changeOutput);

const tx = await transactionBuilder.send();

console.log('transaction details:', stringify(tx));
console.log('transaction details:', stringify(tx));
10 changes: 7 additions & 3 deletions examples/p2pkh.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { stringify } from '@bitauth/libauth';
import { compileFile } from 'cashc';
import { ElectrumNetworkProvider, SignatureTemplate, Contract, TransactionBuilder, Output } from 'cashscript';
import { SignatureTemplate, Contract, TransactionBuilder, Output, MockNetworkProvider, randomUtxo } from 'cashscript';
import { URL } from 'url';

// Import Alice's keys from common.ts
Expand All @@ -9,13 +9,17 @@ import { alicePkh, alicePriv, aliceAddress, alicePub } from './common.js';
// Compile the P2PKH contract to an artifact object
const artifact = compileFile(new URL('p2pkh.cash', import.meta.url));

// Initialise a network provider for network operations on CHIPNET
const provider = new ElectrumNetworkProvider('chipnet');
// Once you're ready to send transactions on a real network (like chipnet or mainnet), use the ElectrumNetworkProvider
// const provider = new ElectrumNetworkProvider();
const provider = new MockNetworkProvider();

// Instantiate a new contract using the compiled artifact and network provider
// AND providing the constructor parameters (pkh: alicePkh)
const contract = new Contract(artifact, [alicePkh], { provider });

// Add a mock UTXO to the mock network provider
provider.addUtxo(contract.address, randomUtxo());

// Get contract balance & output address + balance
console.log('contract address:', contract.address);
const contractUtxos = await contract.getUtxos();
Expand Down
6 changes: 3 additions & 3 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.12.1",
"version": "0.13.0-next.5",
"description": "Usage examples of the CashScript SDK",
"main": "p2pkh.js",
"type": "module",
Expand All @@ -13,8 +13,8 @@
"dependencies": {
"@bitauth/libauth": "^3.1.0-next.8",
"@types/node": "^22.17.0",
"cashc": "^0.12.1",
"cashscript": "^0.12.1",
"cashc": "^0.13.0-next.5",
"cashscript": "^0.13.0-next.5",
"eslint": "^8.56.0",
"typescript": "^5.9.2"
}
Expand Down
2 changes: 2 additions & 0 deletions examples/testing-suite/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/dist/
**/node_modules/
Loading
Loading