Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/komodo_defi_framework/pubspec_overrides.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# melos_managed_dependency_overrides: komodo_defi_rpc_methods,komodo_defi_types,komodo_wallet_build_transformer
# melos_managed_dependency_overrides: komodo_defi_rpc_methods,komodo_defi_types,komodo_wallet_build_transformer,komodo_coins
dependency_overrides:
komodo_coins:
path: ../komodo_coins
komodo_defi_rpc_methods:
path: ../komodo_defi_rpc_methods
komodo_defi_types:
Expand Down
4 changes: 3 additions & 1 deletion packages/komodo_defi_local_auth/pubspec_overrides.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# melos_managed_dependency_overrides: komodo_defi_framework,komodo_defi_rpc_methods,komodo_defi_types,komodo_wallet_build_transformer
# melos_managed_dependency_overrides: komodo_defi_framework,komodo_defi_rpc_methods,komodo_defi_types,komodo_wallet_build_transformer,komodo_coins
dependency_overrides:
komodo_coins:
path: ../komodo_coins
komodo_defi_framework:
path: ../komodo_defi_framework
komodo_defi_rpc_methods:
Expand Down
18 changes: 3 additions & 15 deletions packages/komodo_defi_sdk/lib/src/pubkeys/pubkey_manager.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:komodo_defi_local_auth/komodo_defi_local_auth.dart';
import 'package:komodo_defi_sdk/src/_internal_exports.dart';
import 'package:komodo_defi_types/komodo_defi_type_utils.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

/// Manager responsible for handling pubkey operations across different assets
Expand All @@ -12,33 +13,20 @@ class PubkeyManager {

/// Get pubkeys for a given asset, handling HD/non-HD differences internally
Future<AssetPubkeys> getPubkeys(Asset asset) async {
final finalStatus = await _activationManager.activateAsset(asset).last;
if (finalStatus.isComplete && !finalStatus.isSuccess) {
throw StateError(
'Failed to activate asset ${asset.id.name}. ${finalStatus.toJson()}',
);
}

await retry(() => _activationManager.activateAsset(asset).last);
final strategy = await _resolvePubkeyStrategy(asset);
return strategy.getPubkeys(asset.id, _client);
}

/// Create a new pubkey for an asset if supported
Future<PubkeyInfo> createNewPubkey(Asset asset) async {
final activationStatus = await _activationManager.activateAsset(asset).last;
if (activationStatus.isComplete && !activationStatus.isSuccess) {
throw StateError(
'Failed to activate asset ${asset.id.name}. ${activationStatus.toJson()}',
);
}

await retry(() => _activationManager.activateAsset(asset).last);
final strategy = await _resolvePubkeyStrategy(asset);
if (!strategy.supportsMultipleAddresses) {
throw UnsupportedError(
'Asset ${asset.id.name} does not support multiple addresses',
);
}

return strategy.getNewAddress(asset.id, _client);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ class LegacyWithdrawalManager implements WithdrawalManager {
coin: result.coin,
toAddress: result.to.first,
fee: result.fee,
kmdRewardsEligible: result.kmdRewards != null &&
kmdRewardsEligible:
result.kmdRewards != null &&
Decimal.parse(result.kmdRewards!.amount) > Decimal.zero,
),
);
Expand All @@ -67,7 +68,8 @@ class LegacyWithdrawalManager implements WithdrawalManager {
coin: parameters.asset,
toAddress: parameters.toAddress,
fee: result.fee,
kmdRewardsEligible: result.kmdRewards != null &&
kmdRewardsEligible:
result.kmdRewards != null &&
Decimal.parse(result.kmdRewards!.amount) > Decimal.zero,
),
);
Expand Down Expand Up @@ -112,7 +114,7 @@ class LegacyWithdrawalManager implements WithdrawalManager {
}

return response.details as WithdrawResult;
} catch (e) {
} catch (e, s) {
if (e is WithdrawalException) {
rethrow;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:decimal/decimal.dart';
import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart';
import 'package:komodo_defi_sdk/src/_internal_exports.dart';
import 'package:komodo_defi_sdk/src/withdrawals/legacy_withdrawal_manager.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

/// Manages asset withdrawals using task-based API
Expand Down Expand Up @@ -42,6 +43,18 @@ class WithdrawalManager {
WithdrawParameters parameters,
) async {
try {
final asset =
_assetProvider.findAssetsByConfigId(parameters.asset).single;
final isTendermintProtocol = asset.protocol is TendermintProtocol;

// Tendermint assets are not yet supported by the task-based API
// and require a legacy implementation
if (isTendermintProtocol) {
final legacyManager = LegacyWithdrawalManager(_client);
return await legacyManager.previewWithdrawal(parameters);
}

// Use task-based approach for non-Tendermint assets
final stream = (await _client.rpc.withdraw.init(
parameters,
)).watch<WithdrawStatusResponse>(
Expand Down Expand Up @@ -86,6 +99,16 @@ class WithdrawalManager {
try {
final asset =
_assetProvider.findAssetsByConfigId(parameters.asset).single;
final isTendermintProtocol = asset.protocol is TendermintProtocol;

// Tendermint assets are not yet supported by the task-based API
// and require a legacy implementation
if (isTendermintProtocol) {
final legacyManager = LegacyWithdrawalManager(_client);
yield* legacyManager.withdraw(parameters);
return;
}

final activationStatus =
await _activationManager.activateAsset(asset).last;

Expand Down
3 changes: 3 additions & 0 deletions packages/komodo_defi_types/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
# Avoid committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock

# Flutter/Dart build output folder
build/
1 change: 1 addition & 0 deletions packages/komodo_defi_types/lib/komodo_defi_type_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export 'src/utils/json_type_utils.dart';
export 'src/utils/live_data.dart';
export 'src/utils/live_data_builder.dart';
export 'src/utils/mnemonic_validator.dart';
export 'src/utils/retry_utils.dart';
export 'src/utils/security_utils.dart';
10 changes: 10 additions & 0 deletions packages/komodo_defi_types/lib/src/transactions/fee_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ sealed class FeeInfo with _$FeeInfo {
gasPrice: Decimal.parse(json['gas_price'].toString()),
gasLimit: json['gas_limit'] as int,
);
// Legacy withdraw API returns "Tendermint" instead of "CosmosGas",
// so add this case for compatibility and as a fallback.
case 'Tendermint':
return FeeInfo.cosmosGas(
coin: json['coin'] as String? ?? '',
// The doc sometimes shows 0.05 as a number (double),
// so we convert it to string, then parse:
gasPrice: Decimal.parse(json['amount'].toString()),
gasLimit: json['gas_limit'] as int,
);
case 'CosmosGas':
return FeeInfo.cosmosGas(
coin: json['coin'] as String? ?? '',
Expand Down
153 changes: 153 additions & 0 deletions packages/komodo_defi_types/lib/src/utils/backoff_strategy.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import 'dart:math' as math;

import 'package:komodo_defi_types/src/utils/retry_utils.dart';

/// Base class for defining backoff strategies.
///
/// Implement this abstract class to create custom backoff strategies
/// for use with [retry].
abstract class BackoffStrategy {
/// Calculates the next delay based on the current attempt and state.
///
/// [attempt] is the current retry attempt (0-based)
/// [currentDelay] is the most recently used delay
Duration nextDelay(int attempt, Duration currentDelay);

/// Creates a deep copy of this strategy.
///
/// This is used by the retry function to avoid mutating the original strategy.
BackoffStrategy clone();
}

/// Implements exponential backoff with optional jitter.
///
/// This strategy doubles the delay after each attempt, capped by a maximum delay.
/// When jitter is enabled, it adds a random variance to prevent synchronized
/// retries in distributed systems.
class ExponentialBackoff implements BackoffStrategy {
/// Creates an exponential backoff strategy
///
/// [initialDelay] Starting delay between retries (default: 200ms)
/// [maxDelay] Maximum delay between retries (default: 5s)
/// [withJitter] Whether to add random jitter to prevent thundering herd (default: false)
/// [random] Optional random number generator for testing
ExponentialBackoff({
this.initialDelay = const Duration(milliseconds: 200),
this.maxDelay = const Duration(seconds: 5),
this.withJitter = false,
math.Random? random,
}) : _random = random ?? math.Random();

/// Initial delay duration before applying backoff
final Duration initialDelay;

/// Maximum delay duration to cap exponential growth
final Duration maxDelay;

/// Whether to add random jitter to the delay
final bool withJitter;

/// Random number generator for jitter calculation
final math.Random _random;

@override
Duration nextDelay(int attempt, Duration currentDelay) {
if (attempt == 0) {
return _applyJitter(initialDelay);
}

final nextDelay = currentDelay * 2;
final cappedDelay = nextDelay > maxDelay ? maxDelay : nextDelay;

return _applyJitter(cappedDelay);
}

/// Applies jitter to the delay if enabled
Duration _applyJitter(Duration delay) {
if (!withJitter) return delay;

final jitterFactor = 0.85 + (_random.nextDouble() * 0.3);
final jitteredMs = (delay.inMilliseconds * jitterFactor).round();

return Duration(milliseconds: jitteredMs);
}

@override
BackoffStrategy clone() {
return ExponentialBackoff(
initialDelay: initialDelay,
maxDelay: maxDelay,
withJitter: withJitter,
);
}
}

/// Implements a constant backoff strategy with fixed delay.
///
/// This strategy uses the same delay for all retry attempts.
class ConstantBackoff implements BackoffStrategy {
/// Creates a constant backoff strategy
///
/// [delay] Fixed delay between retries (default: 1s)
ConstantBackoff({
this.delay = const Duration(seconds: 1),
});

/// Fixed delay to use between retry attempts
final Duration delay;

@override
Duration nextDelay(int attempt, Duration currentDelay) {
return delay;
}

@override
BackoffStrategy clone() {
return ConstantBackoff(delay: delay);
}
}

/// Implements a linear backoff strategy.
///
/// This strategy increases the delay by a fixed amount after each attempt,
/// capped by a maximum delay.
class LinearBackoff implements BackoffStrategy {
/// Creates a linear backoff strategy
///
/// [initialDelay] Starting delay between retries (default: 200ms)
/// [increment] Amount to increase delay by after each attempt (default: 200ms)
/// [maxDelay] Maximum delay between retries (default: 5s)
LinearBackoff({
this.initialDelay = const Duration(milliseconds: 200),
this.increment = const Duration(milliseconds: 200),
this.maxDelay = const Duration(seconds: 5),
});

/// Initial delay duration
final Duration initialDelay;

/// Increment to add to the delay after each attempt
final Duration increment;

/// Maximum delay duration
final Duration maxDelay;

@override
Duration nextDelay(int attempt, Duration currentDelay) {
if (attempt == 0) {
return initialDelay;
}

final nextDelay = currentDelay + increment;
return nextDelay > maxDelay ? maxDelay : nextDelay;
}

@override
BackoffStrategy clone() {
return LinearBackoff(
initialDelay: initialDelay,
increment: increment,
maxDelay: maxDelay,
);
}
}
Loading
Loading