dev
dart 325 lines 10.3 KB
Raw
1 import 'dart:async';
2
3 import 'package:cake_wallet/evm/evm.dart';
4 import 'package:cake_wallet/exchange/exchange_provider_description.dart';
5 import 'package:cake_wallet/exchange/trade_state.dart';
6 import 'package:cw_core/crypto_currency.dart';
7 import 'package:cw_core/db/sqlite.dart';
8 import 'package:cw_core/format_amount.dart';
9 import 'package:cw_core/generate_name.dart';
10 import 'package:sqflite/sqflite.dart';
11
12 class Trade {
13 Trade({
14 this.internalId = 0,
15 required this.id,
16 required this.amount,
17 ExchangeProviderDescription? provider,
18 this.from,
19 this.to,
20 TradeState? state,
21 this.receiveAmount,
22 this.createdAt,
23 this.expiredAt,
24 this.inputAddress,
25 this.extraId,
26 this.outputTransaction,
27 this.refundAddress,
28 this.walletId,
29 this.payoutAddress,
30 this.toAddressExtraId,
31 this.password,
32 this.providerId,
33 this.providerName,
34 this.fromWalletAddress,
35 this.memo,
36 this.fee,
37 this.txId,
38 this.isRefund,
39 this.isSendAll,
40 this.router,
41 // The following fields are used for SwapXyz trades only
42 this.needToRegisterInSwapXyz,
43 this.sourceTokenAddress,
44 this.sourceTokenDecimals,
45 this.routerData,
46 this.routerValue,
47 this.routerChainId,
48 this.sourceTokenAmountRaw,
49 this.requiresTokenApproval,
50 this.chainId,
51 }) {
52 if (provider != null) providerRaw = provider.raw;
53 if (state != null) stateRaw = state.raw;
54 }
55
56 static const tableName = 'Trade';
57 static const selfIdColumn = 'tradeId';
58
59 static const boxName = 'Trades';
60 static const boxKey = 'tradesBoxKey';
61
62 static final StreamController<void> onChanged = StreamController<void>.broadcast();
63
64 int internalId;
65
66 String id;
67
68 int providerRaw = 0;
69
70 ExchangeProviderDescription get provider =>
71 ExchangeProviderDescription.deserialize(raw: providerRaw);
72
73 CryptoCurrency? from;
74 CryptoCurrency? to;
75
76 String stateRaw = '';
77
78 TradeState get state => TradeState.deserialize(raw: stateRaw);
79
80 DateTime? createdAt;
81 DateTime? expiredAt;
82 String amount;
83 String? receiveAmount;
84 String? inputAddress;
85 String? extraId;
86 String? outputTransaction;
87 String? refundAddress;
88 String? walletId;
89 String? payoutAddress;
90
91 // holds the receive address memo or destination tag that was passed for this trade
92 String? toAddressExtraId;
93 String? password;
94 String? providerId;
95 String? providerName;
96 String? fromWalletAddress;
97 String? memo;
98 String? txId;
99 bool? isRefund;
100 bool? isSendAll;
101 String? router;
102
103 // The following fields are used for SwapXyz trades only
104 bool? needToRegisterInSwapXyz;
105 String? sourceTokenAddress;
106 int? sourceTokenDecimals;
107 String? routerData;
108 String? routerValue;
109 int? routerChainId;
110 String? sourceTokenAmountRaw;
111 bool? requiresTokenApproval;
112
113 int? chainId;
114 double? fee;
115
116 String get chainName {
117 if (chainId == null) return '';
118
119 return evm!.getChainNameByChainId(chainId!).capitalized();
120 }
121
122 // ── SQLite CRUD ──────────────────────────────────────
123
124 Future<int> save() async {
125 final json = toSqliteMap();
126 if (json[selfIdColumn] == 0) {
127 json[selfIdColumn] = null;
128 }
129 internalId = await db!.insert(
130 tableName,
131 json,
132 conflictAlgorithm: ConflictAlgorithm.replace,
133 );
134 onChanged.add(null);
135 return internalId;
136 }
137
138 static Future<List<Trade>> getAll({String? orderBy}) async {
139 final list = await db!.query(
140 tableName,
141 orderBy: orderBy ?? 'createdAt DESC',
142 );
143 return List.generate(
144 list.length,
145 (i) => Trade.fromSqliteRow(list[i]),
146 );
147 }
148
149 static Future<Trade?> getByTradeId(String id) async {
150 final list = await db!.query(
151 tableName,
152 where: 'id = ?',
153 whereArgs: [id],
154 limit: 1,
155 );
156 if (list.isEmpty) return null;
157 return Trade.fromSqliteRow(list.first);
158 }
159
160 static Future<int> deleteTrade(Trade trade) async {
161 final rows = await db!.delete(
162 tableName,
163 where: '$selfIdColumn = ?',
164 whereArgs: [trade.internalId],
165 );
166 onChanged.add(null);
167 return rows;
168 }
169
170 // ── SQLite serialization ─────────────────────────────
171 void mergeFindTradeByIdResult(Trade updated) {
172 if (updated.stateRaw.isNotEmpty) stateRaw = updated.stateRaw;
173 if (createdAt == null && updated.createdAt != null) {
174 createdAt = updated.createdAt;
175 }
176 if (updated.expiredAt != null) expiredAt = updated.expiredAt;
177 if (updated.isRefund != null) isRefund = updated.isRefund;
178
179 if (updated.receiveAmount != null) receiveAmount = updated.receiveAmount;
180 if (updated.inputAddress != null) inputAddress = updated.inputAddress;
181 if (updated.extraId != null) extraId = updated.extraId;
182 if (updated.outputTransaction != null) {
183 outputTransaction = updated.outputTransaction;
184 }
185 if (updated.refundAddress != null) refundAddress = updated.refundAddress;
186 if (updated.payoutAddress != null) payoutAddress = updated.payoutAddress;
187 if (updated.password != null) password = updated.password;
188 if (updated.providerId != null) providerId = updated.providerId;
189 if (updated.providerName != null) providerName = updated.providerName;
190 if (updated.memo != null) memo = updated.memo;
191 if (updated.txId != null) txId = updated.txId;
192 }
193
194 Map<String, dynamic> toSqliteMap() {
195 return <String, dynamic>{
196 selfIdColumn: internalId,
197 'id': id,
198 'providerRaw': providerRaw,
199 'fromTitle': from?.title,
200 'fromName': from?.name,
201 'fromTag': from?.tag,
202 'fromFullName': from?.fullName,
203 'fromDecimals': from?.decimals,
204 'fromRaw': from?.raw,
205 'fromIconPath': from?.iconPath,
206 'fromFlatIconPath': from?.flatIconPath,
207 'fromChainIconPath': from?.chainIconPath,
208 'toTitle': to?.title,
209 'toName': to?.name,
210 'toTag': to?.tag,
211 'toFullName': to?.fullName,
212 'toDecimals': to?.decimals,
213 'toRaw': to?.raw,
214 'toIconPath': to?.iconPath,
215 'toFlatIconPath': to?.flatIconPath,
216 'toChainIconPath': to?.chainIconPath,
217 'stateRaw': stateRaw,
218 'createdAt': createdAt?.millisecondsSinceEpoch,
219 'expiredAt': expiredAt?.millisecondsSinceEpoch,
220 'amount': amount,
221 'receiveAmount': receiveAmount,
222 'inputAddress': inputAddress,
223 'extraId': extraId,
224 'outputTransaction': outputTransaction,
225 'refundAddress': refundAddress,
226 'walletId': walletId,
227 'payoutAddress': payoutAddress,
228 'toAddressExtraId': toAddressExtraId,
229 'password': password,
230 'providerId': providerId,
231 'providerName': providerName,
232 'fromWalletAddress': fromWalletAddress,
233 'memo': memo,
234 'txId': txId,
235 'isRefund': isRefund == true ? 1 : 0,
236 'isSendAll': isSendAll == true ? 1 : 0,
237 'router': router,
238 'needToRegisterInSwapXyz': needToRegisterInSwapXyz == true ? 1 : 0,
239 'sourceTokenAddress': sourceTokenAddress,
240 'sourceTokenDecimals': sourceTokenDecimals,
241 'routerData': routerData,
242 'routerValue': routerValue,
243 'routerChainId': routerChainId,
244 'sourceTokenAmountRaw': sourceTokenAmountRaw,
245 'requiresTokenApproval': requiresTokenApproval == true ? 1 : 0,
246 'chainId': chainId,
247 'fee': fee,
248 };
249 }
250
251 factory Trade.fromSqliteRow(Map<String, dynamic> row) {
252 final trade = Trade(
253 id: row['id'] as String? ?? '',
254 amount: row['amount'] as String? ?? '',
255 receiveAmount: row['receiveAmount'] as String?,
256 createdAt: row['createdAt'] != null
257 ? DateTime.fromMillisecondsSinceEpoch(
258 row['createdAt'] as int,
259 )
260 : null,
261 expiredAt: row['expiredAt'] != null
262 ? DateTime.fromMillisecondsSinceEpoch(
263 row['expiredAt'] as int,
264 )
265 : null,
266 inputAddress: row['inputAddress'] as String?,
267 extraId: row['extraId'] as String?,
268 outputTransaction: row['outputTransaction'] as String?,
269 refundAddress: row['refundAddress'] as String?,
270 walletId: row['walletId'] as String?,
271 payoutAddress: row['payoutAddress'] as String?,
272 toAddressExtraId: row['toAddressExtraId'] as String?,
273 password: row['password'] as String?,
274 providerId: row['providerId'] as String?,
275 providerName: row['providerName'] as String?,
276 fromWalletAddress: row['fromWalletAddress'] as String?,
277 memo: row['memo'] as String?,
278 fee: row['fee'] as double?,
279 txId: row['txId'] as String?,
280 isRefund: (row['isRefund'] as int?) == 1,
281 isSendAll: (row['isSendAll'] as int?) == 1,
282 router: row['router'] as String?,
283 from: _currencyFromRow(row, 'from'),
284 to: _currencyFromRow(row, 'to'),
285 needToRegisterInSwapXyz: (row['needToRegisterInSwapXyz'] as int?) == 1,
286 sourceTokenAddress: row['sourceTokenAddress'] as String?,
287 sourceTokenDecimals: row['sourceTokenDecimals'] as int?,
288 routerData: row['routerData'] as String?,
289 routerValue: row['routerValue'] as String?,
290 routerChainId: row['routerChainId'] as int?,
291 sourceTokenAmountRaw: row['sourceTokenAmountRaw'] as String?,
292 requiresTokenApproval: (row['requiresTokenApproval'] as int?) == 1,
293 chainId: row['chainId'] as int?,
294 );
295 trade.internalId = row[selfIdColumn] as int? ?? 0;
296 trade.providerRaw = row['providerRaw'] as int? ?? 0;
297 trade.stateRaw = row['stateRaw'] as String? ?? '';
298 return trade;
299 }
300
301 static CryptoCurrency? _currencyFromRow(Map<String, dynamic> row, String prefix) {
302 final title = row['${prefix}Title'] as String?;
303 if (title == null || title.isEmpty) return null;
304
305 final tag = row['${prefix}Tag'] as String?;
306
307 final live = CryptoCurrency.safeParseCurrencyFromString(title, tag: tag);
308 if (live != null) return live;
309
310 return CryptoCurrency(
311 title: title,
312 name: row['${prefix}Name'] as String? ?? '',
313 tag: tag,
314 fullName: row['${prefix}FullName'] as String?,
315 decimals: row['${prefix}Decimals'] as int? ?? 1,
316 raw: row['${prefix}Raw'] as int? ?? -1,
317 iconPath: row['${prefix}IconPath'] as String?,
318 flatIconPath: row['${prefix}FlatIconPath'] as String?,
319 chainIconPath: row['${prefix}ChainIconPath'] as String?,
320 );
321 }
322
323 String amountFormatted() => formatAmount(amount);
324 String receiveAmountFormatted() => formatAmount(receiveAmount ?? '');
325 }