Transit Card Research
Research date: 2026-05-09. These notes capture public implementation checks and CoreExtendedNFC decisions behind transit-card balance work.
Japan IC
- FeliCa system code:
0003. - Balance service:
008B, encoded for CoreNFC as8B 00. - Balance bytes: block bytes 11-12, little-endian, JPY.
Rakuten Edy, WAON and nanaco
These prepaid e-money purses live in the FeliCa common area, system code FE00, which iOS reports only when FE00 is in the app's FeliCa system code list. One card can carry several purses, so each reader checks for its own balance service with Request Service. Automatic detection tries Edy, then WAON, then nanaco. All dates are Japan Standard Time.
| Purse | Service | Layout |
|---|---|---|
| Rakuten Edy | 110B | Block 0: bytes 2-9 Edy ID (BCD), bytes 10-13 issue date. |
| Rakuten Edy | 1317 | Bytes 0-3 balance, little-endian. |
| Rakuten Edy | 170F | 6 cyclic records, newest first: byte 0 type (20 payment, 02 charge, 04 gift / value charge), bytes 2-3 sequence number, bytes 4-7 timestamp (upper 15 bits days since 2000-01-01, lower 17 bits seconds since midnight), bytes 8-11 amount, bytes 12-15 balance; all big-endian. |
| WAON | 6817 | Bytes 0-1 balance, little-endian. WDIC reads 3 bytes and TRETJapanNFCReader 4; all agree while the balance stays within WAON's ¥50,000 limit. |
| WAON | 684F | Block 0 bytes 0-7 WAON number (BCD). |
| WAON | 684B | Block 0 bytes 0-2 point balance, big-endian as in TRETJapanNFCReader. The byte order is not documented elsewhere. |
| WAON | 680B | Three 32-byte records in blocks 0-5: bytes 0-12 terminal number, 13-14 card sequence, 17 type (04 payment, 0C / 10 charge), then MSB-first bits from byte 18: year (5, from 2005), month (4), day (5), hour (5), minute (6), balance (18), usage amount (18), charge amount (17). |
| nanaco | 5597 | Bytes 0-3 balance, little-endian. |
| nanaco | 558B | Block 0 bytes 0-7 nanaco number (BCD). |
| nanaco | 55CB | Bytes 0-1 last-use date and bytes 4-5 issue date, each year (7 bits from 2000), month (4), day (5). |
| nanaco | 560B | Block 1: two point buckets at bytes 0-2 and 5-7, big-endian, summed as in TRETJapanNFCReader. |
| nanaco | 564F | 5 cyclic records, newest first: byte 0 type (47 payment, 5C / 6F / 70 charge, 83 point exchange, 35 transfer), bytes 1-4 amount, 5-8 balance, 9-12 year (11 bits from 2000), month (4), day (5), hour (6), minute (6), 13-14 sequence number; all big-endian. |
Sources: Metrodroid EdyTransitData / EdyTrip, TRETJapanNFCReader (RakutenEdy, Waon and Nanaco card data), and the jennychan IC card format notes, whose four Edy history samples are unit-test vectors. CardBal's Edy, WAON and nanaco decoders have been tested on real cards. Regression tests replay ACR122U captures from 2026-10-31 through these readers: Edy with a confirmed 542-yen balance and six purchase records; nanaco with a confirmed 200-yen balance, 36 points (25 + 11 in the two big-endian buckets) and five purchase records; WAON in Apple Wallet with a confirmed 77-yen balance and three purchase records, and another WAON with a confirmed 239-yen balance, 0 points and three purchase records. The tests check amounts, balances after each purchase, JST timestamps, sequence numbers and WAON's newest-first ordering. Card and terminal identifiers are omitted or replaced in the fixtures.
Hong Kong Octopus
- FeliCa system code:
8008. - Balance service:
0117, encoded as17 01. - Formula in HKD cents:
(raw - 350) * 10, the offset used by CardBal and Y Mobile for live reads. - Metrodroid and FareBot use an offset of 500 for cards read after 2017-10-01. Which offset applies to current physical cards has not been verified.
OctopusReader(transport:balanceOffset:)lets callers override the offset.
China T-Union
- AID:
A000000632010105. - Primary purse APDU:
80 5C 00 02. - Negative purse APDU:
80 5C 01 02. - Final balance: primary purse minus negative purse, CNY fen. Metrodroid shows the primary purse whenever it is positive, but a logged Shanghai card with 800 fen in both purses showed CNY 0.00 in the issuer app, which matches the plain difference.
- A status-word rejection of the negative-purse command means there is no negative purse (0). Session loss and other failures are propagated.
KS X 6924
CoreExtendedNFC uses balance APDU 90 4C 00 00 and reads transaction records from SFI 4.
| Label | AID |
|---|---|
| Hyundai Capital Services | A0000004520001 |
| T-Money / Snapper | D4100000030001 |
| Cashbee | D4100000140001 |
| MOIBA (from Metrodroid, untested there) | D4100000300001 |
| EB Card | D410000029000001 |
| K-Cash | D4106509900020 |
Snapper (Wellington, NZ) shares the T-Money AID. Following Metrodroid SnapperTransitData.check, cards whose 46-byte SFI 4 records all have bytes 26-45 set to FF are reported as Snapper in NZD.
Singapore CEPAS
Implementation added 2026-09-25. Traditional EZ-Link purse and recent logs are read from the card; SimplyGo account-based history is outside this reader. On that date, one traditional EZ-Link card was read successfully on iPhone Air running iOS 27.0 (24A5355q). The app displayed its balance and four records: two journeys, one top-up, and one entry displayed as unknown. This test used the dedicated Singapore reader. The integrated Scan Transit Card flow has not yet been verified on a physical card. The result has not yet been compared with a ticket-machine reference, repeated offline, or verified on other card variants.
The example app includes A000000341000101 as an existing unverified AID candidate. No reliable CEPAS source has been established for this value, and the actual initial AID from the successful read has not been confirmed. The single-card result therefore does not verify this candidate. Core NFC must first expose a compatible ISO 7816 tag. Transit scanning passes the original NFCSessionManager.scan result to readTransitBalance without application refinement. On ISO 7816 tags, SELECT file 4000 and GET PURSE probe CEPAS before the KS X 6924 and T-Union readers; only recognized non-matching responses permit fallback.
| Operation | Encoded command |
|---|---|
| SELECT CEPAS file | 00 A4 00 00 02 40 00 |
| GET PURSE, purse 3 | 90 32 03 00 00 |
| GET HISTORY, purse 3 | 90 32 03 00 01 <offset> <Le> |
Both reads use instruction 32. History adds one data byte for the record offset and requests at most 15 records per batch, with 16 bytes per record. CEPAS timestamps and dates count from 1995-01-01 00:00 in Singapore (UTC+08:00), i.e. 1994-12-31T16:00:00Z, the same epoch as FareBot and Metrodroid.
A direct iOS Simulator check of NFCISO7816APDU(data:) accepted the standard five-byte GET PURSE command 90 32 03 00 00, but returned nil for 90 32 03 00 00 00. The reader therefore uses the existing structured APDU transport and the five-byte command. The subsequent single-card iPhone Air test also completed the five-byte GET PURSE and history commands successfully. The simulator check alone does not establish physical-card compatibility.
Metrodroid’s historical six-byte GET PURSE fix records a compatibility issue with the shorter form. Older cards may return 6700 to the standard encoding used here. Preserve that failure in Logs; do not report such a card as supported.
Follow the physical-card verification steps and retain the existing Logs export for both successes and failures. Mock data checks parser behavior, not iOS detection or card support.