feat(wallet): transaction builder, UTXO selection, and status poller (Task 4)
## What's new ### API module additions - ProtocolParameters: data class for fee calculation params - PaymentRequest: transaction request model - SignedTransaction: signed transaction result model - TransactionBuilder: interface for building/signing transactions - PaymentStatusPoller: interface for polling tx confirmation ### CardanoClient updates - Added getProtocolParameters() to interface - Implemented in KoiosCardanoClient with retry logic ### Implementation - DefaultTransactionBuilder: builds and signs transactions using cardano-client-lib - Largest-first UTXO selection - Fee calculation via protocol parameters - Min UTXO validation (1 ADA minimum) - Secure key handling (zeroed after use) - DefaultPaymentStatusPoller: polls Koios for tx confirmation - 10s polling interval, 60 attempts max (~10 minutes) - Emits TxStatus.PENDING -> CONFIRMED/FAILED flow ### Test module - FakeTransactionBuilder: configurable success/failure responses - FakePaymentStatusPoller: configurable status sequences - Updated FakeCardanoClient with getProtocolParameters() ### Unit tests - TransactionBuilderTest: UTXO selection, fee calculation, error handling - PaymentStatusPollerTest: polling behavior, error recovery
This commit is contained in:
parent
19637833a6
commit
9439f5a227
15 changed files with 1298 additions and 0 deletions
|
|
@ -44,4 +44,13 @@ interface CardanoClient {
|
|||
* @return Current [TxStatus] of the transaction
|
||||
*/
|
||||
suspend fun getTxStatus(txHash: String): Result<TxStatus>
|
||||
|
||||
/**
|
||||
* Get the current protocol parameters from the network.
|
||||
*
|
||||
* Protocol parameters are needed for fee calculation and transaction building.
|
||||
*
|
||||
* @return Current [ProtocolParameters] from the latest epoch
|
||||
*/
|
||||
suspend fun getProtocolParameters(): Result<ProtocolParameters>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Sulkta Coop.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package io.element.android.features.wallet.api
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
/**
|
||||
* A request to build and sign a Cardano payment transaction.
|
||||
*
|
||||
* @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 sessionId The Matrix session ID for key retrieval
|
||||
*/
|
||||
data class PaymentRequest(
|
||||
val fromAddress: String,
|
||||
val toAddress: String,
|
||||
val amountLovelace: Long,
|
||||
val sessionId: SessionId,
|
||||
)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Sulkta Coop.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package io.element.android.features.wallet.api
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Interface for polling transaction confirmation status.
|
||||
*/
|
||||
interface PaymentStatusPoller {
|
||||
/**
|
||||
* Polls for transaction confirmation status.
|
||||
*
|
||||
* Emits [TxStatus] changes as a Flow:
|
||||
* - Initially PENDING
|
||||
* - CONFIRMED when transaction is in a block
|
||||
* - FAILED if confirmation times out or error occurs
|
||||
*
|
||||
* Polling behavior:
|
||||
* - Poll every 10 seconds
|
||||
* - Maximum 60 attempts (~10 minutes total)
|
||||
* - Stops when status changes from PENDING
|
||||
*
|
||||
* @param txHash The transaction hash to poll
|
||||
* @return Flow of [TxStatus] changes
|
||||
*/
|
||||
fun pollUntilConfirmed(txHash: String): Flow<TxStatus>
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Sulkta Coop.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package io.element.android.features.wallet.api
|
||||
|
||||
/**
|
||||
* Cardano protocol parameters needed for transaction building and fee calculation.
|
||||
*
|
||||
* These parameters are set via governance and determine transaction costs
|
||||
* and constraints on the network.
|
||||
*
|
||||
* @property minFeeA The linear fee coefficient (lovelace per byte)
|
||||
* @property minFeeB The constant fee (base fee in lovelace)
|
||||
* @property maxTxSize Maximum transaction size in bytes
|
||||
* @property utxoCostPerByte Cost in lovelace per byte of UTXO storage (for min UTXO calculation)
|
||||
*/
|
||||
data class ProtocolParameters(
|
||||
val minFeeA: Long,
|
||||
val minFeeB: Long,
|
||||
val maxTxSize: Int,
|
||||
val utxoCostPerByte: Long,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Sulkta Coop.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package io.element.android.features.wallet.api
|
||||
|
||||
/**
|
||||
* A signed Cardano transaction ready for submission.
|
||||
*
|
||||
* @property txCbor The CBOR-encoded signed transaction as a hex string
|
||||
* @property txHash The transaction hash (for tracking)
|
||||
* @property fee The transaction fee in lovelace
|
||||
* @property actualAmount The actual amount sent (may differ slightly from requested due to min UTXO rules)
|
||||
*/
|
||||
data class SignedTransaction(
|
||||
val txCbor: String,
|
||||
val txHash: String,
|
||||
val fee: Long,
|
||||
val actualAmount: Long,
|
||||
)
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Sulkta Coop.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package io.element.android.features.wallet.api
|
||||
|
||||
/**
|
||||
* Interface for building and signing Cardano transactions.
|
||||
*
|
||||
* The implementation handles:
|
||||
* - UTXO selection (largest-first coin selection)
|
||||
* - Fee calculation based on protocol parameters
|
||||
* - Change output calculation
|
||||
* - Transaction signing with the spending key
|
||||
*
|
||||
* ## Error handling
|
||||
* The following errors may be returned:
|
||||
* - [CardanoException.InsufficientFundsException] - Not enough ADA in wallet
|
||||
* - [CardanoException.InvalidAddressException] - Invalid address format
|
||||
* - [CardanoException.ApiException] - Various API/build errors
|
||||
*/
|
||||
interface TransactionBuilder {
|
||||
/**
|
||||
* Builds and signs a payment transaction.
|
||||
*
|
||||
* This method will:
|
||||
* 1. Fetch UTXOs for the sender address
|
||||
* 2. Select UTXOs to cover amount + fee (largest-first)
|
||||
* 3. Build the transaction with proper change output
|
||||
* 4. Retrieve the spending key (triggers biometric prompt)
|
||||
* 5. Sign the transaction
|
||||
* 6. Return the signed transaction ready for submission
|
||||
*
|
||||
* @param request The payment request details
|
||||
* @return [SignedTransaction] on success, error on failure
|
||||
*/
|
||||
suspend fun buildAndSign(request: PaymentRequest): Result<SignedTransaction>
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue