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.
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.
- Inventory the evidence.Protocol documents, API contracts, reference code, existing integration pieces, and device behavior.
- Separate responsibilities.Distinguish vendor protocol behavior from application, persistence, and transaction behavior.
- Map the system.Commands, responses, timing windows, state transitions, error paths, and complete procedures.
- Prove the smallest actions.Connect, poll, accept one bill, return one bill, and dispense one bill.
- Create stable abstractions.Translate protocol bytes into application events and device operations.
- Assemble complete workflows.Acceptance, payout, inventory operations, callbacks, persistence, monitoring, and recovery.
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.
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.
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.
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.
Above the parser, finite state machines governed startup, enabling, escrow, authorization, stacking, returning, dispensing, collection, inventory operations, power-up behavior, timeouts, and recovery.
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.
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.
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.
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