Rúnar

Language Feature Matrix

Runar supports six source languages, each with different syntax and maturity levels. This page provides a comprehensive side-by-side comparison to help you choose the right language for your contract and understand the syntactic differences between them.

Language Status

Not all language frontends are at the same level of maturity. Choose accordingly based on your needs:

LanguageFile ExtensionFrontend PackageStatusNotes
TypeScript.runar.tsrunar-langStablePrimary language. Most complete tooling and documentation.
Go.runar.gorunar-goStableFull-featured, conformance-tested against TypeScript.
Rust.runar.rsrunar-rsStableFull-featured, conformance-tested against TypeScript.
Python.runar.pyrunar-pyStableFull-featured, conformance-tested against TypeScript.
Solidity.runar.solrunar-langExperimentalFamiliar syntax for Ethereum developers. Subset of Solidity.
Move.runar.moverunar-langExperimentalResource-oriented syntax. Subset of Move.

Stable means the frontend is conformance-tested, produces identical Bitcoin Script output to TypeScript for the same contract, and is suitable for production use. Experimental means the frontend is functional but may have edge cases, and the API could change in minor releases.

Contract Declaration

How each language defines a contract:

FeatureTypeScriptGoRustPythonSolidityMove
Statelessclass C extends SmartContracttype C struct { runar.SmartContract }#[runar::contract] struct Cclass C(SmartContract)contract C is SmartContractmodule c { struct C has key }
Statefulclass C extends StatefulSmartContracttype C struct { runar.StatefulSmartContract }#[runar::contract(stateful)] struct Cclass C(StatefulSmartContract)contract C is StatefulSmartContractstruct C has key + &mut self methods

Readonly Fields

How each language marks a field as immutable (baked into the locking script):

LanguageSyntaxExample
TypeScriptreadonly keywordreadonly owner: PubKey;
Gorunar:"readonly" struct tagOwner runar.PubKey \runar:“readonly”“
Rust#[readonly] attribute#[readonly] owner: PubKey,
PythonReadonly[T] type wrapperowner: Readonly[PubKey]
Solidityimmutable keywordPubKey immutable owner;
MoveInferred from usageFields never modified in &mut self methods

Mutable State Fields

How each language declares mutable state (only valid in stateful contracts):

LanguageSyntaxExample
TypeScriptPlain field (no readonly)count: bigint;
GoField without struct tagCount int64
RustField without #[readonly]count: i64,
PythonPlain type annotationcount: int = 0
SolidityPlain variableint64 count;
MovePlain struct fieldcount: u64,

Public Methods (Entry Points)

How each language defines a spending condition:

LanguageSyntaxExample
TypeScriptpublic keywordpublic unlock(sig: Sig) { ... }
GoExported method (capitalized)func (c *C) Unlock(sig runar.Sig) { ... }
Rust#[public] attribute#[public] fn unlock(&self, sig: Sig) { ... }
Python@public decorator@public def unlock(self, sig: Sig): ...
Soliditypublic visibilityfunction unlock(Sig sig) public { ... }
Movepublic fun keywordpublic fun unlock(self: &C, sig: Sig) { ... }

Private Methods (Helpers)

How each language defines a private method that gets inlined:

LanguageSyntaxExample
TypeScriptprivate keywordprivate verify(pk: PubKey): boolean { ... }
GoUnexported method (lowercase)func (c *C) verify(pk runar.PubKey) bool { ... }
RustMethod without #[public]fn verify(&self, pk: PubKey) -> bool { ... }
PythonMethod without @publicdef _verify(self, pk: PubKey) -> bool: ...
Solidityprivate visibilityfunction verify(PubKey pk) private returns (bool) { ... }
Movefun without publicfun verify(self: &C, pk: PubKey): bool { ... }

Assertions

How each language asserts conditions:

LanguageFunctionExample
TypeScriptassert()assert(checkSig(sig, pk));
Gorunar.Assert()runar.Assert(runar.CheckSig(sig, pk))
Rustassert!() macroassert!(check_sig(&sig, &pk));
Pythonassert_()assert_(check_sig(sig, pk))
Solidityrequire() or assert()require(checkSig(sig, pk));
Moveassert!() macroassert!(check_sig(&sig, &pk));

Constructors

How each language initializes contract state:

LanguageSyntax
TypeScriptconstructor(args) { super(args); this.field = arg; }
GoStruct literal at deploy time (no explicit constructor in contract)
RustStruct literal at deploy time (no explicit constructor in contract)
Pythondef __init__(self, args): super().__init__(args); self.field = arg
Solidityconstructor(args) { field = arg; }
MoveModule-level init function or struct literal at deploy time

Imports

How each language imports Runar types and functions:

LanguageImport Statement
TypeScriptimport { SmartContract, assert, PubKey, Sig } from 'runar-lang';
Goimport "runar"
Rustuse runar::prelude::*;
Pythonfrom runar import SmartContract, assert_, PubKey, Sig
SolidityImplicit (all types and functions are globally available)
Moveuse runar::*;

Numeric Types

How each language represents integers:

LanguageTypeLiteral SyntaxExample
TypeScriptbigint42nconst x: bigint = 42n;
Goint6442x := int64(42)
Rusti6442let x: i64 = 42;
Pythonint42x: int = 42
Solidityint6442int64 x = 42;
Moveu6442let x: u64 = 42;

Note that Move uses unsigned u64 while Go, Rust, and Solidity use signed int64/i64. TypeScript’s bigint and Python’s int are arbitrary-precision. All compile to the same Bitcoin Script numeric representation.

Fixed Arrays

How each language declares fixed-size arrays:

LanguageSyntaxExample
TypeScriptFixedArray<T, N>signers: FixedArray<PubKey, 3>
Go[N]TSigners [3]runar.PubKey
Rust[T; N]signers: [PubKey; 3]
PythonFixedArray[T, N]signers: FixedArray[PubKey, 3]
SolidityT[N]PubKey[3] signers;
Movevector<T> (fixed)signers: vector<PubKey>

For Loops

How each language writes a compile-time-bounded loop:

LanguageSyntax
TypeScriptfor (let i = 0n; i < 10n; i++) { ... }
Gofor i := int64(0); i < 10; i++ { ... }
Rustfor i in 0..10 { ... }
Pythonfor i in range(10): ...
Solidityfor (int64 i = 0; i < 10; i++) { ... }
Movewhile (i < 10) { ...; i = i + 1; }

All loops are unrolled at compile time. The bound (10 in these examples) must be a compile-time constant in every language.

Conditionals

How each language writes conditional logic:

LanguageIf/ElseTernary / Expression
TypeScriptif (cond) { ... } else { ... }cond ? a : b
Goif cond { ... } else { ... }None (use if/else)
Rustif cond { ... } else { ... }if cond { a } else { b } (expression)
Pythonif cond: ... else: ...a if cond else b
Solidityif (cond) { ... } else { ... }cond ? a : b
Moveif (cond) { ... } else { ... }if (cond) { a } else { b } (expression)

Signature Verification

How each language verifies a signature:

LanguageSingle SigMulti-Sig
TypeScriptcheckSig(sig, pk)checkMultiSig(sigs, pks)
Gorunar.CheckSig(sig, pk)runar.CheckMultiSig(sigs, pks)
Rustcheck_sig(&sig, &pk)check_multi_sig(&sigs, &pks)
Pythoncheck_sig(sig, pk)check_multi_sig(sigs, pks)
SoliditycheckSig(sig, pk)checkMultiSig(sigs, pks)
Movecheck_sig(&sig, &pk)check_multi_sig(&sigs, &pks)

Hashing

How each language calls hash functions:

LanguageSHA-256Hash-160Double SHA-256
TypeScriptsha256(data)hash160(data)hash256(data)
Gorunar.Sha256(data)runar.Hash160(data)runar.Hash256(data)
Rustsha256(&data)hash160(&data)hash256(&data)
Pythonsha256(data)hash160(data)hash256(data)
Soliditysha256(data)hash160(data)hash256(data)
Movesha256(&data)hash160(&data)hash256(&data)

Complete Minimal Example in Each Language

For reference, here is the same simple P2PKH contract written in all six languages.

TypeScript

import { SmartContract, assert, PubKey, Sig, Ripemd160, hash160, checkSig } from 'runar-lang';

class P2PKH extends SmartContract {
  readonly pubKeyHash: Ripemd160;
  constructor(pubKeyHash: Ripemd160) {
    super(pubKeyHash);
    this.pubKeyHash = pubKeyHash;
  }
  public unlock(sig: Sig, pubKey: PubKey) {
    assert(hash160(pubKey) === this.pubKeyHash);
    assert(checkSig(sig, pubKey));
  }
}

Go

package p2pkh
import "runar"
type P2PKH struct {
    runar.SmartContract
    PubKeyHash runar.Ripemd160 `runar:"readonly"`
}
func (p *P2PKH) Unlock(sig runar.Sig, pubKey runar.PubKey) {
    runar.Assert(runar.Hash160(pubKey) == p.PubKeyHash)
    runar.Assert(runar.CheckSig(sig, pubKey))
}

Rust

use runar::prelude::*;
#[runar::contract]
struct P2PKH {
    #[readonly] pub_key_hash: Ripemd160,
}
#[runar::methods]
impl P2PKH {
    #[public]
    fn unlock(&self, sig: Sig, pub_key: PubKey) {
        assert!(hash160(&pub_key) == self.pub_key_hash);
        assert!(check_sig(&sig, &pub_key));
    }
}

Python

from runar import SmartContract, PubKey, Sig, Ripemd160, Readonly, public, assert_, hash160, check_sig
class P2PKH(SmartContract):
    pub_key_hash: Readonly[Ripemd160]
    def __init__(self, pub_key_hash: Ripemd160):
        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))

Solidity

pragma runar ^0.1.0;
contract P2PKH is SmartContract {
    Ripemd160 immutable pubKeyHash;
    constructor(Ripemd160 _pubKeyHash) {
        pubKeyHash = _pubKeyHash;
    }
    function unlock(Sig sig, PubKey pubKey) public {
        require(hash160(pubKey) == pubKeyHash);
        require(checkSig(sig, pubKey));
    }
}

Move

module p2pkh {
    use runar::*;
    struct P2PKH has key {
        pub_key_hash: Ripemd160,
    }
    public fun unlock(self: &P2PKH, sig: Sig, pub_key: PubKey) {
        assert!(hash160(&pub_key) == self.pub_key_hash);
        assert!(check_sig(&sig, &pub_key));
    }
}

Choosing a Language

Choose TypeScript if:

  • You want the most mature tooling, documentation, and community support
  • You are new to Runar and want the smoothest learning path
  • Your team primarily works in JavaScript/TypeScript

Choose Go if:

  • You prefer Go’s simplicity and explicit error handling style
  • Your backend services are written in Go
  • You want a statically compiled frontend with fast compilation

Choose Rust if:

  • You want Rust’s ownership model to catch errors at compile time
  • You are already comfortable with Rust’s syntax and borrow checker
  • You value the alignment between Rust’s ownership and UTXO resource semantics

Choose Python if:

  • You want the most concise syntax with minimal boilerplate
  • Your team primarily works in Python
  • You are prototyping contracts quickly

Choose Solidity if:

  • You are coming from Ethereum and want a familiar syntax
  • You are porting existing Solidity contracts to BSV
  • You understand the EVM-to-UTXO conceptual differences

Choose Move if:

  • You are coming from Sui or Aptos and want a familiar syntax
  • You value Move’s resource-oriented safety guarantees
  • You want the closest conceptual alignment between language and UTXO model

Regardless of which language you choose, the compiled Bitcoin Script output is identical. A P2PKH contract compiled from TypeScript produces the exact same script as the same contract compiled from Go, Rust, Python, Solidity, or Move.

Next Steps