hanzo-numscript

Numscript is a domain-specific language for modeling complex financial transactions.

Hanzo Numscript - DSL for Financial Transactions

Category: Hanzo Ecosystem Related Skills: hanzo/hanzo-reconciliation.md, hanzo/hanzo-billing.md

Overview

Numscript is a domain-specific language for modeling complex financial transactions. It provides declarative syntax for multi-party fund transfers with percentage splits, ordered funding sources, overdraft controls, and parameterized scripts. Used by Hanzo Ledger for programmable money movement. Includes a CLI, Go library, LSP server, and MCP server.

Why Numscript?

Tech Stack

OSS Base

Repo: hanzoai/numscript (Formance numscript fork). Go module path is github.com/formancehq/numscript (upstream module name retained).

When to use

Hard requirements

  1. Go 1.24+ for building from source
  2. ANTLR4 for regenerating the parser from grammar files
  3. Hanzo Ledger for executing scripts against real accounts (optional for parsing)

Quick reference

| Item | Value | |------|-------| | Go module | github.com/formancehq/numscript | | CLI binary | numscript | | Go version | 1.24 | | Grammar | Numscript.g4 + Lexer.g4 (ANTLR4) | | Default branch | main | | License | MIT | | Repo | github.com/hanzoai/numscript |

One-file quickstart

Install CLI

go install github.com/hanzoai/numscript/cmd/numscript@latest

Parse and validate

numscript check script.num
numscript fmt script.num

Go library

package main

import (
 "context"
 "fmt"
 "math/big"

 "github.com/formancehq/numscript"
)

func main() {
 program := numscript.Parse(`
 send [USD/2 5000] (
 source = @users:alice
 destination = {
 85% to @merchants:shop
 10% to @platform:fees
 5% to @platform:reserve
 }
 )
 `)

 if errs := program.GetParsingErrors(); len(errs) > 0 {
 fmt.Println("Parse errors:", numscript.ParseErrorsToString(errs))
 return
 }

 // Get needed variables (for parameterized scripts)
 vars := program.GetNeededVariables()
 fmt.Println("Variables:", vars)

 // Execute against a store
 result, err := program.Run(context.Background(),
 numscript.VariablesMap{},
 &numscript.StaticStore{
 Balances: numscript.Balances{
 "users:alice": {"USD/2": big.NewInt(10000)},
 },
 },
 )
 if err != nil {
 fmt.Println("Error:", err)
 return
 }

 for _, posting := range result.Postings {
 fmt.Printf("%s -> %s: %s %s\n",
 posting.Source, posting.Destination,
 posting.Amount, posting.Asset)
 }
}

Core Concepts

Language Syntax

// Variables with types
vars {
 monetary $amount
 account $sender
 account $receiver
}

// Simple transfer
send $amount (
 source = $sender
 destination = $receiver
)

// Multi-source with fallback (ordered, first with funds wins)
send [USD/2 10000] (
 source = {
 @users:alice
 @users:alice:savings
 @world // infinite source (minting)
 }
 destination = @merchants:shop
)

// Percentage split destination
send [USD/2 10000] (
 source = @users:alice
 destination = {
 85% to @merchants:shop
 10% to @platform:fees
 remaining to @platform:reserve
 }
)

// Overdraft controls
send [USD/2 100] (
 source = {
 @a allowing overdraft up to [USD/2 10]
 @b allowing unbounded overdraft
 }
 destination = @dest
)

// Save (earmark funds)
save [USD/2 10] from @alice

// Metadata-driven accounts
vars {
 account $dest = meta(@config, "payout_account")
}

// Function calls
set_tx_meta("key", "value")

Architecture

 Parse Run
numscript.go ──────────► ParseResult ──────► ExecutionResult
 | |
 | ANTLR4 parser | interpreter
 | (Lexer.g4 + | (balance queries,
 | Numscript.g4) | metadata queries,
 | | posting generation)
 | |
 v v
 ParserError[] Posting[]
 Metadata{}
 AccountsMetadata{}

Public API

// Parse source code
numscript.Parse(code string) ParseResult

// Inspect parsed result
ParseResult.GetParsingErrors() []ParserError
ParseResult.GetNeededVariables() map[string]string
ParseResult.GetSource() string

// Execute
ParseResult.Run(ctx, vars, store) (ExecutionResult, InterpreterError)
ParseResult.RunWithFeatureFlags(ctx, vars, store, flags) (ExecutionResult, InterpreterError)

// Types
type Posting struct {
 Source, Destination string
 Amount *big.Int
 Asset string
}

type ExecutionResult struct {
 Postings []Posting
 Metadata Metadata // set_tx_meta() results
 AccountsMetadata AccountsMetadata // set_account_meta() results
}

// Store interface (implement for your ledger)
type Store interface {
 GetBalances(ctx, BalanceQuery) (Balances, error)
 GetAccountsMetadata(ctx, MetadataQuery) (AccountsMetadata, error)
}

Feature Flags

flags.ExperimentalOneofFeatureFlag // oneof {} source selection
flags.ExperimentalMidScriptFunctionCall // balance() calls mid-script

Directory structure

github.com/hanzoai/numscript/
 numscript.go # Public API: Parse, Run, types
 numscript_test.go # Integration tests (20+ test cases)
 Numscript.g4 # ANTLR4 grammar (parser rules)
 Lexer.g4 # ANTLR4 grammar (lexer rules)
 Justfile # Build commands (generate, test, lint, release)
 .goreleaser.yaml # Cross-platform binary releases
 inputs.schema.json # JSON schema for script inputs
 specs.schema.json # JSON schema for specs
 cmd/numscript/
 main.go # CLI entrypoint (check, fmt subcommands)
 internal/
 parser/ # ANTLR4-generated parser + parse tree
 interpreter/ # Script execution engine
 analysis/ # Static analysis and validation
 lsp/ # Language Server Protocol implementation
 mcp_impl/ # Model Context Protocol server
 jsonrpc2/ # JSON-RPC 2.0 transport
 cmd/ # CLI command implementations
 flags/ # Feature flag definitions
 ansi/ # Terminal color output
 specs_format/ # Spec formatting utilities
 utils/ # Shared utilities

Troubleshooting

| Issue | Cause | Solution | |-------|-------|----------| | Parse error on @world | Missing source block | @world must be inside a source = {} block | | MissingFundsErr | Source accounts have insufficient balance | Add @world as final fallback or use allowing overdraft | | Variables not resolved | Missing from VariablesMap | Check GetNeededVariables() and provide all required vars | | ANTLR regeneration fails | ANTLR4 not installed | Install via brew install antlr or use Nix flake | | Module path mismatch | Upstream module name | Import as github.com/formancehq/numscript (retained from fork) |

Related Skills


Last Updated: 2026-03-13 Category: Hanzo Ecosystem Related: dsl, fintech, ledger, transactions, payments, go Prerequisites: Go 1.24+, ANTLR4 (for grammar regeneration)