mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-18 05:51:34 +02:00
The big one. Touches 760+ files. Locks in the user's decision to break import-line compatibility with upstream cake-tech/cake_wallet in exchange for a binary named hash_wallet on every platform. Changes: - pubspec_description.yaml: project name cake_wallet -> hash_wallet - 762 Dart files: every 'package:cake_wallet/...' import rewritten to 'package:hash_wallet/...' (lib/, cw_*/lib/, tool/, test/, integration_test/) - linux/CMakeLists.txt: BINARY_NAME cake_wallet -> hash_wallet - windows/CMakeLists.txt: project + BINARY_NAME (CakeWallet -> HashWallet) - windows/runner/Runner.rc: company / file / product strings to Hash Wallet + Such Software LLC copyright - macos/Runner.xcodeproj/project_base.pbxproj: cake_wallet_new.app refs -> hash_wallet.app (macOS output bundle name) - com.suchsoftware.HashWallet.yml (Flatpak): all binary path refs cake_wallet -> hash_wallet (e.g. /app/hash_wallet/hash_wallet) - linux/com.suchsoftware.HashWallet.desktop: Exec=hash_wallet - scripts/android/copy_monero_deps.sh: CW_DIR target path - configure_hash_wallet.sh: error message typo (old filename) INTENTIONALLY UNCHANGED: - cw_* subpackage names (cw_core, cw_bitcoin, cw_wownero, etc.) — they remain as the historical 'cake wallet' naming; renaming them would cascade through every sub-package's internal structure - Method channel name 'com.cake_wallet/native_utils' in iOS/macOS/Android AppDelegates and MainActivity — it's a string identifier that has to match the Dart side exactly. Renaming requires both-sides change; it's internal-only and not user-visible. - ghcr.io/cake-tech/cake_wallet:debian13-... docker image references in build scripts — that's Cake Labs' published image we use as the build environment, not ours. User data directory now follows the new binary name: ~/.local/share/hash_wallet/ (was ~/.local/share/cake_wallet/) ~/.config/hash_wallet/ (was ~/.config/cake_wallet/) Existing users on the old name will need migration if any exist.
320 lines
10 KiB
Dart
320 lines
10 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:hash_wallet/evm/evm.dart';
|
|
import 'package:hash_wallet/exchange/exchange_provider_description.dart';
|
|
import 'package:hash_wallet/exchange/trade_state.dart';
|
|
import 'package:cw_core/crypto_currency.dart';
|
|
import 'package:cw_core/db/sqlite.dart';
|
|
import 'package:cw_core/format_amount.dart';
|
|
import 'package:cw_core/generate_name.dart';
|
|
import 'package:sqflite/sqflite.dart';
|
|
|
|
class Trade {
|
|
Trade({
|
|
this.internalId = 0,
|
|
required this.id,
|
|
required this.amount,
|
|
ExchangeProviderDescription? provider,
|
|
this.from,
|
|
this.to,
|
|
TradeState? state,
|
|
this.receiveAmount,
|
|
this.createdAt,
|
|
this.expiredAt,
|
|
this.inputAddress,
|
|
this.extraId,
|
|
this.outputTransaction,
|
|
this.refundAddress,
|
|
this.walletId,
|
|
this.payoutAddress,
|
|
this.password,
|
|
this.providerId,
|
|
this.providerName,
|
|
this.fromWalletAddress,
|
|
this.memo,
|
|
this.fee,
|
|
this.txId,
|
|
this.isRefund,
|
|
this.isSendAll,
|
|
this.router,
|
|
// The following fields are used for SwapXyz trades only
|
|
this.needToRegisterInSwapXyz,
|
|
this.sourceTokenAddress,
|
|
this.sourceTokenDecimals,
|
|
this.routerData,
|
|
this.routerValue,
|
|
this.routerChainId,
|
|
this.sourceTokenAmountRaw,
|
|
this.requiresTokenApproval,
|
|
this.chainId,
|
|
}) {
|
|
if (provider != null) providerRaw = provider.raw;
|
|
if (state != null) stateRaw = state.raw;
|
|
}
|
|
|
|
static const tableName = 'Trade';
|
|
static const selfIdColumn = 'tradeId';
|
|
|
|
static const boxName = 'Trades';
|
|
static const boxKey = 'tradesBoxKey';
|
|
|
|
static final StreamController<void> onChanged = StreamController<void>.broadcast();
|
|
|
|
int internalId;
|
|
|
|
String id;
|
|
|
|
int providerRaw = 0;
|
|
|
|
ExchangeProviderDescription get provider =>
|
|
ExchangeProviderDescription.deserialize(raw: providerRaw);
|
|
|
|
CryptoCurrency? from;
|
|
CryptoCurrency? to;
|
|
|
|
String stateRaw = '';
|
|
|
|
TradeState get state => TradeState.deserialize(raw: stateRaw);
|
|
|
|
DateTime? createdAt;
|
|
DateTime? expiredAt;
|
|
String amount;
|
|
String? receiveAmount;
|
|
String? inputAddress;
|
|
String? extraId;
|
|
String? outputTransaction;
|
|
String? refundAddress;
|
|
String? walletId;
|
|
String? payoutAddress;
|
|
String? password;
|
|
String? providerId;
|
|
String? providerName;
|
|
String? fromWalletAddress;
|
|
String? memo;
|
|
String? txId;
|
|
bool? isRefund;
|
|
bool? isSendAll;
|
|
String? router;
|
|
|
|
// The following fields are used for SwapXyz trades only
|
|
bool? needToRegisterInSwapXyz;
|
|
String? sourceTokenAddress;
|
|
int? sourceTokenDecimals;
|
|
String? routerData;
|
|
String? routerValue;
|
|
int? routerChainId;
|
|
String? sourceTokenAmountRaw;
|
|
bool? requiresTokenApproval;
|
|
|
|
int? chainId;
|
|
double? fee;
|
|
|
|
String get chainName {
|
|
if (chainId == null) return '';
|
|
|
|
return evm!.getChainNameByChainId(chainId!).capitalized();
|
|
}
|
|
|
|
// ── SQLite CRUD ──────────────────────────────────────
|
|
|
|
Future<int> save() async {
|
|
final json = toSqliteMap();
|
|
if (json[selfIdColumn] == 0) {
|
|
json[selfIdColumn] = null;
|
|
}
|
|
internalId = await db!.insert(
|
|
tableName,
|
|
json,
|
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
);
|
|
onChanged.add(null);
|
|
return internalId;
|
|
}
|
|
|
|
static Future<List<Trade>> getAll({String? orderBy}) async {
|
|
final list = await db!.query(
|
|
tableName,
|
|
orderBy: orderBy ?? 'createdAt DESC',
|
|
);
|
|
return List.generate(
|
|
list.length,
|
|
(i) => Trade.fromSqliteRow(list[i]),
|
|
);
|
|
}
|
|
|
|
static Future<Trade?> getByTradeId(String id) async {
|
|
final list = await db!.query(
|
|
tableName,
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
limit: 1,
|
|
);
|
|
if (list.isEmpty) return null;
|
|
return Trade.fromSqliteRow(list.first);
|
|
}
|
|
|
|
static Future<int> deleteTrade(Trade trade) async {
|
|
final rows = await db!.delete(
|
|
tableName,
|
|
where: '$selfIdColumn = ?',
|
|
whereArgs: [trade.internalId],
|
|
);
|
|
onChanged.add(null);
|
|
return rows;
|
|
}
|
|
|
|
// ── SQLite serialization ─────────────────────────────
|
|
void mergeFindTradeByIdResult(Trade updated) {
|
|
if (updated.stateRaw.isNotEmpty) stateRaw = updated.stateRaw;
|
|
if (createdAt == null && updated.createdAt != null) {
|
|
createdAt = updated.createdAt;
|
|
}
|
|
if (updated.expiredAt != null) expiredAt = updated.expiredAt;
|
|
if (updated.isRefund != null) isRefund = updated.isRefund;
|
|
|
|
if (updated.receiveAmount != null) receiveAmount = updated.receiveAmount;
|
|
if (updated.inputAddress != null) inputAddress = updated.inputAddress;
|
|
if (updated.extraId != null) extraId = updated.extraId;
|
|
if (updated.outputTransaction != null) {
|
|
outputTransaction = updated.outputTransaction;
|
|
}
|
|
if (updated.refundAddress != null) refundAddress = updated.refundAddress;
|
|
if (updated.payoutAddress != null) payoutAddress = updated.payoutAddress;
|
|
if (updated.password != null) password = updated.password;
|
|
if (updated.providerId != null) providerId = updated.providerId;
|
|
if (updated.providerName != null) providerName = updated.providerName;
|
|
if (updated.memo != null) memo = updated.memo;
|
|
if (updated.txId != null) txId = updated.txId;
|
|
}
|
|
|
|
Map<String, dynamic> toSqliteMap() {
|
|
return <String, dynamic>{
|
|
selfIdColumn: internalId,
|
|
'id': id,
|
|
'providerRaw': providerRaw,
|
|
'fromTitle': from?.title,
|
|
'fromName': from?.name,
|
|
'fromTag': from?.tag,
|
|
'fromFullName': from?.fullName,
|
|
'fromDecimals': from?.decimals,
|
|
'fromRaw': from?.raw,
|
|
'fromIconPath': from?.iconPath,
|
|
'fromFlatIconPath': from?.flatIconPath,
|
|
'fromChainIconPath': from?.chainIconPath,
|
|
'toTitle': to?.title,
|
|
'toName': to?.name,
|
|
'toTag': to?.tag,
|
|
'toFullName': to?.fullName,
|
|
'toDecimals': to?.decimals,
|
|
'toRaw': to?.raw,
|
|
'toIconPath': to?.iconPath,
|
|
'toFlatIconPath': to?.flatIconPath,
|
|
'toChainIconPath': to?.chainIconPath,
|
|
'stateRaw': stateRaw,
|
|
'createdAt': createdAt?.millisecondsSinceEpoch,
|
|
'expiredAt': expiredAt?.millisecondsSinceEpoch,
|
|
'amount': amount,
|
|
'receiveAmount': receiveAmount,
|
|
'inputAddress': inputAddress,
|
|
'extraId': extraId,
|
|
'outputTransaction': outputTransaction,
|
|
'refundAddress': refundAddress,
|
|
'walletId': walletId,
|
|
'payoutAddress': payoutAddress,
|
|
'password': password,
|
|
'providerId': providerId,
|
|
'providerName': providerName,
|
|
'fromWalletAddress': fromWalletAddress,
|
|
'memo': memo,
|
|
'txId': txId,
|
|
'isRefund': isRefund == true ? 1 : 0,
|
|
'isSendAll': isSendAll == true ? 1 : 0,
|
|
'router': router,
|
|
'needToRegisterInSwapXyz': needToRegisterInSwapXyz == true ? 1 : 0,
|
|
'sourceTokenAddress': sourceTokenAddress,
|
|
'sourceTokenDecimals': sourceTokenDecimals,
|
|
'routerData': routerData,
|
|
'routerValue': routerValue,
|
|
'routerChainId': routerChainId,
|
|
'sourceTokenAmountRaw': sourceTokenAmountRaw,
|
|
'requiresTokenApproval': requiresTokenApproval == true ? 1 : 0,
|
|
'chainId': chainId,
|
|
'fee': fee,
|
|
};
|
|
}
|
|
|
|
factory Trade.fromSqliteRow(Map<String, dynamic> row) {
|
|
final trade = Trade(
|
|
id: row['id'] as String? ?? '',
|
|
amount: row['amount'] as String? ?? '',
|
|
receiveAmount: row['receiveAmount'] as String?,
|
|
createdAt: row['createdAt'] != null
|
|
? DateTime.fromMillisecondsSinceEpoch(
|
|
row['createdAt'] as int,
|
|
)
|
|
: null,
|
|
expiredAt: row['expiredAt'] != null
|
|
? DateTime.fromMillisecondsSinceEpoch(
|
|
row['expiredAt'] as int,
|
|
)
|
|
: null,
|
|
inputAddress: row['inputAddress'] as String?,
|
|
extraId: row['extraId'] as String?,
|
|
outputTransaction: row['outputTransaction'] as String?,
|
|
refundAddress: row['refundAddress'] as String?,
|
|
walletId: row['walletId'] as String?,
|
|
payoutAddress: row['payoutAddress'] as String?,
|
|
password: row['password'] as String?,
|
|
providerId: row['providerId'] as String?,
|
|
providerName: row['providerName'] as String?,
|
|
fromWalletAddress: row['fromWalletAddress'] as String?,
|
|
memo: row['memo'] as String?,
|
|
fee: row['fee'] as double?,
|
|
txId: row['txId'] as String?,
|
|
isRefund: (row['isRefund'] as int?) == 1,
|
|
isSendAll: (row['isSendAll'] as int?) == 1,
|
|
router: row['router'] as String?,
|
|
from: _currencyFromRow(row, 'from'),
|
|
to: _currencyFromRow(row, 'to'),
|
|
needToRegisterInSwapXyz: (row['needToRegisterInSwapXyz'] as int?) == 1,
|
|
sourceTokenAddress: row['sourceTokenAddress'] as String?,
|
|
sourceTokenDecimals: row['sourceTokenDecimals'] as int?,
|
|
routerData: row['routerData'] as String?,
|
|
routerValue: row['routerValue'] as String?,
|
|
routerChainId: row['routerChainId'] as int?,
|
|
sourceTokenAmountRaw: row['sourceTokenAmountRaw'] as String?,
|
|
requiresTokenApproval: (row['requiresTokenApproval'] as int?) == 1,
|
|
chainId: row['chainId'] as int?,
|
|
);
|
|
trade.internalId = row[selfIdColumn] as int? ?? 0;
|
|
trade.providerRaw = row['providerRaw'] as int? ?? 0;
|
|
trade.stateRaw = row['stateRaw'] as String? ?? '';
|
|
return trade;
|
|
}
|
|
|
|
static CryptoCurrency? _currencyFromRow(Map<String, dynamic> row, String prefix) {
|
|
final title = row['${prefix}Title'] as String?;
|
|
if (title == null || title.isEmpty) return null;
|
|
|
|
final tag = row['${prefix}Tag'] as String?;
|
|
|
|
final live = CryptoCurrency.safeParseCurrencyFromString(title, tag: tag);
|
|
if (live != null) return live;
|
|
|
|
return CryptoCurrency(
|
|
title: title,
|
|
name: row['${prefix}Name'] as String? ?? '',
|
|
tag: tag,
|
|
fullName: row['${prefix}FullName'] as String?,
|
|
decimals: row['${prefix}Decimals'] as int? ?? 1,
|
|
raw: row['${prefix}Raw'] as int? ?? -1,
|
|
iconPath: row['${prefix}IconPath'] as String?,
|
|
flatIconPath: row['${prefix}FlatIconPath'] as String?,
|
|
chainIconPath: row['${prefix}ChainIconPath'] as String?,
|
|
);
|
|
}
|
|
|
|
String amountFormatted() => formatAmount(amount);
|
|
String receiveAmountFormatted() => formatAmount(receiveAmount ?? '');
|
|
}
|