feat: Add memo support for swap (#3229)

* feat: Add memo support for swap

* fix: Error on swap page select receiver bottomsheet when picking receiveing currency that's not a wallet type

* feat: exclude providers that do not support memo when receive currency needs it, also show passed memo in confirmation and trade history sheets

* fix: overflow for destination tag on swap confirmation

* Update lib/view_model/exchange/exchange_view_model.dart [skip ci]

* Update lib/exchange/provider/trocador_exchange_provider.dart [skip ci]

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
David Adegoke
2026-05-20 20:09:34 +02:00
committed by GitHub
co-authored by Omar Hatem
parent d10f2f2a60
commit 1d652c76aa
56 changed files with 330 additions and 55 deletions
@@ -51,6 +51,9 @@ class ChainflipExchangeProvider extends ExchangeProvider {
@override
bool get supportsFixedRate => false;
@override
bool get supportsMemoOrDestinationTag => false;
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.chainflip;
@@ -189,6 +189,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
if (!isFixedRateMode) 'fromAmount': request.fromAmount,
if (isFixedRateMode) 'toAmount': request.toAmount,
'address': request.toAddress,
if (request.toAddressExtraId.isNotEmpty) 'extraId': request.toAddressExtraId,
'flow': _getFlow(isFixedRateMode),
'type': type,
'refundAddress': request.refundAddress,
@@ -248,6 +249,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
state: TradeState.created,
payoutAddress: payoutAddress,
isSendAll: isSendAll,
toAddressExtraId: request.toAddressExtraId,
);
}
@@ -19,6 +19,8 @@ abstract class ExchangeProvider {
bool get supportsOnionAddress => false;
bool get supportsMemoOrDestinationTag => true;
@override
String toString() => title;
@@ -198,6 +198,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
'networkFrom': _networkFor(request.fromCurrency),
'networkTo': _networkFor(request.toCurrency),
'withdrawalAddress': await _normalizeAddress(request.toAddress),
if (request.toAddressExtraId.isNotEmpty)
'withdrawalExtraId': request.toAddressExtraId,
'refundAddress': await _normalizeAddress(request.refundAddress),
'rateType': _getRateType(isFixedRateMode),
'apiToken': apiKey,
@@ -315,6 +317,7 @@ class ExolixExchangeProvider extends ExchangeProvider {
state: TradeState.created,
payoutAddress: payoutAddress,
isSendAll: isSendAll,
toAddressExtraId: request.toAddressExtraId,
);
}
@@ -41,6 +41,9 @@ class JupiterExchangeProvider extends ExchangeProvider {
@override
bool get supportsFixedRate => false; // Jupiter doesn't support fixed rate
@override
bool get supportsMemoOrDestinationTag => false;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.jupiter;
@@ -174,7 +174,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
if (!isFixedRateMode) 'deposit_amount': request.fromAmount.toString(),
'withdrawal': withdrawalAddress,
if (isFixedRateMode) 'withdrawal_amount': request.toAmount.toString(),
'withdrawal_extra_id': '',
'withdrawal_extra_id': request.toAddressExtraId,
'return': returnAddress,
'rate_id': rateId,
if (networkFrom != null) 'network_from': networkFrom,
@@ -302,6 +302,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
expiredAt: expiredAt,
extraId: extraId,
isSendAll: isSendAll,
toAddressExtraId: request.toAddressExtraId,
);
} catch (e, s) {
ExchangeProviderLogger.logError(
@@ -88,6 +88,9 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
@override
bool get supportsFixedRate => true;
@override
bool get supportsMemoOrDestinationTag => false;
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.nearIntents;
@@ -216,6 +216,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
final body = {
'affiliateId': affiliateId,
'settleAddress': request.toAddress,
if (request.toAddressExtraId.isNotEmpty) 'settleMemo': request.toAddressExtraId,
'refundAddress': request.refundAddress,
};
@@ -336,6 +337,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
createdAt: DateTime.now(),
isSendAll: isSendAll,
extraId: depositMemo,
toAddressExtraId: request.toAddressExtraId,
);
}
@@ -180,7 +180,8 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
"amount": request.fromAmount,
"fixed": isFixedRateMode,
"user_refund_address": _normalizeAddress(request.refundAddress),
"address_to": _normalizeAddress(request.toAddress)
"address_to": _normalizeAddress(request.toAddress),
if (request.toAddressExtraId.isNotEmpty) "extra_id_to": request.toAddressExtraId,
};
final uri = Uri.https(apiAuthority, createExchangePath, params);
@@ -289,6 +290,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
payoutAddress: payoutAddress,
createdAt: DateTime.now(),
isSendAll: isSendAll,
toAddressExtraId: request.toAddressExtraId,
);
}
@@ -186,6 +186,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
'amount':
isFixedRateMode ? double.parse(request.toAmount) : double.parse(request.fromAmount),
'address': _normalizeAddress(request.toAddress),
if (request.toAddressExtraId.isNotEmpty) 'extra_id': request.toAddressExtraId,
'refund_address': _normalizeAddress(request.refundAddress),
'additional_fee_percent': _additionalFeePercent,
};
@@ -299,6 +300,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
expiredAt: expiredAt,
extraId: extraId,
isSendAll: isSendAll,
toAddressExtraId: request.toAddressExtraId,
);
} catch (e, s) {
ExchangeProviderLogger.logError(
@@ -54,6 +54,9 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
@override
bool get supportsFixedRate => false;
@override
bool get supportsMemoOrDestinationTag => false;
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.swapsXyz;
@@ -37,6 +37,9 @@ class SwapTradeExchangeProvider extends ExchangeProvider {
@override
bool get supportsFixedRate => false;
@override
bool get supportsMemoOrDestinationTag => false;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.swapTrade;
@@ -36,6 +36,9 @@ class ThorChainExchangeProvider extends ExchangeProvider {
@override
bool get supportsFixedRate => false;
@override
bool get supportsMemoOrDestinationTag => false;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.thorChain;
@@ -235,6 +235,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
if (!isFixedRateMode) 'amount_from': request.fromAmount,
if (isFixedRateMode) 'amount_to': request.toAmount,
'address': request.toAddress,
if (request.toAddressExtraId.isNotEmpty) 'address_memo': request.toAddressExtraId,
'refund': request.refundAddress,
'refund_memo': '0',
};
@@ -392,6 +393,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
payoutAddress: payoutAddress,
isSendAll: isSendAll,
extraId: addressProviderMemo,
toAddressExtraId: request.toAddressExtraId,
);
}
@@ -321,6 +321,7 @@ class XOSwapExchangeProvider extends ExchangeProvider {
'fromAddress': request.refundAddress,
'toAmount': request.toAmount,
'toAddress': request.toAddress,
if (request.toAddressExtraId.isNotEmpty) 'toAddressTag': request.toAddressExtraId,
'pairId': pairId,
};
@@ -424,6 +425,7 @@ class XOSwapExchangeProvider extends ExchangeProvider {
payoutAddress: payoutAddress,
extraId: extraId,
isSendAll: isSendAll,
toAddressExtraId: request.toAddressExtraId,
);
} catch (e, s) {
ExchangeProviderLogger.logError(
+6
View File
@@ -27,6 +27,7 @@ class Trade {
this.refundAddress,
this.walletId,
this.payoutAddress,
this.toAddressExtraId,
this.password,
this.providerId,
this.providerName,
@@ -86,6 +87,9 @@ class Trade {
String? refundAddress;
String? walletId;
String? payoutAddress;
// holds the receive address memo or destination tag that was passed for this trade
String? toAddressExtraId;
String? password;
String? providerId;
String? providerName;
@@ -221,6 +225,7 @@ class Trade {
'refundAddress': refundAddress,
'walletId': walletId,
'payoutAddress': payoutAddress,
'toAddressExtraId': toAddressExtraId,
'password': password,
'providerId': providerId,
'providerName': providerName,
@@ -264,6 +269,7 @@ class Trade {
refundAddress: row['refundAddress'] as String?,
walletId: row['walletId'] as String?,
payoutAddress: row['payoutAddress'] as String?,
toAddressExtraId: row['toAddressExtraId'] as String?,
password: row['password'] as String?,
providerId: row['providerId'] as String?,
providerName: row['providerName'] as String?,
+2
View File
@@ -8,6 +8,7 @@ class TradeRequest {
required this.refundAddress,
required this.fromAmount,
this.toAmount = '',
this.toAddressExtraId = '',
this.isFixedRate = false});
final CryptoCurrency fromCurrency;
@@ -16,5 +17,6 @@ class TradeRequest {
final String refundAddress;
final String fromAmount;
final String toAmount;
final String toAddressExtraId;
final bool isFixedRate;
}