mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-28 06:28:41 +01:00
v6.5.0 Release Candidate (#3707)
* v6.5.0 Release Candidate Added things: - Account education - Account hiding - OnRamper fix - iOS send report fix - Bitbox fix - lightning toggle fix * v6.5.0 Release Candidate Added things: - Account education - Account hiding - OnRamper fix - iOS send report fix - Bitbox fix - lightning toggle fix * v6.5.0 Release Candidate Added things: - Account education - Account hiding - OnRamper fix - iOS send report fix - Bitbox fix - lightning toggle fix * v6.5.0 Release Candidate Added things: - Account education - Account hiding - OnRamper fix - iOS send report fix - Bitbox fix - lightning toggle fix * undo remove decred [skip ci]
This commit is contained in:
@@ -27,6 +27,8 @@ class TronClient {
|
||||
// This is an internal tracker, so we don't have to "refetch".
|
||||
int _nativeTxEstimatedFee = 0;
|
||||
|
||||
static const _historyRequestTimeout = Duration(seconds: 30);
|
||||
|
||||
Future<List<TronTransactionModel>> fetchTransactions(String address,
|
||||
{String? contractAddress}) async {
|
||||
try {
|
||||
@@ -43,7 +45,7 @@ class TronClient {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
);
|
||||
).timeout(_historyRequestTimeout);
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 &&
|
||||
@@ -76,7 +78,7 @@ class TronClient {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
);
|
||||
).timeout(_historyRequestTimeout);
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 &&
|
||||
|
||||
@@ -82,6 +82,8 @@ class TronTransactionModel {
|
||||
|
||||
String? get contractAddress => contracts?.first.parameter?.value?.contractAddress;
|
||||
|
||||
bool get isTrc20TransferCall => contracts?.first.parameter?.value?.isTrc20TransferCall ?? false;
|
||||
|
||||
TronTransactionModel({
|
||||
this.ret,
|
||||
this.txID,
|
||||
@@ -147,29 +149,58 @@ class Value {
|
||||
String? ownerAddress;
|
||||
String? contractAddress;
|
||||
int? amount;
|
||||
int? callValue;
|
||||
String? toAddress;
|
||||
String? assetName;
|
||||
|
||||
/// Function selector of TRC20 `transfer(address,uint256)`.
|
||||
static const _transferSelector = "a9059cbb";
|
||||
|
||||
/// Hex length of `transfer` calldata: the selector plus two 32-byte arguments.
|
||||
static const _transferCallDataLength = 8 + 64 + 64;
|
||||
|
||||
String? get _callData {
|
||||
final callData = data?.toLowerCase();
|
||||
if (callData != null && callData.startsWith("0x")) {
|
||||
return callData.substring(2);
|
||||
}
|
||||
|
||||
return callData;
|
||||
}
|
||||
|
||||
/// Only a TRC20 `transfer` carries a receiver and a token amount in its calldata.
|
||||
/// Any other contract call (approve, swaps, deposit(), claim(), ...) has a different layout.
|
||||
bool get isTrc20TransferCall {
|
||||
final callData = _callData;
|
||||
|
||||
return contractAddress != null &&
|
||||
callData != null &&
|
||||
callData.length >= _transferCallDataLength &&
|
||||
callData.startsWith(_transferSelector);
|
||||
}
|
||||
|
||||
//Getters to extract address for tron transactions
|
||||
/// If the contract address is null, it returns the toAddress
|
||||
/// If it's not null, it decodes the data field and gets the receiver address.
|
||||
/// For a TRC20 transfer, it decodes the data field and gets the receiver address.
|
||||
/// For any other contract call, it returns the called contract.
|
||||
String? get receiverAddress {
|
||||
if (contractAddress == null) return toAddress;
|
||||
|
||||
if (data == null) return null;
|
||||
if (!isTrc20TransferCall) return contractAddress;
|
||||
|
||||
return _decodeAddressFromEncodedDataField(data!);
|
||||
return _decodeAddressFromEncodedDataField(_callData!);
|
||||
}
|
||||
|
||||
//Getters to extract amount for tron transactions
|
||||
/// If the contract address is null, it returns the amount
|
||||
/// If it's not null, it decodes the data field and gets the tx amount.
|
||||
/// For a TRC20 transfer, it decodes the data field and gets the token amount.
|
||||
/// For any other contract call, it returns the TRX sent along with the call.
|
||||
BigInt? get txAmount {
|
||||
if (contractAddress == null) return BigInt.from(amount ?? 0);
|
||||
|
||||
if (data == null) return null;
|
||||
if (!isTrc20TransferCall) return BigInt.from(callValue ?? 0);
|
||||
|
||||
return _decodeAmountInvolvedFromEncodedDataField(data!);
|
||||
return _decodeAmountInvolvedFromEncodedDataField(_callData!);
|
||||
}
|
||||
|
||||
Value(
|
||||
@@ -177,6 +208,7 @@ class Value {
|
||||
this.ownerAddress,
|
||||
this.contractAddress,
|
||||
this.amount,
|
||||
this.callValue,
|
||||
this.toAddress,
|
||||
this.assetName});
|
||||
|
||||
@@ -185,6 +217,7 @@ class Value {
|
||||
ownerAddress = json['owner_address'];
|
||||
contractAddress = json['contract_address'];
|
||||
amount = json['amount'];
|
||||
callValue = int.tryParse(json['call_value']?.toString() ?? "");
|
||||
toAddress = json['to_address'];
|
||||
assetName = json['asset_name'];
|
||||
}
|
||||
@@ -192,7 +225,7 @@ class Value {
|
||||
/// To get the address from the encoded data field
|
||||
String _decodeAddressFromEncodedDataField(String output) {
|
||||
// To get the receiver address from the encoded params
|
||||
output = output.replaceFirst('0x', '').substring(8);
|
||||
output = output.substring(8);
|
||||
final abiCoder = ABICoder.fromType('address');
|
||||
final decoded = abiCoder.decode(AbiParameter.bytes, hex.decode(output));
|
||||
final tronAddress = TronAddress.fromEthAddress((decoded.result as ETHAddress).toBytes());
|
||||
@@ -200,13 +233,8 @@ class Value {
|
||||
return tronAddress.toString();
|
||||
}
|
||||
|
||||
/// To get the amount from the encoded data field
|
||||
BigInt _decodeAmountInvolvedFromEncodedDataField(String output) {
|
||||
output = output.replaceFirst('0x', '').substring(72);
|
||||
final amountAbiCoder = ABICoder.fromType('uint256');
|
||||
final decodedA = amountAbiCoder.decode(AbiParameter.uint256, hex.decode(output));
|
||||
final amount = decodedA.result as BigInt;
|
||||
|
||||
return amount;
|
||||
}
|
||||
/// To get the amount from the encoded data field.
|
||||
/// Parsed directly, as on_chain's uint256 decoder rejects any value of 2^255 or more.
|
||||
BigInt _decodeAmountInvolvedFromEncodedDataField(String output) =>
|
||||
BigInt.parse(output.substring(72, _transferCallDataLength), radix: 16);
|
||||
}
|
||||
|
||||
+124
-63
@@ -85,6 +85,11 @@ abstract class TronWalletBase
|
||||
|
||||
Timer? _transactionsUpdateTimer;
|
||||
|
||||
bool _isTimerUpdatingHistory = false;
|
||||
|
||||
/// TRC20 symbol and decimals by contract address; they never change on-chain.
|
||||
final Map<String, CryptoCurrency> _trc20Currencies = {};
|
||||
|
||||
@override
|
||||
WalletAddresses walletAddresses;
|
||||
|
||||
@@ -305,8 +310,14 @@ abstract class TronWalletBase
|
||||
return;
|
||||
}
|
||||
await _updateBalance();
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
|
||||
// History is best-effort: a TronGrid or parsing failure must not fail the sync.
|
||||
try {
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
} catch (e) {
|
||||
printV("Failed to fetch the Tron transaction history: $e");
|
||||
}
|
||||
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e) {
|
||||
@@ -400,52 +411,44 @@ abstract class TronWalletBase
|
||||
|
||||
final ownerAddress = TronAddress(_tronAddress);
|
||||
|
||||
// Looked up once per token contract in this update, not once per transaction.
|
||||
final Map<String, CryptoCurrency> trc20Currencies = {};
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
if (transactionModel.isError) {
|
||||
continue;
|
||||
// A transaction that can't be parsed must not drop the rest of the history.
|
||||
try {
|
||||
if (transactionModel.isError) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter out spam transactions that involve receiving TRC10 assets transaction, we deal with TRX and TRC20 transactions
|
||||
if (transactionModel.contracts?.first.type == "TransferAssetContract") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only a TRC20 transfer moves tokens, any other contract call is a TRX transaction.
|
||||
var txCurrency = currency;
|
||||
if (transactionModel.isTrc20TransferCall) {
|
||||
final contractAddress = transactionModel.contractAddress!;
|
||||
txCurrency = trc20Currencies[contractAddress] ??=
|
||||
await _getTrc20Currency(contractAddress, contract, ownerAddress);
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
amount: Money(transactionModel.amount ?? BigInt.zero, txCurrency),
|
||||
direction: TronAddress(transactionModel.from!, visible: false).toAddress() == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
fee: transactionModel.fee != null ? Money.fromInt(transactionModel.fee!, currency) : null,
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
} catch (e) {
|
||||
printV("Skipping Tron transaction ${transactionModel.txID}: $e");
|
||||
}
|
||||
|
||||
// Filter out spam transactions that involve receiving TRC10 assets transaction, we deal with TRX and TRC20 transactions
|
||||
if (transactionModel.contracts?.first.type == "TransferAssetContract") {
|
||||
continue;
|
||||
}
|
||||
|
||||
var txCurrency = currency;
|
||||
if (transactionModel.contractAddress != null) {
|
||||
final tokenAddress = TronAddress(transactionModel.contractAddress!);
|
||||
|
||||
final tokenSymbol = (await _client.getTokenDetail(
|
||||
contract,
|
||||
"symbol",
|
||||
ownerAddress,
|
||||
tokenAddress,
|
||||
) as String?) ??
|
||||
'';
|
||||
|
||||
final decimals = (await _client.getTokenDetail(
|
||||
contract,
|
||||
"decimals",
|
||||
ownerAddress,
|
||||
tokenAddress,
|
||||
) as BigInt?)
|
||||
?.toInt() ??
|
||||
txCurrency.decimals;
|
||||
|
||||
txCurrency = CryptoCurrency(name: tokenSymbol, title: tokenSymbol, decimals: decimals);
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
amount: Money(transactionModel.amount ?? BigInt.zero, txCurrency),
|
||||
direction: TronAddress(transactionModel.from!, visible: false).toAddress() == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
fee: transactionModel.fee != null ? Money.fromInt(transactionModel.fee!, currency) : null,
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
@@ -455,6 +458,47 @@ abstract class TronWalletBase
|
||||
return transactionHistory.transactions;
|
||||
}
|
||||
|
||||
Future<CryptoCurrency> _getTrc20Currency(
|
||||
String contractAddress,
|
||||
ContractABI contract,
|
||||
TronAddress ownerAddress,
|
||||
) async {
|
||||
final cachedCurrency = _trc20Currencies[contractAddress];
|
||||
if (cachedCurrency != null) {
|
||||
return cachedCurrency;
|
||||
}
|
||||
|
||||
final tokenAddress = TronAddress(contractAddress);
|
||||
|
||||
final tokenSymbol = await _client.getTokenDetail(
|
||||
contract,
|
||||
"symbol",
|
||||
ownerAddress,
|
||||
tokenAddress,
|
||||
) as String?;
|
||||
|
||||
final decimals = (await _client.getTokenDetail(
|
||||
contract,
|
||||
"decimals",
|
||||
ownerAddress,
|
||||
tokenAddress,
|
||||
) as BigInt?)
|
||||
?.toInt();
|
||||
|
||||
final txCurrency = CryptoCurrency(
|
||||
name: tokenSymbol ?? "",
|
||||
title: tokenSymbol ?? "",
|
||||
decimals: decimals ?? currency.decimals,
|
||||
);
|
||||
|
||||
// Failed lookups aren't cached, so the next history update retries them.
|
||||
if (tokenSymbol != null && decimals != null) {
|
||||
_trc20Currencies[contractAddress] = txCurrency;
|
||||
}
|
||||
|
||||
return txCurrency;
|
||||
}
|
||||
|
||||
Future<void> fetchTrc20ExcludedTransactions() async {
|
||||
if (!await checkIfScanProviderIsEnabled()) {
|
||||
return;
|
||||
@@ -467,22 +511,26 @@ abstract class TronWalletBase
|
||||
final Map<String, TronTransactionInfo> result = {};
|
||||
|
||||
for (final transactionModel in transactions) {
|
||||
if (transactionHistory.transactions.containsKey(transactionModel.hash)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (transactionHistory.transactions.containsKey(transactionModel.hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
amount: Money(transactionModel.amount ?? BigInt.zero, transactionModel.currency),
|
||||
direction: transactionModel.from! == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
fee: transactionModel.fee != null ? Money.fromInt(transactionModel.fee!, currency) : null,
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
amount: Money(transactionModel.amount ?? BigInt.zero, transactionModel.currency),
|
||||
direction: transactionModel.from! == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
fee: transactionModel.fee != null ? Money.fromInt(transactionModel.fee!, currency) : null,
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
} catch (e) {
|
||||
printV("Skipping Tron TRC20 transaction ${transactionModel.transactionId}: $e");
|
||||
}
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
@@ -639,8 +687,21 @@ abstract class TronWalletBase
|
||||
|
||||
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) async {
|
||||
_updateBalance();
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
|
||||
// A history update can outlast the period; don't stack another one on top of it.
|
||||
if (_isTimerUpdatingHistory) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isTimerUpdatingHistory = true;
|
||||
try {
|
||||
await fetchTransactions();
|
||||
await fetchTrc20ExcludedTransactions();
|
||||
} catch (e) {
|
||||
printV("Failed to update the Tron transaction history: $e");
|
||||
} finally {
|
||||
_isTimerUpdatingHistory = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import "package:cw_tron/tron_transaction_model.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
// Wallet TCFuFnoos25WL99j9y3u92LR67mVwrTvrQ and the USDT contract TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t.
|
||||
const _owner = "41191882614f605dd84703252b196c0c006b5482f8";
|
||||
const _usdtContract = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c";
|
||||
|
||||
// Real TronGrid entries of TCFuFnoos25WL99j9y3u92LR67mVwrTvrQ.
|
||||
const _unlimitedApprove = {
|
||||
"ret": [
|
||||
{"contractRet": "SUCCESS", "fee": 9976400},
|
||||
],
|
||||
"txID": "fc20c9c9489842cf105c56c80e9fce540e55f1158d68459bc625332bae5b9806",
|
||||
"block_timestamp": 1769113440000,
|
||||
"raw_data": {
|
||||
"contract": [
|
||||
{
|
||||
"parameter": {
|
||||
"value": {
|
||||
"data": "095ea7b3000000000000000000000000bbbd3bd87c268b8030bc2edae7021830af599ed2"
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
"owner_address": _owner,
|
||||
"contract_address": _usdtContract,
|
||||
},
|
||||
"type_url": "type.googleapis.com/protocol.TriggerSmartContract",
|
||||
},
|
||||
"type": "TriggerSmartContract",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const _usdtTransfer = {
|
||||
"ret": [
|
||||
{"contractRet": "SUCCESS", "fee": 6428500},
|
||||
],
|
||||
"txID": "0584051e5208526f48d05965a4e15c48f6ec84c23c71441ed6e38a8a49c17faf",
|
||||
"block_timestamp": 1768779375000,
|
||||
"raw_data": {
|
||||
"contract": [
|
||||
{
|
||||
"parameter": {
|
||||
"value": {
|
||||
"data": "a9059cbb000000000000000000000000ca5125c5947162497b1b924d6c5e282282e3351f"
|
||||
"00000000000000000000000000000000000000000000000000000000139c2440",
|
||||
"owner_address": _owner,
|
||||
"contract_address": _usdtContract,
|
||||
},
|
||||
"type_url": "type.googleapis.com/protocol.TriggerSmartContract",
|
||||
},
|
||||
"type": "TriggerSmartContract",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const _trxTransfer = {
|
||||
"ret": [
|
||||
{"contractRet": "SUCCESS", "fee": 0},
|
||||
],
|
||||
"txID": "a5bf8fb4aa16496e53ad36fcfbe3f49c92c0e0cc0caffc97d3cc2fe8a9e16ede",
|
||||
"block_timestamp": 1791643617000,
|
||||
"raw_data": {
|
||||
"contract": [
|
||||
{
|
||||
"parameter": {
|
||||
"value": {
|
||||
"amount": 1,
|
||||
"owner_address": "415576b7edd2a7cb0ebb6f794d7f2c28aa5dc85a82",
|
||||
"to_address": _owner,
|
||||
},
|
||||
"type_url": "type.googleapis.com/protocol.TransferContract",
|
||||
},
|
||||
"type": "TransferContract",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
TronTransactionModel _contractCall(String? data, {Object? callValue}) =>
|
||||
TronTransactionModel.fromJson({
|
||||
"ret": [
|
||||
{"contractRet": "SUCCESS", "fee": 1},
|
||||
],
|
||||
"txID": "test",
|
||||
"block_timestamp": 1,
|
||||
"raw_data": {
|
||||
"contract": [
|
||||
{
|
||||
"parameter": {
|
||||
"value": {
|
||||
if (data != null) "data": data,
|
||||
if (callValue != null) "call_value": callValue,
|
||||
"owner_address": _owner,
|
||||
"contract_address": _usdtContract,
|
||||
},
|
||||
},
|
||||
"type": "TriggerSmartContract",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
String _word(String hex) => hex.padLeft(64, "0");
|
||||
|
||||
void main() {
|
||||
group("TronTransactionModel", () {
|
||||
test("decodes the receiver and token amount of a TRC20 transfer", () {
|
||||
final transaction = TronTransactionModel.fromJson(_usdtTransfer);
|
||||
|
||||
expect(transaction.isTrc20TransferCall, isTrue);
|
||||
expect(transaction.amount, BigInt.from(329000000));
|
||||
expect(transaction.to, "TUQxi3DkhbrL9qL5EHEfAmkZbsPBGP1UnH");
|
||||
expect(transaction.from, _owner);
|
||||
expect(transaction.contractAddress, _usdtContract);
|
||||
});
|
||||
|
||||
test("reads an unlimited approve as a TRX call to the contract instead of throwing", () {
|
||||
final transaction = TronTransactionModel.fromJson(_unlimitedApprove);
|
||||
|
||||
expect(transaction.isError, isFalse);
|
||||
expect(transaction.isTrc20TransferCall, isFalse);
|
||||
expect(transaction.amount, BigInt.zero);
|
||||
expect(transaction.to, _usdtContract);
|
||||
expect(transaction.fee, 9976400);
|
||||
});
|
||||
|
||||
test("decodes transfer amounts of 2^255 and above", () {
|
||||
final maxUint256 = (BigInt.one << 256) - BigInt.one;
|
||||
final transaction = _contractCall(
|
||||
"a9059cbb${_word("ca5125c5947162497b1b924d6c5e282282e3351f")}${"f" * 64}",
|
||||
);
|
||||
|
||||
expect(transaction.isTrc20TransferCall, isTrue);
|
||||
expect(transaction.amount, maxUint256);
|
||||
});
|
||||
|
||||
test("decodes 0x-prefixed, upper-case transfer calldata", () {
|
||||
final transaction = _contractCall(
|
||||
"0xA9059CBB${_word("CA5125C5947162497B1B924D6C5E282282E3351F")}${_word("139C2440")}",
|
||||
);
|
||||
|
||||
expect(transaction.isTrc20TransferCall, isTrue);
|
||||
expect(transaction.amount, BigInt.from(329000000));
|
||||
expect(transaction.to, "TUQxi3DkhbrL9qL5EHEfAmkZbsPBGP1UnH");
|
||||
});
|
||||
|
||||
test("reads other contract calls as the TRX sent with them", () {
|
||||
final deposit = _contractCall("d0e30db0", callValue: 5000000);
|
||||
final withdraw = _contractCall("2e1a7d4d${_word("3e8")}");
|
||||
final noCalldata = _contractCall(null, callValue: 7);
|
||||
final truncatedTransfer =
|
||||
_contractCall("a9059cbb${_word("ca5125c5947162497b1b924d6c5e282282e3351f")}");
|
||||
|
||||
for (final transaction in [deposit, withdraw, noCalldata, truncatedTransfer]) {
|
||||
expect(transaction.isTrc20TransferCall, isFalse);
|
||||
expect(transaction.to, _usdtContract);
|
||||
}
|
||||
expect(deposit.amount, BigInt.from(5000000));
|
||||
expect(withdraw.amount, BigInt.zero);
|
||||
expect(noCalldata.amount, BigInt.from(7));
|
||||
expect(truncatedTransfer.amount, BigInt.zero);
|
||||
});
|
||||
|
||||
test("parses call_value sent as a string", () {
|
||||
expect(_contractCall("d0e30db0", callValue: "42").amount, BigInt.from(42));
|
||||
});
|
||||
|
||||
test("keeps TRX transfers unchanged", () {
|
||||
final transaction = TronTransactionModel.fromJson(_trxTransfer);
|
||||
|
||||
expect(transaction.isTrc20TransferCall, isFalse);
|
||||
expect(transaction.contractAddress, isNull);
|
||||
expect(transaction.amount, BigInt.one);
|
||||
expect(transaction.to, _owner);
|
||||
expect(transaction.from, "415576b7edd2a7cb0ebb6f794d7f2c28aa5dc85a82");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -924,9 +924,10 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
|
||||
@override
|
||||
Balance balanceForAccount(Object wallet, int accountIndex) {
|
||||
Money balanceForAccount(Object wallet, int accountIndex) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.balanceForAccount(accountIndex);
|
||||
final balance = bitcoinWallet.balanceForAccount(accountIndex);
|
||||
return balance.confirmed + balance.unconfirmed;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1727,7 +1727,6 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
static const walletTypesToBeRemoved = [
|
||||
WalletType.zano,
|
||||
WalletType.decred,
|
||||
];
|
||||
|
||||
Future<bool> shouldShowRemovalPopup() async {
|
||||
|
||||
@@ -303,7 +303,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
ChainflipExchangeProvider(),
|
||||
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
|
||||
SwapTradeExchangeProvider(),
|
||||
LetsExchangeExchangeProvider(),
|
||||
// LetsExchangeExchangeProvider(),
|
||||
// StealthExExchangeProvider(),
|
||||
XOSwapExchangeProvider(),
|
||||
SwapsXyzExchangeProvider(),
|
||||
|
||||
+1
-4
@@ -99,10 +99,7 @@ abstract class BitcoinAccountListViewModelBase with Store implements WalletAccou
|
||||
});
|
||||
}
|
||||
|
||||
Money _fullBalance(int accountIndex) {
|
||||
final balance = bitcoin!.balanceForAccount(_wallet, accountIndex);
|
||||
return balance.confirmed + balance.unconfirmed;
|
||||
}
|
||||
Money _fullBalance(int accountIndex) => bitcoin!.balanceForAccount(_wallet, accountIndex);
|
||||
|
||||
@override
|
||||
@action
|
||||
|
||||
@@ -15,14 +15,14 @@ APP_ANDROID_TYPE=$1
|
||||
|
||||
MONERO_COM_NAME="Monero.com"
|
||||
MONERO_COM_VERSION="6.5.0"
|
||||
MONERO_COM_BUILD_NUMBER=4170
|
||||
MONERO_COM_BUILD_NUMBER=4171
|
||||
MONERO_COM_BUNDLE_ID="com.monero.app"
|
||||
MONERO_COM_PACKAGE="com.monero.app"
|
||||
MONERO_COM_SCHEME="monero.com"
|
||||
|
||||
CAKEWALLET_NAME="Cake Wallet"
|
||||
CAKEWALLET_VERSION="6.5.0"
|
||||
CAKEWALLET_BUILD_NUMBER=4426
|
||||
CAKEWALLET_BUILD_NUMBER=4428
|
||||
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
|
||||
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
|
||||
CAKEWALLET_SCHEME="cakewallet"
|
||||
|
||||
@@ -13,12 +13,12 @@ APP_IOS_TYPE=$1
|
||||
|
||||
MONERO_COM_NAME="Monero.com"
|
||||
MONERO_COM_VERSION="6.5.0"
|
||||
MONERO_COM_BUILD_NUMBER=177
|
||||
MONERO_COM_BUILD_NUMBER=178
|
||||
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
|
||||
|
||||
CAKEWALLET_NAME="Cake Wallet"
|
||||
CAKEWALLET_VERSION="6.5.0"
|
||||
CAKEWALLET_BUILD_NUMBER=446
|
||||
CAKEWALLET_BUILD_NUMBER=448
|
||||
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ fi
|
||||
|
||||
CAKEWALLET_NAME="Cake Wallet"
|
||||
CAKEWALLET_VERSION="6.5.0"
|
||||
CAKEWALLET_BUILD_NUMBER=83
|
||||
CAKEWALLET_BUILD_NUMBER=84
|
||||
|
||||
if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then
|
||||
echo "Wrong app type."
|
||||
|
||||
@@ -17,12 +17,12 @@ fi
|
||||
|
||||
MONERO_COM_NAME="Monero.com"
|
||||
MONERO_COM_VERSION="6.5.0"
|
||||
MONERO_COM_BUILD_NUMBER=86
|
||||
MONERO_COM_BUILD_NUMBER=87
|
||||
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
|
||||
|
||||
CAKEWALLET_NAME="Cake Wallet"
|
||||
CAKEWALLET_VERSION="6.5.0"
|
||||
CAKEWALLET_BUILD_NUMBER=154
|
||||
CAKEWALLET_BUILD_NUMBER=155
|
||||
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
|
||||
|
||||
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then
|
||||
|
||||
+4
-4
@@ -308,7 +308,7 @@ abstract class Bitcoin {
|
||||
Future<String?> getLightningInvoice(Object wallet, BigInt amount);
|
||||
String? getBreezSdkError(Object exception);
|
||||
Future<Account> getCurrentAccount(Object wallet);
|
||||
Balance balanceForAccount(Object wallet, int accountIndex);
|
||||
Money balanceForAccount(Object wallet, int accountIndex);
|
||||
Map<int, Object> accountBalancesSnapshot(Object wallet);
|
||||
Future<void> setCurrentAccount(Object wallet, int accountIndex);
|
||||
List<TransactionInfo> getCurrentAccountBitcoinTransactions(Object wallet);
|
||||
@@ -2092,9 +2092,9 @@ Future<void> generateWalletTypes({
|
||||
outputContent += '\tWalletType.nano,\n';
|
||||
}
|
||||
|
||||
// if (hasDecred) {
|
||||
// outputContent += '\tWalletType.decred,\n';
|
||||
// }
|
||||
if (hasDecred) {
|
||||
outputContent += '\tWalletType.decred,\n';
|
||||
}
|
||||
|
||||
// if (hasZano) {
|
||||
// outputContent += '\tWalletType.zano,\n';
|
||||
|
||||
Reference in New Issue
Block a user