A ledger core that is exact, verifiable and safe to hand to an agent
LedgerForge records every movement of money as a balanced double entry in integer units, keeps the evidence to prove its history, and enforces spending limits on the keys your services and agents use. All of it is in the open-source core.
- Ledger corePOST /transactions
- LifecyclesINFLIGHT → APPLIED
- Verificationledgerforge verify
- PoliciesPUT /policies/:id
- MCP serverledgerforge-mcp
- Hardened APIX-LedgerForge-Key
Every transfer debits one balance and credits another by the same integer amount
- Integer postings
- precise_amount: 1250 at precision: 100 is 12.50, on both sides.
- Precision is a multiplier
- Not a count of decimal places. Keep one multiplier per currency and send it with each transaction.
- Deterministic splits
- Leftover attribution never depends on floating-point sums. Mixed or malformed distributions are rejected.
- Large integers preserved
- Decimal request tokens are kept exactly; clients must not decode amounts through floating point.
| Input | Result | Why |
|---|---|---|
| precise_amount: 1250, precision: 100 | 1250 units (12.50) | Exact. Preferred for integrations. |
| amount: 1.005, precision: 100 | 101 units | Compatibility field, rounded to the nearest unit, ties away from zero. |
| amount: 0.001, precision: 100 | Rejected | A positive amount that rounds to zero units. |
| precision: 9007199254740993 | Rejected | The token can't be decoded without changing its value. |
Holds, refunds and retries that keep their meaning across queues and workers
- QUEUED
- Accepted for worker processing. Not evidence of settlement.
- SCHEDULED
- Deferred until a scheduled execution time.
- INFLIGHT
- Funds reserved pending commit, void or expiry.
- APPLIED
- Posted to committed balances.
- VOID
- An inflight reservation was released.
- REJECTED
- Execution failed validation or another processing constraint.
{ "error": "reference has already been used", "error_detail": { "code": "TXN_DUPLICATE_REFERENCE", "message": "reference has already been used" }}Inflight holds
PUT /transactions/inflight/:txIDCommit all or part of a hold, or void the remainder. Expiry and commit dates are handled by workers.
Linked refunds
POST /refund-transaction/:idReverses an APPLIED original for its exact units. A durable claim prevents duplicate reversals.
Previews
dry_run: trueProjects balance effects without posting, consuming the reference or sending a webhook.
{ "source": "bln_e0f81b15-bd0a-43c5-9e84-7fd4394192e8", "destination": "bln_df56a248-7d7e-4c0c-8fbf-a5215c87b603", "currency": "USD", "precise_amount": 1250, "precision": 100, "reference": "order-1042", "description": "Order 1042", "skip_queue": true, "allow_overdraft": true, "overdraft_limit": 12.5}{ "rate": 0, "precise_amount": 1250, "amount": 12.5, "amount_string": "12.5", "precision": 100, "overdraft_limit": 12.5, "transaction_id": "txn_c493a7e3-ac39-4456-90c3-ac43f00c5e0d", "parent_transaction": "", "source": "bln_e0f81b15-bd0a-43c5-9e84-7fd4394192e8", "destination": "bln_df56a248-7d7e-4c0c-8fbf-a5215c87b603", "reference": "order-1042", "currency": "USD", "description": "Order 1042", "status": "APPLIED", "hash": "9b7d7ad924e2e626055e3b33edcf194142e3fcd562de1807dd738d9afd7c1f87", "allow_overdraft": true, "inflight": false, "skip_queue": true, "atomic": false, "created_at": "2026-10-10T05:54:45.691597464Z", "effective_date": "2026-10-10T05:54:45.691597464Z", "scheduled_for": "0001-01-01T00:00:00Z", "inflight_expiry_date": "0001-01-01T00:00:00Z", "inflight_commit_date": "0001-01-01T00:00:00Z", "meta_data": { "allow_overdraft": true }}Check the books yourself, and keep evidence the database can't rewrite
- Replay checker
- ledgerforge verify reads one REPEATABLE READ snapshot and compares replayed postings with stored counters. It never repairs data.
- Versioned hash chain
- Canonical v3 hashes commit amounts, precision, status, references, overdraft caps and execution controls. Enable sealing in the server config.
- Checkpoints and anchors
- audit export prints checkpoint roots. Store them independently; audit verify --anchor reports any root the chain no longer matches.
- Pending isn't tampering
- Unsealed rows are reported as pending coverage, separately from tampering. Neither check proves a real-world payment happened; reconcile for that.
$ ledgerforge verifyChecked 6 balances and 4 transactions: 0 issues$ ledgerforge audit verifyChain: 4 sealed, head ff10247395ae53c4580005ac9c6cfb6cc7f79cbf5aeed252850e619b82ea2821, 0 pending, 0 tampering findings$ ledgerforge audit export > checkpoints.json$ ledgerforge audit verify --anchor checkpoints.json --json{"verified":true,"last_sequence":4,"head_hash":"ff10247395ae53c4580005ac9c6cfb6cc7f79cbf5aeed252850e619b82ea2821","pending":0,"tampering":[]}Hash chain (canonical v3, SHA-256)
- seq 1h1 = H(h0, txn 1)
- seq 2h2 = H(h1, txn 2)
- seq 3h3 = H(h2, txn 3)
- seq 4h4 = H(h3, txn 4)
Checkpoints
- seq 1count 19f52ae4fe574506a5dbbc60fa563764579e97c3a818e6bb84f9ffc43ef53d081
- seq 3count 33e859daaffd0aefeb952ad9d9f00f154d64f82f8e4d6ad0cbb3c80f5baca73ae
- seq 4count 4ff10247395ae53c4580005ac9c6cfb6cc7f79cbf5aeed252850e619b82ea2821
External anchor
- 1audit export writes checkpoint roots as JSON
- 2Sign the exact bytes with a separate identity
- 3Store them outside the database administrators' reach
- 4audit verify --anchor compares them with the live chain
Give an agent its own key, a spending policy and someone else to approve large payments
- Per-key spending policies
- Currency and precision rules, per-transaction caps, UTC daily limits, approval thresholds and ledger and balance allowlists.
- Shared delegated budgets
- A child key inherits its parent's restrictions and draws on the same daily budget.
- Independent approvals
- The initiating key and its delegation family can't approve or reject its own hold.
- MCP bound to a key
- ledgerforge-mcp is read-only unless started with --allow-write, and carries the bound key's policy on every call.
{ "rules": [ { "currency": "USD", "precision": 100, "max_transaction": "5000", "daily_limit": "10000", "approval_above": "2000" } ], "ledger_ids": [ "ldg_e04e431f-8a27-4b19-8744-eb1867ed7b23" ], "balance_ids": [ "bln_df56a248-7d7e-4c0c-8fbf-a5215c87b603", "bln_173d086e-57cb-4690-9abe-1cf060fafb13" ]}Scoped keys, honest errors and webhooks your receivers can verify
- Constrained delegation
- A non-master key creates keys only for its own owner, with scopes it already has and no longer expiry than its own.
- Authoritative revocation
- Every request checks key hash, scope, expiry and revocation in PostgreSQL. A stale cache can't restore a revoked key.
- Signed webhooks
- A dedicated signing secret, URL validation, optional private-destination blocking, no redirects and bounded responses.
- Sanitised errors
- Stable error codes and deliberate validation messages. SQL text and driver errors stay in operator logs.
X-LedgerForge-Timestamp: <unix seconds>X-LedgerForge-Signature: hex(HMAC-SHA256(secret, timestamp + "." + raw_body))Everything else a production ledger needs
Split and bulk transfers
Several sources or destinations per transfer, and bulk requests with explicit atomic and asynchronous options.
Reconciliation
Upload external records, define matching rules and run batch or instant reconciliation against ledger data.
Fund lineage
Balances can track where funds came from, with lineage queries for balances and transactions.
Snapshots and history
Take balance snapshots and read a balance as it stood at a point in time.
Identities
Link people and organisations to balances, and tokenise sensitive identity fields.
Metadata and webhooks
Attach your own metadata, receive signed events when it changes and register transaction hooks.
Search
Optional Typesense indexes for searching ledger records. PostgreSQL stays the source of truth.
Metrics and tracing
Prometheus metrics behind a bearer token, and optional OpenTelemetry export.
Run it yourself, anywhere PostgreSQL and Redis run
Try it locally
Builds your checkout, generates a random master key, runs migrations and binds the API to 127.0.0.1:5001.
$ git clone https://github.com/devaccuracy/ledgerforge.git$ cd ledgerforge$ ./examples/quickstart/start.sh$ ./examples/quickstart/transfer.shsource: -1250 unitsdestination: 1250 unitstotal debits = total credits = 1250 unitsreference retry: balances unchangedRun the published image
The compose file runs the server, workers, PostgreSQL and Redis. Pin ghcr.io/devaccuracy/ledgerforge:1.0.0 in production.
$ cp ledgerforge.example.json ledgerforge.json# Replace server.secret_key first, for example with: openssl rand -hex 32$ LEDGERFORGE_IMAGE=ghcr.io/devaccuracy/ledgerforge:1.0.0 docker compose up -dDownload a release binary
Archives for Linux, macOS and Windows on amd64 and arm64, checked against checksums.txt.
$ curl -LO https://github.com/devaccuracy/ledgerforge/releases/download/v1.0.0/ledgerforge_v1.0.0_linux_amd64.tar.gz$ curl -LO https://github.com/devaccuracy/ledgerforge/releases/download/v1.0.0/checksums.txt$ sha256sum -c checksums.txt --ignore-missingledgerforge_v1.0.0_linux_amd64.tar.gz: OK$ tar -xzf ledgerforge_v1.0.0_linux_amd64.tar.gz$ ./ledgerforge_v1.0.0_linux_amd64/ledgerforge versionledgerforge 1.0.0 (commit 64c7aec, built 2026-10-10T05:53:57+00:00)Build with Go
Install both commands from source with Go 1.26.9 or later.
$ go install github.com/devaccuracy/ledgerforge/cmd/ledgerforge@v1.0.0$ go install github.com/devaccuracy/ledgerforge/cmd/ledgerforge-mcp@v1.0.0Put a provable ledger under your money movement
Start with the open-source core, or let the LedgerForge team run it for you on dedicated infrastructure.