Implementing an In-Game NFT Wallet in a Mobile GameFi App
In GameFi, there's no "just show NFT." In-game wallet is a transaction layer over game mechanics. Player equips sword—on-chain call. Sells character—ERC-721 transfer. Gets reward—token mint. Each action has gas, confirmation delay, and failure risk. Designing UX ignoring this guarantees negative App Store reviews.
Architectural Decisions Define Everything
Custodial vs Non-Custodial
This choice determines everything, decide early.
Custodial (keys on server): simpler for user—no seed phrase, no Lost Access. Requires KYC in some jurisdictions and server trust. For mass-audience mobile game—often preferable.
Non-custodial (keys on device): user controls assets. Keys in Android Keystore or iOS Secure Enclave. Harder onboarding—explain seed phrase to person who came to play.
Embedded wallet (compromise): libraries like Privy, Magic, Dynamic generate wallet via email/social login, private key stored in HSM and reconstructed via MPC. On mobile via WebView or native SDKs. De facto standard for casual GameFi 2024–2025.
L2 and Gas
Main GameFi networks: Immutable X (StarkEx), Polygon, Ronin, Arbitrum, Base. Mainnet Ethereum gas unacceptable for game microtransactions. Immutable X—gas-free for NFT transfers. Polygon—<0.001 MATIC per transaction.
Local NFT Data Storage
Fetch NFT metadata via tokenURI (ERC-721) or IPFS gateway. Cache locally—Room / Core Data. NFT images—separate cache via Glide (Android) / Kingfisher (iOS) with IPFS URL.
@Entity(tableName = "nft_items")
data class NftItem(
@PrimaryKey val tokenId: String,
val contractAddress: String,
val name: String,
val imageUrl: String,
val metadata: String, // JSON
val isEquipped: Boolean = false,
val cachedAt: Long = System.currentTimeMillis()
)
Translate IPFS URLs like ipfs://QmXxx... via public or private gateway: https://ipfs.io/ipfs/QmXxx.... Private gateway more stable for production.
Transaction UX—Most Complex
Blockchain transaction isn't HTTP request. It's sent, sits in mempool, waits for block inclusion (10 sec on Polygon, instant on Ronin). User continues playing. Can't block game on confirmation wait.
Pattern: Optimistic UI + background transaction monitoring.
// iOS — optimistic update
func equipItem(_ nft: NftItem) {
// Immediately update UI
store.dispatch(EquipAction(tokenId: nft.tokenId))
// Background transaction
Task {
do {
let txHash = try await walletService.equip(nft)
await monitor(txHash: txHash, onFail: {
store.dispatch(UnequipAction(tokenId: nft.tokenId))
showError("Transaction failed")
})
} catch {
store.dispatch(UnequipAction(tokenId: nft.tokenId))
}
}
}
Monitor via WebSocket or JSON-RPC eth_getTransactionReceipt polling every 3–5 sec. On Immutable X—via IMX REST API. On Polygon—Alchemy or Infura WebSocket subscription.
Gas Estimation and Fee UI
For non-custodial, show gas before confirmation. eth_estimateGas → multiply by gasPrice → convert to USD via price feed (CoinGecko API or Chainlink oracle). Never show native token without USD equivalent—user doesn't know 0.0023 MATIC's cost.
Private Key Security
If non-custodial: private key only in Android Keystore or iOS Secure Enclave. Never SharedPreferences, UserDefaults, or local DB plain. Transaction signing happens inside Keystore—key never leaves secure storage.
// Android — sign via Keystore
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val privateKey = keyStore.getKey(KEY_ALIAS, null) as PrivateKey
val signature = Signature.getInstance("SHA256withECDSA")
signature.initSign(privateKey)
signature.update(transactionHash)
val signedBytes = signature.sign()
NFT Inventory and Filtering
Game inventory with 200+ NFT must filter instantly—locally, not via API. Room query with filters by type, rarity, equipped status. LazyColumn (Jetpack Compose) or UICollectionView with DiffableDataSource for smooth scroll.
Sort by rarity: rarity_score attribute from NFT metadata, calculated from rare trait weights. Implement client-side or get from server.
App Store / Google Play and Web3
Apple accepts NFT apps but forbids NFT purchases via third-party payment without App Store commission (30%). Allowed: show NFT, transfer between wallets, use in game. Forbidden: sell for fiat without In-App Purchase. For marketplace—WebView only.
Google Play 2023 explicitly allows NFT apps, same payment rules apply to fiat sales.
Work Scope
- Wallet type selection and integration (embedded / non-custodial)
- Smart contract integration (ERC-721 / ERC-1155 on target L2)
- Local NFT metadata and image cache
- Optimistic UI for game transactions
- Background transaction monitoring with rollback
- Secure key storage (Keystore / Secure Enclave)
- Gas estimation and USD conversion
- NFT inventory with filtering and rarity sorting
Timeline
Custodial wallet with NFT viewing and basic transfers: 1–2 weeks. Full-featured non-custodial with game mechanics, optimistic UI, marketplace WebView: 3–5 weeks. Cost depends on chosen network, smart contract integration volume, and security requirements.







