feat(wallet): token send support with asset picker

- Add UtxoAsset model for native assets in UTXOs
- Update KoiosCardanoClient.getUtxos() to parse asset_list
- Add asset fields to PaymentRequest (policyId, name, quantity)
- DefaultTransactionBuilder: multi-asset tx with Amount.asset()
- Min UTXO: always include 1.5 ADA with token sends (protocol req)
- PaymentEntryPresenter: load available assets, handle selection
- PaymentEntryView: asset picker dropdown when tokens available
- PaymentConfirmation: show token name/quantity instead of ADA
- PaymentProgress: displayAmount field for token sends
- Wire asset data through entire nav flow (FlowNode/Nodes)
- Updated NativeAsset with metadata fields for NFT prep
This commit is contained in:
Kayos 2026-03-29 10:58:17 -07:00
parent af05e51916
commit a57fd79098
20 changed files with 648 additions and 100 deletions

View file

@ -14,6 +14,11 @@ package io.element.android.features.wallet.api
* @property quantity The amount of this asset
* @property displayName Human-readable name if available
* @property fingerprint The asset fingerprint (CIP-14)
* @property imageUrl Resolved image URL (IPFS gateway or HTTPS) for NFTs
* @property decimals Decimal places for fungible tokens (null for NFTs)
* @property ticker Token ticker symbol (e.g., "HOSKY")
* @property description Token/NFT description
* @property isNft True if this is likely an NFT (quantity == 1 with image metadata)
*/
data class NativeAsset(
val policyId: String,
@ -21,6 +26,11 @@ data class NativeAsset(
val quantity: Long,
val displayName: String?,
val fingerprint: String?,
val imageUrl: String? = null,
val decimals: Int? = null,
val ticker: String? = null,
val description: String? = null,
val isNft: Boolean = false,
) {
/**
* Truncated policy ID for display.
@ -36,7 +46,7 @@ data class NativeAsset(
* Display name, falling back to truncated asset name.
*/
val name: String
get() = displayName ?: assetName.takeIf { it.isNotEmpty() }?.let {
get() = displayName ?: ticker ?: assetName.takeIf { it.isNotEmpty() }?.let {
// Try to decode hex to ASCII if it looks printable
try {
val decoded = it.chunked(2).map { hex -> hex.toInt(16).toChar() }.joinToString("")
@ -45,4 +55,22 @@ data class NativeAsset(
it
}
} ?: "Unknown"
/**
* Unit string for this asset (concatenated policyId + assetName).
*/
val unit: String
get() = "$policyId$assetName"
/**
* Format quantity with decimals for display.
*/
fun formatQuantity(): String {
return if (decimals != null && decimals > 0) {
val divisor = Math.pow(10.0, decimals.toDouble())
String.format("%.${decimals}f", quantity / divisor).trimEnd('0').trimEnd('.')
} else {
quantity.toString()
}
}
}

View file

@ -13,12 +13,25 @@ import io.element.android.libraries.matrix.api.core.SessionId
*
* @property fromAddress The sender's Cardano address (Bech32)
* @property toAddress The recipient's Cardano address (Bech32)
* @property amountLovelace The amount to send in lovelace (1 ADA = 1,000,000 lovelace)
* @property amountLovelace The amount of ADA to send in lovelace (1 ADA = 1,000,000 lovelace).
* For token-only sends, this should be the minimum UTXO (~1.5 ADA).
* @property sessionId The Matrix session ID for key retrieval
* @property assetPolicyId Policy ID of the native asset to send (null for ADA-only)
* @property assetName Asset name in hex (null for ADA-only)
* @property assetQuantity Quantity of the native asset to send (null for ADA-only)
*/
data class PaymentRequest(
val fromAddress: String,
val toAddress: String,
val amountLovelace: Long,
val sessionId: SessionId,
)
val assetPolicyId: String? = null,
val assetName: String? = null,
val assetQuantity: Long? = null,
) {
/**
* True if this request includes a native asset (token) send.
*/
val hasAsset: Boolean
get() = assetPolicyId != null && assetName != null && assetQuantity != null && assetQuantity > 0
}

View file

@ -13,10 +13,25 @@ package io.element.android.features.wallet.api
* @property outputIndex The index of this output within the transaction.
* @property amount The amount in lovelace (1 ADA = 1,000,000 lovelace).
* @property address The address holding this UTxO.
* @property assets Native assets (tokens) contained in this UTxO.
*/
data class Utxo(
val txHash: String,
val outputIndex: Int,
val amount: Long,
val address: String,
val assets: List<UtxoAsset> = emptyList(),
)
/**
* Represents a native asset within a UTxO.
*
* @property policyId The minting policy ID (56 hex chars).
* @property assetName The asset name (hex-encoded).
* @property quantity The amount of this asset in the UTxO.
*/
data class UtxoAsset(
val policyId: String,
val assetName: String,
val quantity: Long,
)