An Android NFC Point-of-Sale manager for Mifare Classic and NTAG2xx NFC cards - simple, fast, easy to use.
NFC Coins is a Point-of-Sale Android application that reads, writes, and manages coin balances stored on Mifare Classic and NTAG NFC cards (NTAG20x and NTAG21x series).
It is designed for venues, events, or any environment where physical tokens need a digital equivalent.
Tap a card to pay, top it up, and issue new ones, in a simple interface.
- Read balance from Mifare Classic or NTAG NFC cards with a single tap
- Add balance to any card via the card management menu
- Deduct coins instantly with one-tap preset buttons (-1 or -2). Button stays active for rapid back-to-back transactions
- Custom deduction, tap the balance display and type any amount via the on-screen keyboard
- Format new cards, initialise blank or factory cards with a key per-card and an optional starting balance
- Reset cards, when you no longer want to use the service, wipe a card back to factory defaults (restores a default key)
- Dynamic key derivation - each card gets a unique authentication key computed as
HMAC-SHA1(PSK, UID), so stealing one card's key reveals nothing about others - Static key fallback - optionally use a shared hex or passphrase key for simpler deployments
- Transaction checksums - every transaction block is protected by
HMAC-SHA256(PSK + UID, counterBlock + txPayload), binding the history to this specific card and PSK - Tamper detection - mismatched checksums trigger an immediate "card tampered" warning
- Interrupted write recovery - if a card is removed mid-write, the app detects the inconsistency on the next tap and automatically retries the write before proceeding
- Single-recharge mode - optionally lock a card so it can only be topped up once (enforced via Mifare sector trailer access bits)
- OPTIONAL: Birth year is encoded in the sector trailer GPB byte (
ageByte = birthYear - 1900). - A minor indicator (π§) is displayed whenever the card holder's calculated age is below the configurable legal-age threshold
- Up to 4 recent transactions are stored directly on the card.
- Each entry records: timestamp offset, operation (ADD / SUBTRACT), and amount
- Fully visible in the app's main screen without any backend
- Portrait and landscape layouts, both optimised for counter-top use
- 6 theme colour presets plus a full HSV colour wheel picker for custom brand colours
- Decimal mode - display balances as currency (e.g.
1.25instead of125) - Audio feedback - three distinct beep patterns: 1 high beep (success), 2 mid beeps (NFC error), 3 low beeps (insufficient balance)
- Vibration feedback - optional haptic confirmation on every transaction
- Background flash - green on success, red on error, purple on card management actions
- Keep screen on option for always-on counter terminals
Data is stored on a single sector, so you can have multiple applications, or serve NDEF messages (such as URLs).
| Block | Contents |
|---|---|
| 0 | Value Block - current balance (Mifare native format, 3x redundancy) |
| 1 | Transaction history - bytes 0-15 (init timestamp + first 2 transactions) |
| 2 | Transaction history - bytes 16-31 (last 2 transactions + 4-byte HMAC checksum) |
| 3 | Sector Trailer - Key A (derived), access bits, GPB (age byte), Key B (derived) |
NTAG20x (e.g. NTAG203) and NTAG21x (e.g. NTAG213, NTAG215, NTAG216) tags are supported. Data is stored in user-memory pages as a contiguous 44-byte (11-page) block, placed at the end of user memory so that the beginning of user pages remains available for NDEF records.
| Bytes | Pages | Contents |
|---|---|---|
| 0β3 | +0 | Magic - "COIN" ASCII identifier |
| 4β7 | +1 | Meta - version, flags (single-recharge), birth year |
| 8β9 | +2 | Balance (uint16, big-endian) |
| 10β41 | +3β10 | Transactions + HMAC checksum (4 records Γ 6 bytes + 4-byte HMAC) |
| 42β43 | +10 | Magic - C01V version matching and read validation |
Bytes 0-3 : Init timestamp (uint32, Unix seconds, big-endian)
Per record : [seconds offset: 3 bytes] [opcode: 1 byte] [amount: 2 bytes big-endian]
opcodes: 0x01 = ADD, 0x02 = SUBTRACT
Last 4 bytes: HMAC-SHA256 checksum (first 4 bytes of full hash)
Dynamic key = HMAC-SHA1(PSK.utf8, cardUID)[0..5] β 6 bytes, unique per card
Static key = provided hex literal OR SHA-256(passphrase)[0..5]
The pre-shared key embedded in the APK can be overridden at build and run time so that your cards will not be readable by a this public build. You can customize this in application settings.
Warning
Keep your π PSK secret. Anyone with the PSK and the app can authenticate to your cards.
Mifare Classic Value Blocks support hardware-atomic increment / decrement + transfer operations.
NFC Coins uses these to guarantee that a power loss or card removal during a transaction will never corrupt the balance.
| Requirement | Minimum |
|---|---|
| Android version | 8.0 Oreo (API 26) |
| NFC hardware | Required (Mifare Classic or NTAG support) |
| Target SDK | 35 |
Note
Some Android devices (notably many recent Google Pixel phones) do not include a Mifare Classic-compatible NFC controller. The app requires a device that exposes the MifareClassic or MifareUltralight technology class. NTAG20x and NTAG21x cards (which use the MifareUltralight / NfcA tech class) are supported on a broader range of devices.
This software is provided as-is, without any warranty of any kind. The author accepts no responsibility for:
- Data loss - any kind of data loss, balance or transaction history stored on NFC cards
- Card access loss - including cards becoming permanently unreadable due to key changes or incorrect formatting
- Any other damage - direct or indirect, arising from the use or misuse of this software
Always test on spare cards before deploying to production. Back up your π PSK - losing it means losing access to every card formatted with it. Use this software at your own risk.
This project is licensed under the MIT License. See the LICENSE file for details.

