Numscript is a domain-specific language for modeling complex financial transactions.
Category: Hanzo Ecosystem Related Skills: hanzo/hanzo-reconciliation.md, hanzo/hanzo-billing.md
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.
Repo: hanzoai/numscript (Formance numscript fork). Go module path is github.com/formancehq/numscript (upstream module name retained).
| 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 |
go install github.com/hanzoai/numscript/cmd/numscript@latest
numscript check script.num
numscript fmt script.num
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)
}
}
// 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")
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{}
// 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)
}
flags.ExperimentalOneofFeatureFlag // oneof {} source selection
flags.ExperimentalMidScriptFunctionCall // balance() calls mid-script
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
| 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) |
hanzo/hanzo-reconciliation.md - Transaction reconciliation enginehanzo/hanzo-billing.md - Billing and subscription managementLast Updated: 2026-03-13 Category: Hanzo Ecosystem Related: dsl, fintech, ledger, transactions, payments, go Prerequisites: Go 1.24+, ANTLR4 (for grammar regeneration)