Implementing Cryptocurrency Sending in Mobile Wallet
The send screen is one of the most critical in a mobile wallet. Here, the user enters the recipient address, amount, and confirms the transaction. A mistake at any step means irreversible funds loss. So send logic is built with explicit emphasis on validation, protection against accidental confirmation, and transparent fee display.
Address Validation Before Sending
The most common cause of funds loss: invalid or incorrect address. For Ethereum and EVM-compatible networks, checksum validation per EIP-55 is mandatory:
// iOS — web3swift
import web3swift
let address = EthereumAddress(inputString)
guard address != nil else { /* show error */ }
// Android — web3j
import org.web3j.crypto.WalletUtils
val isValid = WalletUtils.isValidAddress(inputAddress)
For Bitcoin, parse the format separately—P2PKH, P2SH, or bech32 (SegWit). BitcoinKit (iOS) and bitcoinj (Android) cover all three. Solana addresses are base58, 32 bytes; SolanaSwift provides PublicKey(string:) with exception on bad input.
EVM addresses in lowercase and checksum—different strings, both valid. Display the checksum version to the user.
Building and Signing Transaction
Send flow:
- User enters address and amount.
- App fetches current
gasPrice/maxFeePerGasviaeth_gasPriceoreth_feeHistory. - Estimates
gasLimitviaeth_estimateGaswith transaction params—don't hardcode 21000 unless plain ETH transfer. - Shows total fee in USD at current rate.
- User confirms—app signs the transaction with private key locally.
- Send signed hex via
eth_sendRawTransaction.
Private key never leaves the device. Storage: iOS Keychain with kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, Android Keystore with KeyPairGenerator and setUserAuthenticationRequired(true) flag.
// Android — signing via web3j
val credentials = Credentials.create(privateKey)
val rawTransaction = RawTransaction.createEtherTransaction(
nonce, gasPrice, gasLimit, toAddress, value
)
val signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials)
val hexValue = Numeric.toHexString(signedMessage)
web3j.ethSendRawTransaction(hexValue).send()
Confirmation UX and Error Protection
Confirmation screen must show full recipient address (not truncated), amount, network, and total fee. "Send" button: not next to "Cancel," place it at the bottom with space. On iOS, UIImpactFeedbackGenerator on successful send reduces anxiety.
After eth_sendRawTransaction, the app gets txHash. Track status via eth_getTransactionReceipt polling every 3–5 seconds or WebSocket eth_subscribe("newHeads"). Show user a link to Etherscan / BscScan / Solscan—mandatory.
Typical Implementation Mistakes
Clipboard address substitution—real attack vector. The app should compare first and last 4 bytes of pasted address with what the user sees on screen; mismatch triggers warning. Some wallets add visual ID to the address (Blockies or Jazzicon).
Nonce management: if the user sent a transaction with pending status, the next one must use nonce + 1. Otherwise, the second transaction hangs or replaces the first. Store nonce locally, sync with eth_getTransactionCount(..., "pending") before each send.
Timeline: 3–5 days: address validation and amount input screen, transaction building, signing, fee display, confirmation screen, status tracking. ERC-20 transfer adds a day for ABI-encoding transfer(address,uint256) data.







