Old Norse plural for "runes" (rún = secret/script/mystery). Pronounced ROO-nar.
Write Bitcoin smart contracts in TypeScript, Go, Rust, Python, Solidity, or Move. Compile to Bitcoin Script.
Rúnar lets you write Bitcoin SV smart contracts in the language you already know. All formats compile through the same pipeline and produce identical Bitcoin Script.
|
TypeScript class P2PKH extends SmartContract {
readonly pubKeyHash: Addr;
constructor(pubKeyHash: Addr) {
super(pubKeyHash);
this.pubKeyHash = pubKeyHash;
}
public unlock(sig: Sig, pubKey: PubKey) {
assert(hash160(pubKey) === this.pubKeyHash);
assert(checkSig(sig, pubKey));
}
} |
Go type P2PKH struct {
runar.SmartContract
PubKeyHash runar.Addr `runar:"readonly"`
}
func (c *P2PKH) Unlock(sig runar.Sig, pubKey runar.PubKey) {
runar.Assert(runar.Hash160(pubKey) == c.PubKeyHash)
runar.Assert(runar.CheckSig(sig, pubKey))
} |
|
Rust #[runar::contract]
pub struct P2PKH {
#[readonly]
pub pub_key_hash: Addr,
}
#[runar::methods(P2PKH)]
impl P2PKH {
#[public]
pub fn unlock(&self, sig: &Sig, pub_key: &PubKey) {
assert!(hash160(pub_key) == self.pub_key_hash);
assert!(check_sig(sig, pub_key));
}
} |
Solidity-like pragma runar ^0.1.0;
contract P2PKH is SmartContract {
Addr immutable pubKeyHash;
constructor(Addr _pubKeyHash) {
pubKeyHash = _pubKeyHash;
}
function unlock(Sig sig, PubKey pubKey) public {
require(hash160(pubKey) == pubKeyHash);
require(checkSig(sig, pubKey));
}
} |
|
Move-style module P2PKH {
use runar::types::{Addr, PubKey, Sig};
use runar::crypto::{hash160, check_sig};
resource struct P2PKH {
pub_key_hash: Addr,
}
public fun unlock(contract: &P2PKH, sig: Sig, pub_key: PubKey) {
assert!(hash160(pub_key) == contract.pub_key_hash, 0);
assert!(check_sig(sig, pub_key), 0);
}
} |
Python from runar import (SmartContract, Addr, PubKey,
Sig, hash160, check_sig, assert_, public)
class P2PKH(SmartContract):
pub_key_hash: Addr
def __init__(self, pub_key_hash: Addr):
super().__init__(pub_key_hash)
self.pub_key_hash = pub_key_hash
@public
def unlock(self, sig: Sig, pub_key: PubKey):
assert_(hash160(pub_key) == self.pub_key_hash)
assert_(check_sig(sig, pub_key)) |
All six formats produce the same Bitcoin Script: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
Bitcoin Script development today forces a choice between hand-writing opcodes (error-prone, unauditable) or adopting a framework with heavy decorator-based DSLs that obscure what happens on-chain. Rúnar takes a different path:
- No decorators — uses native language keywords (
readonly,public,immutable,#[readonly]) - Write in your language — TypeScript, Go, Rust, Python, Solidity-like, or Move-style
- Test natively —
vitestfor TS,go testfor Go,cargo testfor Rust,pytestfor Python - Four compilers — TypeScript (reference), Go, Rust, Python — all produce byte-identical output
- Post-quantum ready — WOTS+ and SLH-DSA (FIPS 205) signature verification in Bitcoin Script
- Nanopass architecture — 6 small passes, each auditable in a single sitting
- Full IDE support — type checking, autocompletion, go-to-definition in every language
pnpm add runar-lang runar-compiler runar-cli
runar compile MyContract.runar.ts # => artifacts/MyContract.runar.json# In your go.mod, add:
# require github.com/icellan/runar/packages/runar-go v0.1.0
# Contracts are real Go — test with go test, compile with the Rúnar Go compiler
go test ./...# In Cargo.toml: runar = { package = "runar-lang", version = "0.1.0" }
# Contracts are real Rust — test with cargo test, compile with the Rúnar Rust compiler
cargo test# pip install runar-lang
# Contracts are real Python — test with pytest, compile with any Rúnar compiler
PYTHONPATH=packages/runar-py python3 -m pytestEvery contract format has native testing support. Business logic tests run the contract as real code in the host language. Rúnar compile checks verify the contract will produce valid Bitcoin Script.
TypeScript (vitest):
import { TestContract } from 'runar-testing';
const counter = TestContract.fromSource(source, { count: 0n });
counter.call('increment');
expect(counter.state.count).toBe(1n);Go (go test):
func TestCounter_Increment(t *testing.T) {
c := &Counter{Count: 0}
c.Increment()
if c.Count != 1 { t.Errorf("expected 1, got %d", c.Count) }
}
func TestCounter_Compile(t *testing.T) {
if err := runar.CompileCheck("Counter.runar.go"); err != nil {
t.Fatalf("Rúnar compile check failed: %v", err)
}
}Rust (cargo test):
#[path = "Counter.runar.rs"]
mod contract;
use contract::*;
#[test]
fn test_increment() {
let mut c = Counter { count: 0 };
c.increment();
assert_eq!(c.count, 1);
}
#[test]
fn test_compile() {
runar::compile_check(include_str!("Counter.runar.rs"), "Counter.runar.rs").unwrap();
}Python (pytest):
from conftest import load_contract
from runar import hash160, mock_sig, mock_pub_key
contract_mod = load_contract("P2PKH.runar.py")
P2PKH = contract_mod.P2PKH
def test_unlock():
pk = mock_pub_key()
c = P2PKH(pub_key_hash=hash160(pk))
c.unlock(mock_sig(), pk)| Format | Extension | Compilers | IDE Support | Status |
|---|---|---|---|---|
| TypeScript | .runar.ts |
TS, Go, Rust, Python | Full (tsc) |
Stable |
| Go | .runar.go |
Go, Python | Full (gopls) |
Experimental |
| Rust DSL | .runar.rs |
Rust, Python | Full (rust-analyzer) |
Experimental |
| Python | .runar.py |
TS, Go, Rust, Python | Full (pyright) |
Experimental |
| Solidity-like | .runar.sol |
TS, Go, Rust, Python | Syntax highlighting | Experimental |
| Move-style | .runar.move |
TS, Go, Rust, Python | Syntax highlighting | Experimental |
All formats parse into the same ContractNode AST. From there, the pipeline is identical:
.runar.ts ──┐
.runar.sol ──┤
.runar.move ─┤
.runar.py ───┼──► ContractNode AST ──► Validate ──► TypeCheck ──► ANF ──► Stack ──► Bitcoin Script
.runar.go ───┤
.runar.rs ───┘
16 example contracts demonstrate all major patterns:
| Contract | Pattern | Stateful | Multi-method |
|---|---|---|---|
| P2PKH | Pay-to-Public-Key-Hash | No | No |
| Escrow | Multi-party authorization | No | Yes (4 paths) |
| Counter | Stateful state machine | Yes | Yes |
| Auction | Bidding with deadline | Yes | Yes |
| CovenantVault | Spending constraints | No | No |
| OraclePriceFeed | Rabin signature oracle | No | No |
| FungibleToken | Token with split/merge | Yes | Yes (3 paths) |
| SimpleNFT | NFT with transfer/burn | Yes | Yes |
| PostQuantumWallet | WOTS+ signature verification | No | No |
| SPHINCSWallet | SLH-DSA (FIPS 205) verification | No | No |
| SchnorrZKP | Schnorr zero-knowledge proof (EC ops) | No | No |
| FunctionPatterns | Public/private methods, built-ins | Yes | Yes |
| MathDemo | Math built-in functions | Yes | Yes |
| ConvergenceProof | Convergence proof pattern | No | No |
| ECDemo | EC point operations | No | No |
| BoundedCounter | Property initializers with defaults | Yes | Yes |
11 contracts are available in all 6 formats (TypeScript, Go, Rust, Solidity, Move, Python). FunctionPatterns, PostQuantumWallet, SPHINCSWallet, SchnorrZKP, and ConvergenceProof are available in TypeScript, Go, Rust, and Python.
examples/
ts/p2pkh/ P2PKH.runar.ts + P2PKH.test.ts
go/p2pkh/ P2PKH.runar.go + P2PKH_test.go
rust/p2pkh/ P2PKH.runar.rs + P2PKH_test.rs
sol/p2pkh/ P2PKH.runar.sol + P2PKH.test.ts
move/p2pkh/ P2PKH.runar.move + P2PKH.test.ts
python/p2pkh/ P2PKH.runar.py + test_p2pkh.py
The compiler is structured as six small, composable nanopass transforms. Each pass does one thing, transforms one IR into the next, and is small enough to audit in a single sitting.
| Pass | Name | Input | Output |
|---|---|---|---|
| 1 | Parse | Source (any format) | Rúnar AST |
| 2 | Validate | Rúnar AST | Validated AST |
| 3 | Type-check | Validated AST | Typed AST |
| 4 | ANF Lower | Typed AST | ANF IR |
| 5 | Stack Lower | ANF IR | Stack IR |
| 6 | Emit | Stack IR | Bitcoin Script |
The constant folding optimizer (+ dead binding elimination) is available between passes 4 and 5 but is disabled by default to preserve ANF conformance. The peephole optimizer runs between passes 5 and 6 (always enabled).
Rúnar defines a canonical IR conformance boundary at the ANF level. Any compiler that produces byte-identical ANF IR for a given source file is conformant:
- The TypeScript compiler is the reference implementation
- The Go compiler produces identical output for all example contracts including post-quantum
- The Rust compiler produces identical output for all example contracts including post-quantum
- The Python compiler produces identical output for all example contracts including post-quantum
The conformance suite in conformance/ contains 25 golden-file tests (including WOTS+, SLH-DSA, and EC primitives). All four compilers must pass the same suite.
SmartContract— stateless, all propertiesreadonlyStatefulSmartContract— mutable state carried across transactions via OP_PUSH_TXthis.addOutput(satoshis, ...values)— multi-output intrinsic for token splitting/merging- Only Rúnar built-in functions are allowed — the compiler rejects arbitrary function calls
Only a strict subset of each language is valid Rúnar. The compiler enforces this at parse, validate, and typecheck time:
Allowed: Class/struct declarations, readonly/mutable properties, public/private methods, const/let variables, if/else, bounded for loops, arithmetic/comparison/logical/bitwise operators, ternary expressions, Rúnar built-in function calls.
Disallowed: Unbounded loops, recursion, async/await, closures, exceptions, dynamic arrays, arbitrary function calls (Math.floor, console.log, etc.).
packages/
runar-lang/ # Language types and builtins (developer imports)
runar-compiler/ # TypeScript compiler (6 nanopass passes)
runar-ir-schema/ # Shared IR type definitions and JSON schemas
runar-testing/ # TestContract API, Script VM, interpreter, fuzzer
runar-sdk/ # Deployment SDK (providers, signers)
runar-cli/ # CLI tool
runar-go/ # Go package: types, mock crypto, real hashes, CompileCheck(), deployment SDK
runar-rs/ # Rust crate: prelude types, mock crypto, real hashes, compile_check(), deployment SDK
runar-rs-macros/ # Rust proc-macros (#[runar::contract], #[public], etc.)
runar-py/ # Python package: types, mock crypto, real hashes, deployment SDK
compilers/
go/ # Go compiler (tree-sitter + native Go frontend)
rust/ # Rust compiler (SWC + native Rust frontend)
python/ # Python compiler (native Python frontend)
conformance/ # Cross-compiler conformance test suite
examples/
ts/ # TypeScript contracts + tests
go/ # Go contracts + tests
rust/ # Rust contracts + tests
sol/ # Solidity-like contracts + tests
move/ # Move-style contracts + tests
python/ # Python contracts + tests
sdk-usage/ # SDK usage reference docs (not runnable)
end2end-example/ # End-to-end example (ts, go, rust, sol, move, webapp, webapp-blackjack)
spec/ # Language specification
docs/ # Documentation + format guides
- Node.js >= 20, pnpm 9.15+
- Go 1.26+ (for Go compiler and Go contract tests)
- Rust 1.75+ (for Rust compiler and Rust contract tests)
- Python 3.10+ (for Python compiler and Python contract tests)
git clone https://github.com/icellan/runar.git && cd runar
pnpm install && pnpm build
# TypeScript (packages + all format examples)
npx vitest run
# Go compiler + Go contract tests
cd compilers/go && go test ./...
cd examples/go && go test ./...
# Rust compiler + Rust contract tests
cd compilers/rust && cargo test
cd examples/rust && cargo test
# Python package + Python contract tests
cd packages/runar-py && python3 -m pytest
cd examples/python && PYTHONPATH=../../packages/runar-py python3 -m pytest| Technique | Reference | Used In |
|---|---|---|
| Nanopass compilation | Sarkar, Waddell & Dybvig (ICFP 2004) | 6-pass pipeline architecture |
| Administrative Normal Form | Flanagan, Sabry, Duba & Felleisen (PLDI 1993) | IR between typed AST and stack machine |
| Affine types | Walker (2005), Move language (2019) | Compile-time resource safety |
| Definitional interpreter | Reynolds (1972), Amin & Rompf (POPL 2017) | Reference interpreter oracle |
| Differential testing | Yang, Chen, Eide & Regehr (PLDI 2011, CSmith) | Cross-compiler fuzzing |
| Stack scheduling | Koopman (1989) | ANF → Bitcoin Script stack mapping |
MIT