All files created and wired. See git diff for details.
New files (14):
lib/core/models/nft_token.dart+.g.dart(Hive type IDs 30-34)lib/core/services/nft_service.dart,nft_wallet_service.dart,nft_contract_service.dartlib/features/nft/providers/nft_provider.dartlib/features/nft/screens/(4 screens)lib/features/nft/widgets/(4 widgets)
Modified files (8):
supported_chain.dart— addednftContractAddresssecure_storage_service.dart— addednftWalletPrivateKeydeep_link_service.dart—fxfiles://nft-claimhandlerapp.dart— NFT claim deep link subscriptionrouter.dart— 4 new routesfeatured_section.dart— NFTs cardhome_screen.dart—isNftEnabledpass-throughmain.dart—NftService.instance.init()
contracts/core/FulaFileNFT.sol— ERC1155 + GovernanceModule, FULA locking, claim escrowcontracts/governance/interfaces/IFulaFileNFT.sol— Events + errors interfacescripts/deployFulaFileNFT.ts— UUPS proxy deploymenttest/governance/integration/FulaFileNFT.test.ts— 25 tests, all passing
nft_token.dart+.g.dart— Added@HiveField(13) approvalTxHashsupported_chain.dart— Zero address placeholder for both chains (replace after deployment)nft_contract_service.dart— All 7 selectors filled (e90284f4,49db23ce,fbcb5ae7,76ee030a,8c7a63ae,695aaae9,74abfa54), addedpollForReceipt()+parseTokenIdFromReceipt()wallet_service.dart— AddedsendContractTransaction()generic methodnft_service.dart— ImplementedstartMint()full flow (upload → approve → mint → poll → parse)nft_provider.dart— AddedstartMint()notifier wrappernft_detail_screen.dart— Enabled mint button with spinner + wired share button on NftCard
- Replace
0x000...000inSupportedChainwith real proxy addresses
nft_contract_service.dart— AddedparseClaimOfferHash()anddecodeTokenInfo()for reading contract statenft_service.dart— AddedcreateClaimOffer()(send tx → parse linkHash → build deep link → save NftClaimRecord),claimNft()(send claimNFT tx → poll receipt),fetchTokenInfo()(eth_call → decode)nft_provider.dart— AddedcreateClaimOffer()andclaimNft()notifier methodsnft_detail_screen.dart— Share button opens claimer address dialog → creates on-chain offer → shows share sheet with linknft_claim_screen.dart— Fetches token info on load (image, creator, FULA, supply), enables claim button for connected wallets
NftWalletService.signTransaction()— Full secp256k1 signing for derived wallets (requires RLP encoding + keccak256 + secp256k1). Currently only Reown AppKit wallets can mint/claim.
pubspec.yaml— Addedqr_flutter: ^4.1.0andmobile_scanner: ^6.0.0transfer_back_qr_dialog.dart— Replaced placeholder withQrImageView, JSON payload{chain, token, amount, claimer, nonce}, detail summary below QRnft_qr_scanner_screen.dart— Replaced placeholder withMobileScannerwidget, parses JSON payload, fetches token info for FULA display, shows confirmation dialog with transfer-back details, flashlight + camera switch controlsnft_service.dart— AddedtransferBack()flow (encodeTransferBack → sendContractTransaction → pollForReceipt) +markClaimReturned()to update claim statusnft_provider.dart— AddedtransferBack()notifier wrappernft_claim_screen.dart— After claiming: shows "Transfer Back" button (calls contract directly) + QR code button (shows QR for sender verification), transfer-back success statenft_detail_screen.dart— Added QR scan button in app bar (navigates to/nft-qr-scan)
- QR payload signature (secp256k1 signing) — requires
NftWalletService.signTransaction()which is deferred
-
Error handling
nft_service.dart— FULA balance check before approve instartMint()with specific "Required vs Available" messagenft_service.dart— Expired claim check via_fetchClaimOffer()+decodeClaimOffer()before attempting claimnft_service.dart— Already-claimed detection from contract revert messagesnft_service.dart— Not-claim-recipient error handlingnft_service.dart— Reverted transaction detection with user-friendly messagesnft_contract_service.dart— AddeddecodeClaimOffer()for on-chain expiry/claimed status checks
-
Chain switching
nft_service.dart— Added_ensureCorrectChain()helper usingWalletService.switchChain()- Called before
startMint(),claimNft(),transferBack(), andretryMint()
-
Block explorer links
nft_card.dart— "View on Explorer" link for completed mints usingSupportedChain.getTxExplorerUrl()nft_claim_screen.dart— Clickable explorer links for claim tx and transfer-back tx hashes
-
Cloud sync for NFT collections
nft_service.dart— AddedrestoreFromCloud()following WebsiteService pattern (guard checks, preserve in-progress mints, merge by tagId, silent on NoSuchKey)auth_service.dart— AddedNftService.instance.restoreFromCloud()at all 4 auth completion points (auto-login, Google, Apple, reinitialize)
-
Loading states and status indicators
nft_card.dart— Animated pulsing status badge with spinner for in-progress operations (approving/minting/confirming)nfts_browser_screen.dart— Shimmer skeleton loading while tags initializenft_detail_screen.dart— Status message display below mint button showing current step (with spinner)
-
Retry logic for failed operations
nft_service.dart— AddedretryMint()that resumes from last successful step (skips upload if CID exists, skips approval if tx exists, re-polls mint tx)nft_provider.dart— AddedretryMint()notifier wrappernft_card.dart— Retry button (refresh icon) on error-state cardsnft_detail_screen.dart— WiredonRetrycallback to_retryMint()methodnft_claim_screen.dart— Retry button for failed claim attempts
- Replace
0x000...000inSupportedChainwith real proxy addresses
Three fundamental design changes applied:
- Contract: Removed
transferBack(). Addedburn()andburnBatch()overrides that release locked FULA viastorageToken.safeTransfer(). Standard ERC1155 transfers (safeTransferFrom) preserved — FULA stays locked on transfer, only released on burn. - Flutter: Renamed
returnedBack→burned, removed QR scanner/transfer-back dialog, added burn button with confirmation dialog, added transfer button with address input. - Deleted:
transfer_back_qr_dialog.dart,nft_qr_scanner_screen.dart
- Contract:
createClaimOffer()allowsclaimer = address(0)for open claims.claimNFT()skips claimer check when offer has zero-address claimer. - Flutter: "Anyone can claim" toggle (default ON) in claim sheet. Open claims skip address input.
- Tests: 43 tests passing (added open claim + burn tests, removed transferBack + zero-claimer-revert tests).
- NftWalletService: Fixed base64 decode bug, uses web3dart
EthPrivateKeyfor proper secp256k1 derivation, addedsendSignedTransaction()andsendApproveTransaction(). - WalletSource dispatch:
enum WalletSource { internal, external }— all service methods dispatch to eitherNftWalletServiceorWalletService. - Wallet Picker UI: Dialog shown before every NFT operation (mint, claim, burn, transfer). Auto-selects if only one wallet available.
- Settings: NFT Wallet section shows internal wallet address with copy button.
// SPDX-License-Identifier: MIT
// ERC1155 deployed identically on Base (8453) and Skale Europa (2046399126)
// Minting - caller must approve() FULA spend first
mintWithFula(string ipfsCid, uint256 fulaPerNft, uint256 count) -> uint256 firstTokenId
// Claiming - sender creates offer, recipient claims
createClaimOffer(uint256 tokenId, address claimer, uint256 expiresAt) -> bytes32 linkHash
// claimer = address(0) → open claim (anyone), claimer = 0x1234... → targeted
claimNFT(bytes32 linkHash)
// Burn - permanently destroys NFT, releases locked FULA to burner
burn(address account, uint256 id, uint256 value)
burnBatch(address account, uint256[] ids, uint256[] values)
// Transfer - standard ERC1155, FULA stays locked
safeTransferFrom(from, to, id, amount, data) // inherited
// Cancel - sender (after expiry) or admin can cancel stuck offers
cancelClaimOffer(bytes32 linkHash)
// Read functions
getTokenInfo(uint256 tokenId) -> (creator, ipfsCid, fulaAmount, totalSupply)
getClaimOffer(bytes32 linkHash) -> (tokenId, sender, claimer, expiresAt, claimed)
getCreatorTokens(address) -> uint256[]encryptionKey = Argon2id("{provider}:{userId}:{email}", "fula-files-v1") -> 32 bytes (existing)
nftPrivateKey = HMAC-SHA256("nft-wallet", base64Decode(encryptionKey)) -> 32 bytes
nftAddress = EthPrivateKey(nftPrivateKey).address.eip55With0x -> "0x..." (via web3dart + wallet)
Uses web3dart for proper secp256k1 public key derivation and keccak256 address computation.
fxfiles://nft-claim?chain=8453&contract=0x...&token=123&hash=0xabc...