From b4b8756146a887e036216d89fcdcf59a0f6f1b0a Mon Sep 17 00:00:00 2001 From: Francois Date: Fri, 21 Nov 2025 10:25:06 +0200 Subject: [PATCH 1/9] feat(activation): add optional password field and user action rpc thanks to @CharlVS 's patch file --- .../sia_activation_params.dart | 49 ++++--- .../lib/src/rpc_methods/sia/enable_sia.dart | 123 +++++++++++++----- .../rpc_methods/sia/sia_rpc_namespace.dart | 33 ++++- 3 files changed, 153 insertions(+), 52 deletions(-) diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/activation_params/sia_activation_params.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/activation_params/sia_activation_params.dart index 01c46ec8..01439800 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/activation_params/sia_activation_params.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/activation_params/sia_activation_params.dart @@ -1,26 +1,33 @@ import 'package:komodo_defi_rpc_methods/src/common_structures/common_structures.dart'; import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; -import 'package:komodo_defi_types/komodo_defi_types.dart'; -/// SIA-specific activation parameters +/// Activation parameters specific to SIA protocol coins. /// -/// Supports: -/// - serverUrl: SiaScan-compatible wallet API base URL -/// - txHistory: whether to fetch transaction history on enable -/// - requiredConfirmations: confirmations to wait for swap steps +/// This extends the generic [ActivationParams] with: +/// - a required [serverUrl] pointing at a SiaScan-compatible wallet API +/// - an optional [password] for the SIA wallet daemon +/// - a [txHistory] flag that controls whether transaction history is enabled +/// +/// The shape produced by [toRpcParams] matches the KDF +/// `task::enable_sia::init` API, nesting values under `activation_params.client_conf`. class SiaActivationParams extends ActivationParams { const SiaActivationParams({ required this.serverUrl, + this.password, this.txHistory = true, - int? requiredConfirmations, - PrivateKeyPolicy privKeyPolicy = const PrivateKeyPolicy.contextPrivKey(), - }) : super( - requiredConfirmations: requiredConfirmations, - privKeyPolicy: privKeyPolicy, - ); + super.requiredConfirmations, + PrivateKeyPolicy super.privKeyPolicy, + }); + /// Creates [SiaActivationParams] from a coins-config JSON entry. + /// + /// The SIA server URL is taken from: + /// - `server_url`, or + /// - the first `nodes[].url` entry as a fallback. + /// + /// Throws [ArgumentError] if no usable URL can be found. factory SiaActivationParams.fromConfigJson(JsonMap json) { - String? serverUrl = json.valueOrNull('server_url'); + var serverUrl = json.valueOrNull('server_url'); if (serverUrl == null && json.containsKey('nodes')) { final nodes = json.value>('nodes'); if (nodes.isNotEmpty) { @@ -45,15 +52,19 @@ class SiaActivationParams extends ActivationParams { ); } + /// Base URL of the SIA wallet API (e.g. `https://api.siascan.com/wallet/api`). final String serverUrl; + + /// Optional password to unlock or authenticate the SIA wallet daemon. + final String? password; + + /// Whether SIA transaction history should be enabled on activation. final bool txHistory; @override Map toRpcParams() => super.toRpcParams().deepMerge({ - 'client_conf': { - 'server_url': serverUrl, - }, - 'tx_history': txHistory, - }); + // SIA activation uses a nested client_conf object + 'client_conf': {'server_url': serverUrl, 'password': ?password}, + 'tx_history': txHistory, + }); } - diff --git a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/sia/enable_sia.dart b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/sia/enable_sia.dart index 7095879a..9a04995c 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/sia/enable_sia.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/sia/enable_sia.dart @@ -1,13 +1,14 @@ import 'package:komodo_defi_rpc_methods/src/internal_exports.dart'; import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; +/// Request for the `task::enable_sia::init` RPC. +/// +/// Starts a task-managed activation flow for a SIA protocol coin and returns +/// a [NewTaskResponse] containing the activation task ID. class TaskEnableSiaInit extends BaseRequest { - TaskEnableSiaInit({ - required this.ticker, - required this.params, - super.rpcPass, - }) : super(method: 'task::enable_sia::init', mmrpc: RpcVersion.v2_0); + TaskEnableSiaInit({required this.ticker, required this.params, super.rpcPass}) + : super(method: 'task::enable_sia::init', mmrpc: RpcVersion.v2_0); final String ticker; @@ -17,15 +18,12 @@ class TaskEnableSiaInit @override Map toJson() => { - ...super.toJson(), - 'userpass': rpcPass, - 'mmrpc': mmrpc, - 'method': method, - 'params': { - 'ticker': ticker, - 'activation_params': params.toRpcParams(), - }, - }; + ...super.toJson(), + 'userpass': rpcPass, + 'mmrpc': mmrpc, + 'method': method, + 'params': {'ticker': ticker, 'activation_params': params.toRpcParams()}, + }; @override NewTaskResponse parse(Map json) { @@ -33,6 +31,9 @@ class TaskEnableSiaInit } } +/// Request for the `task::enable_sia::status` RPC. +/// +/// Polls the status of an ongoing SIA activation task. class TaskEnableSiaStatus extends BaseRequest { TaskEnableSiaStatus({ @@ -46,12 +47,12 @@ class TaskEnableSiaStatus @override Map toJson() => { - ...super.toJson(), - 'userpass': rpcPass, - 'mmrpc': mmrpc, - 'method': method, - 'params': {'task_id': taskId, 'forget_if_finished': forgetIfFinished}, - }; + ...super.toJson(), + 'userpass': rpcPass, + 'mmrpc': mmrpc, + 'method': method, + 'params': {'task_id': taskId, 'forget_if_finished': forgetIfFinished}, + }; @override TaskStatusResponse parse(Map json) { @@ -59,26 +60,86 @@ class TaskEnableSiaStatus } } +/// Request for the `task::enable_sia::cancel` RPC. +/// +/// Cancels an ongoing SIA activation task and returns a [SiaCancelResponse] +/// indicating whether the cancel operation was successful. class TaskEnableSiaCancel - extends BaseRequest { - TaskEnableSiaCancel({ + extends BaseRequest { + TaskEnableSiaCancel({required this.taskId, super.rpcPass}) + : super(method: 'task::enable_sia::cancel', mmrpc: RpcVersion.v2_0); + + final int taskId; + + @override + Map toJson() => { + ...super.toJson(), + 'userpass': rpcPass, + 'mmrpc': mmrpc, + 'method': method, + 'params': {'task_id': taskId}, + }; + + @override + SiaCancelResponse parse(Map json) => + SiaCancelResponse.parse(json); +} + +/// Request for the `task::enable_sia::user_action` RPC. +/// +/// Used when the SIA activation flow requires user interaction, such as +/// providing a hardware-wallet PIN or passphrase. +class TaskEnableSiaUserAction + extends BaseRequest { + TaskEnableSiaUserAction({ required this.taskId, + required this.actionType, + this.pin, + this.passphrase, super.rpcPass, - }) : super(method: 'task::enable_sia::cancel', mmrpc: RpcVersion.v2_0); + }) : super(method: 'task::enable_sia::user_action', mmrpc: RpcVersion.v2_0); final int taskId; + final String actionType; + final String? pin; + final String? passphrase; @override Map toJson() => { - ...super.toJson(), - 'userpass': rpcPass, - 'mmrpc': mmrpc, - 'method': method, - 'params': {'task_id': taskId}, - }; + ...super.toJson(), + 'userpass': rpcPass, + 'mmrpc': mmrpc, + 'method': method, + 'params': { + 'task_id': taskId, + 'user_action': { + 'action_type': actionType, + if (pin != null) 'pin': pin, + if (passphrase != null) 'passphrase': passphrase, + }, + }, + }; @override - TaskStatusResponse parse(Map json) => - TaskStatusResponse.parse(json); + UserActionResponse parse(Map json) => + UserActionResponse.parse(JsonMap.of(json)); } +/// Response returned by the `task::enable_sia::cancel` RPC. +/// +/// Wraps a simple [result] string, which is `"success"` on success. +class SiaCancelResponse extends BaseResponse { + SiaCancelResponse({required super.mmrpc, required this.result}); + + factory SiaCancelResponse.parse(Map json) { + return SiaCancelResponse( + mmrpc: json.value('mmrpc'), + result: json.value('result'), + ); + } + + final String result; + + @override + Map toJson() => {'mmrpc': mmrpc, 'result': result}; +} diff --git a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/sia/sia_rpc_namespace.dart b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/sia/sia_rpc_namespace.dart index ef36b1f8..03eaac2f 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/sia/sia_rpc_namespace.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/sia/sia_rpc_namespace.dart @@ -1,9 +1,14 @@ import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart'; -import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; +/// High-level namespace for SIA-specific RPC methods. +/// +/// Provides typed helpers over the raw `task::enable_sia::*` APIs. class SiaMethodsNamespace extends BaseRpcMethodNamespace { SiaMethodsNamespace(super.client); + /// Initialize SIA activation using `task::enable_sia::init`. + /// + /// Returns a [NewTaskResponse] with the activation task ID. Future enableSiaInit({ required String ticker, required SiaActivationParams params, @@ -17,6 +22,7 @@ class SiaMethodsNamespace extends BaseRpcMethodNamespace { ); } + /// Get activation status using `task::enable_sia::status`. Future enableSiaStatus( int taskId, { bool forgetIfFinished = true, @@ -30,10 +36,33 @@ class SiaMethodsNamespace extends BaseRpcMethodNamespace { ); } - Future enableSiaCancel({required int taskId}) { + /// Cancel an activation task using `task::enable_sia::cancel`. + /// + /// Returns a [SiaCancelResponse] that indicates success or failure. + Future enableSiaCancel({required int taskId}) { return execute( TaskEnableSiaCancel(taskId: taskId, rpcPass: rpcPass), ); } + + /// Provide user interaction for SIA activation via `task::enable_sia::user_action`. + /// + /// Typically used to pass Trezor PIN or passphrase when required. + Future enableSiaUserAction({ + required int taskId, + required String actionType, + String? pin, + String? passphrase, + }) { + return execute( + TaskEnableSiaUserAction( + taskId: taskId, + actionType: actionType, + pin: pin, + passphrase: passphrase, + rpcPass: rpcPass, + ), + ); + } } From 4258aab5033acb8d7815005ee5b43d2ad7a0e924 Mon Sep 17 00:00:00 2001 From: Francois Date: Fri, 21 Nov 2025 11:03:48 +0200 Subject: [PATCH 2/9] feat(transaction): add sia-specific rpc and missing json to tx info --- .../transaction_history/transaction_info.dart | 13 +++-- .../send_raw_transaction_request.dart | 55 +++++++++++++++---- .../withdrawal/withdrawal_rpc_namespace.dart | 28 +++++++++- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/transaction_history/transaction_info.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/transaction_history/transaction_info.dart index 93e0e595..8b0bfeea 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/common_structures/transaction_history/transaction_info.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/common_structures/transaction_history/transaction_info.dart @@ -14,6 +14,7 @@ class TransactionInfo { required this.coin, required this.internalId, required this.memo, + this.txJson, this.spentByMe, this.receivedByMe, this.transactionFee, @@ -24,14 +25,14 @@ class TransactionInfo { txHash: json.value('tx_hash'), from: List.from(json.value('from')), to: List.from(json.value('to')), + txJson: json.valueOrNull('tx_json'), myBalanceChange: json.value('my_balance_change'), blockHeight: json.value('block_height'), confirmations: json.value('confirmations'), timestamp: json.value('timestamp'), - feeDetails: - json.containsKey('fee_details') - ? FeeInfo.fromJson(json.value('fee_details')) - : null, + feeDetails: json.containsKey('fee_details') + ? FeeInfo.fromJson(json.value('fee_details')) + : null, transactionFee: json.valueOrNull('transaction_fee'), coin: json.value('coin'), internalId: json.value('internal_id'), @@ -47,6 +48,9 @@ class TransactionInfo { final String myBalanceChange; final int blockHeight; final int confirmations; + + /// Raw transaction JSON (present for SIA protocol transactions). + final JsonMap? txJson; final int timestamp; final FeeInfo? feeDetails; final String? transactionFee; @@ -60,6 +64,7 @@ class TransactionInfo { 'tx_hash': txHash, 'from': from, 'to': to, + 'tx_json': ?txJson, 'my_balance_change': myBalanceChange, 'block_height': blockHeight, 'confirmations': confirmations, diff --git a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/send_raw_transaction_request.dart b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/send_raw_transaction_request.dart index b8a95f8c..d541aeaf 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/send_raw_transaction_request.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/send_raw_transaction_request.dart @@ -1,7 +1,10 @@ import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart'; import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; -/// Legacy send raw transaction request +/// Legacy `send_raw_transaction` request for UTXO/EVM-style coins. +/// +/// Sends a pre-built transaction hex ([txHex]) to the network for the given +/// [coin]. For SIA protocol coins, prefer [SiaSendRawTransactionRequest]. class SendRawTransactionLegacyRequest extends BaseRequest { SendRawTransactionLegacyRequest({ @@ -9,23 +12,51 @@ class SendRawTransactionLegacyRequest required this.coin, this.txHex, this.txJson, - }) : assert( - txHex != null || txJson != null, - 'Either txHex or txJson must be provided', - ), - super(method: 'send_raw_transaction', mmrpc: null); + }) : assert( + txHex != null || txJson != null, + 'Either txHex or txJson must be provided', + ), + super(method: 'send_raw_transaction', mmrpc: null); final String coin; final String? txHex; - final Map? txJson; + final JsonMap? txJson; @override Map toJson() => { - ...super.toJson(), - 'coin': coin, - if (txHex != null) 'tx_hex': txHex, - if (txJson != null) 'tx_json': txJson, - }; + ...super.toJson(), + 'coin': coin, + 'tx_hex': ?txHex, + 'tx_json': ?txJson, + }; + + @override + SendRawTransactionResponse parse(Map json) => + SendRawTransactionResponse.parse(json); +} + +/// SIA-specific legacy `send_raw_transaction` request using `tx_json`. +/// +/// For SIA protocol withdrawals, the KDF API expects the transaction details +/// as JSON ([txJson]) instead of a hex string. This request mirrors the SIA +/// examples in the KDF documentation. +class SiaSendRawTransactionRequest + extends BaseRequest { + SiaSendRawTransactionRequest({ + required super.rpcPass, + required this.coin, + required this.txJson, + }) : super(method: 'send_raw_transaction', mmrpc: null); + + final String coin; + final JsonMap txJson; + + @override + Map toJson() => { + ...super.toJson(), + 'coin': coin, + 'tx_json': txJson, + }; @override SendRawTransactionResponse parse(Map json) => diff --git a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/withdrawal_rpc_namespace.dart b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/withdrawal_rpc_namespace.dart index 10f939a8..6911d2d6 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/withdrawal_rpc_namespace.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/withdrawal_rpc_namespace.dart @@ -1,5 +1,6 @@ -import 'package:komodo_defi_rpc_methods/src/internal_exports.dart'; import 'package:decimal/decimal.dart'; +import 'package:komodo_defi_rpc_methods/src/internal_exports.dart'; +import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; import 'package:komodo_defi_types/komodo_defi_types.dart'; class WithdrawMethodsNamespace extends BaseRpcMethodNamespace { @@ -24,7 +25,11 @@ class WithdrawMethodsNamespace extends BaseRpcMethodNamespace { ); } - /// Convenience wrapper for SIA withdrawals with SIA-specific response parsing + /// Convenience wrapper for SIA withdrawals with SIA-specific response parsing. + /// + /// Uses the v2 `withdraw` RPC but parses the result into [SiaWithdrawResponse], + /// exposing the SIA `tx_json` payload that should later be passed to + /// [sendRawTransactionSia]. Future withdrawSia({ required String coin, required String to, @@ -79,7 +84,7 @@ class WithdrawMethodsNamespace extends BaseRpcMethodNamespace { Future sendRawTransaction({ required String coin, String? txHex, - Map? txJson, + JsonMap? txJson, WithdrawalSource? from, }) { return execute( @@ -91,4 +96,21 @@ class WithdrawMethodsNamespace extends BaseRpcMethodNamespace { ), ); } + + /// SIA-specific `send_raw_transaction` using a `tx_json` payload. + /// + /// This should be used together with [withdrawSia], passing the + /// [SiaWithdrawResponse.txJson] value as [txJson]. + Future sendRawTransactionSia({ + required String coin, + required JsonMap txJson, + }) { + return execute( + SiaSendRawTransactionRequest( + rpcPass: rpcPass ?? '', + coin: coin, + txJson: txJson, + ), + ); + } } From cff9a01b6c8469d49fc3bab142eb5d3c123b8e05 Mon Sep 17 00:00:00 2001 From: Francois Date: Fri, 21 Nov 2025 12:02:27 +0200 Subject: [PATCH 3/9] feat(withdraw): add missing fields to sia withdraw response and fee info --- .../withdrawal/sia_withdraw_request.dart | 118 ++++++++++++------ .../test/sia_rpc_methods_test.dart | 37 ++++-- .../lib/src/transactions/fee_info.dart | 11 ++ 3 files changed, 120 insertions(+), 46 deletions(-) diff --git a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/sia_withdraw_request.dart b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/sia_withdraw_request.dart index bb60d9a5..51c92a30 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/sia_withdraw_request.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/sia_withdraw_request.dart @@ -3,9 +3,11 @@ import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart'; import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; import 'package:komodo_defi_types/komodo_defi_types.dart'; -/// SIA-specific withdraw request +/// SIA-specific `withdraw` request. /// -/// Uses the same 'withdraw' RPC but parses SIA-flavored response payload +/// This wraps the standard v2 `withdraw` RPC but expects a SIA protocol coin +/// and parses the response into [SiaWithdrawResponse], which exposes the +/// SIA-specific `tx_json` needed for broadcasting. class SiaWithdrawRequest extends BaseRequest { SiaWithdrawRequest({ @@ -16,8 +18,8 @@ class SiaWithdrawRequest this.fee, this.from, this.max = false, - }) : assert(amount != null || max, 'Specify amount or set max=true'), - super(method: 'withdraw', mmrpc: RpcVersion.v2_0); + }) : assert(amount != null || max, 'Specify amount or set max=true'), + super(method: 'withdraw', mmrpc: RpcVersion.v2_0); final String coin; final String to; @@ -28,66 +30,108 @@ class SiaWithdrawRequest @override Map toJson() => { - ...super.toJson(), - 'params': { - 'coin': coin, - 'to': to, - if (!max && amount != null) 'amount': amount!.toString(), - if (max) 'max': true, - if (fee != null) 'fee': fee!.toJson(), - if (from != null) 'from': from!.toRpcParams(), - }, - }; + ...super.toJson(), + 'params': { + 'coin': coin, + 'to': to, + if (!max && amount != null) 'amount': amount!.toString(), + if (max) 'max': true, + if (fee != null) 'fee': fee!.toJson(), + if (from != null) 'from': from!.toRpcParams(), + }, + }; @override SiaWithdrawResponse parse(Map json) => SiaWithdrawResponse.parse(json); } +/// Response for SIA protocol withdrawals. +/// +/// Mirrors the SIA examples in the v2 `withdraw` documentation and includes: +/// - [txJson]: raw SIA transaction JSON, to be passed to `send_raw_transaction` +/// - high-level accounting fields such as [totalAmount], [spentByMe], +/// [receivedByMe] and [myBalanceChange] +/// - on-chain metadata like [blockHeight], [timestamp] and [feeDetails] +/// - context fields [coin], [internalId], [transactionType] and optional [memo] class SiaWithdrawResponse extends BaseResponse { SiaWithdrawResponse({ required super.mmrpc, - required this.status, + required this.txJson, + required this.txHash, + required this.from, + required this.to, + required this.totalAmount, required this.spentByMe, required this.receivedByMe, required this.myBalanceChange, - this.feeDetails, - this.details, + required this.blockHeight, + required this.timestamp, + required this.feeDetails, + required this.coin, + required this.internalId, + required this.transactionType, + this.memo, }); factory SiaWithdrawResponse.parse(Map json) { final result = json.value('result'); return SiaWithdrawResponse( - mmrpc: json.value('mmrpc'), - status: result.value('status'), + mmrpc: json.valueOrNull('mmrpc'), + txJson: result.value('tx_json'), + txHash: result.value('tx_hash'), + from: result.value('from').cast(), + to: result.value('to').cast(), + totalAmount: result.value('total_amount'), spentByMe: result.value('spent_by_me'), receivedByMe: result.value('received_by_me'), myBalanceChange: result.value('my_balance_change'), - feeDetails: result.valueOrNull('fee_details') == null - ? null - : FeeInfo.fromJson(result.value('fee_details')), - details: result.valueOrNull('details'), + blockHeight: result.value('block_height'), + timestamp: result.value('timestamp'), + feeDetails: FeeInfo.fromJson(result.value('fee_details')), + coin: result.value('coin'), + internalId: result.value('internal_id'), + transactionType: result.value('transaction_type'), + memo: result.valueOrNull('memo'), ); } - final String status; + /// Raw SIA transaction JSON payload to be passed to send_raw_transaction + final JsonMap txJson; + final String txHash; + final List from; + final List to; + final String totalAmount; final String spentByMe; final String receivedByMe; final String myBalanceChange; - final FeeInfo? feeDetails; - final dynamic details; + final int blockHeight; + final int timestamp; + final FeeInfo feeDetails; + final String coin; + final String internalId; + final String transactionType; + final String? memo; @override Map toJson() => { - 'mmrpc': mmrpc, - 'result': { - 'status': status, - 'spent_by_me': spentByMe, - 'received_by_me': receivedByMe, - 'my_balance_change': myBalanceChange, - if (feeDetails != null) 'fee_details': feeDetails!.toJson(), - if (details != null) 'details': details, - }, - }; + 'mmrpc': mmrpc, + 'result': { + 'tx_json': txJson, + 'tx_hash': txHash, + 'from': from, + 'to': to, + 'total_amount': totalAmount, + 'spent_by_me': spentByMe, + 'received_by_me': receivedByMe, + 'my_balance_change': myBalanceChange, + 'block_height': blockHeight, + 'timestamp': timestamp, + 'fee_details': feeDetails.toJson(), + 'coin': coin, + 'internal_id': internalId, + 'transaction_type': transactionType, + if (memo != null) 'memo': memo, + }, + }; } - diff --git a/packages/komodo_defi_rpc_methods/test/sia_rpc_methods_test.dart b/packages/komodo_defi_rpc_methods/test/sia_rpc_methods_test.dart index 0894efcd..a4b883e9 100644 --- a/packages/komodo_defi_rpc_methods/test/sia_rpc_methods_test.dart +++ b/packages/komodo_defi_rpc_methods/test/sia_rpc_methods_test.dart @@ -16,27 +16,46 @@ void main() { ); final json = req.toJson(); expect(json['method'], 'task::enable_sia::init'); - final p = (json['params'] as Map)['activation_params'] as Map; - final clientConf = p['client_conf'] as Map; + final activationParams = + (json['params'] as Map)['activation_params'] as Map; + final clientConf = activationParams['client_conf'] as Map; expect(clientConf['server_url'], 'https://api.siascan.com/wallet/api'); - expect(p['tx_history'], true); - expect(p['required_confirmations'], 1); + expect(activationParams['tx_history'], true); + expect(activationParams['required_confirmations'], 1); }); - test('SiaWithdrawResponse parses nullable fee_details safely', () { + test('SiaWithdrawResponse parses full SIA withdraw shape', () { final response = { 'mmrpc': '2.0', 'result': { - 'status': 'Ok', + 'tx_json': {'siacoinOutputs': []}, + 'tx_hash': 'hash', + 'from': ['from_addr'], + 'to': ['to_addr'], + 'total_amount': '10', 'spent_by_me': '0', 'received_by_me': '100', 'my_balance_change': '100', - // fee_details intentionally omitted + 'block_height': 1, + 'timestamp': 123456, + 'fee_details': { + 'type': 'Sia', + 'coin': 'SC', + 'policy': 'Fixed', + 'total_amount': '0.1', + }, + 'coin': 'SC', + 'internal_id': '', + 'transaction_type': 'SiaV2Transaction', + 'memo': null, }, }; final parsed = SiaWithdrawResponse.parse(JsonMap.of(response)); - expect(parsed.status, 'Ok'); - expect(parsed.feeDetails, isNull); + expect(parsed.txHash, 'hash'); + expect(parsed.from, ['from_addr']); + expect(parsed.to, ['to_addr']); + expect(parsed.totalAmount, '10'); + expect(parsed.feeDetails.coin, 'SC'); }); }); } diff --git a/packages/komodo_defi_types/lib/src/transactions/fee_info.dart b/packages/komodo_defi_types/lib/src/transactions/fee_info.dart index df8cc556..cd419e70 100644 --- a/packages/komodo_defi_types/lib/src/transactions/fee_info.dart +++ b/packages/komodo_defi_types/lib/src/transactions/fee_info.dart @@ -37,6 +37,17 @@ sealed class FeeInfo with _$FeeInfo { coin: json['coin'] as String? ?? '', amount: Decimal.parse(json['amount'] as String), ); + case 'Sia': + final rawTotal = + json['total_amount'] ?? + json['amount']; // some examples use total_amount + if (rawTotal == null) { + throw ArgumentError('Sia fee_details missing total_amount/amount'); + } + return FeeInfo.utxoFixed( + coin: json['coin'] as String? ?? '', + amount: Decimal.parse(rawTotal.toString()), + ); case 'EthGas' || 'Eth': final totalGasFee = json['total_fee'] != null ? Decimal.parse(json['total_fee'].toString()) From 7128934b6ea60e13311411792c8c8803ca907fa0 Mon Sep 17 00:00:00 2001 From: Francois Date: Fri, 21 Nov 2025 20:06:28 +0200 Subject: [PATCH 4/9] fix(withdraw): also use non-task-based withdraw in executeWithdrawal --- .../lib/src/withdrawals/withdrawal_manager.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/komodo_defi_sdk/lib/src/withdrawals/withdrawal_manager.dart b/packages/komodo_defi_sdk/lib/src/withdrawals/withdrawal_manager.dart index d0689ad4..3e78963f 100644 --- a/packages/komodo_defi_sdk/lib/src/withdrawals/withdrawal_manager.dart +++ b/packages/komodo_defi_sdk/lib/src/withdrawals/withdrawal_manager.dart @@ -490,6 +490,9 @@ class WithdrawalManager { final asset = _assetProvider .findAssetsByConfigId(parameters.asset) .single; + + // TODO: refactor into strategy pattern or add a getter to the protocol + // class to check if the protocol requires the legacy withdrawals final isTendermintProtocol = asset.protocol is TendermintProtocol; final isSiaProtocol = asset.protocol is SiaProtocol; @@ -585,16 +588,23 @@ class WithdrawalManager { ) async* { try { final asset = _assetProvider.findAssetsByConfigId(assetId).single; + + // TODO: refactor into strategy pattern or add a getter to the protocol + // class to check if the protocol requires the legacy withdrawals final isTendermintProtocol = asset.protocol is TendermintProtocol; + final isSiaProtocol = asset.protocol is SiaProtocol; // Tendermint assets are not yet supported by the task-based API - if (isTendermintProtocol) { + // and require a legacy implementation + if (isTendermintProtocol || isSiaProtocol) { yield* _legacyManager.executeWithdrawal(preview, assetId); return; } // Ensure asset is activated before broadcasting - final activationResult = await _activationCoordinator.activateAsset(asset); + final activationResult = await _activationCoordinator.activateAsset( + asset, + ); if (activationResult.isFailure) { throw WithdrawalException( 'Failed to activate asset $assetId: ${activationResult.errorMessage ?? activationResult.toString()}', From be84499033880b56b603a8a64ec804b670dc5da6 Mon Sep 17 00:00:00 2001 From: Francois Date: Fri, 21 Nov 2025 20:08:16 +0200 Subject: [PATCH 5/9] chore: remove duplicate and unused sia withdraw rpc and models standard withdraw models are being used regardless, with txJson and txHex added to WithdrawResult --- .../strategies/utxo_activation_strategy.dart | 1 - .../common_structures/common_structures.dart | 1 - .../lib/src/rpc_methods/rpc_methods.dart | 1 - .../withdrawal/sia_withdraw_request.dart | 137 ------------------ .../withdrawal/withdrawal_rpc_namespace.dart | 30 ---- 5 files changed, 170 deletions(-) delete mode 100644 packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/strategies/utxo_activation_strategy.dart delete mode 100644 packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/sia_withdraw_request.dart diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/strategies/utxo_activation_strategy.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/strategies/utxo_activation_strategy.dart deleted file mode 100644 index 8b137891..00000000 --- a/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/strategies/utxo_activation_strategy.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart index db5bc1c8..ebdb3d71 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart @@ -19,7 +19,6 @@ export 'activation/activation_params/utxo_activation_params.dart'; export 'activation/activation_params/zhtlc_activation_params.dart'; export 'activation/coin_protocol.dart'; export 'activation/evm_node.dart'; -export 'activation/strategies/utxo_activation_strategy.dart'; export 'activation/tokens_request.dart'; export 'activation/utxo_merge_params.dart'; export 'general/address_format.dart'; diff --git a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/rpc_methods.dart b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/rpc_methods.dart index 5802c7c3..5035f3e3 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/rpc_methods.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/rpc_methods.dart @@ -104,7 +104,6 @@ export 'wallet/get_wallet_names_response.dart'; export 'wallet/my_balance.dart'; export 'wallet/unban_pubkeys.dart'; export 'withdrawal/send_raw_transaction_request.dart'; -export 'withdrawal/sia_withdraw_request.dart'; export 'withdrawal/withdraw_request.dart'; export 'withdrawal/withdrawal_rpc_namespace.dart'; export 'zhtlc/z_coin_tx_history.dart'; diff --git a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/sia_withdraw_request.dart b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/sia_withdraw_request.dart deleted file mode 100644 index 51c92a30..00000000 --- a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/sia_withdraw_request.dart +++ /dev/null @@ -1,137 +0,0 @@ -import 'package:decimal/decimal.dart'; -import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart'; -import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; -import 'package:komodo_defi_types/komodo_defi_types.dart'; - -/// SIA-specific `withdraw` request. -/// -/// This wraps the standard v2 `withdraw` RPC but expects a SIA protocol coin -/// and parses the response into [SiaWithdrawResponse], which exposes the -/// SIA-specific `tx_json` needed for broadcasting. -class SiaWithdrawRequest - extends BaseRequest { - SiaWithdrawRequest({ - required super.rpcPass, - required this.coin, - required this.to, - this.amount, - this.fee, - this.from, - this.max = false, - }) : assert(amount != null || max, 'Specify amount or set max=true'), - super(method: 'withdraw', mmrpc: RpcVersion.v2_0); - - final String coin; - final String to; - final Decimal? amount; - final FeeInfo? fee; - final WithdrawalSource? from; - final bool max; - - @override - Map toJson() => { - ...super.toJson(), - 'params': { - 'coin': coin, - 'to': to, - if (!max && amount != null) 'amount': amount!.toString(), - if (max) 'max': true, - if (fee != null) 'fee': fee!.toJson(), - if (from != null) 'from': from!.toRpcParams(), - }, - }; - - @override - SiaWithdrawResponse parse(Map json) => - SiaWithdrawResponse.parse(json); -} - -/// Response for SIA protocol withdrawals. -/// -/// Mirrors the SIA examples in the v2 `withdraw` documentation and includes: -/// - [txJson]: raw SIA transaction JSON, to be passed to `send_raw_transaction` -/// - high-level accounting fields such as [totalAmount], [spentByMe], -/// [receivedByMe] and [myBalanceChange] -/// - on-chain metadata like [blockHeight], [timestamp] and [feeDetails] -/// - context fields [coin], [internalId], [transactionType] and optional [memo] -class SiaWithdrawResponse extends BaseResponse { - SiaWithdrawResponse({ - required super.mmrpc, - required this.txJson, - required this.txHash, - required this.from, - required this.to, - required this.totalAmount, - required this.spentByMe, - required this.receivedByMe, - required this.myBalanceChange, - required this.blockHeight, - required this.timestamp, - required this.feeDetails, - required this.coin, - required this.internalId, - required this.transactionType, - this.memo, - }); - - factory SiaWithdrawResponse.parse(Map json) { - final result = json.value('result'); - return SiaWithdrawResponse( - mmrpc: json.valueOrNull('mmrpc'), - txJson: result.value('tx_json'), - txHash: result.value('tx_hash'), - from: result.value('from').cast(), - to: result.value('to').cast(), - totalAmount: result.value('total_amount'), - spentByMe: result.value('spent_by_me'), - receivedByMe: result.value('received_by_me'), - myBalanceChange: result.value('my_balance_change'), - blockHeight: result.value('block_height'), - timestamp: result.value('timestamp'), - feeDetails: FeeInfo.fromJson(result.value('fee_details')), - coin: result.value('coin'), - internalId: result.value('internal_id'), - transactionType: result.value('transaction_type'), - memo: result.valueOrNull('memo'), - ); - } - - /// Raw SIA transaction JSON payload to be passed to send_raw_transaction - final JsonMap txJson; - final String txHash; - final List from; - final List to; - final String totalAmount; - final String spentByMe; - final String receivedByMe; - final String myBalanceChange; - final int blockHeight; - final int timestamp; - final FeeInfo feeDetails; - final String coin; - final String internalId; - final String transactionType; - final String? memo; - - @override - Map toJson() => { - 'mmrpc': mmrpc, - 'result': { - 'tx_json': txJson, - 'tx_hash': txHash, - 'from': from, - 'to': to, - 'total_amount': totalAmount, - 'spent_by_me': spentByMe, - 'received_by_me': receivedByMe, - 'my_balance_change': myBalanceChange, - 'block_height': blockHeight, - 'timestamp': timestamp, - 'fee_details': feeDetails.toJson(), - 'coin': coin, - 'internal_id': internalId, - 'transaction_type': transactionType, - if (memo != null) 'memo': memo, - }, - }; -} diff --git a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/withdrawal_rpc_namespace.dart b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/withdrawal_rpc_namespace.dart index 6911d2d6..a1accd71 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/withdrawal_rpc_namespace.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/withdrawal_rpc_namespace.dart @@ -1,4 +1,3 @@ -import 'package:decimal/decimal.dart'; import 'package:komodo_defi_rpc_methods/src/internal_exports.dart'; import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; import 'package:komodo_defi_types/komodo_defi_types.dart'; @@ -25,32 +24,6 @@ class WithdrawMethodsNamespace extends BaseRpcMethodNamespace { ); } - /// Convenience wrapper for SIA withdrawals with SIA-specific response parsing. - /// - /// Uses the v2 `withdraw` RPC but parses the result into [SiaWithdrawResponse], - /// exposing the SIA `tx_json` payload that should later be passed to - /// [sendRawTransactionSia]. - Future withdrawSia({ - required String coin, - required String to, - Decimal? amount, - FeeInfo? fee, - WithdrawalSource? from, - bool max = false, - }) { - return execute( - SiaWithdrawRequest( - rpcPass: rpcPass ?? '', - coin: coin, - to: to, - amount: amount, - fee: fee, - from: from, - max: max, - ), - ); - } - /// Initialize a new withdrawal task // TODO: Consider refactoring to use individual parameters instead of a single // object for the request parameters for the sake of consistency with other @@ -98,9 +71,6 @@ class WithdrawMethodsNamespace extends BaseRpcMethodNamespace { } /// SIA-specific `send_raw_transaction` using a `tx_json` payload. - /// - /// This should be used together with [withdrawSia], passing the - /// [SiaWithdrawResponse.txJson] value as [txJson]. Future sendRawTransactionSia({ required String coin, required JsonMap txJson, From ecf45496bb20d62f6a2e241b8b72e378e2cc2a2b Mon Sep 17 00:00:00 2001 From: Francois Date: Fri, 21 Nov 2025 20:09:03 +0200 Subject: [PATCH 6/9] refactor(activation): add logging to SIA activation strategy --- .../sia_activation_strategy.dart | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/sia_activation_strategy.dart b/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/sia_activation_strategy.dart index 3ece5c8b..01163ff0 100644 --- a/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/sia_activation_strategy.dart +++ b/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/sia_activation_strategy.dart @@ -1,13 +1,17 @@ import 'dart:async'; +import 'dart:convert'; +import 'package:komodo_defi_framework/komodo_defi_framework.dart'; import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart'; import 'package:komodo_defi_sdk/src/activation/_activation.dart'; import 'package:komodo_defi_types/komodo_defi_types.dart'; +import 'package:logging/logging.dart'; class SiaActivationStrategy extends ProtocolActivationStrategy { SiaActivationStrategy(super.client); static const Duration kPollInterval = Duration(milliseconds: 500); + static final Logger _log = Logger('SiaActivationStrategy'); @override Set get supportedProtocols => {CoinSubClass.sia}; @@ -32,25 +36,35 @@ class SiaActivationStrategy extends ProtocolActivationStrategy { requiredConfirmations: protocol.requiredConfirmations, ); - yield ActivationProgress( + yield const ActivationProgress( status: 'Starting SIA activation...', progressDetails: ActivationProgressDetails( currentStep: ActivationStep.initialization, stepCount: 3, - additionalInfo: { - 'assetType': 'platform', - 'protocol': 'SIA', - }, + additionalInfo: {'assetType': 'platform', 'protocol': 'SIA'}, ), ); try { - final init = await KomodoDefiRpcMethods(client).sia.enableSiaInit( - ticker: asset.id.id, - params: params, + // Debug logging for SIA activation + if (KdfLoggingConfig.verboseLogging) { + _log + ..info('[SIA] Activating SIA coin: ${asset.id.id}') + ..info( + '[SIA] Activation parameters: ${jsonEncode({'ticker': asset.id.id, 'server_url': serverUrl, 'required_confirmations': protocol.requiredConfirmations})}', ); + } + + final init = await KomodoDefiRpcMethods( + client, + ).sia.enableSiaInit(ticker: asset.id.id, params: params); final taskId = init.taskId; + + if (KdfLoggingConfig.verboseLogging) { + _log.info('[SIA] Task initiated for ${asset.id.id}, task_id: $taskId'); + } + yield ActivationProgress( status: 'SIA activation task started', progressDetails: ActivationProgressDetails( @@ -61,8 +75,9 @@ class SiaActivationStrategy extends ProtocolActivationStrategy { ); while (true) { - final status = - await KomodoDefiRpcMethods(client).sia.enableSiaStatus(taskId); + final status = await KomodoDefiRpcMethods( + client, + ).sia.enableSiaStatus(taskId); yield ActivationProgress( status: 'SIA activation in progress', @@ -76,6 +91,10 @@ class SiaActivationStrategy extends ProtocolActivationStrategy { // Stop polling on any terminal state if (status.status != 'InProgress') { if (status.status == 'Ok') { + if (KdfLoggingConfig.verboseLogging) { + _log.info('[SIA] Activation completed for ${asset.id.id}'); + } + yield ActivationProgress( status: 'SIA activation complete', isComplete: true, @@ -86,6 +105,12 @@ class SiaActivationStrategy extends ProtocolActivationStrategy { ), ); } else { + if (KdfLoggingConfig.verboseLogging) { + _log.warning( + '[SIA] Activation failed for ${asset.id.id}: ${status.status} - ${status.details}', + ); + } + yield ActivationProgress( status: 'SIA activation failed', isComplete: true, @@ -116,6 +141,10 @@ class SiaActivationStrategy extends ProtocolActivationStrategy { await Future.delayed(kPollInterval); } } on Exception catch (e) { + if (KdfLoggingConfig.verboseLogging) { + _log.severe('[SIA] Activation exception for ${asset.id.id}: $e'); + } + yield ActivationProgress( status: 'SIA activation failed', isComplete: true, @@ -129,4 +158,3 @@ class SiaActivationStrategy extends ProtocolActivationStrategy { } } } - From f3b860996ee1dd9aeea0dcd56854acc6bbdb2c24 Mon Sep 17 00:00:00 2001 From: Francois Date: Fri, 21 Nov 2025 21:56:52 +0200 Subject: [PATCH 7/9] refactor(activation): align sia activation with completer approach other strategies use the completer approach with logging, which the sia strategy lacked --- .../sia_activation_strategy.dart | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/sia_activation_strategy.dart b/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/sia_activation_strategy.dart index 01163ff0..3cceb0af 100644 --- a/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/sia_activation_strategy.dart +++ b/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/sia_activation_strategy.dart @@ -8,9 +8,13 @@ import 'package:komodo_defi_types/komodo_defi_types.dart'; import 'package:logging/logging.dart'; class SiaActivationStrategy extends ProtocolActivationStrategy { - SiaActivationStrategy(super.client); + SiaActivationStrategy( + super.client, { + this.pollingInterval = const Duration(milliseconds: 500), + }); - static const Duration kPollInterval = Duration(milliseconds: 500); + /// Delay added between activation task status RPC requests. Defaults to 500ms + final Duration pollingInterval; static final Logger _log = Logger('SiaActivationStrategy'); @override @@ -74,25 +78,16 @@ class SiaActivationStrategy extends ProtocolActivationStrategy { ), ); - while (true) { + var isComplete = false; + while (!isComplete) { final status = await KomodoDefiRpcMethods( client, ).sia.enableSiaStatus(taskId); - yield ActivationProgress( - status: 'SIA activation in progress', - progressDetails: ActivationProgressDetails( - currentStep: ActivationStep.processing, - stepCount: 3, - additionalInfo: {'status': status.status}, - ), - ); - - // Stop polling on any terminal state - if (status.status != 'InProgress') { + if (status.isCompleted) { if (status.status == 'Ok') { if (KdfLoggingConfig.verboseLogging) { - _log.info('[SIA] Activation completed for ${asset.id.id}'); + _log.info('[ELECTRUM] Activation completed for ${asset.id.id}'); } yield ActivationProgress( @@ -107,7 +102,8 @@ class SiaActivationStrategy extends ProtocolActivationStrategy { } else { if (KdfLoggingConfig.verboseLogging) { _log.warning( - '[SIA] Activation failed for ${asset.id.id}: ${status.status} - ${status.details}', + '[ELECTRUM] Activation failed for ${asset.id.id}: ' + '${status.status} - ${status.details}', ); } @@ -125,33 +121,32 @@ class SiaActivationStrategy extends ProtocolActivationStrategy { ), ); } + isComplete = true; + } else { yield ActivationProgress( - status: 'SIA activation concluded', - isComplete: true, + status: 'SIA activation in progress', progressDetails: ActivationProgressDetails( - currentStep: status.status == 'Ok' - ? ActivationStep.complete - : ActivationStep.error, + currentStep: ActivationStep.processing, stepCount: 3, + additionalInfo: {'status': status.status}, ), ); - break; + await Future.delayed(pollingInterval); } - - await Future.delayed(kPollInterval); - } - } on Exception catch (e) { - if (KdfLoggingConfig.verboseLogging) { - _log.severe('[SIA] Activation exception for ${asset.id.id}: $e'); } + } catch (e, s) { + _log.severe('[ELECTRUM] Activation exception for ${asset.id.id}', e, s); yield ActivationProgress( status: 'SIA activation failed', isComplete: true, + errorMessage: e.toString(), progressDetails: ActivationProgressDetails( currentStep: ActivationStep.error, stepCount: 3, additionalInfo: {'error': e.toString()}, + errorDetails: e.toString(), + stackTrace: s.toString(), ), ); rethrow; From 0d9160f00ded8e5d8a2beeb6cccff9c091758e11 Mon Sep 17 00:00:00 2001 From: Francois Date: Fri, 21 Nov 2025 23:41:16 +0200 Subject: [PATCH 8/9] test(sia): update test to use standard withdrawresult model --- .../sia_activation_params.dart | 2 +- .../test/sia_rpc_methods_test.dart | 49 +++++++++---------- .../lib/src/transactions/fee_info.dart | 19 +++---- 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/activation_params/sia_activation_params.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/activation_params/sia_activation_params.dart index 01439800..a7d45306 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/activation_params/sia_activation_params.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/common_structures/activation/activation_params/sia_activation_params.dart @@ -16,7 +16,7 @@ class SiaActivationParams extends ActivationParams { this.password, this.txHistory = true, super.requiredConfirmations, - PrivateKeyPolicy super.privKeyPolicy, + super.privKeyPolicy, }); /// Creates [SiaActivationParams] from a coins-config JSON entry. diff --git a/packages/komodo_defi_rpc_methods/test/sia_rpc_methods_test.dart b/packages/komodo_defi_rpc_methods/test/sia_rpc_methods_test.dart index a4b883e9..15c3d9b3 100644 --- a/packages/komodo_defi_rpc_methods/test/sia_rpc_methods_test.dart +++ b/packages/komodo_defi_rpc_methods/test/sia_rpc_methods_test.dart @@ -1,6 +1,8 @@ +import 'package:decimal/decimal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart'; import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; +import 'package:komodo_defi_types/komodo_defi_types.dart'; void main() { group('SIA RPC', () { @@ -25,37 +27,34 @@ void main() { }); test('SiaWithdrawResponse parses full SIA withdraw shape', () { - final response = { - 'mmrpc': '2.0', - 'result': { - 'tx_json': {'siacoinOutputs': []}, - 'tx_hash': 'hash', - 'from': ['from_addr'], - 'to': ['to_addr'], - 'total_amount': '10', - 'spent_by_me': '0', - 'received_by_me': '100', - 'my_balance_change': '100', - 'block_height': 1, - 'timestamp': 123456, - 'fee_details': { - 'type': 'Sia', - 'coin': 'SC', - 'policy': 'Fixed', - 'total_amount': '0.1', - }, + final responseResult = { + 'tx_json': {'siacoinOutputs': []}, + 'tx_hash': 'hash', + 'from': ['from_addr'], + 'to': ['to_addr'], + 'total_amount': '10', + 'spent_by_me': '0', + 'received_by_me': '100', + 'my_balance_change': '100', + 'block_height': 1, + 'timestamp': 123456, + 'fee_details': { + 'type': 'Sia', 'coin': 'SC', - 'internal_id': '', - 'transaction_type': 'SiaV2Transaction', - 'memo': null, + 'policy': 'Fixed', + 'total_amount': '0.1', }, + 'coin': 'SC', + 'internal_id': '', + 'transaction_type': 'SiaV2Transaction', + 'memo': null, }; - final parsed = SiaWithdrawResponse.parse(JsonMap.of(response)); + final parsed = WithdrawResult.fromJson(JsonMap.of(responseResult)); expect(parsed.txHash, 'hash'); expect(parsed.from, ['from_addr']); expect(parsed.to, ['to_addr']); - expect(parsed.totalAmount, '10'); - expect(parsed.feeDetails.coin, 'SC'); + expect(parsed.balanceChanges.totalAmount, Decimal.fromInt(10)); + expect(parsed.fee.coin, 'SC'); }); }); } diff --git a/packages/komodo_defi_types/lib/src/transactions/fee_info.dart b/packages/komodo_defi_types/lib/src/transactions/fee_info.dart index cd419e70..6af0471f 100644 --- a/packages/komodo_defi_types/lib/src/transactions/fee_info.dart +++ b/packages/komodo_defi_types/lib/src/transactions/fee_info.dart @@ -37,17 +37,6 @@ sealed class FeeInfo with _$FeeInfo { coin: json['coin'] as String? ?? '', amount: Decimal.parse(json['amount'] as String), ); - case 'Sia': - final rawTotal = - json['total_amount'] ?? - json['amount']; // some examples use total_amount - if (rawTotal == null) { - throw ArgumentError('Sia fee_details missing total_amount/amount'); - } - return FeeInfo.utxoFixed( - coin: json['coin'] as String? ?? '', - amount: Decimal.parse(rawTotal.toString()), - ); case 'EthGas' || 'Eth': final totalGasFee = json['total_fee'] != null ? Decimal.parse(json['total_fee'].toString()) @@ -96,9 +85,15 @@ sealed class FeeInfo with _$FeeInfo { gasLimit: json['gas_limit'] as int, ); case 'Sia': + final rawTotal = + json['total_amount'] ?? + json['amount']; // some examples use total_amount + if (rawTotal == null) { + throw ArgumentError('Sia fee_details missing total_amount/amount'); + } return FeeInfo.sia( coin: json['coin'] as String? ?? '', - amount: Decimal.parse(json['total_amount'].toString()), + amount: Decimal.parse(rawTotal.toString()), policy: json['policy'] as String? ?? 'Fixed', ); default: From 1afedc3e494df7f4605dce4d32819ff58c040522 Mon Sep 17 00:00:00 2001 From: Francois Date: Mon, 24 Nov 2025 08:26:40 +0200 Subject: [PATCH 9/9] revert(types): use total_amount for fee_info to match docs --- .../lib/src/common_structures/common_structures.dart | 1 - .../lib/src/common_structures/general/fee_info.dart | 2 -- .../komodo_defi_types/lib/src/transactions/fee_info.dart | 8 +------- 3 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 packages/komodo_defi_rpc_methods/lib/src/common_structures/general/fee_info.dart diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart index ebdb3d71..f6faa0a0 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart @@ -23,7 +23,6 @@ export 'activation/tokens_request.dart'; export 'activation/utxo_merge_params.dart'; export 'general/address_format.dart'; export 'general/balance_info.dart'; -export 'general/fee_info.dart'; export 'general/new_address_info.dart'; export 'general/scan_address_info.dart'; export 'general/sync_status.dart'; diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/general/fee_info.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/general/fee_info.dart deleted file mode 100644 index 257b5992..00000000 --- a/packages/komodo_defi_rpc_methods/lib/src/common_structures/general/fee_info.dart +++ /dev/null @@ -1,2 +0,0 @@ -// Moved to package:komodo_defi_types/komodo_defi_types since this type is -// used in many contexts beyond RPC methods. diff --git a/packages/komodo_defi_types/lib/src/transactions/fee_info.dart b/packages/komodo_defi_types/lib/src/transactions/fee_info.dart index 6af0471f..df8cc556 100644 --- a/packages/komodo_defi_types/lib/src/transactions/fee_info.dart +++ b/packages/komodo_defi_types/lib/src/transactions/fee_info.dart @@ -85,15 +85,9 @@ sealed class FeeInfo with _$FeeInfo { gasLimit: json['gas_limit'] as int, ); case 'Sia': - final rawTotal = - json['total_amount'] ?? - json['amount']; // some examples use total_amount - if (rawTotal == null) { - throw ArgumentError('Sia fee_details missing total_amount/amount'); - } return FeeInfo.sia( coin: json['coin'] as String? ?? '', - amount: Decimal.parse(rawTotal.toString()), + amount: Decimal.parse(json['total_amount'].toString()), policy: json['policy'] as String? ?? 'Fixed', ); default: