Introduction

The Offline Bitcoin ATM is a coin-operated Bitcoin Lightning ATM built around an ESP32 microcontroller. A user inserts physical euro coins; the firmware tallies their value and renders an LNURL-withdraw QR code directly on the display — an e-paper panel, or the 3.5” touchscreen of the JC3248W535C board. The user scans it with any Lightning wallet to withdraw the equivalent amount of bitcoin.

How it works offline

The ATM never needs an internet connection while it is running. Instead of contacting a server to create each withdraw link, it generates the LNURL-withdraw payload locally using the LNbits FOSSA protocol:

  1. On setup you pair the device with an LNbits wallet + FOSSA extension, which gives the ATM a shared secret and an API base URL.

  2. When coins are inserted, the firmware encrypts the amount and a random PIN into a compact payload (AES-256-CBC under the shared secret — encrypted, not signed) and encodes it as a bech32 LNURL string.

  3. That string is shown as a QR code. When the user’s wallet follows it, the wallet’s internet connection talks to your LNbits server, which validates the payload and pays out — so only the user’s phone needs to be online, never the ATM.

This is what lets the ATM operate in places without WiFi, while still settling real Lightning payments.

Warning

The displayed QR code is a bearer instrument. The payload carries no expiry and no device or session identifier, and the ATM is offline, so it cannot revoke what it has minted: anyone who photographs the screen while the code is up can race the customer to redeem it. LNbits rejects a second redemption of a claimed payload, so the exposure is a race for a single use rather than unlimited replay — but a code left on screen (the ATM shows it for up to 10 minutes, and an e-paper panel keeps the last image with no power at all) stays redeemable until it is claimed or overwritten. Site the ATM where the screen is not overlooked, and treat “reset” as discarding a code that remains valid rather than cancelling it.

State machine

The firmware runs a simple loop: it sits idle showing a welcome (or the optional BlockClock display), counts coins while they are being inserted, and once the user presses the button (or taps the touchscreen) it shows the withdraw QR code. See Architecture for the architecture.

Special thanks

This project stands on the shoulders of several others:

Contact & support

If you need help, reach out on Nostr: npub1z9n5ktfjrlpyywds9t7ljekr9cm9jjnzs27h702te5fy8p2c4dgs5zvycf.

If this software and guide were useful to you, feel free to send some sats — message on Nostr for a Lightning address.