Amber DunnEngineering Manager · Product & Platform
← All work
Payments and hardware2019–2020 · production release in 2020

When Software Has to Move Real Money

I designed and built a Node.js controller that translated a binary serial protocol into safe, stateful bill acceptance and dispensing operations for Bitcoin ATMs.

This was one of the most technically demanding systems I have built. The controller did not merely send commands to a peripheral; it had to preserve a trustworthy shared reality while software, external services, cash inventory, and a mechanical device moved through timing-sensitive transactions. In production, that controller formed the cash-handling layer beneath live Bitcoin ATM flows in both directions: blockchain value could be converted into dispensed cash, and deposited cash could support blockchain purchases through the connected platform.

The hard problem was not sending bytes. It was preserving truth when software intent, software records, and physical reality could diverge.

Evidence at a glance

The archived project leaves a substantial engineering trail

135
Repository commits
Sustained implementation and iteration
124
Authored by me
Primary engineering ownership
~69
State events
Sequence and recovery complexity
68
Device commands
Broad protocol implementation
39
REST endpoints
Integration and test surface
95 pages
Technical manual
Handoff and operational maturity

Counts are approximate where they come from static analysis of the archived source. They communicate system surface area, not vanity metrics.

The stakes

Software could not be mostly right

Physical consequences

The recycler could identify, hold, stack, return, dispense, collect, and inventory real currency. A badly timed command could interrupt a mechanical action, while a blind retry could move additional money.

Several versions of truth

The controller had to keep the device, workflow state, cash inventory, persistence, and remote platform aligned — or make disagreement visible enough to recover safely.

Diagram comparing requested action, software record, and actual operational outcome, with reconciliation connecting them.
The reliability problem in one picture: requested, recorded, and actual outcomes can disagree.

How I entered the domain

Reduce risk in small, observable proofs

I approached the unfamiliar domain through progressive risk reduction. I inventoried the evidence, separated vendor behavior from company behavior, mapped commands and states, verified the smallest hardware actions, translated protocol responses into application events, and then assembled those verified pieces into complete workflows. When the system became too complex to hold safely in working memory, I externalized it into diagrams, inventories, checklists, and documentation.

  1. Inventory the evidence.Protocol documents, API contracts, reference code, existing integration pieces, and device behavior.
  2. Separate responsibilities.Distinguish vendor protocol behavior from application, persistence, and transaction behavior.
  3. Map the system.Commands, responses, timing windows, state transitions, error paths, and complete procedures.
  4. Prove the smallest actions.Connect, poll, accept one bill, return one bill, and dispense one bill.
  5. Create stable abstractions.Translate protocol bytes into application events and device operations.
  6. Assemble complete workflows.Acceptance, payout, inventory operations, callbacks, persistence, monitoring, and recovery.
Timeline from protocol research and first hardware communication through state modeling, API integration, operationalization, and documentation.
I turned unknowns into controlled experiments before combining them into a production transaction system.

Architecture

The missing control layer between intent and physical cash

I separated the system into layers so external services did not need to understand hardware commands and raw serial responses did not leak into business workflows.

Layered architecture from transactional platform through integration API, workflow orchestration, device manager, finite state machine, ID003 protocol, RS232, and the bill recycler.
Each layer translated one kind of complexity into a safer abstraction for the layer above it.

Architecture decision

Do not make business workflows speak serial protocol

The protocol layer handled packets and checksums. The state layer handled sequence. The device manager exposed meaningful events. Workflow orchestration coordinated complete transactions. That boundary discipline kept the system explainable.

Workflow one

Accepting a bill inside an authorization window

The machine identified a note and held it in escrow while the controller asked the remote platform whether to authorize the transaction. The controller then had to stack or return the bill, verify the physical outcome, update inventory and persistence, and report the result.

Sequence diagram showing a customer inserting a bill, the recycler reporting escrow, the controller requesting authorization, and the final stack or return outcome being recorded.
Escrow created a limited window in which five parts of the system needed to agree.

Decision principle

A command is not an outcome

I treated the verified state transition as the outcome. A delayed API response, missing event, or timeout could not be converted into a convenient assumption about where the physical bill had gone.

Workflow two

Dispensing was an amount, not a Boolean

A payout could span two recycler boxes. The controller needed to know how much money physically moved, from which box, what remained, and whether continuing was safe after each step.

Diagram showing an 80 dollar request fulfilled by two cash boxes, with a branch explaining why a partial 60 dollar physical outcome must be preserved and reconciled.
Partial fulfillment was a first-class transaction outcome, not an edge case to flatten into success or failure.
If the machine dispensed $60 of an $80 request, the system did not fail at zero. It succeeded at $60 and became uncertain about the remaining $20.

Protocol and state

Turning hostile bytes into safe application behavior

The serial layer synchronized on the protocol header, buffered fragmented reads, waited for the declared message length, validated CRC, distinguished response families, and parsed device meaning from binary payloads.

Diagram showing a binary message made of synchronization, length, command, payload, and CRC fields, plus fragmented serial input being buffered and validated.
A friendly application event began with disciplined handling of incomplete and unfriendly bytes.

Above the parser, finite state machines governed startup, enabling, escrow, authorization, stacking, returning, dispensing, collection, inventory operations, power-up behavior, timeouts, and recovery.

Simplified state map grouping startup, accepting, decision, dispensing, inventory, recovery, and idle behavior.
The production implementation contained approximately 69 unique events; this reconstruction consolidates them into major behavioral regions.

Failure design

Never convert uncertainty into a convenient fiction

The safe response depended on whether the system could still prevent the physical action or needed to reconcile an outcome that might already exist.

Failure matrix comparing communication and mechanical failures detected before physical action with failures detected during or after action, and the prevent, verify, contain, and reconcile responses.
Different uncertainties required different recovery behavior.

Prevent or contain

Reject, retry safely, disable risky operations, or stop before money moves.

Verify or reconcile

Read state, preserve known physical progress, align records, and avoid unsafe blind retries.

Ownership and provenance

Strong engineering uses existing knowledge without obscuring who built what

The project required me to reason across several boundaries at once: vendor documentation, physical hardware, Node controller behavior, existing Python and persistence components, and external transaction APIs. I used supplied and third-party components where appropriate, remained explicit about provenance, and owned the architecture and integration work that turned those inputs into a production controller.

Two-column diagram separating supplied inputs such as protocol documentation and API contracts from Amber's ownership of architecture, controller implementation, workflows, testing, recovery, and documentation.
The work was integration in the deepest sense: understand the parts, establish the boundaries, and build the missing system.

Archive evidence

Principal engineering ownership

The archived repository contains 135 commits, 124 authored under my account. Approximately 98.9% of current core application lines are attributed to my account. Git attribution does not prove that every line was independently invented, but together with the documentation, commit progression, and explicit provenance notes, it strongly supports my role as principal engineer and primary controller author.

Outcome

A production capability, not a demo

The controller supported acceptance, dispensing, floating, and emptying; exposed integration and generic device APIs; coordinated callbacks and persistence; monitored hardware state; and went live in 2020 as part of a worldwide BTM deployment.

2020

Production release

The controller went live as part of a worldwide BTM deployment supporting live blockchain-to-cash and cash-to-blockchain transaction flows through the connected Bitcoin ATM platform.

4

Core cash workflows

Acceptance, dispensing, floating, and emptying were implemented as complete stateful procedures.

39

REST endpoints

The controller exposed integration and generic device-control surfaces for operations and testing.

95 pages

Technical manual

The final documentation covered architecture, protocol behavior, APIs, procedures, testing, and operations.

The outcome was the missing production capability itself: software that could safely connect transactional intent to physical cash movement.

Lessons

What I carry into engineering leadership

State should be explicit

When actions depend on timing, order, and physical consequences, hidden state becomes dangerous.

Partial success is a first-class outcome

Systems must represent what actually happened, not only what was requested.

Documentation is an engineering control

Diagrams, inventories, and procedure maps reduce ambiguity and make a complex system maintainable.

Abstractions should follow verified understanding

I began with small observable hardware actions and built higher-level layers around proven behavior.

Operations are part of the product

Logging, configuration, callbacks, supervision, timeouts, and troubleshooting determine whether software works beyond development.

Reflection

What I would modernize today

I would preserve the layered architecture and explicit state reasoning while applying tools and practices that are more accessible today.

Original contextModern approach
JavaScript with runtime validationTypeScript protocol and event models
Physical-device-heavy testingDeterministic simulator-backed integration suite
Callback and timer orchestrationStructured async workflows and executable statecharts
Distributed timing constantsCentralized device and timing configuration
Conventional logsCorrelated structured telemetry and transaction IDs
Ad hoc uncertain-outcome handlingFormal idempotency and reconciliation controls

This is not a rejection of the original implementation. It is the advantage of evaluating earlier work with more tools, more experience, and full knowledge of what the project had to accomplish at the time.

Hiring lens

What this project says about how I work

This project represents the work I do best: entering an unfamiliar and consequential system, building a reliable mental model, identifying where reality can diverge, and turning scattered technical inputs into a maintainable product.

The same approach shapes how I lead teams: make the system visible, reduce ambiguity, design for reality, and leave the next person with more clarity than I was given.

Related work · Architecture and technical discovery