Earl Devlog 01: An Introduction

  • 1266 words
  • 7 min

Earl is a free open-source MIT licensed finance tool currently in development. Inspired by projects including Ledger, the objective of Earl is to track personal assets, expenses, income, and capital gains across a variety of accounts. In this first devlog, I would like to set out the primary objectives of Earl alongside the planned methods to meet these objectives.

Compared to current plaintext based accounting options such as Ledger and GUI centric tools like GNU Cash, Earl aims to meet a slightly different set of goals. These goals and the corresponding solutions are outlined below in Objective: Solution form.

  1. Full history and rollback: An event-based log used to construct ledger state.
  2. Automated asset value calculation and transaction imports: A clean consumable API to enable separate programs to add to and read from the ledger.
  3. Multiple frontend applications: A focus on separating concerns, enabling distinct CLI, GUI, and web-based implementations dependant on the core library.

Considering each objective

It's important to clarify the rationale behind each of these objectives and the means of achieving the objectives. Before beginning, it would be useful to define some shared vocabulary.

  • Ledger - The ledger represents all accounting data, at it's most simplistic, it is a record of debits and credits between accounts, transactions.
  • Transaction - A single instance of a set of debits and credits between accounts that must balance according to double-entry principles.
  • Commodity - Taken from Ledger jargon, a commodity represents any unit of account. Currency, shares in a company and crypto tokens are all considered commodities.
  • Price Entry - A point in time exchange rate relationship between two commodities.
  • Log - An Event that marks a change to the ledger. One log type may be TransactionCreated, representing a new transaction in the ledger.
  • Logstore - A persistent storage system that maintains an ordered list of logs. The log store is read from to construct the current state of the ledger.

Objective 1: Full history and rollback

Ensuring historic data is not removed or lost, and guaranteeing the traceability of records is a core component of accounting and tracking personal finances. If historic records are lost, trust in an accounting ledger is lost. Likewise, if records can be edited without a clear log of all edits, the previous state of a ledger is lost.

Earl constructs the state of the ledger by replaying each log in order from the first log. Where tools like Ledger CLI present a single document representing the current Ledger state, relying on VCS for history, Earl will enable a better understanding of each edit made to the ledger. For example, a Transaction being added to the ledger is represented by a TransactionCreated log appended to the Logstore. A later edit to the same transaction would be represented by a TransactionModification log appended to the logstore. The Ledger, constructed from this ordered list of logs, could show the current transaction and optionally be queried to show each version of the transaction before and after every modification event.

Furthermore, because each log is identified by a unique hash, it would be trivial to roll back the ledger or view its entire state at a certain point in time by simply passing the hash of the log that should be used as the cutoff point.

Objective 2: Automated asset value calculation and transaction imports

It should be trivial for a user to keep their transactions and the real current value of all their assets up to date. To implement fetching logic - retrieving commodity prices from an API like Yahoo Finance - into Earl would bloat the core CLI and necessitate updates to the binary even if the core accounting logic remains unchanged. Fetching of price entry data and transactions should be completed by third-party tools calling into the core of Earl. This can be achieved by presenting a clean and simple API at the boundary of the core Earl logic, this API should look something like the following Go interface. Note: The callee does not need to consider how these operations translate into state checking and on-disk logs.

type ModelInterface interface {
    AddAuthor(authordID UUID, authorName string) error
    RemoveAuthor(authorID UUID) error

    AddEntry(entry Entry) error
    RemoveEntry(entryID UUID) error

    AddCommodityValueEntry(
      commodityA UUID, 
      commodityB UUID, 
      aToBRate float32
    ) (UUID, error)
    RemoveCommdityValueEntry(entryID) error
}

Objective 3: Multiple frontend applications

I would be comfortable using a CLI, but most individuals are not.

Ensuring the core library is completely self-contained and presents a clean, consumable API enables anyone to implement a frontend for Earl - meeting users wherever they are most comfortable. Initially, Earl will be CLI based but I'm interested in implementing a local server to deliver an Elm webapp that interacts with Earl. This kind of functionality would ship in a separate binary. Ideally, a native GTK app should be written.

The Target Use Case

It's a good thing to set out the initial target use-case for any program. I'll do this by writing User Stories for each use case.

  • As an individual, I want current and historical asset value information so that I can track the value of my portfolio over time.
  • As a non-technical user, I want a variety of friendly GUI tools available on most platforms so that I can interact with the accounting tool in a way that feels within my technical abilities.
  • As a compliance conscious accountant, I want the ability to see historic edits to records and to never lose entries so that I can trace the origins of all data.

Implementation

Currently, I'm split between Rust and Go for the implementation of Earl. Go has the advantage in that I am most familiar with Go and it seems to be the more popular language. I've written some Rust and want to spend more time learning and writing it, however, I'm yet to implement a large project in Rust and am not entirely sure of the language best practices. In the long run, I think a Rust implementation will be easier to maintain but a Go implementation could be more attractive to outside contributions and dependent applications.

I'm going to continue developing Rust and Go implementations side by side to try and make up my mind.

Why do this?

Managing your money should be free. The ability to sanely organize your finances - budget, manage debt, and track investments - for free enables people to better control their lives. Beyond these motivations, Earl is something I want. I need a way to track my finances and want to make a programmable accounting tool that others can build on.

Notes

For investment purposes, I'd like to be able to record notes, alongside transactions, detailing the rationale behind my decision making. This is something that I am considering implementing as a first-class component of Earl - arbitrary notes that may be connected to a transaction.

Cryptocurrency

Tracking cryptocurrency transactions is a challenging issue. You need to consider the fees of each transaction, the exact time of execution, and the value of outbound and inbound tokens or currency. The need to carefully track all transactions continues to grow as tax regulations on crypto investments become more defined. A key aim of Earl, which stems from a part of my use case, is to be able to intelligently account for crypto portfolios. Once Earl is stable, this can be achieved through a new CLI that calls into the core of Earl; separating this kind of logic, fetching transactions, ensures the base Earl CLI remains lean whilst greater functionality can be achieved through extra tools.