A fully functional Android wallet app for the Unicity Protocol, demonstrating real cryptographic token transfers via NFC using Android Host Card Emulation (HCE). Users can mint, store, and transfer genuine Unicity tokens between devices with a simple tap.
Token Type: Real Unicity Protocol tokens with cryptographic signatures
Transfer Method: Hybrid NFC + Bluetooth LE mesh for unlimited token sizes
SDK Integration: Unicity Java SDK v1.0-SNAPSHOT (Native Android integration)
Transfer Protocol: NFC handshake for secure discovery, Bluetooth for data transfer
Transfer Speed: 1-2 seconds NFC handshake + ~50-100 KB/s Bluetooth transfer
User Experience: Quick NFC tap to initiate, automatic Bluetooth transfer
- Real Unicity Tokens: Mint genuine cryptographic tokens on the Unicity Protocol
- Direct NFC Transfer: Tap-to-send tokens between Android devices
- Blockchain Integration: Tokens are committed to the Unicity test network
- Modern UI: Material Design with Unicity branding
- Real-time Progress: Transfer progress indicators on both devices
- Token Management: Expandable cards with detailed token information
- Custom Token Minting: Create tokens with custom names, amounts, and data
- Android 7.0+ (API level 24+)
- NFC-enabled device
- Host Card Emulation support (most modern Android devices)
- Internet connection (for blockchain commitments)
-
Clone the repository
git clone <repository-url> cd nfc-wallet-demo
-
Install Java 11 (required for Unicity Java SDK)
brew install openjdk@11 # macOS # or use your system's package manager export JAVA_HOME=$(/usr/libexec/java_home -v 11)
-
Open in Android Studio
- Import the project
- Sync Gradle dependencies
- Ensure Java 11 is selected in project structure
-
Build and install
./gradlew assembleDebug adb install app/build/outputs/apk/debug/app-debug.apk
-
Test on two NFC devices
- Install on both devices
- Enable NFC in device settings
- Open app on both devices
- Tap one device to the other to transfer tokens
UI Layer (Activities/Fragments)
โ
ViewModel Layer (State Management)
โ
Repository Layer (Data Access)
โ
Data Layer (Models/Storage)
- MainActivity: Token list and wallet management
- ReceiveActivity: Handles incoming token transfers via HCE
- HostCardEmulatorService: NFC HCE service for receiving tokens
- HybridNfcBluetoothClient: Orchestrates hybrid NFC + Bluetooth transfers
- BluetoothMeshTransferService: Handles Bluetooth LE mesh data transfer
- DirectNfcClient: Legacy NFC-only transfer implementation
- WalletRepository: Manages token storage and Unicity SDK integration
- UnicityJavaSdkService: Native Java SDK integration for Unicity Protocol operations
The app now implements a hybrid approach that combines NFC for secure peer discovery with Bluetooth LE mesh networking for high-bandwidth data transfer. This eliminates the 64KB NFC payload limitation while maintaining security and ease of use.
For detailed technical documentation, see HYBRID_TRANSFER_IMPLEMENTATION.md.
The previous NFC-only implementation is still available but limited to tokens under 64KB:
- User taps "Send Token" and expands token card
- User taps sending device to receiving device
DirectNfcClient
establishes NFC connection and waits for stabilization- Handshake Phase:
- Sender requests receiver to generate a new cryptographic address
- Receiver generates identity (secret + nonce) and derives address
- Receiver sends address back to sender
- Transfer Phase:
- Sender creates offline transaction using receiver's address
- Transaction package is chunked and sent via APDU commands
- Receiver saves the offline transaction and broadcasts to UI
- Completion Phase:
- Receiver processes offline transaction with SDK
- Token is added to receiver's wallet
- Success confirmation shown on both devices
HostCardEmulatorService
activates when NFC field detected- Address Generation:
- Receives token transfer request with token metadata
- Generates new cryptographic identity via SDK
- Returns receiver address to sender
- Transaction Reception:
- Receives offline transaction chunks
- Reconstructs complete transaction package
- Saves to SharedPreferences for persistence
- Processing:
ReceiveActivity
catches broadcast or finds saved transfer- Completes offline transfer using SDK
- Updates wallet with new token
- Protocol: Custom APDU commands over NFC-A/ISO 14443 Type A
- Commands:
CMD_SELECT_AID (0xA4)
: Initial application selectionCMD_REQUEST_RECEIVER_ADDRESS (0x05)
: Request receiver to generate addressCMD_GET_RECEIVER_ADDRESS (0x06)
: Query for generated addressCMD_SEND_OFFLINE_TRANSACTION (0x07)
: Send transaction chunksCMD_TEST_PING (0x08)
: Test mode for debugging
- Chunk Size: 200 bytes per APDU (conservative for stability)
- Buffer Limit: ~36KB total transfer size (Android HCE limitation)
- Timeouts:
- 30 seconds for address generation
- Connection stabilization delays between operations
- Persistence: Transfers saved to SharedPreferences to survive app restarts
# Start Android emulator
emulator -avd <your-avd-name>
# Install and run
./gradlew installDebug
adb shell am start -n com.unicity.nfcwalletdemo/.ui.wallet.MainActivity
Note: NFC transfers cannot be tested in emulator - UI functionality only.
- 2 physical Android devices with NFC
- Both devices have NFC enabled in Settings
- App installed on both devices
- Setup: Open app on both devices
- Mint: Use menu โ "Mint a Token" to create a real Unicity token
- Send: Device A - tap token, select "Send Token"
- Transfer: Tap Device A to Device B (back-to-back)
- Verify: Check token appears in Device B's wallet
- Reverse: Test transfer from Device B back to Device A
- "Tag was lost": Devices moved apart during transfer - keep steady contact for entire duration
- "NFC connection lost": Connection interrupted - retry with phones held more firmly together
- "Failed to get receiver address": Handshake failed - ensure receiver app is open and active
- "State data is not part of transaction": SDK processing error - check token format and retry
- App crashes: Check logs for specific errors
- Transfer succeeds but token doesn't appear: Check receiver's wallet after a few seconds (processing delay)
Token Size | Transfer Time | Chunks | Notes |
---|---|---|---|
2KB | ~1-2 seconds | ~8 | Very fast |
4KB | ~2-4 seconds | ~16 | Fast |
8KB | ~4-8 seconds | ~32 | Good |
16KB | ~8-15 seconds | ~64 | Acceptable |
32KB | ~15-30 seconds | ~128 | Slow |
64KB | ~30-60 seconds | ~256 | Very slow |
The app uses the Unicity State Transition SDK to create real cryptographic tokens:
- Generate Identity: Creates cryptographic key pair (secret + nonce)
- Submit to Network: Sends mint transaction to Unicity aggregator
- Wait for Proof: Receives blockchain inclusion proof
- Create Token: Constructs complete token with transaction history
- SigningService: Cryptographic signatures for token ownership
- MaskedPredicate: Privacy-preserving ownership predicates
- StateTransitionClient: Blockchain interaction (no separate offline client)
- InclusionProof: Proof of blockchain commitment with direct verification
- Commitment: Standard commitment class for offline transfers
- CommitmentJsonSerializer: JSON serialization for offline transfer packages
- Identity Generation: Create new cryptographic identities for receiving tokens
The app now uses the native Unicity Java SDK instead of the JavaScript SDK in WebView:
- Native Performance: Direct JVM execution without WebView overhead
- Type Safety: Compile-time type checking and better IDE support
- Reduced Memory: No JavaScript bridge or WebView memory usage
- Better Integration: Direct access to Android platform features
- Simplified Architecture: No need for JavaScript bundling or bridge code
For migration details, see JAVA_SDK_MIGRATION.md.
- Digital signatures via Unicity SDK
- Cryptographic token authentication
- Secure storage for private keys
- Input validation and sanitization
- Real Unicity SDK integration
- Connection to Unicity test network
- Production network credentials
- User authentication system
- Comprehensive device compatibility testing
- Edge case handling (interrupted transfers, etc.)
- Performance testing with various token sizes
- Security penetration testing
- Code signing with production certificates
- ProGuard/R8 code obfuscation
- Remove debug logging
- Play Store compliance review
app/src/main/
โโโ java/com/unicity/nfcwalletdemo/
โ โโโ data/ # Data layer
โ โ โโโ api/ # API interfaces
โ โ โ โโโ CryptoPriceApi.kt
โ โ โโโ model/ # Data models
โ โ โ โโโ Token.kt # Unicity token model
โ โ โ โโโ TransferRequest.kt
โ โ โ โโโ TransferResponse.kt
โ โ โ โโโ Wallet.kt
โ โ โโโ repository/ # Data repositories
โ โ โ โโโ WalletRepository.kt
โ โ โโโ service/ # Services
โ โ โโโ CryptoPriceService.kt
โ โโโ model/ # Domain models
โ โ โโโ CryptoCurrency.kt
โ โโโ nfc/ # NFC implementation
โ โ โโโ ApduTransceiver.kt # APDU command interface
โ โ โโโ BluetoothHandshake.kt # Bluetooth handshake data classes
โ โ โโโ DirectNfcClient.kt # Legacy NFC-only sender
โ โ โโโ HostCardEmulatorLogic.kt # Receiver logic
โ โ โโโ HostCardEmulatorService.kt # HCE service
โ โ โโโ HybridNfcBluetoothClient.kt # Hybrid transfer orchestrator
โ โ โโโ NfcTestChannel.kt # Test mode support
โ โ โโโ RealNfcTransceiver.kt # NFC transceiver
โ โโโ bluetooth/ # Bluetooth implementation
โ โ โโโ BluetoothMeshTransferService.kt # BLE mesh transfer
โ โโโ sdk/ # Unicity SDK integration
โ โ โโโ UnicitySdkService.kt # WebView bridge service
โ โ โโโ UnicityTokenData.kt # SDK data models
โ โโโ ui/ # UI layer
โ โ โโโ receive/
โ โ โ โโโ ReceiveActivity.kt # Token receiving UI
โ โ โโโ send/
โ โ โ โโโ SendActivity.kt # Legacy send activity
โ โ โโโ wallet/
โ โ โโโ AssetDialogAdapter.kt
โ โ โโโ CryptoAdapter.kt
โ โ โโโ MainActivity.kt # Main wallet UI
โ โ โโโ TokenAdapter.kt # Token list adapter
โ โโโ utils/ # Utilities
โ โ โโโ PermissionUtils.kt
โ โโโ viewmodel/ # ViewModels
โ โโโ ReceiveViewModel.kt
โ โโโ SendViewModel.kt
โ โโโ WalletViewModel.kt
โโโ assets/
โ โโโ bridge.html # WebView bridge HTML
โ โโโ unicity-sdk.js # Bundled Unicity SDK v1.4.7
โ โโโ unicity-wrapper.js # JavaScript wrapper (updated for SDK v1.4.7)
โโโ res/
โโโ drawable/ # Icons and graphics
โโโ layout/ # XML layouts
โ โโโ activity_main.xml
โ โโโ activity_receive.xml
โ โโโ dialog_mint_token.xml
โ โโโ item_crypto.xml
โ โโโ item_token.xml
โโโ values/ # Resources
โ โโโ colors.xml
โ โโโ strings.xml
โ โโโ styles.xml
โ โโโ themes.xml
โโโ xml/ # Configuration
โโโ apduservice.xml # HCE service config
DirectNfcClient.kt
: Handles sending tokens via NFC- Implements offline transfer handshake protocol
- Manages connection stability and retries
- Chunks data into APDU commands
HostCardEmulatorLogic.kt
: Processes incoming NFC commands- Generates receiver addresses on demand
- Handles chunked data reception
- Saves transfers to SharedPreferences
HostCardEmulatorService.kt
: Android HCE service wrapperApduTransceiver.kt
: Interface for APDU communicationRealNfcTransceiver.kt
: IsoDep NFC implementation
MainActivity.kt
: Main wallet interface- Token list with expandable cards
- Settings menu with test options
- NFC transfer initiation
ReceiveActivity.kt
: Token receiving UI- Shows transfer progress
- Handles offline transfer processing
- Manages success/error states
TokenAdapter.kt
: RecyclerView adapter for token list
UnicitySdkService.kt
: WebView-based SDK bridge- Manages JavaScript interface
- Handles SDK method calls
- Processes offline transfers
unicity-wrapper.js
: JavaScript wrapper for SDKbridge.html
: WebView container for SDK
WalletRepository.kt
: Token storage and SDK operationsToken.kt
: Unicity token data modelapduservice.xml
: HCE service configuration
# Debug build
./gradlew assembleDebug
# Release build
./gradlew assembleRelease
# Run tests
./gradlew test
# Install debug APK
./gradlew installDebug
- Real Unicity tokens: Full SDK integration for genuine blockchain tokens
- Offline transfer protocol: Receiver generates unique address for each transfer
- WebView bridge: Enables TypeScript SDK usage in Android app
- NFC-only approach: Bluetooth pairing proved unreliable on modern Android
- Direct HCE: Eliminates need for backend servers during transfer
- Chunked transfers: Works within NFC APDU size limitations (200 bytes/chunk)
- Connection stability: Added delays between operations to prevent "tag lost" errors
- Persistence layer: SharedPreferences ensure transfers survive app lifecycle changes
- Material Design: Provides modern, accessible user interface
- Android only: iOS has different NFC/Bluetooth capabilities
- Bluetooth availability: Requires Bluetooth LE support
- Background transfers: May be interrupted by battery optimization
- MAC address privacy: Android 6.0+ uses randomized MACs
- Install the app on two NFC-enabled Android devices
- Open the app - it starts with an empty wallet
- Mint a token using the menu (โฎ) โ "Mint a Token"
- Enter a name (e.g., "My First Token")
- Set amount (default: 100)
- Add custom data (optional)
- Wait for minting - includes blockchain commitment (~2-5 seconds)
- Transfer tokens by tapping devices together
- Reset wallet using menu โ "Reset Wallet" if needed
- Android Gradle Plugin: 8.11.0
- Kotlin: 1.9.0
- Gradle: 8.13
- Target SDK: 34 (Android 14)
- Min SDK: 24 (Android 7.0)
- @unicitylabs/state-transition-sdk: 1.4.7-rc.7cec668
- @unicitylabs/commons: 2.4.0-rc.24d6a7c
- Node.js: 20.14.0 (managed by Gradle)
- Webpack: 5.99.9
Version: 2.0.0
Last Updated: July 2025
License: MIT