Contract Decorators & Types
Rúnar uses a specialized type system and language keywords to define contract classes, methods, and state. This page is a complete reference for every on-chain type, annotation pattern, and built-in function available in the runar-lang package.
Contract Annotations
Rúnar does not use TypeScript decorators (the @decorator syntax). Instead, contract structure is expressed through base class inheritance, the readonly keyword, and the public access modifier. The compiler’s validation pass explicitly rejects decorator syntax.
Contract Declaration
A contract is a class that extends either SmartContract (stateless) or StatefulSmartContract (mutable state):
import { SmartContract } from 'runar-lang';
class P2PKH extends SmartContract {
// Extending SmartContract marks this class as a contract
}
import { StatefulSmartContract } from 'runar-lang';
class Counter extends StatefulSmartContract {
// Extending StatefulSmartContract enables mutable state
}
Only one contract class is allowed per file. The file must use the .runar.ts extension.
Public Methods (Spending Conditions)
The public keyword marks a method as a spending condition — an entry point that callers can invoke to spend the contract UTXO:
class P2PKH extends SmartContract {
readonly pubKeyHash: Ripemd160;
public unlock(sig: Sig, pubKey: PubKey) {
assert(hash160(pubKey) === this.pubKeyHash);
assert(checkSig(sig, pubKey));
}
}
Every public method must call assert() at least once. Methods without the public keyword are private helpers that the compiler inlines at each call site.
Readonly Properties (Immutable)
The readonly keyword marks a field as immutable. Readonly values are baked into the locking script at deployment time and cannot change:
class Escrow extends SmartContract {
readonly buyer: PubKey;
readonly seller: PubKey;
readonly arbiter: PubKey;
constructor(buyer: PubKey, seller: PubKey, arbiter: PubKey) {
super(buyer, seller, arbiter);
this.buyer = buyer;
this.seller = seller;
this.arbiter = arbiter;
}
}
In a SmartContract, all fields must be readonly. In a StatefulSmartContract, readonly fields are fixed at deployment while non-readonly fields are mutable state.
Mutable State Fields
In a StatefulSmartContract, fields without readonly are mutable state. They are serialized into the locking script and can be updated across transactions:
class Counter extends StatefulSmartContract {
count: bigint; // Mutable state -- no readonly keyword
constructor() {
super();
this.count = 0n;
}
public increment() {
this.count = this.count + 1n;
assert(true);
}
}
State updates are enforced on-chain through the OP_PUSH_TX covenant mechanism. The compiler auto-injects the preimage verification and state continuation code.
Constructor Pattern
Every contract constructor must call super() with all readonly field values, in order:
constructor(buyer: PubKey, seller: PubKey, amount: bigint) {
super(buyer, seller, amount);
this.buyer = buyer;
this.seller = seller;
this.amount = amount;
}
The super() arguments define the constructor slots in the compiled artifact. When deploying via the SDK, these values are spliced into the script’s placeholder positions.
On-Chain Primitive Types
All on-chain values in Rúnar must use these types. JavaScript/TypeScript native types like number and string are not allowed in contract code.
bigint
Arbitrary-precision integer. The primary numeric type.
const amount: bigint = 50000n;
const negative: bigint = -1n;
const zero: bigint = 0n;
Script representation: Minimally-encoded signed little-endian integer.
Operations: +, -, *, /, % (modulo), comparison (<, >, <=, >=, ===).
Division is integer division (truncates toward zero). There are no floating-point numbers.
boolean
const isValid: boolean = true;
const isEmpty: boolean = false;
Script representation: OP_TRUE (0x01) or OP_FALSE (empty byte array).
ByteString
A raw byte sequence, represented as a hex string in TypeScript.
const data: ByteString = 'aabbccdd';
const empty: ByteString = '';
Operations:
const combined: ByteString = data1 + data2; // Concatenation (cat)
const length: bigint = len(data); // Byte length
const chunk: ByteString = substr(data, 0n, 4n); // Extract bytes at offset, length
const prefix: ByteString = left(data, 4n); // First N bytes
const suffix: ByteString = right(data, 4n); // Last N bytes
const [head, tail] = split(data, 16n); // Split at position
const flipped: ByteString = reverseBytes(data); // Reverse byte order
PubKey
A compressed secp256k1 public key (33 bytes). Starts with 02 or 03.
const key: PubKey = '02' + 'aa'.repeat(32);
Subtyping: PubKey is a subtype of ByteString. You can pass a PubKey anywhere a ByteString is expected, but not vice versa.
Sig
A DER-encoded ECDSA signature with sighash type byte appended (71-73 bytes).
const signature: Sig = '30' + 'aa'.repeat(35);
Affine type: A Sig value must be consumed exactly once. Using it zero times or more than once is a compile-time error. This prevents signature replay attacks.
Sha256
A SHA-256 hash digest (32 bytes).
const digest: Sha256 = sha256('aabbccdd');
Subtyping: Sha256 is a subtype of ByteString.
Ripemd160
A RIPEMD-160 hash digest (20 bytes).
const hash: Ripemd160 = hash160(pubKey); // SHA-256 then RIPEMD-160
Subtyping: Ripemd160 is a subtype of ByteString.
Addr
A BSV address (20-byte hash of a public key). Functionally identical to Ripemd160 but semantically distinct — indicates a value represents an address.
const address: Addr = hash160(pubKey);
SigHashPreimage
The serialized BIP-143 sighash preimage. Used for transaction introspection in covenants.
public spend(txPreimage: SigHashPreimage) {
assert(checkPreimage(txPreimage));
const outputs = extractOutputHash(txPreimage);
const locktime = extractLocktime(txPreimage);
}
Affine type: Like Sig, a SigHashPreimage must be consumed exactly once.
Point
An elliptic curve point on secp256k1 (64 bytes: 32-byte x + 32-byte y). Used for EC operations in ZK proofs and advanced cryptographic constructions.
const G: Point = ecMulGen(1n); // Generator point
const P: Point = ecMul(G, secretScalar); // Scalar multiplication
const sum: Point = ecAdd(P, G); // Point addition
RabinSig
A Rabin signature value (arbitrary-precision integer). Used with verifyRabinSig for oracle attestations.
const oracleSig: RabinSig = 123456789n;
Subtyping: RabinSig is a subtype of bigint.
RabinPubKey
A Rabin public key (arbitrary-precision integer).
const oracleKey: RabinPubKey = 987654321n;
Subtyping: RabinPubKey is a subtype of bigint.
Collection Types
FixedArray<T, N>
A fixed-length array. The length N must be a compile-time constant.
import { FixedArray, PubKey, Sig } from 'runar-lang';
// Array of 3 public keys
const keys: FixedArray<PubKey, 3> = [key1, key2, key3];
// Array of 5 bigints
const values: FixedArray<bigint, 5> = [1n, 2n, 3n, 4n, 5n];
Operations:
// Index access (index must be a compile-time constant or loop variable)
const first: PubKey = keys[0];
// Iteration (unrolled at compile time)
for (let i = 0; i < 3; i++) {
assert(checkSig(sigs[i], keys[i]));
}
Variable-length arrays are not supported. The compiler unrolls all array operations into sequential opcodes.
Type Hierarchy
The Rúnar type system uses subtyping to allow domain-specific types where generic types are expected:
ByteString
├── PubKey (33 bytes)
├── Sig (71-73 bytes, affine)
├── Sha256 (32 bytes)
├── Ripemd160 (20 bytes)
├── Addr (20 bytes)
├── SigHashPreimage (variable, affine)
└── Point (64 bytes)
bigint
├── RabinSig
└── RabinPubKey
A PubKey can be passed where a ByteString is expected, but a ByteString cannot be passed where a PubKey is expected. Affine types (Sig, SigHashPreimage) must be consumed exactly once regardless of subtyping.
Constants
Elliptic Curve Constants
import { EC_P, EC_N, EC_G } from 'runar-lang';
// EC_P: The prime modulus of secp256k1
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
const p: bigint = EC_P;
// EC_N: The order of the generator point
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
const n: bigint = EC_N;
// EC_G: The generator point
const G: Point = EC_G;
SigHash Flags
import { SigHash } from 'runar-lang';
SigHash.ALL // 0x01 -- Sign all inputs and outputs
SigHash.NONE // 0x02 -- Sign all inputs, no outputs
SigHash.SINGLE // 0x03 -- Sign all inputs, only the matching output
SigHash.FORKID // 0x40 -- BSV fork ID flag (always set on BSV)
SigHash.ANYONECANPAY // 0x80 -- Sign only the current input
Combined with bitwise OR:
// Most common: sign everything with BSV fork ID
const sigType = SigHash.ALL | SigHash.FORKID; // 0x41
// Sign only current input, only matching output
const sigType = SigHash.SINGLE | SigHash.FORKID | SigHash.ANYONECANPAY; // 0xC3
Built-In Functions Reference
Cryptographic Hashing
| Function | Input | Output | Description |
|---|---|---|---|
sha256(data) | ByteString | Sha256 | SHA-256 hash |
hash256(data) | ByteString | Sha256 | Double SHA-256: sha256(sha256(data)) |
hash160(data) | ByteString | Ripemd160 | SHA-256 then RIPEMD-160 |
ripemd160(data) | ByteString | Ripemd160 | RIPEMD-160 hash |
sha256Compress(state, block) | ByteString, ByteString | Sha256 | SHA-256 compression function (partial verification) |
sha256Finalize(state, remaining, msgBitLen) | ByteString, ByteString, bigint | Sha256 | SHA-256 finalization (partial verification) |
Signature Verification
| Function | Description | Return |
|---|---|---|
checkSig(sig, pubKey) | Verify ECDSA signature | boolean |
checkMultiSig(sigs, pubKeys) | Verify M-of-N ECDSA signatures | boolean |
checkPreimage(preimage) | Verify sighash preimage authenticity via OP_PUSH_TX | boolean |
verifyRabinSig(msg, sig, padding, pubKey) | Verify Rabin oracle signature | boolean |
Post-Quantum Verification
| Function | Description |
|---|---|
verifyWOTS(msg, sig, pubKey) | Verify WOTS+ one-time signature (2,144B sig) |
verifySLHDSA_SHA2_128s(msg, sig, pubKey) | SLH-DSA SHA2-128s verification |
verifySLHDSA_SHA2_128f(msg, sig, pubKey) | SLH-DSA SHA2-128f verification |
verifySLHDSA_SHA2_192s(msg, sig, pubKey) | SLH-DSA SHA2-192s verification |
verifySLHDSA_SHA2_192f(msg, sig, pubKey) | SLH-DSA SHA2-192f verification |
verifySLHDSA_SHA2_256s(msg, sig, pubKey) | SLH-DSA SHA2-256s verification |
verifySLHDSA_SHA2_256f(msg, sig, pubKey) | SLH-DSA SHA2-256f verification |
Preimage Introspection
| Function | Return | Description |
|---|---|---|
extractVersion(preimage) | bigint | Transaction version (4 bytes) |
extractHashPrevouts(preimage) | Sha256 | Double-SHA256 of all input outpoints |
extractHashSequence(preimage) | Sha256 | Double-SHA256 of all input sequences |
extractOutpoint(preimage) | ByteString | Outpoint of the current input (36 bytes) |
extractInputIndex(preimage) | bigint | Index of the current input |
extractScriptCode(preimage) | ByteString | Locking script being executed |
extractAmount(preimage) | bigint | Satoshi value of the UTXO being spent |
extractSequence(preimage) | bigint | Sequence number of the current input |
extractOutputHash(preimage) | Sha256 | Double-SHA256 of all serialized outputs |
extractOutputs(preimage) | ByteString | Raw serialized outputs |
extractLocktime(preimage) | bigint | Transaction locktime |
extractSigHashType(preimage) | bigint | Sighash flags |
Elliptic Curve Operations
| Function | Description | Return |
|---|---|---|
ecAdd(p1, p2) | EC point addition | Point |
ecMul(point, scalar) | EC scalar multiplication | Point |
ecMulGen(scalar) | Multiply generator G by scalar | Point |
ecNegate(point) | Negate (reflect over x-axis) | Point |
ecOnCurve(point) | Check if point is on secp256k1 | boolean |
ecModReduce(value) | Reduce modulo curve order N | bigint |
ecEncodeCompressed(point) | Encode as 33-byte compressed pubkey | ByteString |
ecMakePoint(x, y) | Construct point from coordinates | Point |
ecPointX(point) | Extract x-coordinate | bigint |
ecPointY(point) | Extract y-coordinate | bigint |
Byte Operations
| Function | Description | Return |
|---|---|---|
len(data) | Byte length | bigint |
cat(a, b) | Concatenate two byte sequences | ByteString |
substr(data, offset, length) | Extract bytes at offset | ByteString |
left(data, n) | First N bytes | ByteString |
right(data, n) | Last N bytes | ByteString |
split(data, position) | Split into two parts | [ByteString, ByteString] |
reverseBytes(data) | Reverse byte order | ByteString |
toByteString(value) | Convert to byte representation | ByteString |
Conversion Functions
| Function | Description | Return |
|---|---|---|
num2bin(num, length) | Encode integer as fixed-length bytes | ByteString |
bin2num(data) | Decode bytes as integer | bigint |
int2str(num, length) | Integer to fixed-length byte string representation | ByteString |
bool(value) | Convert to boolean | boolean |
Math Functions
| Function | Description | Return |
|---|---|---|
abs(x) | Absolute value | bigint |
min(a, b) | Minimum of two values | bigint |
max(a, b) | Maximum of two values | bigint |
within(x, low, high) | Check if low <= x < high | boolean |
safediv(a, b) | Division with zero-check | bigint |
safemod(a, b) | Modulo with zero-check | bigint |
clamp(x, low, high) | Clamp to range | bigint |
mulDiv(a, b, c) | (a * b) / c without intermediate overflow | bigint |
percentOf(value, percent) | (value * percent) / 100 | bigint |
sign(x) | Sign of value: -1, 0, or 1 | bigint |
pow(base, exp) | Integer exponentiation | bigint |
sqrt(x) | Integer square root (floor) | bigint |
gcd(a, b) | Greatest common divisor | bigint |
divmod(a, b) | Quotient and remainder | [bigint, bigint] |
log2(x) | Integer log base 2 (floor) | bigint |
Control Functions
| Function | Description |
|---|---|
assert(condition) | Abort script execution if condition is false |
State Functions (StatefulSmartContract only)
| Function | Description |
|---|---|
this.addOutput(satoshis, ...stateValues) | Add a continuation output with updated state |
this.addRawOutput(satoshis, scriptBytes) | Add an arbitrary output with caller-specified script bytes |
Further Reading
- Contract Basics — writing contracts with the type system
- Compiler API — programmatic compilation interface
- SDK API — deploying and calling contracts