NFC Crypto Kit docs
v1.0.0
Live demo Get help
● Cards · What is on a card

Card format
and tap to pay, byte by byte.

One card format is shared by all four apps. A card holds a link, an Android record, and optionally a PIN-encrypted recovery backup and the owner's public receive addresses. Never a private key.

NDEFmikodes.com:nfcseedmikodes.com:nfcaddrNTAG215: 504 bytes

01The records on a card

A card holds one NDEF message. Every record keeps its bytes and its place; new records only ever go after the existing ones. The apps and Card Studio find records by type, never by position.

#RecordWritten byNotes
1URI https://<host>/card/<32 hex id> (NFC Crypto) or https://<host>/c/<32 hex id> (CryptoCard)Pairing, or Card StudioiOS opens the app from the first URI record. The ID is random; it identifies the card, it is not a secret.
2External android.com:pkg = the app's Android packagePairing, or Card StudioAndroid Application Record: a tap opens this app.
3External mikodes.com:nfcseed (optional)"Save recovery backup" (NFC Crypto), creating a wallet (CryptoCard), Clone (Card Studio)The recovery phrase, encrypted with a PIN. See below.
4External mikodes.com:nfcaddr (optional)"Share address on card" (NFC Crypto)Public receive addresses. See below.
Keep the record types

The type names mikodes.com:nfcseed and mikodes.com:nfcaddr are part of the format, not branding. Changing them would make your cards unreadable to the other apps of the kit and to cards already issued. Leave them as they are when you rebrand.

02Recovery backup (nfcseed)

BytesContent
0Version (1)
1KDF: 1 = Argon2id
2Memory, as log2 of KiB (standard: 16 = 64 MiB)
3Iterations (standard: 4)
4Lanes (standard: 1)
5–20Salt, 16 bytes
21–32AES-GCM nonce, 12 bytes
33–AES-256-GCM of the BIP-39 entropy, plus the 16-byte tag. Bytes 0–32 are the associated data.
  • Only the entropy is stored (16 bytes for 12 words, 32 for 24), so the record is 65 to 81 bytes.
  • Readers accept only memory 210–217 KiB, 1–6 iterations, 1–4 lanes and 65–81 bytes, so a hostile card cannot make a phone allocate gigabytes.
  • A new PIN must be 8 to 12 digits and not trivial (not one digit repeated, not a run like 12345678). Older backups with 6 or 7 digits still open.
  • Each guess costs about a second of Argon2id on a phone. The PIN is the only thing protecting the phrase, because anyone can read the card. See Security.

Implementations: apps/nfc_crypto/lib/core/nfc/nfc_seed_backup.dart, apps/cryptocard_wallet/lib/core/nfc/seed_backup.dart, apps/nfc_recovery/lib/nfc/card_backup.dart, apps/card_studio/studio/nfc_core.py. Their tests open each other's test vectors.

03Receive addresses (nfcaddr)

payload  0x01                       version
         UTF-8 lines "<key>:<address>", separated by "\n"
keys     evm   EVM address (same on every EVM network)
         sol   base58 Solana address
         btc   mainnet Bitcoin address (P2PKH, P2SH, bech32, bech32m)

All three addresses take about 150 bytes. Link + Android record + the largest backup + three addresses stay under 400 bytes, so an NTAG215 (504 bytes) holds everything.

A reader refuses the whole record if it is not version 1, is longer than 320 bytes, is not valid UTF-8, repeats a key, has a line without :, or holds an address that is not valid for its key. It skips keys it does not know, and refuses a card with two nfcaddr records. Source: lib/core/nfc/nfc_card_addresses.dart.

04Tap to pay, tap to get paid (NFC Crypto)

  1. Get paid. Card tab → Share address quick action, or open a card → Share address on card. The app writes the wallet's EVM, Solana and Bitcoin receive addresses to the card. It takes two taps: the card is read first, so the recovery backup is copied back byte for byte, then rewritten. Only the same card can be written (the write is checked against its link).
  2. Pay. In Send, choose Tap recipient's card and hold the other person's card to the phone. The app shows the full address for the selected network on a confirm screen: Use this address or Cancel.
  • Nothing is filled in from a card without an nfcaddr record, from a damaged record, or for a network the card does not carry.
  • If the card is one of your own, the app warns that the payment would go back to this wallet.
  • Saving or removing the backup later keeps the addresses. Pairing the card again ("Pair a new card") writes only the link and the Android record: backup and addresses are removed, and the app says so.
  • Card Studio's Clone keeps the source card's records, so a clone carries the same addresses.

Tap to pay is covered by unit and widget tests; it has not yet been verified on a phone with real cards.

05Pairing details

  • After writing, the app reads the card back and compares it. A card pulled away mid-write is reported as failed, never as paired.
  • Writes that must hit a specific card (backup, addresses) are guarded on the native side: any other card answers "wrong card" and is not touched. A blank or damaged card answers "unreadable", and the app asks to pair it again.
  • An unknown card can only be paired from inside the app (Test a card or Pair a new card), never from a link: anyone can publish a link, but only someone holding the card can tap it.
  • Card I/O is native, not a plugin: Core NFC (NFCNDEFReaderSession) in ios/Runner/AppDelegate.swift and reader mode in MainActivity.kt, on the method channel nfc_crypto/nfc.

06Desk tool (optional)

apps/nfc_crypto/tool/nfc/nfccard is a command-line tool for a Mac with an ACR122U. It writes exactly the bytes the app writes. Card Studio does all of this with a window, so most operators can skip it.

tool/nfc/nfccard status              # UID, chip, lock state, link
tool/nfc/nfccard program --count 10  # program 10 cards one after another
tool/nfc/nfccard check-backup        # open a backup with its PIN, without printing words
tool/nfc/nfccard erase               # empty the card

--host must match your app's card host. A programmed card is not paired yet: the customer pairs it in the app.