Hanzo
Hanzo Skills Reference

Hanzo Numscript

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, o...

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?

  • Declarative transfers -- Express multi-party fund routing in readable syntax
  • Percentage splits -- Route funds by percentage or fixed amount to multiple destinations
  • Source ordering -- Define fallback funding sources with automatic overdraft prevention
  • Type safety -- Compile-time validation of account references and asset types
  • ANTLR grammar -- Formal grammar (Numscript.g4 + Lexer.g4) for parsing
  • LSP support -- Language Server Protocol for editor integration
  • MCP server -- Model Context Protocol integration for AI tooling

Tech Stack

  • Language: Go 1.24
  • Parser: ANTLR4 (Go target)
  • CLI: cobra (goreleaser for cross-platform builds)
  • Testing: testify + go-snaps (snapshot testing)
  • Error tracking: Sentry
  • LSP: go.lsp.dev/protocol
  • MCP: mark3labs/mcp-go
  • License: MIT

OSS Base

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

When to use

  • Modeling multi-party financial transfers (e.g., marketplace payouts)
  • Splitting payments by percentage across multiple destinations
  • Defining ordered funding sources with overdraft limits
  • Parameterizing transaction scripts for reuse
  • Validating transaction scripts before execution (parse + analysis)
  • Integrating financial transaction DSL into a ledger system

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

ItemValue
Go modulegithub.com/formancehq/numscript
CLI binarynumscript
Go version1.24
GrammarNumscript.g4 + Lexer.g4 (ANTLR4)
Default branchmain
LicenseMIT
Repogithub.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

IssueCauseSolution
Parse error on @worldMissing source block@world must be inside a source = {} block
MissingFundsErrSource accounts have insufficient balanceAdd @world as final fallback or use allowing overdraft
Variables not resolvedMissing from VariablesMapCheck GetNeededVariables() and provide all required vars
ANTLR regeneration failsANTLR4 not installedInstall via brew install antlr or use Nix flake
Module path mismatchUpstream module nameImport as github.com/formancehq/numscript (retained from fork)
  • hanzo/hanzo-reconciliation.md - Transaction reconciliation engine
  • hanzo/hanzo-billing.md - Billing and subscription management

How is this guide?

Last updated on

On this page