Purpose: Multi-wallet management, BCH key management, balance queries, transaction building, Cash Account integration (Phase 1+)
Complexity: Medium - Multi-wallet architecture + standard BCH operations + covenant integration
Implementation Status:
✅ Multi-wallet management (August 2-3, 2026)
✅ Wallet switching UI (August 3, 2026)
✅ Room database + WalletManager (August 2, 2026)
✅ Role-based wallet labels (SENDER, RECIPIENT, MERCHANT - testing convenience only, not enforced)
✅ Balance queries per wallet (Electrum scripthash)
✅ Test wallet seeding (development mode)
✅ WIF private key import (July 28)
⏳ Send transaction flow (August 3, 2026 - in progress)
⏳ Receive screen (placeholder UI for now - QR code generation deferred to Phase 1+)
⏳ Transaction history list
⏳ HD wallet / BIP39 seed phrases (Phase 0 - needed for production)
📋 RFID tag private key storage (Phase 1+)
📋 Cash Account registration (Phase 1+)
The Wallet component is the foundation of Asgaya’s 3-tab architecture (Wallet, Remittances, Marketplace). It provides:
| Key management: ✅ WIF import (done) | ⏳ HD wallets with seed phrase backup (Phase 0) | 📋 RFID tags for private key storage (Phase 1+) |
| Transaction building: ⏳ Send flow (in progress) | ⏳ Receive screen (planned) |
name#number on-chain, resolve to BCH address (Phase 1+)Architecture principle: User controls their own keys. No custody, no backend server holds funds.
Reference: Asgaya Trinity - The 3-part covenant architecture (create, send, claim)
Status: ✅ Implemented August 2-3, 2026
Test: 3 wallets (Sender, Recipient, Merchant) with instant balance switching
Database: Room persistence with Flow-based reactive updates
Asgaya supports multiple wallets on a single device to enable rapid testing and role-based workflows:
Design goals:
Data model:
@Entity(tableName = "wallets")
data class Wallet(
@PrimaryKey val id: String, // UUID
val label: String, // "Sender (Alice)"
val role: WalletRole, // SENDER, RECIPIENT, MERCHANT
val source: WalletSource, // HD_DERIVED or IMPORTED_KEY
val wif: String, // Encrypted WIF (AES-256)
val address: String, // BCH CashAddr
val publicKey: String, // Hex-encoded public key
val isActive: Boolean = false // Only one active at a time
)
enum class WalletRole {
SENDER, // 📤 Creates covenants, sends remittances
RECIPIENT, // 📥 Claims covenants, receives payments
MERCHANT, // 🏪 Cashes out covenants, provides liquidity
ORACLE, // 🔮 (Future) Price/timestamp signing
CUSTOM // User-defined
}
sealed class WalletSource {
data class HDDerived(val accountIndex: Int) // From BIP39 seed (Phase 1+)
data class ImportedKey(val wif: String) // From covenant-params (Phase 0)
}
Room database schema:
CREATE TABLE wallets (
id TEXT PRIMARY KEY,
label TEXT NOT NULL,
role TEXT NOT NULL, -- "SENDER", "RECIPIENT", "MERCHANT"
source TEXT NOT NULL, -- "HD_DERIVED" or "IMPORTED_KEY"
wif TEXT NOT NULL, -- Encrypted
address TEXT NOT NULL UNIQUE, -- CashAddr format
publicKey TEXT NOT NULL,
isActive INTEGER NOT NULL -- 0 or 1 (SQLite boolean)
);
CREATE INDEX idx_wallets_active ON wallets(isActive);
CREATE INDEX idx_wallets_role ON wallets(role);
Why this design:
See: RS081 Multi-Wallet Management Patterns for design rationale
Purpose: Central wallet management with reactive updates
Key operations:
class WalletManager(context: Context) {
private val db = AppDatabase.getDatabase(context)
private val walletDao = db.walletDao()
// Reactive active wallet (updates UI automatically)
val activeWallet: Flow<Wallet?> = walletDao.getActiveWalletFlow()
// Create new wallet (HD or imported)
suspend fun createWallet(label: String, role: WalletRole): Wallet
// Import existing WIF (for test wallets)
suspend fun importWallet(label: String, role: WalletRole, wif: String): Wallet
// Switch active wallet (updates UI via Flow)
suspend fun switchWallet(walletId: String)
// Get all wallets (for selector UI)
suspend fun getAllWallets(): List<Wallet>
// Seed test wallets (development only)
suspend fun seedTestWallets()
}
Reactive pattern:
// In MainActivity/ViewModel
walletManager.activeWallet.collect { wallet ->
wallet?.let {
// UI updates automatically when wallet switches
binding.textWalletLabel.text = it.label
binding.textWalletAddress.text = it.address
queryBalance(it.address) // Refresh balance
}
}
Why Flow instead of LiveData:
Pattern: Top-center selector (MetaMask/Trust Wallet style)
Visual design:
┌─────────────────────────────────┐
│ [☰] Asgaya 💼 Sender ▼ [⚙️] │ ← Tap to switch
├─────────────────────────────────┤
│ Balance: 0.05234 BCH │
│ ≈ €52.34 │
└─────────────────────────────────┘
Selector dialog:
┌─────────────────────────────────┐
│ Select Wallet │
├─────────────────────────────────┤
│ ✓ 📤 Sender │ ← Active (checkmark)
│ bchtest:qrw5nu... │
│ 0.052 BCH │
│ │
│ 📥 Recipient (Isabel) │
│ bchtest:qq2uxg... │
│ 0.007 BCH │
│ │
│ 🏪 Merchant (Bob) │
│ bchtest:qz4lla... │
│ 0.000 BCH │
└─────────────────────────────────┘
Role icons:
Implementation:
// MainActivity.kt
binding.textWalletLabel.setOnClickListener {
showWalletSelectorDialog()
}
private fun showWalletSelectorDialog() {
lifecycleScope.launch {
val wallets = walletManager.getAllWallets()
val activeWallet = walletManager.getActiveWallet()
val items = wallets.map { wallet ->
val checkmark = if (wallet.isActive) "✓ " else " "
val icon = when (wallet.role) {
WalletRole.SENDER -> "📤"
WalletRole.RECIPIENT -> "📥"
WalletRole.MERCHANT -> "🏪"
WalletRole.ORACLE -> "🔮"
WalletRole.CUSTOM -> "💼"
}
"$checkmark$icon ${wallet.label}\n ${wallet.address.take(20)}..."
}.toTypedArray()
AlertDialog.Builder(this@MainActivity)
.setTitle("Select Wallet")
.setItems(items) { _, which ->
lifecycleScope.launch {
walletManager.switchWallet(wallets[which].id)
}
}
.show()
}
}
Purpose: Auto-populate test wallets for rapid development
Trigger: First launch or empty wallet database
Implementation:
// WalletManager.kt
suspend fun seedTestWallets() {
if (walletDao.getWalletCount() > 0) return // Already seeded
// Import Sender wallet (from covenant-params-v2.3-testnet3.json)
importWallet(
label = "Sender",
role = WalletRole.SENDER,
wif = "cSAXMqnRNDxPVuFviX5EPHRFPoB7zdmXhrgJu9zNu9cuDnGsvYnA"
)
// Import Recipient wallet (Isabel)
importWallet(
label = "Recipient (Isabel)",
role = WalletRole.RECIPIENT,
wif = "cRzbt6ZpcgyWLPFdogrddKEH7gYWHBnLaFR9D1KKZEdwxYRd7z9z"
)
// Import Merchant wallet (Bob)
importWallet(
label = "Merchant (Bob)",
role = WalletRole.MERCHANT,
wif = "cMxUSPCEYvF62E4aSbz3y3uRdqzW9vN8w5YGTDxZVFxKiKzK9vEF"
)
// Set sender as default active
val sender = walletDao.getWalletByRole(WalletRole.SENDER)
sender?.let { switchWallet(it.id) }
}
Production: Test wallet seeding disabled (debug builds only)
See: /covenant-tests/covenant-params-v2.3-testnet3.json for source WIFs
Challenge: Electrum protocol uses scripthash, not address
Solution: Convert CashAddr → scripthash → query Electrum
Scripthash calculation:
fun addressToScripthash(address: String): String {
// 1. Decode CashAddr to get payload
val decoded = CashAddressHelper.decode(address)
// 2. Build P2PKH scriptPubKey
val scriptPubKey = buildP2PKHScript(decoded.hash)
// Format: OP_DUP OP_HASH160 <20-byte-hash> OP_EQUALVERIFY OP_CHECKSIG
// 3. SHA256 hash (SINGLE, not double!)
val hash = MessageDigest.getInstance("SHA-256")
.digest(scriptPubKey)
// 4. Reverse bytes (Electrum protocol requirement)
val reversed = hash.reversedArray()
// 5. Hex encode
return reversed.joinToString("") { "%02x".format(it) }
}
Critical bug fix (August 3):
Electrum query:
suspend fun queryBalance(address: String): Double {
val scripthash = addressToScripthash(address)
val request = JSONObject().apply {
put("id", 1)
put("method", "blockchain.scripthash.get_balance")
put("params", JSONArray().apply {
put(scripthash)
})
}
val response = electrumClient.query(request)
val confirmed = response.getJSONObject("result")
.getLong("confirmed")
return confirmed / 100_000_000.0 // Satoshis to BCH
}
Performance: Balance updates instantly when switching wallets (< 200ms on testnet)
See: Electrum Protocol Documentation for scripthash spec
Rationale: Separate basic wallet operations from covenant-specific features
Tab structure:
┌─────────┬─────────────┬──────────────┐
│ 💰 │ 🌎 │ 💹 │
│ Wallet │ Remittances │ Marketplace │
└─────────┴─────────────┴──────────────┘
Tab 1: Wallet (Universal)
Tab 2: Remittances (Active covenant users)
Tab 3: Marketplace (Passive liquidity providers)
Why this works:
Implementation: BottomNavigationView with 3 fragments (TabLayout pattern)
Note: Tab 2 (Remittances) and Tab 3 (Marketplace) are not yet documented in separate files. They are planned for Phase 0+ but UI/UX design is still in progress.
Status: ⏳ Phase 0 (needed for production) - Currently using imported WIF keys for testing
What: Hierarchical Deterministic wallet - one seed phrase generates infinite addresses
Protocol:
m/44'/145'/0'/0/n (BCH = coin type 145)Pseudocode:
function generateWallet():
seed_phrase = generateBIP39Mnemonic(12 words)
master_key = deriveFromSeed(seed_phrase)
return {seed_phrase, master_key}
function deriveAddress(master_key, index):
path = "m/44'/145'/0'/0/" + index
private_key = derivePath(master_key, path)
public_key = getPublicKey(private_key)
address = pubKeyToAddress(public_key) // P2PKH format
return {address, private_key}
Security:
Error handling:
What: Register name#number on BCH blockchain via OP_RETURN
Bizum compatibility challenge:
The standard CashAccount format uses # as separator (Elena#142), but Bizum’s “concepto” field only allows alphanumeric characters (no special symbols). Two potential solutions:
Display format adaptation: Store on-chain as Elena#142 (standard), but display/accept as Elena142 in Bizum UI. App automatically converts: Elena142 → Elena#142 for blockchain lookup.
Alternative separator: Use a letter separator on-chain: ElenaX142 or Elena0142 (but this breaks CashAccount standard compatibility - not recommended).
Recommendation: Use solution #1 (format adaptation in UI layer). The # is part of the CashAccount protocol spec, but Asgaya can handle the conversion transparently when interfacing with Bizum.
Example flow:
Elena142Elena + 142 → queries blockchain for Elena#14201010101 [name_hash] [address]Pseudocode:
function registerCashAccount(name, address):
// Check if name#1 exists
existing = queryCashAccount(name + "#1")
if existing:
// Find next available number
number = findNextNumber(name)
else:
number = 1
cash_account = name + "#" + number
// Build OP_RETURN transaction
op_return_data = buildCashAccountOP_RETURN(name, address)
tx = createTransaction({
outputs: [
{type: "OP_RETURN", data: op_return_data},
{type: "change", address: my_change_address}
]
})
broadcast(tx)
return cash_account
On-chain format:
OP_RETURN
0x01010101 // Cash Account protocol identifier
[name_bytes] // UTF-8 encoded name
[address_bytes] // BCH address (21 bytes)
Cost: ~€0.01 (single OP_RETURN transaction)
Error handling:
What: Lookup Elena#142 → BCH address
Protocol:
Pseudocode:
function resolveCashAccount(cash_account):
// Check local cache first
cached = getCachedAddress(cash_account)
if cached and not_expired(cached):
return cached.address
// Query blockchain
name, number = parseCashAccount(cash_account) // "Elena#142" → "Elena", 142
tx = electrumQuery("blockchain.transaction.get_cash_account", {
name: name,
number: number
})
if not tx:
return null // Cash Account not found
address = parseCashAccountOP_RETURN(tx.op_return_data)
// Cache for 24 hours
cacheAddress(cash_account, address, ttl=86400)
return address
Electrum query pattern:
{
"method": "blockchain.transaction.get_cash_account",
"params": {
"name": "Elena",
"number": 142
}
}
Response:
{
"tx_hash": "abc123...",
"op_return_data": "01010101456c656e61...",
"address": "bitcoincash:qp..."
}
Error handling:
Implementation Status: ✅ WebView + CashScript bridge (August 1-2, 2026)
Production Covenant: v2.6 with 5 functions (claim, merchantCashout, refund, abort, sellerRecoverBuffer)
Validation: 7 successful testnet3 transactions (4 claims, 3 refunds)
Reference: WebView Covenant Bridge | Version History
Definition: BCH script that locks funds until specific conditions met
Asgaya use case: Guarantee the value of a remittance during the time between when the sender creates the covenant and when the recipient claims or cashes it out.
How it works:
Three parties:
Current approach (August 2026): Kotlin ↔ JavaScript bridge using CashScript SDK
Architecture:
CovenantWebView.kt (Kotlin)
↕ JavascriptInterface
covenant-bridge.html (loads CashScript bundle)
↕ import
covenant-operations.js (CashScript SDK)
Usage example (claim covenant):
val covenantWebView = CovenantWebView(this)
val params = JSONObject().apply {
put("covenantParams", covenantParamsJson)
put("oracleSig", oracleSignatureJson)
put("recipientWIF", wallet.wif)
put("recipientAddress", wallet.address)
put("fulcrumHost", FULCRUM_HOST)
put("fulcrumPort", FULCRUM_PORT)
}
covenantWebView.claimCovenant(params.toString()) { txid ->
// Success! Covenant claimed
Log.d("Covenant", "Claimed: $txid")
}
What happens under the hood:
Performance:
Security:
Why this works:
Migration path:
See: WebView Covenant Bridge for complete technical documentation
On-chain states (what the blockchain sees):
State transitions:
Created → Funded (funding transaction broadcasts)
Funded → Spent via claim (recipient/merchant claims with oracle signature)
Funded → Spent via refund (sender refunds - permissionless, anytime)
Key architectural point: The v2.5 covenant has a permissionless refund function. The sender can refund anytime - there are no time or price checks enforced on-chain. “Expired” and “price drop” are reasons the client auto-refunds, not separate covenant states.
Client-side monitoring (what the app tracks):
fun getCovenantClientState(covenant_id, created_at, expiry_time, initial_price):
// Query blockchain for covenant UTXO
utxo = electrumQuery("blockchain.utxo.get_info", covenant_id)
if not utxo:
return "not_funded" // Covenant created but not yet funded
if utxo.spent:
// Analyze spending transaction
spending_tx = electrumQuery("blockchain.transaction.get", utxo.spending_txid)
return if (isClaimTransaction(spending_tx)) "claimed" else "refunded"
// UTXO exists and unspent - check client-side triggers
val current_time = now()
val current_price = getBCHPrice()
if (current_time > expiry_time):
return "should_refund_expired" // Client trigger: auto-refund
if (priceDropped(initial_price, current_price) > 7):
return "should_refund_price_drop" // Client trigger: auto-refund
return "funded_active" // Waiting for claim
Client enforces fairness: The app monitors covenant state and triggers auto-refund when appropriate (expiry or price drop), but the refund transaction itself is just a standard covenant spend - the blockchain doesn’t enforce the conditions.
See: Two-Layer Architecture for detailed explanation of covenant vs client separation
Status: ⏳ In progress (August 3, 2026 - MVP ~80% complete)
Implemented: 3-step wizard, contact selection, amount entry, fee display
TODO: UTXO selection, transaction signing, broadcast
3-step wizard pattern (common in BCH wallets):
Step 1: Address Input
↓
Step 2: Amount Entry
↓
Step 3: Review & Confirm
↓
Broadcast & Success
Philosophy: Remittances = repeat sends. Contacts are more important than QR codes.
UI priority:
Implementation:
// SendAddressFragment.kt
binding.buttonSelectContact.setOnClickListener {
val intent = Intent(requireActivity(), ContactsActivity::class.java).apply {
putExtra("SELECT_MODE", true) // Return contact instead of listing
}
startActivityForResult(intent, REQUEST_SELECT_CONTACT)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_SELECT_CONTACT && resultCode == RESULT_OK) {
val contact = data?.getParcelableExtra<Contact>("SELECTED_CONTACT")
contact?.let {
binding.editTextAddress.setText(it.address)
// Auto-advance to Step 2 (amount entry)
(activity as SendActivity).nextStep()
}
}
}
Validation:
bitcoincash: or bchtest: prefix)See: Paytaca Wallet for contact-first reference implementation
Features:
UI elements:
// SendAmountFragment.kt
binding.editTextAmount.addTextChangedListener { text ->
val bch = text.toString().replace(",", ".").toDoubleOrNull() ?: 0.0
// Update fiat preview
val fiat = bch * currentBchPrice
binding.textFiatPreview.text = "≈ €%.2f".format(fiat)
// Update fee estimate
val fee = estimateFee(numInputs = 1, numOutputs = 2)
binding.textNetworkFee.text = "Network fee: ~%.8f BCH (1 sat/byte)".format(fee)
// Validate sufficient balance
val total = bch + fee
if (total > walletBalance) {
binding.textError.text = "Insufficient funds (need %.8f BCH)".format(total)
}
}
binding.buttonSendMax.setOnClickListener {
val fee = estimateFee(1, 1) // Max = 1 output (no change)
val maxAmount = walletBalance - fee
binding.editTextAmount.setText("%.8f".format(maxAmount))
}
Locale handling:
Fee calculation:
tx_size = (num_inputs × 148) + (num_outputs × 34) + 10
fee = tx_size × 1 sat/byte
Example:
Why no fee slider:
Display:
Implementation:
// SendConfirmFragment.kt
override fun onResume() {
super.onResume()
displayReview() // Refresh on every show (prevents stale data)
}
private fun displayReview() {
val sendData = (activity as SendActivity).sendData
binding.textRecipient.text = sendData.recipientAddress
binding.textRecipientName.text = sendData.contactName ?: "Unknown"
binding.textAmount.text = "%.8f BCH".format(sendData.amount)
binding.textAmountFiat.text = "≈ €%.2f".format(sendData.amount * currentBchPrice)
val fee = estimateFee(1, 2)
binding.textFee.text = "%.8f BCH".format(fee)
val total = sendData.amount + fee
binding.textTotal.text = "%.8f BCH".format(total)
}
Confirmation action (NOT YET IMPLEMENTED - placeholder shown below):
The following code is a placeholder. Transaction building (UTXO selection, signing, broadcast) is not yet implemented:
binding.buttonSend.setOnClickListener {
// TODO: Implement transaction building
// 1. Select UTXOs (coin selection)
// 2. Build transaction
// 3. Sign with active wallet's WIF
// 4. Broadcast via Electrum
// 5. Show success message with TXID
Toast.makeText(context, "Transaction building not yet implemented", Toast.LENGTH_SHORT).show()
}
Current status: UI complete (August 3), transaction building pending (estimated 2-4 hours)
Required operations:
Implementation plan:
suspend fun buildAndBroadcastTransaction(
recipientAddress: String,
amount: Double,
walletWIF: String
): String {
// 1. Get UTXOs for active wallet
val utxos = electrumClient.getUTXOs(activeWallet.address)
// 2. Select coins (see UTXO Management section below)
val selection = selectCoins(utxos, amount, feeRate = 1)
// 3. Build transaction
val tx = buildTransaction(
inputs = selection.inputs,
outputs = listOf(
TxOutput(recipientAddress, amount),
TxOutput(changeAddress, selection.change)
)
)
// 4. Sign transaction
val signedTx = signTransaction(tx, walletWIF)
// 5. Broadcast
val txid = electrumClient.broadcast(signedTx)
return txid
}
Dependency: bitcoinj-core library (already in build.gradle)
Estimated completion: 2-4 hours (UTXO selection + signing logic)
Status: ⏳ Placeholder UI (Phase 0) - Full QR code generation deferred to Phase 1+
Complexity: Low (copy button + address display)
Phase 0 MVP (placeholder):
Phase 1+ features (deferred):
bitcoincash:address?amount=0.001)Why placeholder approach:
Status: ⏳ Planned (Phase 0)
Complexity: Medium (Electrum queries + UI list)
Must have:
Nice to have (Phase 1+):
┌─────────────────────────────────┐
│ Transaction History │
├─────────────────────────────────┤
│ ↓ Received │ ← Green arrow
│ Isabel (Recipient) │ ← Contact name
│ +0.007 BCH (€2.45) │ ← Amount + fiat
│ 2 hours ago │ ← Timestamp
│ │
│ ↑ Sent │ ← Red arrow
│ Merchant (Bob) │ ← Contact name
│ -0.001 BCH (€0.35) │ ← Amount + fiat
│ Yesterday at 14:32 │ ← Timestamp
│ │
│ ↓ Received │
│ bchtest:qrw5nu... │ ← Unknown (no contact)
│ +0.05 BCH (€17.50) │
│ 3 days ago │
└─────────────────────────────────┘
Query Electrum for transaction history:
suspend fun getTransactionHistory(address: String): List<Transaction> {
val scripthash = addressToScripthash(address)
val request = JSONObject().apply {
put("id", 1)
put("method", "blockchain.scripthash.get_history")
put("params", JSONArray().apply {
put(scripthash)
})
}
val response = electrumClient.query(request)
val historyArray = response.getJSONArray("result")
return historyArray.map { item ->
val txHash = item.getString("tx_hash")
val height = item.getInt("height")
val fee = item.optLong("fee", 0)
// Fetch full transaction details
val txDetails = electrumClient.getTransaction(txHash)
Transaction(
txid = txHash,
blockHeight = height,
fee = fee,
inputs = parseInputs(txDetails),
outputs = parseOutputs(txDetails),
timestamp = getBlockTimestamp(height)
)
}
}
Display in RecyclerView:
// TransactionHistoryFragment.kt
lifecycleScope.launch {
val wallet = walletManager.getActiveWallet()
val transactions = electrumClient.getTransactionHistory(wallet.address)
val adapter = TransactionAdapter(transactions, wallet.address)
binding.recyclerViewTransactions.adapter = adapter
}
// TransactionAdapter.kt
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val tx = transactions[position]
val myAddress = walletAddress
// Determine if incoming or outgoing
val isIncoming = tx.outputs.any { it.address == myAddress }
val amount = if (isIncoming) {
tx.outputs.filter { it.address == myAddress }.sumOf { it.value }
} else {
tx.outputs.filter { it.address != myAddress }.sumOf { it.value }
}
// Set UI
holder.iconDirection.setImageResource(
if (isIncoming) R.drawable.ic_arrow_down_green else R.drawable.ic_arrow_up_red
)
holder.textAmount.text = "%s%.8f BCH".format(
if (isIncoming) "+" else "-",
amount
)
holder.textTimestamp.text = formatRelativeTime(tx.timestamp)
// Resolve contact name or show address
val counterpartyAddress = if (isIncoming) {
tx.inputs.firstOrNull()?.address
} else {
tx.outputs.firstOrNull { it.address != myAddress }?.address
}
holder.textContact.text = resolveContactOrAddress(counterpartyAddress)
}
Performance optimization:
Estimated implementation: 4-6 hours (Electrum queries + UI + caching)
Problem: Wallet has multiple UTXOs (previous transactions). Which to spend for new transaction?
Strategy: Minimize fees while avoiding dust
Pseudocode:
function selectCoins(target_amount, fee_rate):
utxos = getWalletUTXOs()
// Sort by value (largest first)
utxos.sort(by: value, desc)
selected = []
total = 0
for utxo in utxos:
selected.push(utxo)
total += utxo.value
estimated_fee = calculateFee(selected.length, 2, fee_rate) // 2 outputs (payment + change)
if total >= target_amount + estimated_fee:
return {
inputs: selected,
change: total - target_amount - estimated_fee
}
return null // Insufficient funds
Alternative strategy (privacy): Random selection to avoid linking UTXOs
Problem: BCH fees are ~1 satoshi/byte. How much to pay?
Approach: Query Electrum for current fee rate
Pseudocode:
function estimateFee(num_inputs, num_outputs):
fee_rate = electrumQuery("blockchain.estimatefee") // satoshis per byte
// Estimate transaction size
tx_size = (num_inputs * 148) + (num_outputs * 34) + 10
fee = tx_size * fee_rate
return fee
Typical BCH transaction:
Error handling:
Problem: María sends €100, but her UTXO is €150. Where does €50 go?
Solution: Generate new change address (privacy)
Pseudocode:
function createTransaction(recipient, amount):
coins = selectCoins(amount, fee_rate)
if not coins:
return error("Insufficient funds")
change_address = deriveAddress(master_key, next_unused_index)
change_amount = coins.change
tx = {
inputs: coins.inputs,
outputs: [
{address: recipient, value: amount},
{address: change_address, value: change_amount} // Change back to María
]
}
return tx
Privacy note: Never reuse addresses. Each transaction uses new change address.
if total_balance < amount + fee:
show_error("Insufficient funds. You have €X, need €Y")
suggest_action("Receive BCH first or reduce amount")
try:
tx_id = broadcast(tx)
catch NetworkError:
queue_for_retry(tx)
show_message("Offline. Transaction queued.")
if tx_rejected_with("double-spend"):
// Another transaction already spent these UTXOs
refresh_wallet_state()
show_error("Transaction conflict. Wallet refreshed, try again.")
if covenant.expiry - now() < 1_hour:
show_warning("Covenant expires in " + time_remaining(covenant.expiry))
suggest_action("Recipient should claim soon or funds return to sender")
Uses:
Used by:
Status: Phase 0 - Active execution (multi-wallet ✅, covenant integration ✅, send flow ⏳)
Updated: 2026-08-03
Complexity: Medium (multi-wallet management + standard BCH operations + covenant integration)
References: WebView Covenant Bridge | Version History | Asgaya Trinity | RS081 Multi-Wallet Patterns
—
| 🏠 Home | ↑ Android App | 📖 Glossary |