Skip to content

xvica-ltd/ledgercore

Repository files navigation

ledgercore

Event-sourced double-entry ledger core in Go.

CI Go Report Card Go Reference License: Apache 2.0

Overview

ledgercore is a small Go package that records financial movement as an append-only log of events and projects account balances from that log. It enforces five correctness invariants at write time: balanced postings, idempotency in TransactionID, strictly monotonic event ordering, configurable negative-balance prevention, and single-currency isolation per transaction. The package has zero third-party dependencies; go.mod is one line.

Example

package main

import (
	"fmt"
	"log"

	"github.com/xvica-ltd/ledgercore"
)

func main() {
	l, err := ledgercore.New(ledgercore.NewInMemoryStore())
	if err != nil { log.Fatal(err) }
	if err := l.OpenAccount("cash:gbp", ledgercore.AccountAsset, "GBP"); err != nil { log.Fatal(err) }
	if err := l.OpenAccount("sales:gbp", ledgercore.AccountRevenue, "GBP"); err != nil { log.Fatal(err) }
	if _, err := l.Post(ledgercore.Transaction{
		ID: "sale-1",
		Entries: []ledgercore.Entry{
			{Account: "cash:gbp", Amount: ledgercore.NewMoney(4250, "GBP")},
			{Account: "sales:gbp", Amount: ledgercore.NewMoney(-4250, "GBP")},
		},
	}); err != nil { log.Fatal(err) }
	cash, _ := l.Balance("cash:gbp")
	sales, _ := l.Balance("sales:gbp")
	fmt.Println(cash, sales)
}

Invariants enforced

  1. Balanced postings: debits and credits sum to zero in every transaction. TestInvariant_DoubleEntryBalance_RejectsUnbalancedTransaction
  2. Idempotency: a duplicate TransactionID produces one event and returns the original EventID with ErrIdempotentReplay. TestInvariant_Idempotency_DuplicateTransactionIDProducesOneEvent
  3. Monotonic ordering: EventID is strictly increasing and replay is deterministic. TestInvariant_MonotonicOrdering_ReplayIsDeterministic
  4. Negative-balance prevention: accounts with AllowNegative=false cannot be driven below zero; off by default for assets. TestInvariant_NegativeBalance_AssetAccountRejectsOverdraw
  5. Currency isolation: every entry in a transaction shares one currency that matches the account it touches. TestInvariant_CurrencyIsolation_RejectsMixedCurrencyTransaction

What this gets right

  • Money is int64 minor units. No floats, no big.Float, no rounding modes to argue about.
  • Idempotency is keyed by caller-supplied TransactionID. Retries under network failure do not double-post.
  • Clock is an interface injected at construction. The ledger never calls time.Now directly, so tests are deterministic.
  • The Ledger is safe for concurrent use. The validate-append-apply sequence runs under one write lock so the event store and the in-memory projection cannot diverge.
  • Every failure mode is a named exported sentinel in errors.go; errors.Is works for every one.

What this deliberately does not do

  • No persistence. The reference EventStore is in-memory; durable backends are the caller's responsibility behind the EventStore interface.
  • No HTTP or gRPC surface. This is a library, not a service.
  • No multi-currency FX. Cross-currency movement must be modelled as an explicit FX transaction by the caller.
  • No multi-tenancy. One Ledger is one set of accounts; namespacing across tenants is out of scope.
  • No regulatory reporting or reconciliation. The package exposes the primitives; report generation belongs in the layer above.

When to use this

Use it when you need a small, auditable correctness core for financial movement and you are willing to wire your own persistence, transport, and reporting around it. The five invariants are the part most teams underestimate, and they are what this package addresses.

Do not use it as a drop-in ledger for an existing service. There is no migration story, no schema versioning, no admin UI, and no operational tooling. Those are concerns the caller owns.

Design

A longer write-up of the design and the trade-offs behind each invariant lives in docs/design.md, with a companion post forthcoming on xvica.com.

About

ledgercore is maintained by XVICA, a UK infrastructure firm. It is the open-source reference implementation of proven patterns in financial systems, inspired by similar patterns in Techvica, our banking infrastructure platform.

The repository is intentionally a library, not a product. If you're building production ledger infrastructure and want to discuss the patterns here applied to your context, reach out at hi@xvica.com, or contact the Techvica sales team at sales@techvica.com.

License

Apache License 2.0. See LICENSE.

About

Event-sourced, double-entry ledger core in Go. Zero dependencies. Enforces balance, idempotency, deterministic replay, and currency isolation.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages