More WIP eth + tokens

This commit is contained in:
julian 2024-01-10 16:53:12 -06:00
parent 37a164bb8f
commit 9996328126
25 changed files with 1260 additions and 1088 deletions

View file

@ -65,6 +65,10 @@ class TransactionV2 {
String? get onChainNote => _getFromOtherData(key: "onChainNote") as String?;
bool get isCancelled => _getFromOtherData(key: "isCancelled") == true;
String? get contractAddress =>
_getFromOtherData(key: "contractAddress") as String?;
int? get nonce => _getFromOtherData(key: "nonce") as int?;
int getConfirmations(int currentChainHeight) {
if (height == null || height! <= 0) return 0;
return max(0, currentChainHeight - (height! - 1));

View file

@ -22,87 +22,97 @@ const TransactionV2Schema = CollectionSchema(
name: r'blockHash',
type: IsarType.string,
),
r'hash': PropertySchema(
r'contractAddress': PropertySchema(
id: 1,
name: r'contractAddress',
type: IsarType.string,
),
r'hash': PropertySchema(
id: 2,
name: r'hash',
type: IsarType.string,
),
r'height': PropertySchema(
id: 2,
id: 3,
name: r'height',
type: IsarType.long,
),
r'inputs': PropertySchema(
id: 3,
id: 4,
name: r'inputs',
type: IsarType.objectList,
target: r'InputV2',
),
r'isCancelled': PropertySchema(
id: 4,
id: 5,
name: r'isCancelled',
type: IsarType.bool,
),
r'isEpiccashTransaction': PropertySchema(
id: 5,
id: 6,
name: r'isEpiccashTransaction',
type: IsarType.bool,
),
r'nonce': PropertySchema(
id: 7,
name: r'nonce',
type: IsarType.long,
),
r'numberOfMessages': PropertySchema(
id: 6,
id: 8,
name: r'numberOfMessages',
type: IsarType.long,
),
r'onChainNote': PropertySchema(
id: 7,
id: 9,
name: r'onChainNote',
type: IsarType.string,
),
r'otherData': PropertySchema(
id: 8,
id: 10,
name: r'otherData',
type: IsarType.string,
),
r'outputs': PropertySchema(
id: 9,
id: 11,
name: r'outputs',
type: IsarType.objectList,
target: r'OutputV2',
),
r'slateId': PropertySchema(
id: 10,
id: 12,
name: r'slateId',
type: IsarType.string,
),
r'subType': PropertySchema(
id: 11,
id: 13,
name: r'subType',
type: IsarType.byte,
enumMap: _TransactionV2subTypeEnumValueMap,
),
r'timestamp': PropertySchema(
id: 12,
id: 14,
name: r'timestamp',
type: IsarType.long,
),
r'txid': PropertySchema(
id: 13,
id: 15,
name: r'txid',
type: IsarType.string,
),
r'type': PropertySchema(
id: 14,
id: 16,
name: r'type',
type: IsarType.byte,
enumMap: _TransactionV2typeEnumValueMap,
),
r'version': PropertySchema(
id: 15,
id: 17,
name: r'version',
type: IsarType.long,
),
r'walletId': PropertySchema(
id: 16,
id: 18,
name: r'walletId',
type: IsarType.string,
)
@ -182,6 +192,12 @@ int _transactionV2EstimateSize(
bytesCount += 3 + value.length * 3;
}
}
{
final value = object.contractAddress;
if (value != null) {
bytesCount += 3 + value.length * 3;
}
}
bytesCount += 3 + object.hash.length * 3;
bytesCount += 3 + object.inputs.length * 3;
{
@ -229,32 +245,34 @@ void _transactionV2Serialize(
Map<Type, List<int>> allOffsets,
) {
writer.writeString(offsets[0], object.blockHash);
writer.writeString(offsets[1], object.hash);
writer.writeLong(offsets[2], object.height);
writer.writeString(offsets[1], object.contractAddress);
writer.writeString(offsets[2], object.hash);
writer.writeLong(offsets[3], object.height);
writer.writeObjectList<InputV2>(
offsets[3],
offsets[4],
allOffsets,
InputV2Schema.serialize,
object.inputs,
);
writer.writeBool(offsets[4], object.isCancelled);
writer.writeBool(offsets[5], object.isEpiccashTransaction);
writer.writeLong(offsets[6], object.numberOfMessages);
writer.writeString(offsets[7], object.onChainNote);
writer.writeString(offsets[8], object.otherData);
writer.writeBool(offsets[5], object.isCancelled);
writer.writeBool(offsets[6], object.isEpiccashTransaction);
writer.writeLong(offsets[7], object.nonce);
writer.writeLong(offsets[8], object.numberOfMessages);
writer.writeString(offsets[9], object.onChainNote);
writer.writeString(offsets[10], object.otherData);
writer.writeObjectList<OutputV2>(
offsets[9],
offsets[11],
allOffsets,
OutputV2Schema.serialize,
object.outputs,
);
writer.writeString(offsets[10], object.slateId);
writer.writeByte(offsets[11], object.subType.index);
writer.writeLong(offsets[12], object.timestamp);
writer.writeString(offsets[13], object.txid);
writer.writeByte(offsets[14], object.type.index);
writer.writeLong(offsets[15], object.version);
writer.writeString(offsets[16], object.walletId);
writer.writeString(offsets[12], object.slateId);
writer.writeByte(offsets[13], object.subType.index);
writer.writeLong(offsets[14], object.timestamp);
writer.writeString(offsets[15], object.txid);
writer.writeByte(offsets[16], object.type.index);
writer.writeLong(offsets[17], object.version);
writer.writeString(offsets[18], object.walletId);
}
TransactionV2 _transactionV2Deserialize(
@ -265,32 +283,32 @@ TransactionV2 _transactionV2Deserialize(
) {
final object = TransactionV2(
blockHash: reader.readStringOrNull(offsets[0]),
hash: reader.readString(offsets[1]),
height: reader.readLongOrNull(offsets[2]),
hash: reader.readString(offsets[2]),
height: reader.readLongOrNull(offsets[3]),
inputs: reader.readObjectList<InputV2>(
offsets[3],
offsets[4],
InputV2Schema.deserialize,
allOffsets,
InputV2(),
) ??
[],
otherData: reader.readStringOrNull(offsets[8]),
otherData: reader.readStringOrNull(offsets[10]),
outputs: reader.readObjectList<OutputV2>(
offsets[9],
offsets[11],
OutputV2Schema.deserialize,
allOffsets,
OutputV2(),
) ??
[],
subType:
_TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[11])] ??
_TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[13])] ??
TransactionSubType.none,
timestamp: reader.readLong(offsets[12]),
txid: reader.readString(offsets[13]),
type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[14])] ??
timestamp: reader.readLong(offsets[14]),
txid: reader.readString(offsets[15]),
type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[16])] ??
TransactionType.outgoing,
version: reader.readLong(offsets[15]),
walletId: reader.readString(offsets[16]),
version: reader.readLong(offsets[17]),
walletId: reader.readString(offsets[18]),
);
object.id = id;
return object;
@ -306,10 +324,12 @@ P _transactionV2DeserializeProp<P>(
case 0:
return (reader.readStringOrNull(offset)) as P;
case 1:
return (reader.readString(offset)) as P;
return (reader.readStringOrNull(offset)) as P;
case 2:
return (reader.readLongOrNull(offset)) as P;
return (reader.readString(offset)) as P;
case 3:
return (reader.readLongOrNull(offset)) as P;
case 4:
return (reader.readObjectList<InputV2>(
offset,
InputV2Schema.deserialize,
@ -317,17 +337,19 @@ P _transactionV2DeserializeProp<P>(
InputV2(),
) ??
[]) as P;
case 4:
return (reader.readBool(offset)) as P;
case 5:
return (reader.readBool(offset)) as P;
case 6:
return (reader.readLongOrNull(offset)) as P;
return (reader.readBool(offset)) as P;
case 7:
return (reader.readStringOrNull(offset)) as P;
return (reader.readLongOrNull(offset)) as P;
case 8:
return (reader.readStringOrNull(offset)) as P;
return (reader.readLongOrNull(offset)) as P;
case 9:
return (reader.readStringOrNull(offset)) as P;
case 10:
return (reader.readStringOrNull(offset)) as P;
case 11:
return (reader.readObjectList<OutputV2>(
offset,
OutputV2Schema.deserialize,
@ -335,22 +357,22 @@ P _transactionV2DeserializeProp<P>(
OutputV2(),
) ??
[]) as P;
case 10:
case 12:
return (reader.readStringOrNull(offset)) as P;
case 11:
case 13:
return (_TransactionV2subTypeValueEnumMap[
reader.readByteOrNull(offset)] ??
TransactionSubType.none) as P;
case 12:
return (reader.readLong(offset)) as P;
case 13:
return (reader.readString(offset)) as P;
case 14:
return (reader.readLong(offset)) as P;
case 15:
return (reader.readString(offset)) as P;
case 16:
return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ??
TransactionType.outgoing) as P;
case 15:
case 17:
return (reader.readLong(offset)) as P;
case 16:
case 18:
return (reader.readString(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
@ -963,6 +985,160 @@ extension TransactionV2QueryFilter
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'contractAddress',
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'contractAddress',
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressEqualTo(
String? value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'contractAddress',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressGreaterThan(
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'contractAddress',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressLessThan(
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'contractAddress',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressBetween(
String? lower,
String? upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'contractAddress',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'contractAddress',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'contractAddress',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'contractAddress',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'contractAddress',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'contractAddress',
value: '',
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
contractAddressIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'contractAddress',
value: '',
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition> hashEqualTo(
String value, {
bool caseSensitive = true,
@ -1335,6 +1511,80 @@ extension TransactionV2QueryFilter
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
nonceIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'nonce',
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
nonceIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'nonce',
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
nonceEqualTo(int? value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'nonce',
value: value,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
nonceGreaterThan(
int? value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'nonce',
value: value,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
nonceLessThan(
int? value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'nonce',
value: value,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
nonceBetween(
int? lower,
int? upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'nonce',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
numberOfMessagesIsNull() {
return QueryBuilder.apply(this, (query) {
@ -2490,6 +2740,20 @@ extension TransactionV2QuerySortBy
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
sortByContractAddress() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'contractAddress', Sort.asc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
sortByContractAddressDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'contractAddress', Sort.desc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByHash() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'hash', Sort.asc);
@ -2541,6 +2805,18 @@ extension TransactionV2QuerySortBy
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByNonce() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'nonce', Sort.asc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByNonceDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'nonce', Sort.desc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
sortByNumberOfMessages() {
return QueryBuilder.apply(this, (query) {
@ -2683,6 +2959,20 @@ extension TransactionV2QuerySortThenBy
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
thenByContractAddress() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'contractAddress', Sort.asc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
thenByContractAddressDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'contractAddress', Sort.desc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByHash() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'hash', Sort.asc);
@ -2746,6 +3036,18 @@ extension TransactionV2QuerySortThenBy
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByNonce() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'nonce', Sort.asc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByNonceDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'nonce', Sort.desc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
thenByNumberOfMessages() {
return QueryBuilder.apply(this, (query) {
@ -2882,6 +3184,14 @@ extension TransactionV2QueryWhereDistinct
});
}
QueryBuilder<TransactionV2, TransactionV2, QDistinct>
distinctByContractAddress({bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'contractAddress',
caseSensitive: caseSensitive);
});
}
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctByHash(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
@ -2909,6 +3219,12 @@ extension TransactionV2QueryWhereDistinct
});
}
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctByNonce() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'nonce');
});
}
QueryBuilder<TransactionV2, TransactionV2, QDistinct>
distinctByNumberOfMessages() {
return QueryBuilder.apply(this, (query) {
@ -2990,6 +3306,13 @@ extension TransactionV2QueryProperty
});
}
QueryBuilder<TransactionV2, String?, QQueryOperations>
contractAddressProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'contractAddress');
});
}
QueryBuilder<TransactionV2, String, QQueryOperations> hashProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'hash');
@ -3022,6 +3345,12 @@ extension TransactionV2QueryProperty
});
}
QueryBuilder<TransactionV2, int?, QQueryOperations> nonceProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'nonce');
});
}
QueryBuilder<TransactionV2, int?, QQueryOperations>
numberOfMessagesProperty() {
return QueryBuilder.apply(this, (query) {

View file

@ -20,7 +20,6 @@ import 'package:stackwallet/models/isar/models/transaction_note.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart';
@ -36,6 +35,7 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
@ -202,7 +202,7 @@ class _ConfirmTransactionViewState
}
if (widget.isTokenTx) {
unawaited(ref.read(tokenServiceProvider)!.refresh());
unawaited(ref.read(pCurrentTokenWallet)!.refresh());
} else {
unawaited(wallet.refresh());
}
@ -345,7 +345,7 @@ class _ConfirmTransactionViewState
final String unit;
if (widget.isTokenTx) {
unit = ref.watch(
tokenServiceProvider.select((value) => value!.tokenContract.symbol));
pCurrentTokenWallet.select((value) => value!.tokenContract.symbol));
} else {
unit = coin.ticker;
}
@ -518,7 +518,7 @@ class _ConfirmTransactionViewState
ref.watch(pAmountFormatter(coin)).format(
amountWithoutChange,
ethContract: ref
.watch(tokenServiceProvider)
.watch(pCurrentTokenWallet)
?.tokenContract,
),
style: STextStyles.itemSubtitle12(context),
@ -708,7 +708,7 @@ class _ConfirmTransactionViewState
priceAnd24hChangeNotifierProvider)
.getTokenPrice(
ref
.read(tokenServiceProvider)!
.read(pCurrentTokenWallet)!
.tokenContract
.address,
)
@ -737,7 +737,7 @@ class _ConfirmTransactionViewState
ref.watch(pAmountFormatter(coin)).format(
amountWithoutChange,
ethContract: ref
.read(tokenServiceProvider)
.read(pCurrentTokenWallet)
?.tokenContract),
style: STextStyles
.desktopTextExtraExtraSmall(

View file

@ -12,7 +12,6 @@ import 'package:cw_core/monero_transaction_priority.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
@ -24,6 +23,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
import 'package:stackwallet/widgets/animated_text.dart';
@ -110,8 +110,8 @@ class _TransactionFeeSelectionSheetState
await wallet.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
}
}
@ -144,8 +144,8 @@ class _TransactionFeeSelectionSheetState
await wallet.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
}
}
@ -178,8 +178,8 @@ class _TransactionFeeSelectionSheetState
await wallet.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
}
}
@ -267,7 +267,7 @@ class _TransactionFeeSelectionSheetState
),
FutureBuilder(
future: widget.isToken
? ref.read(tokenServiceProvider)!.fees
? ref.read(pCurrentTokenWallet)!.fees
: wallet.fees,
builder: (context, AsyncSnapshot<FeeObject> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&

View file

@ -21,7 +21,6 @@ import 'package:stackwallet/pages/address_book_views/address_book_view.dart';
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart';
@ -41,6 +40,8 @@ import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/eth/token_balance_provider.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/widgets/animated_text.dart';
@ -353,7 +354,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
}
Future<String> calculateFees() async {
final wallet = ref.read(tokenServiceProvider)!;
final wallet = ref.read(pCurrentTokenWallet)!;
final feeObject = await wallet.fees;
late final int feeRate;
@ -372,7 +373,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
feeRate = -1;
}
final Amount fee = wallet.estimateFeeFor(feeRate);
final Amount fee = await wallet.estimateFeeFor(Amount.zero, feeRate);
cachedFees = ref.read(pAmountFormatter(coin)).format(
fee,
withUnitName: true,
@ -389,7 +390,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
const Duration(milliseconds: 100),
);
final wallet = ref.read(pWallets).getWallet(walletId);
final tokenWallet = ref.read(tokenServiceProvider)!;
final tokenWallet = ref.read(pCurrentTokenWallet)!;
final Amount amount = _amountToSend!;
@ -711,8 +712,11 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
.watch(pAmountFormatter(coin))
.format(
ref
.read(tokenServiceProvider)!
.balance
.read(pTokenBalance((
walletId: widget.walletId,
contractAddress:
tokenContract.address,
)))
.spendable,
ethContract: tokenContract,
withUnitName: false,
@ -729,18 +733,16 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
ref
.watch(pAmountFormatter(coin))
.format(
ref.watch(
tokenServiceProvider.select(
(value) => value!
.balance.spendable,
),
),
ethContract: ref.watch(
tokenServiceProvider.select(
(value) =>
value!.tokenContract,
),
),
ref
.watch(pTokenBalance((
walletId:
widget.walletId,
contractAddress:
tokenContract
.address,
)))
.spendable,
ethContract: tokenContract,
),
style:
STextStyles.titleBold12(context)
@ -750,7 +752,13 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
textAlign: TextAlign.right,
),
Text(
"${(ref.watch(tokenServiceProvider.select((value) => value!.balance.spendable.decimal)) * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getTokenPrice(tokenContract.address).item1))).toAmount(
"${(ref.watch(pTokenBalance((
walletId:
widget.walletId,
contractAddress:
tokenContract
.address,
))).spendable.decimal * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getTokenPrice(tokenContract.address).item1))).toAmount(
fractionDigits: 2,
).fiatString(
locale: locale,

View file

@ -8,17 +8,16 @@
*
*/
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/ethereum/cached_eth_token_balance.dart';
import 'package:stackwallet/services/ethereum/ethereum_token_service.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/constants.dart';
@ -26,9 +25,11 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/eth/token_balance_provider.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/dialogs/basic_dialog.dart';
import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart';
@ -58,7 +59,7 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
WidgetRef ref,
) async {
try {
await ref.read(tokenServiceProvider)!.initialize();
await ref.read(pCurrentTokenWallet)!.init();
return true;
} catch (_) {
await showDialog<void>(
@ -84,14 +85,12 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
}
void _onPressed() async {
final old = ref.read(tokenServiceStateProvider);
// exit previous if there is one
unawaited(old?.exit());
ref.read(tokenServiceStateProvider.state).state = EthTokenWallet(
token: widget.token,
secureStore: ref.read(secureStoreProvider),
ethWallet:
ref.read(pWallets).getWallet(widget.walletId) as EthereumWallet,
tracker: TransactionNotificationTracker(
walletId: widget.walletId,
),
ref.read(pWallets).getWallet(widget.walletId) as EthereumWallet,
widget.token,
);
final success = await showLoading<bool>(

View file

@ -19,7 +19,6 @@ import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart';
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
import 'package:stackwallet/pages/receive_view/receive_view.dart';
import 'package:stackwallet/pages/send_view/token_send_view.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart';
import 'package:stackwallet/providers/global/locale_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
@ -33,6 +32,8 @@ import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/eth/token_balance_provider.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
@ -51,9 +52,9 @@ class TokenSummary extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final token =
ref.watch(tokenServiceProvider.select((value) => value!.tokenContract));
final balance =
ref.watch(tokenServiceProvider.select((value) => value!.balance));
ref.watch(pCurrentTokenWallet.select((value) => value!.tokenContract));
final balance = ref.watch(
pTokenBalance((walletId: walletId, contractAddress: token.address)));
return Stack(
children: [
@ -157,7 +158,7 @@ class TokenSummary extends ConsumerWidget {
walletId: walletId,
initialSyncStatus: initialSyncStatus,
tokenContractAddress: ref.watch(
tokenServiceProvider.select(
pCurrentTokenWallet.select(
(value) => value!.tokenContract.address,
),
),

View file

@ -12,25 +12,18 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list_item.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/trade_card.dart';
import 'package:stackwallet/widgets/transaction_card.dart';
import 'package:tuple/tuple.dart';
class TokenTransactionsList extends ConsumerStatefulWidget {
const TokenTransactionsList({
@ -49,7 +42,10 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
late final int minConfirms;
bool _hasLoaded = false;
List<Transaction> _transactions2 = [];
List<TransactionV2> _transactions = [];
late final StreamSubscription<List<TransactionV2>> _subscription;
late final QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> _query;
BorderRadius get _borderRadiusFirst {
return BorderRadius.only(
@ -73,139 +69,6 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
);
}
Widget itemBuilder(
BuildContext context,
Transaction tx,
BorderRadius? radius,
Coin coin,
) {
final matchingTrades = ref
.read(tradesServiceProvider)
.trades
.where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid);
if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
final trade = matchingTrades.first;
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TransactionCard(
// this may mess with combined firo transactions
key: tx.isConfirmed(
ref.watch(pWalletChainHeight(widget.walletId)),
minConfirms)
? Key(tx.txid + tx.type.name + tx.address.value.toString())
: UniqueKey(), //
transaction: tx,
walletId: widget.walletId,
),
TradeCard(
// this may mess with combined firo transactions
key: Key(tx.txid +
tx.type.name +
tx.address.value.toString() +
trade.uuid), //
trade: trade,
onTap: () async {
final walletName = ref.read(pWalletName(widget.walletId));
if (Util.isDesktop) {
await showDialog<void>(
context: context,
builder: (context) => Navigator(
initialRoute: TradeDetailsView.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
FadePageRoute(
DesktopDialog(
maxHeight: null,
maxWidth: 580,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
bottom: 16,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Trade details",
style: STextStyles.desktopH3(context),
),
DesktopDialogCloseButton(
onPressedOverride: Navigator.of(
context,
rootNavigator: true,
).pop,
),
],
),
),
Flexible(
child: TradeDetailsView(
tradeId: trade.tradeId,
transactionIfSentFromStack: tx,
walletName: walletName,
walletId: widget.walletId,
),
),
],
),
),
const RouteSettings(
name: TradeDetailsView.routeName,
),
),
];
},
),
);
} else {
unawaited(
Navigator.of(context).pushNamed(
TradeDetailsView.routeName,
arguments: Tuple4(
trade.tradeId,
tx,
widget.walletId,
walletName,
),
),
);
}
},
)
],
),
);
} else {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: TransactionCard(
// this may mess with combined firo transactions
key: tx.isConfirmed(
ref.watch(pWalletChainHeight(widget.walletId)), minConfirms)
? Key(tx.txid + tx.type.name + tx.address.value.toString())
: UniqueKey(),
transaction: tx,
walletId: widget.walletId,
),
);
}
}
@override
void initState() {
minConfirms = ref
@ -213,21 +76,44 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
.getWallet(widget.walletId)
.cryptoCurrency
.minConfirms;
_query = ref
.read(mainDBProvider)
.isar
.transactionV2s
.where()
.walletIdEqualTo(widget.walletId)
.filter()
.subTypeEqualTo(TransactionSubType.ethToken)
.sortByTimestampDesc();
_subscription = _query.watch().listen((event) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_transactions = event;
});
});
});
super.initState();
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final wallet =
ref.watch(pWallets.select((value) => value.getWallet(widget.walletId)));
return FutureBuilder(
future: ref
.watch(tokenServiceProvider.select((value) => value!.transactions)),
builder: (fbContext, AsyncSnapshot<List<Transaction>> snapshot) {
future: _query.findAll(),
builder: (fbContext, AsyncSnapshot<List<TransactionV2>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
_transactions2 = snapshot.data!;
_transactions = snapshot.data!;
_hasLoaded = true;
}
if (!_hasLoaded) {
@ -246,35 +132,34 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
],
);
}
if (_transactions2.isEmpty) {
if (_transactions.isEmpty) {
return const NoTransActionsFound();
} else {
_transactions2.sort((a, b) => b.timestamp - a.timestamp);
_transactions.sort((a, b) => b.timestamp - a.timestamp);
return RefreshIndicator(
onRefresh: () async {
if (!ref.read(tokenServiceProvider)!.isRefreshing) {
unawaited(ref.read(tokenServiceProvider)!.refresh());
if (!ref.read(pCurrentTokenWallet)!.refreshMutex.isLocked) {
unawaited(ref.read(pCurrentTokenWallet)!.refresh());
}
},
child: Util.isDesktop
? ListView.separated(
itemBuilder: (context, index) {
BorderRadius? radius;
if (_transactions2.length == 1) {
if (_transactions.length == 1) {
radius = BorderRadius.circular(
Constants.size.circularBorderRadius,
);
} else if (index == _transactions2.length - 1) {
} else if (index == _transactions.length - 1) {
radius = _borderRadiusLast;
} else if (index == 0) {
radius = _borderRadiusFirst;
}
final tx = _transactions2[index];
return itemBuilder(
context,
tx,
radius,
wallet.info.coin,
final tx = _transactions[index];
return TxListItem(
tx: tx,
coin: wallet.info.coin,
radius: radius,
);
},
separatorBuilder: (context, index) {
@ -286,27 +171,26 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
.background,
);
},
itemCount: _transactions2.length,
itemCount: _transactions.length,
)
: ListView.builder(
itemCount: _transactions2.length,
itemCount: _transactions.length,
itemBuilder: (context, index) {
BorderRadius? radius;
if (_transactions2.length == 1) {
if (_transactions.length == 1) {
radius = BorderRadius.circular(
Constants.size.circularBorderRadius,
);
} else if (index == _transactions2.length - 1) {
} else if (index == _transactions.length - 1) {
radius = _borderRadiusLast;
} else if (index == 0) {
radius = _borderRadiusFirst;
}
final tx = _transactions2[index];
return itemBuilder(
context,
tx,
radius,
wallet.info.coin,
final tx = _transactions[index];
return TxListItem(
tx: tx,
coin: wallet.info.coin,
radius: radius,
);
},
),

View file

@ -15,23 +15,19 @@ import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/token_summary.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/token_transaction_list_widget.dart';
import 'package:stackwallet/pages/token_view/token_contract_details_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
import 'package:stackwallet/services/ethereum/ethereum_token_service.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart';
import 'package:tuple/tuple.dart';
final tokenServiceStateProvider = StateProvider<EthTokenWallet?>((ref) => null);
final tokenServiceProvider = ChangeNotifierProvider<EthTokenWallet?>(
(ref) => ref.watch(tokenServiceStateProvider));
/// [eventBus] should only be set during testing
class TokenView extends ConsumerStatefulWidget {
const TokenView({
@ -56,7 +52,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
@override
void initState() {
initialSyncStatus = ref.read(tokenServiceProvider)!.isRefreshing
initialSyncStatus = ref.read(pCurrentTokenWallet)!.refreshMutex.isLocked
? WalletSyncStatus.syncing
: WalletSyncStatus.synced;
super.initState();
@ -105,7 +101,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
children: [
EthTokenIcon(
contractAddress: ref.watch(
tokenServiceProvider.select(
pCurrentTokenWallet.select(
(value) => value!.tokenContract.address,
),
),
@ -116,7 +112,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
),
Flexible(
child: Text(
ref.watch(tokenServiceProvider
ref.watch(pCurrentTokenWallet
.select((value) => value!.tokenContract.name)),
style: STextStyles.navBarTitle(context),
overflow: TextOverflow.ellipsis,
@ -145,7 +141,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
Navigator.of(context).pushNamed(
TokenContractDetailsView.routeName,
arguments: Tuple2(
ref.watch(tokenServiceProvider
ref.watch(pCurrentTokenWallet
.select((value) => value!.tokenContract.address)),
widget.walletId,
),
@ -190,7 +186,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
text: "See all",
onTap: () {
Navigator.of(context).pushNamed(
AllTransactionsView.routeName,
AllTransactionsV2View.routeName,
arguments: (
walletId: widget.walletId,
isTokens: true,

View file

@ -13,13 +13,13 @@ import 'dart:async';
import 'package:event_bus/event_bus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/widgets/animated_widgets/rotating_arrows.dart';
/// [eventBus] should only be set during testing
@ -140,8 +140,8 @@ class _RefreshButtonState extends ConsumerState<WalletRefreshButton> {
wallet.refresh().then((_) => _spinController.stop?.call());
}
} else {
if (!ref.read(tokenServiceProvider)!.isRefreshing) {
ref.read(tokenServiceProvider)!.refresh();
if (!ref.read(pCurrentTokenWallet)!.refreshMutex.isLocked) {
ref.read(pCurrentTokenWallet)!.refresh();
}
}
},

View file

@ -19,7 +19,6 @@ import 'package:stackwallet/models/isar/models/contact_entry.dart';
import 'package:stackwallet/models/isar/models/transaction_note.dart';
import 'package:stackwallet/models/transaction_filter.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
@ -61,13 +60,11 @@ class AllTransactionsView extends ConsumerStatefulWidget {
const AllTransactionsView({
Key? key,
required this.walletId,
this.isTokens = false,
}) : super(key: key);
static const String routeName = "/allTransactions";
final String walletId;
final bool isTokens;
@override
ConsumerState<AllTransactionsView> createState() =>
@ -487,41 +484,25 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
final criteria =
ref.watch(transactionFilterProvider.state).state;
//todo: check if print needed
// debugPrint("Consumer build called");
final WhereClause ww;
return FutureBuilder(
future: widget.isTokens
? ref
.watch(mainDBProvider)
.getTransactions(walletId)
.filter()
.otherDataEqualTo(ref
.watch(tokenServiceProvider)!
.tokenContract
.address)
.sortByTimestampDesc()
.findAll()
: ref.watch(mainDBProvider).isar.transactions.buildQuery<
Transaction>(
whereClauses: [
IndexWhereClause.equalTo(
indexName: 'walletId',
value: [widget.walletId],
)
],
// TODO: [prio=high] add filters to wallet or cryptocurrency class
// filter: [
// // todo
// ],
sortBy: [
const SortProperty(
property: "timestamp",
sort: Sort.desc,
),
]).findAll(),
future: ref.watch(mainDBProvider).isar.transactions.buildQuery<
Transaction>(
whereClauses: [
IndexWhereClause.equalTo(
indexName: 'walletId',
value: [widget.walletId],
)
],
// TODO: [prio=high] add filters to wallet or cryptocurrency class
// filter: [
// // todo
// ],
sortBy: [
const SortProperty(
property: "timestamp",
sort: Sort.desc,
),
]).findAll(),
builder: (_, AsyncSnapshot<List<Transaction>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {

View file

@ -15,7 +15,6 @@ import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/token_summary.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/token_transaction_list_widget.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart';
@ -25,6 +24,7 @@ import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
@ -57,7 +57,7 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
@override
void initState() {
initialSyncStatus = ref.read(tokenServiceProvider)!.isRefreshing
initialSyncStatus = ref.read(pCurrentTokenWallet)!.refreshMutex.isLocked
? WalletSyncStatus.syncing
: WalletSyncStatus.synced;
super.initState();
@ -114,7 +114,7 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
children: [
EthTokenIcon(
contractAddress: ref.watch(
tokenServiceProvider.select(
pCurrentTokenWallet.select(
(value) => value!.tokenContract.address,
),
),
@ -125,7 +125,7 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
),
Text(
ref.watch(
tokenServiceProvider.select(
pCurrentTokenWallet.select(
(value) => value!.tokenContract.name,
),
),
@ -153,7 +153,7 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
children: [
EthTokenIcon(
contractAddress: ref.watch(
tokenServiceProvider.select(
pCurrentTokenWallet.select(
(value) => value!.tokenContract.address,
),
),
@ -241,7 +241,7 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
child: MyWallet(
walletId: widget.walletId,
contractAddress: ref.watch(
tokenServiceProvider.select(
pCurrentTokenWallet.select(
(value) => value!.tokenContract.address,
),
),

View file

@ -15,7 +15,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
@ -27,6 +26,7 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
import 'package:stackwallet/widgets/animated_text.dart';
@ -103,8 +103,8 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
await wallet.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(tokenFeeSessionCacheProvider).fast[amount] = fee;
}
}
@ -147,8 +147,8 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
await wallet.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(tokenFeeSessionCacheProvider).average[amount] = fee;
}
}
@ -191,8 +191,8 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
await wallet.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(tokenFeeSessionCacheProvider).slow[amount] = fee;
}
}

View file

@ -20,7 +20,6 @@ import 'package:qr_flutter/qr_flutter.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
@ -32,6 +31,7 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
@ -307,7 +307,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
children: [
Text(
"Your ${widget.contractAddress == null ? coin.ticker : ref.watch(
tokenServiceProvider.select(
pCurrentTokenWallet.select(
(value) => value!.tokenContract.symbol,
),
)} SPARK address",
@ -398,7 +398,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
children: [
Text(
"Your ${widget.contractAddress == null ? coin.ticker : ref.watch(
tokenServiceProvider.select(
pCurrentTokenWallet.select(
(value) => value!.tokenContract.symbol,
),
)} address",

View file

@ -19,7 +19,6 @@ import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart';
@ -39,6 +38,8 @@ import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/eth/token_balance_provider.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
@ -103,10 +104,15 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
late VoidCallback onCryptoAmountChanged;
Future<void> previewSend() async {
final tokenWallet = ref.read(tokenServiceProvider)!;
final tokenWallet = ref.read(pCurrentTokenWallet)!;
final Amount amount = _amountToSend!;
final Amount availableBalance = tokenWallet.balance.spendable;
final Amount availableBalance = ref
.read(pTokenBalance((
walletId: walletId,
contractAddress: tokenWallet.tokenContract.address
)))
.spendable;
// confirm send all
if (amount == availableBalance) {
@ -214,7 +220,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
child: Padding(
padding: const EdgeInsets.all(32),
child: BuildingTransactionDialog(
coin: tokenWallet.coin,
coin: tokenWallet.cryptoCurrency.coin,
onCancel: () {
wasCancelled = true;
@ -389,11 +395,11 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
_amountToSend = cryptoAmount.contains(",")
? Decimal.parse(cryptoAmount.replaceFirst(",", ".")).toAmount(
fractionDigits:
ref.read(tokenServiceProvider)!.tokenContract.decimals,
ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
)
: Decimal.parse(cryptoAmount).toAmount(
fractionDigits:
ref.read(tokenServiceProvider)!.tokenContract.decimals,
ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
);
if (_cachedAmountToSend != null &&
_cachedAmountToSend == _amountToSend) {
@ -406,7 +412,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
final price = ref
.read(priceAnd24hChangeNotifierProvider)
.getTokenPrice(
ref.read(tokenServiceProvider)!.tokenContract.address,
ref.read(pCurrentTokenWallet)!.tokenContract.address,
)
.item1;
@ -485,7 +491,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
if (results["amount"] != null) {
final amount = Decimal.parse(results["amount"]!).toAmount(
fractionDigits:
ref.read(tokenServiceProvider)!.tokenContract.decimals,
ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
);
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
amount,
@ -543,7 +549,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
void fiatTextFieldOnChanged(String baseAmountString) {
final int tokenDecimals =
ref.read(tokenServiceProvider)!.tokenContract.decimals;
ref.read(pCurrentTokenWallet)!.tokenContract.decimals;
if (baseAmountString.isNotEmpty &&
baseAmountString != "." &&
@ -556,7 +562,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
final Decimal _price = ref
.read(priceAnd24hChangeNotifierProvider)
.getTokenPrice(
ref.read(tokenServiceProvider)!.tokenContract.address,
ref.read(pCurrentTokenWallet)!.tokenContract.address,
)
.item1;
@ -579,7 +585,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
final amountString = ref.read(pAmountFormatter(coin)).format(
_amountToSend!,
withUnitName: false,
ethContract: ref.read(tokenServiceProvider)!.tokenContract,
ethContract: ref.read(pCurrentTokenWallet)!.tokenContract,
);
_cryptoAmountChangeLock = true;
@ -597,12 +603,14 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
Future<void> sendAllTapped() async {
cryptoAmountController.text = ref
.read(tokenServiceProvider)!
.balance
.read(pTokenBalance((
walletId: walletId,
contractAddress: ref.read(pCurrentTokenWallet)!.tokenContract.address
)))
.spendable
.decimal
.toStringAsFixed(
ref.read(tokenServiceProvider)!.tokenContract.decimals,
ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
);
}
@ -686,7 +694,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
final tokenContract = ref.watch(tokenServiceProvider)!.tokenContract;
final tokenContract = ref.watch(pCurrentTokenWallet)!.tokenContract;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,

View file

@ -11,7 +11,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart';
import 'package:stackwallet/providers/providers.dart';
@ -24,6 +23,8 @@ import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/isar/providers/eth/token_balance_provider.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
class DesktopWalletSummary extends ConsumerStatefulWidget {
@ -70,8 +71,7 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
final tokenContract = widget.isToken
? ref
.watch(tokenServiceProvider.select((value) => value!.tokenContract))
? ref.watch(pCurrentTokenWallet.select((value) => value!.tokenContract))
: null;
final priceTuple = widget.isToken
@ -104,7 +104,8 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
}
} else {
Balance balance = widget.isToken
? ref.watch(tokenServiceProvider.select((value) => value!.balance))
? ref.watch(pTokenBalance(
(walletId: walletId, contractAddress: tokenContract!.address)))
: ref.watch(pWalletBalance(walletId));
balanceToShow = _showAvailable ? balance.spendable : balance.total;

View file

@ -1,64 +0,0 @@
/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/ethereum/ethereum_token_service.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart';
class ContractWalletId implements Equatable {
final String walletId;
final String tokenContractAddress;
ContractWalletId({
required this.walletId,
required this.tokenContractAddress,
});
@override
List<Object?> get props => [walletId, tokenContractAddress];
@override
bool? get stringify => true;
}
/// provide the token wallet given a contract address and eth wallet id
final tokenWalletProvider =
Provider.family<EthTokenWallet?, ContractWalletId>((ref, arg) {
final ethWallet =
ref.watch(pWallets).getWallet(arg.walletId) as EthereumWallet?;
final contract =
ref.read(mainDBProvider).getEthContractSync(arg.tokenContractAddress);
if (ethWallet == null || contract == null) {
Logging.instance.log(
"Attempted to access a token wallet with walletId=${arg.walletId} where"
" contractAddress=${arg.tokenContractAddress}",
level: LogLevel.Warning,
);
return null;
}
final secureStore = ref.watch(secureStoreProvider);
return EthTokenWallet(
token: contract,
ethWallet: ethWallet,
secureStore: secureStore,
tracker: TransactionNotificationTracker(
walletId: arg.walletId,
),
);
});

View file

@ -1300,18 +1300,6 @@ class RouteGenerator {
return _routeError("${settings.name} invalid args: ${args.toString()}");
case AllTransactionsView.routeName:
if (args is ({String walletId, bool isTokens})) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => AllTransactionsView(
walletId: args.walletId,
isTokens: args.isTokens,
),
settings: RouteSettings(
name: settings.name,
),
);
}
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,

View file

@ -1,611 +1,152 @@
/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
import 'dart:async';
import 'package:ethereum_addresses/ethereum_addresses.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/dto/ethereum/eth_token_tx_dto.dart';
import 'package:stackwallet/dto/ethereum/eth_token_tx_extra_dto.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/services/ethereum/ethereum_api.dart';
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/eth_token_cache.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/utilities/extensions/extensions.dart';
import 'package:stackwallet/utilities/extensions/impl/contract_abi.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart';
import 'package:tuple/tuple.dart';
import 'package:web3dart/web3dart.dart' as web3dart;
class EthTokenWallet extends ChangeNotifier with EthTokenCache {
final EthereumWallet ethWallet;
final TransactionNotificationTracker tracker;
final SecureStorageInterface _secureStore;
// late web3dart.EthereumAddress _contractAddress;
late web3dart.EthPrivateKey _credentials;
late web3dart.DeployedContract _deployedContract;
late web3dart.ContractFunction _sendFunction;
late web3dart.Web3Client _client;
static const _gasLimit = 200000;
EthTokenWallet({
required EthContract token,
required this.ethWallet,
required SecureStorageInterface secureStore,
required this.tracker,
}) : _secureStore = secureStore,
_tokenContract = token {
// _contractAddress = web3dart.EthereumAddress.fromHex(token.address);
initCache(ethWallet.walletId, token);
}
EthContract get tokenContract => _tokenContract;
EthContract _tokenContract;
Balance get balance => _balance ??= getCachedBalance();
Balance? _balance;
Coin get coin => Coin.ethereum;
Future<TxData> prepareSend({
required TxData txData,
}) async {
final feeRateType = txData.feeRateType!;
int fee = 0;
final feeObject = await fees;
switch (feeRateType) {
case FeeRateType.fast:
fee = feeObject.fast;
break;
case FeeRateType.average:
fee = feeObject.medium;
break;
case FeeRateType.slow:
fee = feeObject.slow;
break;
case FeeRateType.custom:
throw UnimplementedError("custom eth token fees");
}
final feeEstimate = estimateFeeFor(fee);
final client = await getEthClient();
final myAddress = await currentReceivingAddress;
final myWeb3Address = web3dart.EthereumAddress.fromHex(myAddress);
final nonce = txData.nonce ??
await client.getTransactionCount(myWeb3Address,
atBlock: const web3dart.BlockNum.pending());
final amount = txData.recipients!.first.amount;
final address = txData.recipients!.first.address;
final tx = web3dart.Transaction.callContract(
contract: _deployedContract,
function: _sendFunction,
parameters: [web3dart.EthereumAddress.fromHex(address), amount.raw],
maxGas: _gasLimit,
gasPrice: web3dart.EtherAmount.fromUnitAndValue(
web3dart.EtherUnit.wei,
fee,
),
nonce: nonce,
);
return txData.copyWith(
fee: feeEstimate,
feeInWei: BigInt.from(fee),
web3dartTransaction: tx,
chainId: await client.getChainId(),
nonce: tx.nonce,
);
}
Future<String> confirmSend({required Map<String, dynamic> txData}) async {
try {
final txid = await _client.sendTransaction(
_credentials,
txData["ethTx"] as web3dart.Transaction,
chainId: txData["chainId"] as int,
);
try {
txData["txid"] = txid;
await updateSentCachedTxData(txData);
} catch (e, s) {
// do not rethrow as that would get handled as a send failure further up
// also this is not critical code and transaction should show up on \
// refresh regardless
Logging.instance.log("$e\n$s", level: LogLevel.Warning);
}
notifyListeners();
return txid;
} catch (e) {
// rethrow to pass error in alert
rethrow;
}
}
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
final txid = txData["txid"] as String;
final addressString = checksumEthereumAddress(txData["address"] as String);
final response = await EthereumAPI.getEthTransactionByHash(txid);
final transaction = Transaction(
walletId: ethWallet.walletId,
txid: txid,
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
type: TransactionType.outgoing,
subType: TransactionSubType.ethToken,
// precision may be lost here hence the following amountString
amount: (txData["recipientAmt"] as Amount).raw.toInt(),
amountString: (txData["recipientAmt"] as Amount).toJsonString(),
fee: (txData["fee"] as Amount).raw.toInt(),
height: null,
isCancelled: false,
isLelantus: false,
otherData: tokenContract.address,
slateId: null,
nonce: (txData["nonce"] as int?) ??
response.value?.nonce.toBigIntFromHex.toInt(),
inputs: [],
outputs: [],
numberOfMessages: null,
);
Address? address = await ethWallet.mainDB.getAddress(
ethWallet.walletId,
addressString,
);
address ??= Address(
walletId: ethWallet.walletId,
value: addressString,
publicKey: [],
derivationIndex: -1,
derivationPath: null,
type: AddressType.ethereum,
subType: AddressSubType.nonWallet,
);
await ethWallet.mainDB.addNewTransactionData(
[
Tuple2(transaction, address),
],
ethWallet.walletId,
);
}
Future<String> get currentReceivingAddress async {
final address = await _currentReceivingAddress;
return checksumEthereumAddress(
address?.value ?? _credentials.address.toString());
}
Future<Address?> get _currentReceivingAddress => ethWallet.mainDB
.getAddresses(ethWallet.walletId)
.filter()
.typeEqualTo(AddressType.ethereum)
.subTypeEqualTo(AddressSubType.receiving)
.sortByDerivationIndexDesc()
.findFirst();
Amount estimateFeeFor(int feeRate) {
return estimateFee(feeRate, _gasLimit, coin.decimals);
}
Future<FeeObject> get fees => EthereumAPI.getFees();
Future<EthContract> _updateTokenABI({
required EthContract forContract,
required String usingContractAddress,
}) async {
final abiResponse = await EthereumAPI.getTokenAbi(
name: forContract.name,
contractAddress: usingContractAddress,
);
// Fetch token ABI so we can call token functions
if (abiResponse.value != null) {
final updatedToken = forContract.copyWith(abi: abiResponse.value!);
// Store updated contract
final id = await MainDB.instance.putEthContract(updatedToken);
return updatedToken..id = id;
} else {
throw abiResponse.exception!;
}
}
Future<void> initialize() async {
final contractAddress =
web3dart.EthereumAddress.fromHex(tokenContract.address);
if (tokenContract.abi == null) {
_tokenContract = await _updateTokenABI(
forContract: tokenContract,
usingContractAddress: contractAddress.hex,
);
}
String? mnemonicString = await ethWallet.getMnemonic();
//Get private key for given mnemonic
String privateKey = getPrivateKey(
mnemonicString!,
(await ethWallet.getMnemonicPassphrase()) ?? "",
);
_credentials = web3dart.EthPrivateKey.fromHex(privateKey);
try {
_deployedContract = web3dart.DeployedContract(
ContractAbiExtensions.fromJsonList(
jsonList: tokenContract.abi!,
name: tokenContract.name,
),
contractAddress,
);
} catch (_) {
rethrow;
}
try {
_sendFunction = _deployedContract.function('transfer');
} catch (_) {
//====================================================================
// final list = List<Map<String, dynamic>>.from(
// jsonDecode(tokenContract.abi!) as List);
// final functionNames = list.map((e) => e["name"] as String);
//
// if (!functionNames.contains("balanceOf")) {
// list.add(
// {
// "encoding": "0x70a08231",
// "inputs": [
// {"name": "account", "type": "address"}
// ],
// "name": "balanceOf",
// "outputs": [
// {"name": "val_0", "type": "uint256"}
// ],
// "signature": "balanceOf(address)",
// "type": "function"
// },
// );
// }
//
// if (!functionNames.contains("transfer")) {
// list.add(
// {
// "encoding": "0xa9059cbb",
// "inputs": [
// {"name": "dst", "type": "address"},
// {"name": "rawAmount", "type": "uint256"}
// ],
// "name": "transfer",
// "outputs": [
// {"name": "val_0", "type": "bool"}
// ],
// "signature": "transfer(address,uint256)",
// "type": "function"
// },
// );
// }
//--------------------------------------------------------------------
//====================================================================
// function not found so likely a proxy so we need to fetch the impl
//====================================================================
// final updatedToken = tokenContract.copyWith(abi: jsonEncode(list));
// // Store updated contract
// final id = await MainDB.instance.putEthContract(updatedToken);
// _tokenContract = updatedToken..id = id;
//--------------------------------------------------------------------
final contractAddressResponse =
await EthereumAPI.getProxyTokenImplementationAddress(
contractAddress.hex);
if (contractAddressResponse.value != null) {
_tokenContract = await _updateTokenABI(
forContract: tokenContract,
usingContractAddress: contractAddressResponse.value!,
);
} else {
throw contractAddressResponse.exception!;
}
//====================================================================
}
try {
_deployedContract = web3dart.DeployedContract(
ContractAbiExtensions.fromJsonList(
jsonList: tokenContract.abi!,
name: tokenContract.name,
),
contractAddress,
);
} catch (_) {
rethrow;
}
_sendFunction = _deployedContract.function('transfer');
_client = await getEthClient();
unawaited(refresh());
}
bool get isRefreshing => _refreshLock;
bool _refreshLock = false;
Future<void> refresh() async {
if (!_refreshLock) {
_refreshLock = true;
try {
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.syncing,
ethWallet.walletId + tokenContract.address,
coin,
),
);
await refreshCachedBalance();
await _refreshTransactions();
} catch (e, s) {
Logging.instance.log(
"Caught exception in ${tokenContract.name} ${ethWallet.info.name} ${ethWallet.walletId} refresh(): $e\n$s",
level: LogLevel.Warning,
);
} finally {
_refreshLock = false;
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.synced,
ethWallet.walletId + tokenContract.address,
coin,
),
);
notifyListeners();
}
}
}
Future<void> refreshCachedBalance() async {
final response = await EthereumAPI.getWalletTokenBalance(
address: _credentials.address.hex,
contractAddress: tokenContract.address,
);
if (response.value != null) {
await updateCachedBalance(
Balance(
total: response.value!,
spendable: response.value!,
blockedTotal: Amount(
rawValue: BigInt.zero,
fractionDigits: tokenContract.decimals,
),
pendingSpendable: Amount(
rawValue: BigInt.zero,
fractionDigits: tokenContract.decimals,
),
),
);
notifyListeners();
} else {
Logging.instance.log(
"CachedEthTokenBalance.fetchAndUpdateCachedBalance failed: ${response.exception}",
level: LogLevel.Warning,
);
}
}
Future<List<Transaction>> get transactions => ethWallet.mainDB
.getTransactions(ethWallet.walletId)
.filter()
.otherDataEqualTo(tokenContract.address)
.sortByTimestampDesc()
.findAll();
String _addressFromTopic(String topic) =>
checksumEthereumAddress("0x${topic.substring(topic.length - 40)}");
Future<void> _refreshTransactions() async {
String addressString =
checksumEthereumAddress(await currentReceivingAddress);
final response = await EthereumAPI.getTokenTransactions(
address: addressString,
tokenContractAddress: tokenContract.address,
);
if (response.value == null) {
if (response.exception != null &&
response.exception!.message
.contains("response is empty but status code is 200")) {
Logging.instance.log(
"No ${tokenContract.name} transfers found for $addressString",
level: LogLevel.Info,
);
return;
}
throw response.exception ??
Exception("Failed to fetch token transaction data");
}
// no need to continue if no transactions found
if (response.value!.isEmpty) {
return;
}
final response2 = await EthereumAPI.getEthTokenTransactionsByTxids(
response.value!.map((e) => e.transactionHash).toSet().toList(),
);
if (response2.value == null) {
throw response2.exception ??
Exception("Failed to fetch token transactions");
}
final List<Tuple2<EthTokenTxDto, EthTokenTxExtraDTO>> data = [];
for (final tokenDto in response.value!) {
try {
final txExtra = response2.value!.firstWhere(
(e) => e.hash == tokenDto.transactionHash,
);
data.add(
Tuple2(
tokenDto,
txExtra,
),
);
} catch (_) {
// Server indexing failed for some reason. Instead of hard crashing or
// showing no transactions we just skip it here. Not ideal but better
// than nothing showing up
Logging.instance.log(
"Server error: Transaction ${tokenDto.transactionHash} not found.",
level: LogLevel.Error,
);
}
}
final List<Tuple2<Transaction, Address?>> txnsData = [];
for (final tuple in data) {
// ignore all non Transfer events (for now)
if (tuple.item1.topics[0] == kTransferEventSignature) {
final Amount amount;
String fromAddress, toAddress;
amount = Amount(
rawValue: tuple.item1.data.toBigIntFromHex,
fractionDigits: tokenContract.decimals,
);
fromAddress = _addressFromTopic(
tuple.item1.topics[1],
);
toAddress = _addressFromTopic(
tuple.item1.topics[2],
);
bool isIncoming;
bool isSentToSelf = false;
if (fromAddress == addressString) {
isIncoming = false;
if (toAddress == addressString) {
isSentToSelf = true;
}
} else if (toAddress == addressString) {
isIncoming = true;
} else {
// ignore for now I guess since anything here is not reflected in
// balance anyways
continue;
// throw Exception("Unknown token transaction found for "
// "${ethWallet.walletName} ${ethWallet.walletId}: "
// "${tuple.item1.toString()}");
}
final txn = Transaction(
walletId: ethWallet.walletId,
txid: tuple.item1.transactionHash,
timestamp: tuple.item2.timestamp,
type:
isIncoming ? TransactionType.incoming : TransactionType.outgoing,
subType: TransactionSubType.ethToken,
amount: amount.raw.toInt(),
amountString: amount.toJsonString(),
fee: (tuple.item2.gasUsed.raw * tuple.item2.gasPrice.raw).toInt(),
height: tuple.item1.blockNumber,
isCancelled: false,
isLelantus: false,
slateId: null,
nonce: tuple.item2.nonce,
otherData: tuple.item1.address,
inputs: [],
outputs: [],
numberOfMessages: null,
);
Address? transactionAddress = await ethWallet.mainDB
.getAddresses(ethWallet.walletId)
.filter()
.valueEqualTo(toAddress)
.findFirst();
transactionAddress ??= Address(
walletId: ethWallet.walletId,
value: toAddress,
publicKey: [],
derivationIndex: isSentToSelf ? 0 : -1,
derivationPath: isSentToSelf
? (DerivationPath()..value = "$hdPathEthereum/0")
: null,
type: AddressType.ethereum,
subType: isSentToSelf
? AddressSubType.receiving
: AddressSubType.nonWallet,
);
txnsData.add(Tuple2(txn, transactionAddress));
}
}
await ethWallet.mainDB.addNewTransactionData(txnsData, ethWallet.walletId);
// quick hack to notify manager to call notifyListeners if
// transactions changed
if (txnsData.isNotEmpty) {
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"${tokenContract.name} transactions updated/added for: ${ethWallet.walletId} ${ethWallet.info.name}",
ethWallet.walletId,
),
);
}
}
bool validateAddress(String address) {
return isValidEthereumAddress(address);
}
NodeModel getCurrentNode() {
return NodeService(secureStorageInterface: _secureStore)
.getPrimaryNodeFor(coin: coin) ??
DefaultNodes.getNodeFor(coin);
}
Future<web3dart.Web3Client> getEthClient() async {
final node = getCurrentNode();
return web3dart.Web3Client(node.host, Client());
}
}
// /*
// * This file is part of Stack Wallet.
// *
// * Copyright (c) 2023 Cypher Stack
// * All Rights Reserved.
// * The code is distributed under GPLv3 license, see LICENSE file for details.
// * Generated by Cypher Stack on 2023-05-26
// *
// */
//
// import 'dart:async';
//
// import 'package:ethereum_addresses/ethereum_addresses.dart';
// import 'package:flutter/widgets.dart';
// import 'package:http/http.dart';
// import 'package:isar/isar.dart';
// import 'package:stackwallet/db/isar/main_db.dart';
// import 'package:stackwallet/dto/ethereum/eth_token_tx_dto.dart';
// import 'package:stackwallet/dto/ethereum/eth_token_tx_extra_dto.dart';
// import 'package:stackwallet/models/balance.dart';
// import 'package:stackwallet/models/isar/models/isar_models.dart';
// import 'package:stackwallet/models/node_model.dart';
// import 'package:stackwallet/models/paymint/fee_object_model.dart';
// import 'package:stackwallet/services/ethereum/ethereum_api.dart';
// import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
// import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
// import 'package:stackwallet/services/event_bus/global_event_bus.dart';
// import 'package:stackwallet/services/mixins/eth_token_cache.dart';
// import 'package:stackwallet/services/node_service.dart';
// import 'package:stackwallet/services/transaction_notification_tracker.dart';
// import 'package:stackwallet/utilities/amount/amount.dart';
// import 'package:stackwallet/utilities/default_nodes.dart';
// import 'package:stackwallet/utilities/enums/coin_enum.dart';
// import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
// import 'package:stackwallet/utilities/eth_commons.dart';
// import 'package:stackwallet/utilities/extensions/extensions.dart';
// import 'package:stackwallet/utilities/extensions/impl/contract_abi.dart';
// import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
// import 'package:stackwallet/utilities/logger.dart';
// import 'package:stackwallet/wallets/models/tx_data.dart';
// import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart';
// import 'package:tuple/tuple.dart';
// import 'package:web3dart/web3dart.dart' as web3dart;
//
// class EthTokenWallet extends ChangeNotifier {
// final EthereumWallet ethWallet;
// final TransactionNotificationTracker tracker;
//
//
// Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
// final txid = txData["txid"] as String;
// final addressString = checksumEthereumAddress(txData["address"] as String);
// final response = await EthereumAPI.getEthTransactionByHash(txid);
//
// final transaction = Transaction(
// walletId: ethWallet.walletId,
// txid: txid,
// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
// type: TransactionType.outgoing,
// subType: TransactionSubType.ethToken,
// // precision may be lost here hence the following amountString
// amount: (txData["recipientAmt"] as Amount).raw.toInt(),
// amountString: (txData["recipientAmt"] as Amount).toJsonString(),
// fee: (txData["fee"] as Amount).raw.toInt(),
// height: null,
// isCancelled: false,
// isLelantus: false,
// otherData: tokenContract.address,
// slateId: null,
// nonce: (txData["nonce"] as int?) ??
// response.value?.nonce.toBigIntFromHex.toInt(),
// inputs: [],
// outputs: [],
// numberOfMessages: null,
// );
//
// Address? address = await ethWallet.mainDB.getAddress(
// ethWallet.walletId,
// addressString,
// );
//
// address ??= Address(
// walletId: ethWallet.walletId,
// value: addressString,
// publicKey: [],
// derivationIndex: -1,
// derivationPath: null,
// type: AddressType.ethereum,
// subType: AddressSubType.nonWallet,
// );
//
// await ethWallet.mainDB.addNewTransactionData(
// [
// Tuple2(transaction, address),
// ],
// ethWallet.walletId,
// );
// }
//
//
//
// bool get isRefreshing => _refreshLock;
//
// bool _refreshLock = false;
//
// Future<void> refresh() async {
// if (!_refreshLock) {
// _refreshLock = true;
// try {
// GlobalEventBus.instance.fire(
// WalletSyncStatusChangedEvent(
// WalletSyncStatus.syncing,
// ethWallet.walletId + tokenContract.address,
// coin,
// ),
// );
//
// await refreshCachedBalance();
// await _refreshTransactions();
// } catch (e, s) {
// Logging.instance.log(
// "Caught exception in ${tokenContract.name} ${ethWallet.info.name} ${ethWallet.walletId} refresh(): $e\n$s",
// level: LogLevel.Warning,
// );
// } finally {
// _refreshLock = false;
// GlobalEventBus.instance.fire(
// WalletSyncStatusChangedEvent(
// WalletSyncStatus.synced,
// ethWallet.walletId + tokenContract.address,
// coin,
// ),
// );
// notifyListeners();
// }
// }
// }
//
//
//
// Future<List<Transaction>> get transactions => ethWallet.mainDB
// .getTransactions(ethWallet.walletId)
// .filter()
// .otherDataEqualTo(tokenContract.address)
// .sortByTimestampDesc()
// .findAll();
//
//
//
//
//
// }

View file

@ -12,7 +12,6 @@ import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
import 'package:decimal/decimal.dart';
import "package:hex/hex.dart";
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -71,21 +70,3 @@ String getPrivateKey(String mnemonic, String mnemonicPassphrase) {
return HEX.encode(addressAtIndex.privateKey as List<int>);
}
Amount estimateFee(int feeRate, int gasLimit, int decimals) {
final gweiAmount = feeRate.toDecimal() / (Decimal.ten.pow(9).toDecimal());
final fee = gasLimit.toDecimal() *
gweiAmount.toDecimal(
scaleOnInfinitePrecision: Coin.ethereum.decimals,
);
//Convert gwei to ETH
final feeInWei = fee * Decimal.ten.pow(9).toDecimal();
final ethAmount = feeInWei / Decimal.ten.pow(decimals).toDecimal();
return Amount.fromDecimal(
ethAmount.toDecimal(
scaleOnInfinitePrecision: Coin.ethereum.decimals,
),
fractionDigits: decimals,
);
}

View file

@ -0,0 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart';
final tokenServiceStateProvider = StateProvider<EthTokenWallet?>((ref) => null);
final pCurrentTokenWallet =
Provider<EthTokenWallet?>((ref) => ref.watch(tokenServiceStateProvider));

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'package:decimal/decimal.dart';
import 'package:ethereum_addresses/ethereum_addresses.dart';
import 'package:http/http.dart';
import 'package:isar/isar.dart';
@ -56,6 +57,24 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
return web3.Web3Client(node.host, client);
}
Amount estimateEthFee(int feeRate, int gasLimit, int decimals) {
final gweiAmount = feeRate.toDecimal() / (Decimal.ten.pow(9).toDecimal());
final fee = gasLimit.toDecimal() *
gweiAmount.toDecimal(
scaleOnInfinitePrecision: cryptoCurrency.fractionDigits,
);
//Convert gwei to ETH
final feeInWei = fee * Decimal.ten.pow(9).toDecimal();
final ethAmount = feeInWei / Decimal.ten.pow(decimals).toDecimal();
return Amount.fromDecimal(
ethAmount.toDecimal(
scaleOnInfinitePrecision: cryptoCurrency.fractionDigits,
),
fractionDigits: decimals,
);
}
// ==================== Private ==============================================
Future<void> _initCredentials(
@ -118,7 +137,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
@override
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
return estimateFee(
return estimateEthFee(
feeRate,
(cryptoCurrency as Ethereum).gasLimit,
cryptoCurrency.fractionDigits,
@ -249,7 +268,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
//Calculate fees (GasLimit * gasPrice)
// int txFee = element.gasPrice * element.gasUsed;
Amount txFee = element.gasCost;
final Amount txFee = element.gasCost;
final transactionAmount = element.value;
final addressFrom = checksumEthereumAddress(element.from);
final addressTo = checksumEthereumAddress(element.to);
@ -267,7 +286,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
continue;
}
// hack epic tx data into inputs and outputs
// hack eth tx data into inputs and outputs
final List<OutputV2> outputs = [];
final List<InputV2> inputs = [];

View file

@ -0,0 +1,491 @@
import 'dart:convert';
import 'package:ethereum_addresses/ethereum_addresses.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/dto/ethereum/eth_token_tx_dto.dart';
import 'package:stackwallet/dto/ethereum/eth_token_tx_extra_dto.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/services/ethereum/ethereum_api.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/utilities/extensions/extensions.dart';
import 'package:stackwallet/utilities/extensions/impl/contract_abi.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/isar/models/token_wallet_info.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet.dart';
import 'package:web3dart/web3dart.dart' as web3dart;
class EthTokenWallet extends Wallet {
@override
int get isarTransactionVersion => 2;
EthTokenWallet(this.ethWallet, this._tokenContract)
: super(ethWallet.cryptoCurrency);
final EthereumWallet ethWallet;
EthContract get tokenContract => _tokenContract;
EthContract _tokenContract;
late web3dart.DeployedContract _deployedContract;
late web3dart.ContractFunction _sendFunction;
static const _gasLimit = 200000;
// ===========================================================================
// ===========================================================================
Future<EthContract> _updateTokenABI({
required EthContract forContract,
required String usingContractAddress,
}) async {
final abiResponse = await EthereumAPI.getTokenAbi(
name: forContract.name,
contractAddress: usingContractAddress,
);
// Fetch token ABI so we can call token functions
if (abiResponse.value != null) {
final updatedToken = forContract.copyWith(abi: abiResponse.value!);
// Store updated contract
final id = await mainDB.putEthContract(updatedToken);
return updatedToken..id = id;
} else {
throw abiResponse.exception!;
}
}
String _addressFromTopic(String topic) =>
checksumEthereumAddress("0x${topic.substring(topic.length - 40)}");
// ===========================================================================
@override
FilterOperation? get changeAddressFilterOperation =>
ethWallet.changeAddressFilterOperation;
@override
FilterOperation? get receivingAddressFilterOperation =>
ethWallet.receivingAddressFilterOperation;
@override
Future<void> init() async {
await super.init();
final contractAddress =
web3dart.EthereumAddress.fromHex(tokenContract.address);
if (tokenContract.abi == null) {
_tokenContract = await _updateTokenABI(
forContract: tokenContract,
usingContractAddress: contractAddress.hex,
);
}
// String? mnemonicString = await ethWallet.getMnemonic();
//
// //Get private key for given mnemonic
// String privateKey = getPrivateKey(
// mnemonicString,
// (await ethWallet.getMnemonicPassphrase()),
// );
// _credentials = web3dart.EthPrivateKey.fromHex(privateKey);
try {
_deployedContract = web3dart.DeployedContract(
ContractAbiExtensions.fromJsonList(
jsonList: tokenContract.abi!,
name: tokenContract.name,
),
contractAddress,
);
} catch (_) {
rethrow;
}
try {
_sendFunction = _deployedContract.function('transfer');
} catch (_) {
//====================================================================
// final list = List<Map<String, dynamic>>.from(
// jsonDecode(tokenContract.abi!) as List);
// final functionNames = list.map((e) => e["name"] as String);
//
// if (!functionNames.contains("balanceOf")) {
// list.add(
// {
// "encoding": "0x70a08231",
// "inputs": [
// {"name": "account", "type": "address"}
// ],
// "name": "balanceOf",
// "outputs": [
// {"name": "val_0", "type": "uint256"}
// ],
// "signature": "balanceOf(address)",
// "type": "function"
// },
// );
// }
//
// if (!functionNames.contains("transfer")) {
// list.add(
// {
// "encoding": "0xa9059cbb",
// "inputs": [
// {"name": "dst", "type": "address"},
// {"name": "rawAmount", "type": "uint256"}
// ],
// "name": "transfer",
// "outputs": [
// {"name": "val_0", "type": "bool"}
// ],
// "signature": "transfer(address,uint256)",
// "type": "function"
// },
// );
// }
//--------------------------------------------------------------------
//====================================================================
// function not found so likely a proxy so we need to fetch the impl
//====================================================================
// final updatedToken = tokenContract.copyWith(abi: jsonEncode(list));
// // Store updated contract
// final id = await MainDB.instance.putEthContract(updatedToken);
// _tokenContract = updatedToken..id = id;
//--------------------------------------------------------------------
final contractAddressResponse =
await EthereumAPI.getProxyTokenImplementationAddress(
contractAddress.hex);
if (contractAddressResponse.value != null) {
_tokenContract = await _updateTokenABI(
forContract: tokenContract,
usingContractAddress: contractAddressResponse.value!,
);
} else {
throw contractAddressResponse.exception!;
}
//====================================================================
}
try {
_deployedContract = web3dart.DeployedContract(
ContractAbiExtensions.fromJsonList(
jsonList: tokenContract.abi!,
name: tokenContract.name,
),
contractAddress,
);
} catch (_) {
rethrow;
}
_sendFunction = _deployedContract.function('transfer');
}
@override
Future<TxData> prepareSend({required TxData txData}) async {
final feeRateType = txData.feeRateType!;
int fee = 0;
final feeObject = await fees;
switch (feeRateType) {
case FeeRateType.fast:
fee = feeObject.fast;
break;
case FeeRateType.average:
fee = feeObject.medium;
break;
case FeeRateType.slow:
fee = feeObject.slow;
break;
case FeeRateType.custom:
throw UnimplementedError("custom eth token fees");
}
final feeEstimate = await estimateFeeFor(Amount.zero, fee);
final client = ethWallet.getEthClient();
final myAddress = (await getCurrentReceivingAddress())!.value;
final myWeb3Address = web3dart.EthereumAddress.fromHex(myAddress);
final nonce = txData.nonce ??
await client.getTransactionCount(myWeb3Address,
atBlock: const web3dart.BlockNum.pending());
final amount = txData.recipients!.first.amount;
final address = txData.recipients!.first.address;
final tx = web3dart.Transaction.callContract(
contract: _deployedContract,
function: _sendFunction,
parameters: [web3dart.EthereumAddress.fromHex(address), amount.raw],
maxGas: _gasLimit,
gasPrice: web3dart.EtherAmount.fromUnitAndValue(
web3dart.EtherUnit.wei,
fee,
),
nonce: nonce,
);
return txData.copyWith(
fee: feeEstimate,
feeInWei: BigInt.from(fee),
web3dartTransaction: tx,
chainId: await client.getChainId(),
nonce: tx.nonce,
);
}
@override
Future<TxData> confirmSend({required TxData txData}) async {
try {
return await ethWallet.confirmSend(txData: txData);
} catch (e) {
// rethrow to pass error in alert
rethrow;
}
}
@override
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
return ethWallet.estimateEthFee(
feeRate,
_gasLimit,
cryptoCurrency.fractionDigits,
);
}
@override
Future<FeeObject> get fees => EthereumAPI.getFees();
@override
Future<bool> pingCheck() async {
return await ethWallet.pingCheck();
}
@override
Future<void> recover({required bool isRescan}) {
// TODO: implement recover
throw UnimplementedError();
}
@override
Future<void> updateBalance() async {
try {
final info = await mainDB.isar.tokenWalletInfo
.where()
.walletIdTokenAddressEqualTo(walletId, tokenContract.address)
.findFirst();
final response = await EthereumAPI.getWalletTokenBalance(
address: (await getCurrentReceivingAddress())!.value,
contractAddress: tokenContract.address,
);
if (response.value != null && info != null) {
await info.updateCachedBalance(
Balance(
total: response.value!,
spendable: response.value!,
blockedTotal: Amount(
rawValue: BigInt.zero,
fractionDigits: tokenContract.decimals,
),
pendingSpendable: Amount(
rawValue: BigInt.zero,
fractionDigits: tokenContract.decimals,
),
),
isar: mainDB.isar,
);
} else {
Logging.instance.log(
"CachedEthTokenBalance.fetchAndUpdateCachedBalance failed: ${response.exception}",
level: LogLevel.Warning,
);
}
} catch (e, s) {
Logging.instance.log(
"$runtimeType wallet failed to update balance: $e\n$s",
level: LogLevel.Warning,
);
}
}
@override
Future<void> updateChainHeight() async {
await ethWallet.updateChainHeight();
}
@override
Future<void> updateTransactions() async {
try {
final String addressString =
checksumEthereumAddress((await getCurrentReceivingAddress())!.value);
final response = await EthereumAPI.getTokenTransactions(
address: addressString,
tokenContractAddress: tokenContract.address,
);
if (response.value == null) {
if (response.exception != null &&
response.exception!.message
.contains("response is empty but status code is 200")) {
Logging.instance.log(
"No ${tokenContract.name} transfers found for $addressString",
level: LogLevel.Info,
);
return;
}
throw response.exception ??
Exception("Failed to fetch token transaction data");
}
// no need to continue if no transactions found
if (response.value!.isEmpty) {
return;
}
final response2 = await EthereumAPI.getEthTokenTransactionsByTxids(
response.value!.map((e) => e.transactionHash).toSet().toList(),
);
if (response2.value == null) {
throw response2.exception ??
Exception("Failed to fetch token transactions");
}
final List<({EthTokenTxDto tx, EthTokenTxExtraDTO extra})> data = [];
for (final tokenDto in response.value!) {
try {
final txExtra = response2.value!.firstWhere(
(e) => e.hash == tokenDto.transactionHash,
);
data.add(
(
tx: tokenDto,
extra: txExtra,
),
);
} catch (_) {
// Server indexing failed for some reason. Instead of hard crashing or
// showing no transactions we just skip it here. Not ideal but better
// than nothing showing up
Logging.instance.log(
"Server error: Transaction ${tokenDto.transactionHash} not found.",
level: LogLevel.Error,
);
}
}
final List<TransactionV2> txns = [];
for (final tuple in data) {
// ignore all non Transfer events (for now)
if (tuple.tx.topics[0] == kTransferEventSignature) {
final Amount amount;
final Amount txFee = tuple.extra.gasUsed * tuple.extra.gasPrice;
String fromAddress, toAddress;
amount = Amount(
rawValue: tuple.tx.data.toBigIntFromHex,
fractionDigits: tokenContract.decimals,
);
fromAddress = _addressFromTopic(
tuple.tx.topics[1],
);
toAddress = _addressFromTopic(
tuple.tx.topics[2],
);
bool isIncoming;
bool isSentToSelf = false;
if (fromAddress == addressString) {
isIncoming = false;
if (toAddress == addressString) {
isSentToSelf = true;
}
} else if (toAddress == addressString) {
isIncoming = true;
} else {
// ignore for now I guess since anything here is not reflected in
// balance anyways
continue;
// throw Exception("Unknown token transaction found for "
// "${ethWallet.walletName} ${ethWallet.walletId}: "
// "${tuple.item1.toString()}");
}
final TransactionType txType;
if (isIncoming) {
if (fromAddress == toAddress) {
txType = TransactionType.sentToSelf;
} else {
txType = TransactionType.incoming;
}
} else {
txType = TransactionType.outgoing;
}
final otherData = {
"nonce": tuple.extra.nonce,
"isCancelled": false,
"overrideFee": txFee.toJsonString(),
"contractAddress": tuple.tx.address,
};
// hack eth tx data into inputs and outputs
final List<OutputV2> outputs = [];
final List<InputV2> inputs = [];
// TODO: ins outs
final txn = TransactionV2(
walletId: walletId,
blockHash: tuple.extra.blockHash,
hash: tuple.tx.transactionHash,
txid: tuple.tx.transactionHash,
timestamp: tuple.extra.timestamp,
height: tuple.tx.blockNumber,
inputs: List.unmodifiable(inputs),
outputs: List.unmodifiable(outputs),
version: -1,
type: txType,
subType: TransactionSubType.ethToken,
otherData: jsonEncode(otherData),
);
txns.add(txn);
}
}
await mainDB.updateOrPutTransactionV2s(txns);
} catch (e, s) {
Logging.instance.log(
"$runtimeType wallet failed to update transactions: $e\n$s",
level: LogLevel.Warning,
);
}
}
@override
Future<void> updateNode() async {
await ethWallet.updateNode();
}
@override
Future<bool> updateUTXOs() async {
return await ethWallet.updateUTXOs();
}
}

View file

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
@ -14,6 +13,7 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
import 'package:stackwallet/widgets/animated_text.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
@ -168,8 +168,8 @@ class _DesktopFeeDialogState extends ConsumerState<DesktopFeeDialog> {
await wallet.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(tokenFeeSessionCacheProvider).slow[amount] = fee;
}
}

View file

@ -18,16 +18,15 @@ import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/ethereum/ethereum_token_service.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
@ -55,17 +54,16 @@ class SimpleWalletCard extends ConsumerWidget {
Wallet wallet,
EthContract contract,
) async {
final old = ref.read(tokenServiceStateProvider);
// exit previous if there is one
unawaited(old?.exit());
ref.read(tokenServiceStateProvider.state).state = EthTokenWallet(
token: contract,
secureStore: ref.read(secureStoreProvider),
ethWallet: wallet as EthereumWallet,
tracker: TransactionNotificationTracker(
walletId: walletId,
),
wallet as EthereumWallet,
contract,
);
try {
await ref.read(tokenServiceProvider)!.initialize();
await ref.read(pCurrentTokenWallet)!.init();
return true;
} catch (_) {
await showDialog<void>(