Merge remote-tracking branch 'origin/wallets_refactor' into wallets_refactor

This commit is contained in:
sneurlax 2024-01-10 16:54:24 -06:00
commit 7bf817ca66
40 changed files with 2829 additions and 1281 deletions

View file

@ -22,6 +22,7 @@ import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/stack_file_system.dart';
import 'package:stackwallet/wallets/isar/models/spark_coin.dart'; import 'package:stackwallet/wallets/isar/models/spark_coin.dart';
import 'package:stackwallet/wallets/isar/models/token_wallet_info.dart';
import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
import 'package:stackwallet/wallets/isar/models/wallet_info_meta.dart'; import 'package:stackwallet/wallets/isar/models/wallet_info_meta.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -65,6 +66,7 @@ class MainDB {
TransactionV2Schema, TransactionV2Schema,
SparkCoinSchema, SparkCoinSchema,
WalletInfoMetaSchema, WalletInfoMetaSchema,
TokenWalletInfoSchema,
], ],
directory: (await StackFileSystem.applicationIsarDirectory()).path, directory: (await StackFileSystem.applicationIsarDirectory()).path,
// inspector: kDebugMode, // inspector: kDebugMode,

View file

@ -65,6 +65,10 @@ class TransactionV2 {
String? get onChainNote => _getFromOtherData(key: "onChainNote") as String?; String? get onChainNote => _getFromOtherData(key: "onChainNote") as String?;
bool get isCancelled => _getFromOtherData(key: "isCancelled") == true; 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) { int getConfirmations(int currentChainHeight) {
if (height == null || height! <= 0) return 0; if (height == null || height! <= 0) return 0;
return max(0, currentChainHeight - (height! - 1)); return max(0, currentChainHeight - (height! - 1));
@ -76,8 +80,8 @@ class TransactionV2 {
} }
Amount getFee({required Coin coin}) { Amount getFee({required Coin coin}) {
// try anon fee first // check for override fee
final fee = _getAnonFee(); final fee = _getOverrideFee();
if (fee != null) { if (fee != null) {
return fee; return fee;
} }
@ -136,10 +140,11 @@ class TransactionV2 {
...outputs.map((e) => e.addresses).expand((e) => e), ...outputs.map((e) => e.addresses).expand((e) => e),
}; };
Amount? _getAnonFee() { Amount? _getOverrideFee() {
try { try {
final map = jsonDecode(otherData!) as Map; return Amount.fromSerializedJsonString(
return Amount.fromSerializedJsonString(map["anonFees"] as String); _getFromOtherData(key: "overrideFee") as String,
);
} catch (_) { } catch (_) {
return null; return null;
} }

View file

@ -22,87 +22,97 @@ const TransactionV2Schema = CollectionSchema(
name: r'blockHash', name: r'blockHash',
type: IsarType.string, type: IsarType.string,
), ),
r'hash': PropertySchema( r'contractAddress': PropertySchema(
id: 1, id: 1,
name: r'contractAddress',
type: IsarType.string,
),
r'hash': PropertySchema(
id: 2,
name: r'hash', name: r'hash',
type: IsarType.string, type: IsarType.string,
), ),
r'height': PropertySchema( r'height': PropertySchema(
id: 2, id: 3,
name: r'height', name: r'height',
type: IsarType.long, type: IsarType.long,
), ),
r'inputs': PropertySchema( r'inputs': PropertySchema(
id: 3, id: 4,
name: r'inputs', name: r'inputs',
type: IsarType.objectList, type: IsarType.objectList,
target: r'InputV2', target: r'InputV2',
), ),
r'isCancelled': PropertySchema( r'isCancelled': PropertySchema(
id: 4, id: 5,
name: r'isCancelled', name: r'isCancelled',
type: IsarType.bool, type: IsarType.bool,
), ),
r'isEpiccashTransaction': PropertySchema( r'isEpiccashTransaction': PropertySchema(
id: 5, id: 6,
name: r'isEpiccashTransaction', name: r'isEpiccashTransaction',
type: IsarType.bool, type: IsarType.bool,
), ),
r'nonce': PropertySchema(
id: 7,
name: r'nonce',
type: IsarType.long,
),
r'numberOfMessages': PropertySchema( r'numberOfMessages': PropertySchema(
id: 6, id: 8,
name: r'numberOfMessages', name: r'numberOfMessages',
type: IsarType.long, type: IsarType.long,
), ),
r'onChainNote': PropertySchema( r'onChainNote': PropertySchema(
id: 7, id: 9,
name: r'onChainNote', name: r'onChainNote',
type: IsarType.string, type: IsarType.string,
), ),
r'otherData': PropertySchema( r'otherData': PropertySchema(
id: 8, id: 10,
name: r'otherData', name: r'otherData',
type: IsarType.string, type: IsarType.string,
), ),
r'outputs': PropertySchema( r'outputs': PropertySchema(
id: 9, id: 11,
name: r'outputs', name: r'outputs',
type: IsarType.objectList, type: IsarType.objectList,
target: r'OutputV2', target: r'OutputV2',
), ),
r'slateId': PropertySchema( r'slateId': PropertySchema(
id: 10, id: 12,
name: r'slateId', name: r'slateId',
type: IsarType.string, type: IsarType.string,
), ),
r'subType': PropertySchema( r'subType': PropertySchema(
id: 11, id: 13,
name: r'subType', name: r'subType',
type: IsarType.byte, type: IsarType.byte,
enumMap: _TransactionV2subTypeEnumValueMap, enumMap: _TransactionV2subTypeEnumValueMap,
), ),
r'timestamp': PropertySchema( r'timestamp': PropertySchema(
id: 12, id: 14,
name: r'timestamp', name: r'timestamp',
type: IsarType.long, type: IsarType.long,
), ),
r'txid': PropertySchema( r'txid': PropertySchema(
id: 13, id: 15,
name: r'txid', name: r'txid',
type: IsarType.string, type: IsarType.string,
), ),
r'type': PropertySchema( r'type': PropertySchema(
id: 14, id: 16,
name: r'type', name: r'type',
type: IsarType.byte, type: IsarType.byte,
enumMap: _TransactionV2typeEnumValueMap, enumMap: _TransactionV2typeEnumValueMap,
), ),
r'version': PropertySchema( r'version': PropertySchema(
id: 15, id: 17,
name: r'version', name: r'version',
type: IsarType.long, type: IsarType.long,
), ),
r'walletId': PropertySchema( r'walletId': PropertySchema(
id: 16, id: 18,
name: r'walletId', name: r'walletId',
type: IsarType.string, type: IsarType.string,
) )
@ -182,6 +192,12 @@ int _transactionV2EstimateSize(
bytesCount += 3 + value.length * 3; 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.hash.length * 3;
bytesCount += 3 + object.inputs.length * 3; bytesCount += 3 + object.inputs.length * 3;
{ {
@ -229,32 +245,34 @@ void _transactionV2Serialize(
Map<Type, List<int>> allOffsets, Map<Type, List<int>> allOffsets,
) { ) {
writer.writeString(offsets[0], object.blockHash); writer.writeString(offsets[0], object.blockHash);
writer.writeString(offsets[1], object.hash); writer.writeString(offsets[1], object.contractAddress);
writer.writeLong(offsets[2], object.height); writer.writeString(offsets[2], object.hash);
writer.writeLong(offsets[3], object.height);
writer.writeObjectList<InputV2>( writer.writeObjectList<InputV2>(
offsets[3], offsets[4],
allOffsets, allOffsets,
InputV2Schema.serialize, InputV2Schema.serialize,
object.inputs, object.inputs,
); );
writer.writeBool(offsets[4], object.isCancelled); writer.writeBool(offsets[5], object.isCancelled);
writer.writeBool(offsets[5], object.isEpiccashTransaction); writer.writeBool(offsets[6], object.isEpiccashTransaction);
writer.writeLong(offsets[6], object.numberOfMessages); writer.writeLong(offsets[7], object.nonce);
writer.writeString(offsets[7], object.onChainNote); writer.writeLong(offsets[8], object.numberOfMessages);
writer.writeString(offsets[8], object.otherData); writer.writeString(offsets[9], object.onChainNote);
writer.writeString(offsets[10], object.otherData);
writer.writeObjectList<OutputV2>( writer.writeObjectList<OutputV2>(
offsets[9], offsets[11],
allOffsets, allOffsets,
OutputV2Schema.serialize, OutputV2Schema.serialize,
object.outputs, object.outputs,
); );
writer.writeString(offsets[10], object.slateId); writer.writeString(offsets[12], object.slateId);
writer.writeByte(offsets[11], object.subType.index); writer.writeByte(offsets[13], object.subType.index);
writer.writeLong(offsets[12], object.timestamp); writer.writeLong(offsets[14], object.timestamp);
writer.writeString(offsets[13], object.txid); writer.writeString(offsets[15], object.txid);
writer.writeByte(offsets[14], object.type.index); writer.writeByte(offsets[16], object.type.index);
writer.writeLong(offsets[15], object.version); writer.writeLong(offsets[17], object.version);
writer.writeString(offsets[16], object.walletId); writer.writeString(offsets[18], object.walletId);
} }
TransactionV2 _transactionV2Deserialize( TransactionV2 _transactionV2Deserialize(
@ -265,32 +283,32 @@ TransactionV2 _transactionV2Deserialize(
) { ) {
final object = TransactionV2( final object = TransactionV2(
blockHash: reader.readStringOrNull(offsets[0]), blockHash: reader.readStringOrNull(offsets[0]),
hash: reader.readString(offsets[1]), hash: reader.readString(offsets[2]),
height: reader.readLongOrNull(offsets[2]), height: reader.readLongOrNull(offsets[3]),
inputs: reader.readObjectList<InputV2>( inputs: reader.readObjectList<InputV2>(
offsets[3], offsets[4],
InputV2Schema.deserialize, InputV2Schema.deserialize,
allOffsets, allOffsets,
InputV2(), InputV2(),
) ?? ) ??
[], [],
otherData: reader.readStringOrNull(offsets[8]), otherData: reader.readStringOrNull(offsets[10]),
outputs: reader.readObjectList<OutputV2>( outputs: reader.readObjectList<OutputV2>(
offsets[9], offsets[11],
OutputV2Schema.deserialize, OutputV2Schema.deserialize,
allOffsets, allOffsets,
OutputV2(), OutputV2(),
) ?? ) ??
[], [],
subType: subType:
_TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[11])] ?? _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[13])] ??
TransactionSubType.none, TransactionSubType.none,
timestamp: reader.readLong(offsets[12]), timestamp: reader.readLong(offsets[14]),
txid: reader.readString(offsets[13]), txid: reader.readString(offsets[15]),
type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[14])] ?? type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[16])] ??
TransactionType.outgoing, TransactionType.outgoing,
version: reader.readLong(offsets[15]), version: reader.readLong(offsets[17]),
walletId: reader.readString(offsets[16]), walletId: reader.readString(offsets[18]),
); );
object.id = id; object.id = id;
return object; return object;
@ -306,10 +324,12 @@ P _transactionV2DeserializeProp<P>(
case 0: case 0:
return (reader.readStringOrNull(offset)) as P; return (reader.readStringOrNull(offset)) as P;
case 1: case 1:
return (reader.readString(offset)) as P; return (reader.readStringOrNull(offset)) as P;
case 2: case 2:
return (reader.readLongOrNull(offset)) as P; return (reader.readString(offset)) as P;
case 3: case 3:
return (reader.readLongOrNull(offset)) as P;
case 4:
return (reader.readObjectList<InputV2>( return (reader.readObjectList<InputV2>(
offset, offset,
InputV2Schema.deserialize, InputV2Schema.deserialize,
@ -317,17 +337,19 @@ P _transactionV2DeserializeProp<P>(
InputV2(), InputV2(),
) ?? ) ??
[]) as P; []) as P;
case 4:
return (reader.readBool(offset)) as P;
case 5: case 5:
return (reader.readBool(offset)) as P; return (reader.readBool(offset)) as P;
case 6: case 6:
return (reader.readLongOrNull(offset)) as P; return (reader.readBool(offset)) as P;
case 7: case 7:
return (reader.readStringOrNull(offset)) as P; return (reader.readLongOrNull(offset)) as P;
case 8: case 8:
return (reader.readStringOrNull(offset)) as P; return (reader.readLongOrNull(offset)) as P;
case 9: case 9:
return (reader.readStringOrNull(offset)) as P;
case 10:
return (reader.readStringOrNull(offset)) as P;
case 11:
return (reader.readObjectList<OutputV2>( return (reader.readObjectList<OutputV2>(
offset, offset,
OutputV2Schema.deserialize, OutputV2Schema.deserialize,
@ -335,22 +357,22 @@ P _transactionV2DeserializeProp<P>(
OutputV2(), OutputV2(),
) ?? ) ??
[]) as P; []) as P;
case 10: case 12:
return (reader.readStringOrNull(offset)) as P; return (reader.readStringOrNull(offset)) as P;
case 11: case 13:
return (_TransactionV2subTypeValueEnumMap[ return (_TransactionV2subTypeValueEnumMap[
reader.readByteOrNull(offset)] ?? reader.readByteOrNull(offset)] ??
TransactionSubType.none) as P; TransactionSubType.none) as P;
case 12:
return (reader.readLong(offset)) as P;
case 13:
return (reader.readString(offset)) as P;
case 14: case 14:
return (reader.readLong(offset)) as P;
case 15:
return (reader.readString(offset)) as P;
case 16:
return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ?? return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ??
TransactionType.outgoing) as P; TransactionType.outgoing) as P;
case 15: case 17:
return (reader.readLong(offset)) as P; return (reader.readLong(offset)) as P;
case 16: case 18:
return (reader.readString(offset)) as P; return (reader.readString(offset)) as P;
default: default:
throw IsarError('Unknown property with id $propertyId'); 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( QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition> hashEqualTo(
String value, { String value, {
bool caseSensitive = true, 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> QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
numberOfMessagesIsNull() { numberOfMessagesIsNull() {
return QueryBuilder.apply(this, (query) { 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() { QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByHash() {
return QueryBuilder.apply(this, (query) { return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'hash', Sort.asc); 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> QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
sortByNumberOfMessages() { sortByNumberOfMessages() {
return QueryBuilder.apply(this, (query) { 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() { QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByHash() {
return QueryBuilder.apply(this, (query) { return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'hash', Sort.asc); 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> QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
thenByNumberOfMessages() { thenByNumberOfMessages() {
return QueryBuilder.apply(this, (query) { 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( QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctByHash(
{bool caseSensitive = true}) { {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) { 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> QueryBuilder<TransactionV2, TransactionV2, QDistinct>
distinctByNumberOfMessages() { distinctByNumberOfMessages() {
return QueryBuilder.apply(this, (query) { 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() { QueryBuilder<TransactionV2, String, QQueryOperations> hashProperty() {
return QueryBuilder.apply(this, (query) { return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'hash'); 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> QueryBuilder<TransactionV2, int?, QQueryOperations>
numberOfMessagesProperty() { numberOfMessagesProperty() {
return QueryBuilder.apply(this, (query) { return QueryBuilder.apply(this, (query) {

View file

@ -180,10 +180,7 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
tokenEntities.addAll(contracts.map((e) => AddTokenListElementData(e))); tokenEntities.addAll(contracts.map((e) => AddTokenListElementData(e)));
final walletContracts = final walletContracts = ref.read(pWalletTokenAddresses(widget.walletId));
(ref.read(pWallets).getWallet(widget.walletId) as EthereumWallet)
.info
.tokenContractAddresses;
final shouldMarkAsSelectedContracts = [ final shouldMarkAsSelectedContracts = [
...walletContracts, ...walletContracts,

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/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.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/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/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/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'; 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/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.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/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/models/tx_data.dart'; import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
@ -202,7 +202,7 @@ class _ConfirmTransactionViewState
} }
if (widget.isTokenTx) { if (widget.isTokenTx) {
unawaited(ref.read(tokenServiceProvider)!.refresh()); unawaited(ref.read(pCurrentTokenWallet)!.refresh());
} else { } else {
unawaited(wallet.refresh()); unawaited(wallet.refresh());
} }
@ -345,7 +345,7 @@ class _ConfirmTransactionViewState
final String unit; final String unit;
if (widget.isTokenTx) { if (widget.isTokenTx) {
unit = ref.watch( unit = ref.watch(
tokenServiceProvider.select((value) => value!.tokenContract.symbol)); pCurrentTokenWallet.select((value) => value!.tokenContract.symbol));
} else { } else {
unit = coin.ticker; unit = coin.ticker;
} }
@ -518,7 +518,7 @@ class _ConfirmTransactionViewState
ref.watch(pAmountFormatter(coin)).format( ref.watch(pAmountFormatter(coin)).format(
amountWithoutChange, amountWithoutChange,
ethContract: ref ethContract: ref
.watch(tokenServiceProvider) .watch(pCurrentTokenWallet)
?.tokenContract, ?.tokenContract,
), ),
style: STextStyles.itemSubtitle12(context), style: STextStyles.itemSubtitle12(context),
@ -708,7 +708,7 @@ class _ConfirmTransactionViewState
priceAnd24hChangeNotifierProvider) priceAnd24hChangeNotifierProvider)
.getTokenPrice( .getTokenPrice(
ref ref
.read(tokenServiceProvider)! .read(pCurrentTokenWallet)!
.tokenContract .tokenContract
.address, .address,
) )
@ -737,7 +737,7 @@ class _ConfirmTransactionViewState
ref.watch(pAmountFormatter(coin)).format( ref.watch(pAmountFormatter(coin)).format(
amountWithoutChange, amountWithoutChange,
ethContract: ref ethContract: ref
.read(tokenServiceProvider) .read(pCurrentTokenWallet)
?.tokenContract), ?.tokenContract),
style: STextStyles style: STextStyles
.desktopTextExtraExtraSmall( .desktopTextExtraExtraSmall(

View file

@ -12,7 +12,6 @@ import 'package:cw_core/monero_transaction_priority.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/paymint/fee_object_model.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/providers.dart';
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_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/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.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/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/animated_text.dart';
@ -90,18 +90,28 @@ class _TransactionFeeSelectionSheetState
final fee = await wallet.estimateFeeFor( final fee = await wallet.estimateFeeFor(
amount, MoneroTransactionPriority.fast.raw!); amount, MoneroTransactionPriority.fast.raw!);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) && } else if (coin == Coin.firo || coin == Coin.firoTestNet) {
ref.read(publicPrivateBalanceStateProvider.state).state == final Amount fee;
"Private") { switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
ref.read(feeSheetSessionCacheProvider).fast[amount] = case FiroType.spark:
await (wallet as FiroWallet).estimateFeeForLelantus(amount); fee =
await (wallet as FiroWallet).estimateFeeForSpark(amount);
case FiroType.lelantus:
fee = await (wallet as FiroWallet)
.estimateFeeForLelantus(amount);
case FiroType.public:
fee = await (wallet as FiroWallet)
.estimateFeeFor(amount, feeRate);
}
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else { } else {
ref.read(feeSheetSessionCacheProvider).fast[amount] = ref.read(feeSheetSessionCacheProvider).fast[amount] =
await wallet.estimateFeeFor(amount, feeRate); await wallet.estimateFeeFor(amount, feeRate);
} }
} else { } else {
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = tokenWallet.estimateFeeFor(feeRate); final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} }
} }
@ -115,18 +125,27 @@ class _TransactionFeeSelectionSheetState
final fee = await wallet.estimateFeeFor( final fee = await wallet.estimateFeeFor(
amount, MoneroTransactionPriority.regular.raw!); amount, MoneroTransactionPriority.regular.raw!);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee; ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) && } else if (coin == Coin.firo || coin == Coin.firoTestNet) {
ref.read(publicPrivateBalanceStateProvider.state).state == final Amount fee;
"Private") { switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
ref.read(feeSheetSessionCacheProvider).average[amount] = case FiroType.spark:
await (wallet as FiroWallet).estimateFeeForLelantus(amount); fee =
await (wallet as FiroWallet).estimateFeeForSpark(amount);
case FiroType.lelantus:
fee = await (wallet as FiroWallet)
.estimateFeeForLelantus(amount);
case FiroType.public:
fee = await (wallet as FiroWallet)
.estimateFeeFor(amount, feeRate);
}
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else { } else {
ref.read(feeSheetSessionCacheProvider).average[amount] = ref.read(feeSheetSessionCacheProvider).average[amount] =
await wallet.estimateFeeFor(amount, feeRate); await wallet.estimateFeeFor(amount, feeRate);
} }
} else { } else {
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = tokenWallet.estimateFeeFor(feeRate); final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee; ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} }
} }
@ -140,18 +159,27 @@ class _TransactionFeeSelectionSheetState
final fee = await wallet.estimateFeeFor( final fee = await wallet.estimateFeeFor(
amount, MoneroTransactionPriority.slow.raw!); amount, MoneroTransactionPriority.slow.raw!);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) && } else if (coin == Coin.firo || coin == Coin.firoTestNet) {
ref.read(publicPrivateBalanceStateProvider.state).state == final Amount fee;
"Private") { switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
ref.read(feeSheetSessionCacheProvider).slow[amount] = case FiroType.spark:
await (wallet as FiroWallet).estimateFeeForLelantus(amount); fee =
await (wallet as FiroWallet).estimateFeeForSpark(amount);
case FiroType.lelantus:
fee = await (wallet as FiroWallet)
.estimateFeeForLelantus(amount);
case FiroType.public:
fee = await (wallet as FiroWallet)
.estimateFeeFor(amount, feeRate);
}
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else { } else {
ref.read(feeSheetSessionCacheProvider).slow[amount] = ref.read(feeSheetSessionCacheProvider).slow[amount] =
await wallet.estimateFeeFor(amount, feeRate); await wallet.estimateFeeFor(amount, feeRate);
} }
} else { } else {
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = tokenWallet.estimateFeeFor(feeRate); final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} }
} }
@ -239,7 +267,7 @@ class _TransactionFeeSelectionSheetState
), ),
FutureBuilder( FutureBuilder(
future: widget.isToken future: widget.isToken
? ref.read(tokenServiceProvider)!.fees ? ref.read(pCurrentTokenWallet)!.fees
: wallet.fees, : wallet.fees,
builder: (context, AsyncSnapshot<FeeObject> snapshot) { builder: (context, AsyncSnapshot<FeeObject> snapshot) {
if (snapshot.connectionState == ConnectionState.done && 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/confirm_transaction_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.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/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/providers.dart';
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
import 'package:stackwallet/providers/ui/preview_tx_button_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/prefs.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.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/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/models/tx_data.dart'; import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/animated_text.dart';
@ -353,7 +354,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
} }
Future<String> calculateFees() async { Future<String> calculateFees() async {
final wallet = ref.read(tokenServiceProvider)!; final wallet = ref.read(pCurrentTokenWallet)!;
final feeObject = await wallet.fees; final feeObject = await wallet.fees;
late final int feeRate; late final int feeRate;
@ -372,7 +373,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
feeRate = -1; feeRate = -1;
} }
final Amount fee = wallet.estimateFeeFor(feeRate); final Amount fee = await wallet.estimateFeeFor(Amount.zero, feeRate);
cachedFees = ref.read(pAmountFormatter(coin)).format( cachedFees = ref.read(pAmountFormatter(coin)).format(
fee, fee,
withUnitName: true, withUnitName: true,
@ -389,7 +390,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
const Duration(milliseconds: 100), const Duration(milliseconds: 100),
); );
final wallet = ref.read(pWallets).getWallet(walletId); final wallet = ref.read(pWallets).getWallet(walletId);
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final Amount amount = _amountToSend!; final Amount amount = _amountToSend!;
@ -711,8 +712,11 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
.watch(pAmountFormatter(coin)) .watch(pAmountFormatter(coin))
.format( .format(
ref ref
.read(tokenServiceProvider)! .read(pTokenBalance((
.balance walletId: widget.walletId,
contractAddress:
tokenContract.address,
)))
.spendable, .spendable,
ethContract: tokenContract, ethContract: tokenContract,
withUnitName: false, withUnitName: false,
@ -729,18 +733,16 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
ref ref
.watch(pAmountFormatter(coin)) .watch(pAmountFormatter(coin))
.format( .format(
ref.watch( ref
tokenServiceProvider.select( .watch(pTokenBalance((
(value) => value! walletId:
.balance.spendable, widget.walletId,
), contractAddress:
), tokenContract
ethContract: ref.watch( .address,
tokenServiceProvider.select( )))
(value) => .spendable,
value!.tokenContract, ethContract: tokenContract,
),
),
), ),
style: style:
STextStyles.titleBold12(context) STextStyles.titleBold12(context)
@ -750,7 +752,13 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
textAlign: TextAlign.right, textAlign: TextAlign.right,
), ),
Text( 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, fractionDigits: 2,
).fiatString( ).fiatString(
locale: locale, locale: locale,

View file

@ -15,14 +15,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart'; import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/my_tokens_list.dart'; import 'package:stackwallet/pages/token_view/sub_widgets/my_tokens_list.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart'; import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart';
import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
@ -233,11 +231,7 @@ class _MyTokensViewState extends ConsumerState<MyTokensView> {
child: MyTokensList( child: MyTokensList(
walletId: widget.walletId, walletId: widget.walletId,
searchTerm: _searchString, searchTerm: _searchString,
tokenContracts: ref tokenContracts: ref.watch(pWalletTokenAddresses(widget.walletId)),
.watch(pWallets.select((value) =>
value.getWallet(widget.walletId) as EthereumWallet))
.info
.tokenContractAddresses,
), ),
), ),
], ],

View file

@ -8,16 +8,16 @@
* *
*/ */
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/pages/token_view/token_view.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/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/ethereum/cached_eth_token_balance.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/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart'; import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
@ -25,8 +25,11 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.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/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.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/desktop/primary_button.dart';
import 'package:stackwallet/widgets/dialogs/basic_dialog.dart'; import 'package:stackwallet/widgets/dialogs/basic_dialog.dart';
import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart';
@ -56,7 +59,7 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
WidgetRef ref, WidgetRef ref,
) async { ) async {
try { try {
await ref.read(tokenServiceProvider)!.initialize(); await ref.read(pCurrentTokenWallet)!.init();
return true; return true;
} catch (_) { } catch (_) {
await showDialog<void>( await showDialog<void>(
@ -82,14 +85,12 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
} }
void _onPressed() async { void _onPressed() async {
final old = ref.read(tokenServiceStateProvider);
// exit previous if there is one
unawaited(old?.exit());
ref.read(tokenServiceStateProvider.state).state = EthTokenWallet( ref.read(tokenServiceStateProvider.state).state = EthTokenWallet(
token: widget.token,
secureStore: ref.read(secureStoreProvider),
ethWallet:
ref.read(pWallets).getWallet(widget.walletId) as EthereumWallet, ref.read(pWallets).getWallet(widget.walletId) as EthereumWallet,
tracker: TransactionNotificationTracker( widget.token,
walletId: widget.walletId,
),
); );
final success = await showLoading<bool>( final success = await showLoading<bool>(
@ -116,11 +117,14 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
cachedBalance = CachedEthTokenBalance(widget.walletId, widget.token); cachedBalance = CachedEthTokenBalance(widget.walletId, widget.token);
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
if (mounted) {
final address = ref.read(pWalletReceivingAddress(widget.walletId)); final address = ref.read(pWalletReceivingAddress(widget.walletId));
await cachedBalance.fetchAndUpdateCachedBalance(address); await cachedBalance.fetchAndUpdateCachedBalance(
address, ref.read(mainDBProvider));
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
}
}); });
super.initState(); super.initState();
@ -172,7 +176,14 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
const Spacer(), const Spacer(),
Text( Text(
ref.watch(pAmountFormatter(Coin.ethereum)).format( ref.watch(pAmountFormatter(Coin.ethereum)).format(
cachedBalance.getCachedBalance().total, ref
.watch(pTokenBalance(
(
walletId: widget.walletId,
contractAddress: widget.token.address
),
))
.total,
ethContract: widget.token, ethContract: widget.token,
), ),
style: isDesktop style: isDesktop

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/exchange_view/wallet_initiated_exchange_view.dart';
import 'package:stackwallet/pages/receive_view/receive_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/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/pages/wallet_view/sub_widgets/wallet_refresh_button.dart';
import 'package:stackwallet/providers/global/locale_provider.dart'; import 'package:stackwallet/providers/global/locale_provider.dart';
import 'package:stackwallet/providers/global/prefs_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/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.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/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_container.dart';
@ -51,9 +52,9 @@ class TokenSummary extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final token = final token =
ref.watch(tokenServiceProvider.select((value) => value!.tokenContract)); ref.watch(pCurrentTokenWallet.select((value) => value!.tokenContract));
final balance = final balance = ref.watch(
ref.watch(tokenServiceProvider.select((value) => value!.balance)); pTokenBalance((walletId: walletId, contractAddress: token.address)));
return Stack( return Stack(
children: [ children: [
@ -157,7 +158,7 @@ class TokenSummary extends ConsumerWidget {
walletId: walletId, walletId: walletId,
initialSyncStatus: initialSyncStatus, initialSyncStatus: initialSyncStatus,
tokenContractAddress: ref.watch( tokenContractAddress: ref.watch(
tokenServiceProvider.select( pCurrentTokenWallet.select(
(value) => value!.tokenContract.address, (value) => value!.tokenContract.address,
), ),
), ),

View file

@ -12,25 +12,18 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:isar/isar.dart';
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
import 'package:stackwallet/pages/token_view/token_view.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/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/providers/global/wallets_provider.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.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/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart'; import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/loading_indicator.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 { class TokenTransactionsList extends ConsumerStatefulWidget {
const TokenTransactionsList({ const TokenTransactionsList({
@ -49,7 +42,10 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
late final int minConfirms; late final int minConfirms;
bool _hasLoaded = false; 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 { BorderRadius get _borderRadiusFirst {
return BorderRadius.only( 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 @override
void initState() { void initState() {
minConfirms = ref minConfirms = ref
@ -213,21 +76,44 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
.getWallet(widget.walletId) .getWallet(widget.walletId)
.cryptoCurrency .cryptoCurrency
.minConfirms; .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(); super.initState();
} }
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final wallet = final wallet =
ref.watch(pWallets.select((value) => value.getWallet(widget.walletId))); ref.watch(pWallets.select((value) => value.getWallet(widget.walletId)));
return FutureBuilder( return FutureBuilder(
future: ref future: _query.findAll(),
.watch(tokenServiceProvider.select((value) => value!.transactions)), builder: (fbContext, AsyncSnapshot<List<TransactionV2>> snapshot) {
builder: (fbContext, AsyncSnapshot<List<Transaction>> snapshot) {
if (snapshot.connectionState == ConnectionState.done && if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) { snapshot.hasData) {
_transactions2 = snapshot.data!; _transactions = snapshot.data!;
_hasLoaded = true; _hasLoaded = true;
} }
if (!_hasLoaded) { if (!_hasLoaded) {
@ -246,35 +132,34 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
], ],
); );
} }
if (_transactions2.isEmpty) { if (_transactions.isEmpty) {
return const NoTransActionsFound(); return const NoTransActionsFound();
} else { } else {
_transactions2.sort((a, b) => b.timestamp - a.timestamp); _transactions.sort((a, b) => b.timestamp - a.timestamp);
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
if (!ref.read(tokenServiceProvider)!.isRefreshing) { if (!ref.read(pCurrentTokenWallet)!.refreshMutex.isLocked) {
unawaited(ref.read(tokenServiceProvider)!.refresh()); unawaited(ref.read(pCurrentTokenWallet)!.refresh());
} }
}, },
child: Util.isDesktop child: Util.isDesktop
? ListView.separated( ? ListView.separated(
itemBuilder: (context, index) { itemBuilder: (context, index) {
BorderRadius? radius; BorderRadius? radius;
if (_transactions2.length == 1) { if (_transactions.length == 1) {
radius = BorderRadius.circular( radius = BorderRadius.circular(
Constants.size.circularBorderRadius, Constants.size.circularBorderRadius,
); );
} else if (index == _transactions2.length - 1) { } else if (index == _transactions.length - 1) {
radius = _borderRadiusLast; radius = _borderRadiusLast;
} else if (index == 0) { } else if (index == 0) {
radius = _borderRadiusFirst; radius = _borderRadiusFirst;
} }
final tx = _transactions2[index]; final tx = _transactions[index];
return itemBuilder( return TxListItem(
context, tx: tx,
tx, coin: wallet.info.coin,
radius, radius: radius,
wallet.info.coin,
); );
}, },
separatorBuilder: (context, index) { separatorBuilder: (context, index) {
@ -286,27 +171,26 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
.background, .background,
); );
}, },
itemCount: _transactions2.length, itemCount: _transactions.length,
) )
: ListView.builder( : ListView.builder(
itemCount: _transactions2.length, itemCount: _transactions.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
BorderRadius? radius; BorderRadius? radius;
if (_transactions2.length == 1) { if (_transactions.length == 1) {
radius = BorderRadius.circular( radius = BorderRadius.circular(
Constants.size.circularBorderRadius, Constants.size.circularBorderRadius,
); );
} else if (index == _transactions2.length - 1) { } else if (index == _transactions.length - 1) {
radius = _borderRadiusLast; radius = _borderRadiusLast;
} else if (index == 0) { } else if (index == 0) {
radius = _borderRadiusFirst; radius = _borderRadiusFirst;
} }
final tx = _transactions2[index]; final tx = _transactions[index];
return itemBuilder( return TxListItem(
context, tx: tx,
tx, coin: wallet.info.coin,
radius, radius: radius,
wallet.info.coin,
); );
}, },
), ),

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

View file

@ -13,13 +13,13 @@ import 'dart:async';
import 'package:event_bus/event_bus.dart'; import 'package:event_bus/event_bus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/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/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/util.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'; import 'package:stackwallet/widgets/animated_widgets/rotating_arrows.dart';
/// [eventBus] should only be set during testing /// [eventBus] should only be set during testing
@ -140,8 +140,8 @@ class _RefreshButtonState extends ConsumerState<WalletRefreshButton> {
wallet.refresh().then((_) => _spinController.stop?.call()); wallet.refresh().then((_) => _spinController.stop?.call());
} }
} else { } else {
if (!ref.read(tokenServiceProvider)!.isRefreshing) { if (!ref.read(pCurrentTokenWallet)!.refreshMutex.isLocked) {
ref.read(tokenServiceProvider)!.refresh(); 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/isar/models/transaction_note.dart';
import 'package:stackwallet/models/transaction_filter.dart'; import 'package:stackwallet/models/transaction_filter.dart';
import 'package:stackwallet/notifications/show_flush_bar.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/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_details_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_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({ const AllTransactionsView({
Key? key, Key? key,
required this.walletId, required this.walletId,
this.isTokens = false,
}) : super(key: key); }) : super(key: key);
static const String routeName = "/allTransactions"; static const String routeName = "/allTransactions";
final String walletId; final String walletId;
final bool isTokens;
@override @override
ConsumerState<AllTransactionsView> createState() => ConsumerState<AllTransactionsView> createState() =>
@ -487,24 +484,8 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
final criteria = final criteria =
ref.watch(transactionFilterProvider.state).state; ref.watch(transactionFilterProvider.state).state;
//todo: check if print needed
// debugPrint("Consumer build called");
final WhereClause ww;
return FutureBuilder( return FutureBuilder(
future: widget.isTokens future: ref.watch(mainDBProvider).isar.transactions.buildQuery<
? ref
.watch(mainDBProvider)
.getTransactions(walletId)
.filter()
.otherDataEqualTo(ref
.watch(tokenServiceProvider)!
.tokenContract
.address)
.sortByTimestampDesc()
.findAll()
: ref.watch(mainDBProvider).isar.transactions.buildQuery<
Transaction>( Transaction>(
whereClauses: [ whereClauses: [
IndexWhereClause.equalTo( IndexWhereClause.equalTo(

View file

@ -25,7 +25,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet.dart';
import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/conditional_parent.dart';
@ -119,9 +119,8 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
if (widget.coin == Coin.ethereum) { if (widget.coin == Coin.ethereum) {
for (final data in walletsData) { for (final data in walletsData) {
final List<EthContract> contracts = []; final List<EthContract> contracts = [];
final wallet = ref.read(pWallets).getWallet(data.walletId);
final contractAddresses = final contractAddresses =
(wallet as EthereumWallet).info.tokenContractAddresses; ref.read(pWalletTokenAddresses(data.walletId));
// fetch each contract // fetch each contract
for (final contractAddress in contractAddresses) { for (final contractAddress in contractAddresses) {

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/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_summary.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/token_transaction_list_widget.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/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_features.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.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/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.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/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
@ -57,7 +57,7 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
@override @override
void initState() { void initState() {
initialSyncStatus = ref.read(tokenServiceProvider)!.isRefreshing initialSyncStatus = ref.read(pCurrentTokenWallet)!.refreshMutex.isLocked
? WalletSyncStatus.syncing ? WalletSyncStatus.syncing
: WalletSyncStatus.synced; : WalletSyncStatus.synced;
super.initState(); super.initState();
@ -114,7 +114,7 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
children: [ children: [
EthTokenIcon( EthTokenIcon(
contractAddress: ref.watch( contractAddress: ref.watch(
tokenServiceProvider.select( pCurrentTokenWallet.select(
(value) => value!.tokenContract.address, (value) => value!.tokenContract.address,
), ),
), ),
@ -125,7 +125,7 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
), ),
Text( Text(
ref.watch( ref.watch(
tokenServiceProvider.select( pCurrentTokenWallet.select(
(value) => value!.tokenContract.name, (value) => value!.tokenContract.name,
), ),
), ),
@ -153,7 +153,7 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
children: [ children: [
EthTokenIcon( EthTokenIcon(
contractAddress: ref.watch( contractAddress: ref.watch(
tokenServiceProvider.select( pCurrentTokenWallet.select(
(value) => value!.tokenContract.address, (value) => value!.tokenContract.address,
), ),
), ),
@ -241,7 +241,7 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
child: MyWallet( child: MyWallet(
walletId: widget.walletId, walletId: widget.walletId,
contractAddress: ref.watch( contractAddress: ref.watch(
tokenServiceProvider.select( pCurrentTokenWallet.select(
(value) => value!.tokenContract.address, (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:flutter_svg/svg.dart';
import 'package:stackwallet/models/models.dart'; import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.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/global/wallets_provider.dart';
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
@ -27,7 +26,9 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/text_styles.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/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/animated_text.dart';
final tokenFeeSessionCacheProvider = final tokenFeeSessionCacheProvider =
@ -83,21 +84,27 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
final fee = await wallet.estimateFeeFor( final fee = await wallet.estimateFeeFor(
amount, MoneroTransactionPriority.fast.raw!); amount, MoneroTransactionPriority.fast.raw!);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) && } else if (coin == Coin.firo || coin == Coin.firoTestNet) {
ref.read(publicPrivateBalanceStateProvider.state).state != final Amount fee;
"Private") { switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
// TODO: [prio=high] firo fees case FiroType.spark:
throw UnimplementedError("Firo public fees"); fee =
// ref.read(feeSheetSessionCacheProvider).fast[amount] = await (wallet as FiroWallet).estimateFeeForSpark(amount);
// await (manager.wallet as FiroWallet) case FiroType.lelantus:
// .estimateFeeForPublic(amount, feeRate); fee = await (wallet as FiroWallet)
.estimateFeeForLelantus(amount);
case FiroType.public:
fee = await (wallet as FiroWallet)
.estimateFeeFor(amount, feeRate);
}
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else { } else {
ref.read(feeSheetSessionCacheProvider).fast[amount] = ref.read(feeSheetSessionCacheProvider).fast[amount] =
await wallet.estimateFeeFor(amount, feeRate); await wallet.estimateFeeFor(amount, feeRate);
} }
} else { } else {
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = tokenWallet.estimateFeeFor(feeRate); final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(tokenFeeSessionCacheProvider).fast[amount] = fee; ref.read(tokenFeeSessionCacheProvider).fast[amount] = fee;
} }
} }
@ -121,21 +128,27 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
final fee = await wallet.estimateFeeFor( final fee = await wallet.estimateFeeFor(
amount, MoneroTransactionPriority.regular.raw!); amount, MoneroTransactionPriority.regular.raw!);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee; ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) && } else if (coin == Coin.firo || coin == Coin.firoTestNet) {
ref.read(publicPrivateBalanceStateProvider.state).state != final Amount fee;
"Private") { switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
// TODO: [prio=high] firo fees case FiroType.spark:
throw UnimplementedError("Firo public fees"); fee =
// ref.read(feeSheetSessionCacheProvider).average[amount] = await (wallet as FiroWallet).estimateFeeForSpark(amount);
// await (manager.wallet as FiroWallet) case FiroType.lelantus:
// .estimateFeeForPublic(amount, feeRate); fee = await (wallet as FiroWallet)
.estimateFeeForLelantus(amount);
case FiroType.public:
fee = await (wallet as FiroWallet)
.estimateFeeFor(amount, feeRate);
}
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else { } else {
ref.read(feeSheetSessionCacheProvider).average[amount] = ref.read(feeSheetSessionCacheProvider).average[amount] =
await wallet.estimateFeeFor(amount, feeRate); await wallet.estimateFeeFor(amount, feeRate);
} }
} else { } else {
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = tokenWallet.estimateFeeFor(feeRate); final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(tokenFeeSessionCacheProvider).average[amount] = fee; ref.read(tokenFeeSessionCacheProvider).average[amount] = fee;
} }
} }
@ -159,21 +172,27 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
final fee = await wallet.estimateFeeFor( final fee = await wallet.estimateFeeFor(
amount, MoneroTransactionPriority.slow.raw!); amount, MoneroTransactionPriority.slow.raw!);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) && } else if (coin == Coin.firo || coin == Coin.firoTestNet) {
ref.read(publicPrivateBalanceStateProvider.state).state != final Amount fee;
"Private") { switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
// TODO: [prio=high] firo fees case FiroType.spark:
throw UnimplementedError("Firo public fees"); fee =
// ref.read(feeSheetSessionCacheProvider).slow[amount] = await (wallet as FiroWallet).estimateFeeForSpark(amount);
// await (manager.wallet as FiroWallet) case FiroType.lelantus:
// .estimateFeeForPublic(amount, feeRate); fee = await (wallet as FiroWallet)
.estimateFeeForLelantus(amount);
case FiroType.public:
fee = await (wallet as FiroWallet)
.estimateFeeFor(amount, feeRate);
}
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else { } else {
ref.read(feeSheetSessionCacheProvider).slow[amount] = ref.read(feeSheetSessionCacheProvider).slow[amount] =
await wallet.estimateFeeFor(amount, feeRate); await wallet.estimateFeeFor(amount, feeRate);
} }
} else { } else {
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = tokenWallet.estimateFeeFor(feeRate); final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(tokenFeeSessionCacheProvider).slow[amount] = fee; 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/models/isar/models/isar_models.dart';
import 'package:stackwallet/notifications/show_flush_bar.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/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/db/main_db_provider.dart';
import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.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/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.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/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/multi_address_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
@ -307,7 +307,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
children: [ children: [
Text( Text(
"Your ${widget.contractAddress == null ? coin.ticker : ref.watch( "Your ${widget.contractAddress == null ? coin.ticker : ref.watch(
tokenServiceProvider.select( pCurrentTokenWallet.select(
(value) => value!.tokenContract.symbol, (value) => value!.tokenContract.symbol,
), ),
)} SPARK address", )} SPARK address",
@ -398,7 +398,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
children: [ children: [
Text( Text(
"Your ${widget.contractAddress == null ? coin.ticker : ref.watch( "Your ${widget.contractAddress == null ? coin.ticker : ref.watch(
tokenServiceProvider.select( pCurrentTokenWallet.select(
(value) => value!.tokenContract.symbol, (value) => value!.tokenContract.symbol,
), ),
)} address", )} 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/models/send_view_auto_fill_data.dart';
import 'package:stackwallet/pages/send_view/confirm_transaction_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/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/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/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'; 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/prefs.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.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/wallets/models/tx_data.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
@ -103,10 +104,15 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
late VoidCallback onCryptoAmountChanged; late VoidCallback onCryptoAmountChanged;
Future<void> previewSend() async { Future<void> previewSend() async {
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final Amount amount = _amountToSend!; 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 // confirm send all
if (amount == availableBalance) { if (amount == availableBalance) {
@ -214,7 +220,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
child: Padding( child: Padding(
padding: const EdgeInsets.all(32), padding: const EdgeInsets.all(32),
child: BuildingTransactionDialog( child: BuildingTransactionDialog(
coin: tokenWallet.coin, coin: tokenWallet.cryptoCurrency.coin,
onCancel: () { onCancel: () {
wasCancelled = true; wasCancelled = true;
@ -389,11 +395,11 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
_amountToSend = cryptoAmount.contains(",") _amountToSend = cryptoAmount.contains(",")
? Decimal.parse(cryptoAmount.replaceFirst(",", ".")).toAmount( ? Decimal.parse(cryptoAmount.replaceFirst(",", ".")).toAmount(
fractionDigits: fractionDigits:
ref.read(tokenServiceProvider)!.tokenContract.decimals, ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
) )
: Decimal.parse(cryptoAmount).toAmount( : Decimal.parse(cryptoAmount).toAmount(
fractionDigits: fractionDigits:
ref.read(tokenServiceProvider)!.tokenContract.decimals, ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
); );
if (_cachedAmountToSend != null && if (_cachedAmountToSend != null &&
_cachedAmountToSend == _amountToSend) { _cachedAmountToSend == _amountToSend) {
@ -406,7 +412,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
final price = ref final price = ref
.read(priceAnd24hChangeNotifierProvider) .read(priceAnd24hChangeNotifierProvider)
.getTokenPrice( .getTokenPrice(
ref.read(tokenServiceProvider)!.tokenContract.address, ref.read(pCurrentTokenWallet)!.tokenContract.address,
) )
.item1; .item1;
@ -485,7 +491,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
if (results["amount"] != null) { if (results["amount"] != null) {
final amount = Decimal.parse(results["amount"]!).toAmount( final amount = Decimal.parse(results["amount"]!).toAmount(
fractionDigits: fractionDigits:
ref.read(tokenServiceProvider)!.tokenContract.decimals, ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
); );
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format( cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
amount, amount,
@ -543,7 +549,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
void fiatTextFieldOnChanged(String baseAmountString) { void fiatTextFieldOnChanged(String baseAmountString) {
final int tokenDecimals = final int tokenDecimals =
ref.read(tokenServiceProvider)!.tokenContract.decimals; ref.read(pCurrentTokenWallet)!.tokenContract.decimals;
if (baseAmountString.isNotEmpty && if (baseAmountString.isNotEmpty &&
baseAmountString != "." && baseAmountString != "." &&
@ -556,7 +562,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
final Decimal _price = ref final Decimal _price = ref
.read(priceAnd24hChangeNotifierProvider) .read(priceAnd24hChangeNotifierProvider)
.getTokenPrice( .getTokenPrice(
ref.read(tokenServiceProvider)!.tokenContract.address, ref.read(pCurrentTokenWallet)!.tokenContract.address,
) )
.item1; .item1;
@ -579,7 +585,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
final amountString = ref.read(pAmountFormatter(coin)).format( final amountString = ref.read(pAmountFormatter(coin)).format(
_amountToSend!, _amountToSend!,
withUnitName: false, withUnitName: false,
ethContract: ref.read(tokenServiceProvider)!.tokenContract, ethContract: ref.read(pCurrentTokenWallet)!.tokenContract,
); );
_cryptoAmountChangeLock = true; _cryptoAmountChangeLock = true;
@ -597,12 +603,14 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
Future<void> sendAllTapped() async { Future<void> sendAllTapped() async {
cryptoAmountController.text = ref cryptoAmountController.text = ref
.read(tokenServiceProvider)! .read(pTokenBalance((
.balance walletId: walletId,
contractAddress: ref.read(pCurrentTokenWallet)!.tokenContract.address
)))
.spendable .spendable
.decimal .decimal
.toStringAsFixed( .toStringAsFixed(
ref.read(tokenServiceProvider)!.tokenContract.decimals, ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
); );
} }
@ -686,7 +694,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType"); debugPrint("BUILD: $runtimeType");
final tokenContract = ref.watch(tokenServiceProvider)!.tokenContract; final tokenContract = ref.watch(pCurrentTokenWallet)!.tokenContract;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View file

@ -11,7 +11,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/balance.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/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/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart';
import 'package:stackwallet/providers/providers.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/coin_enum.dart';
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
import 'package:stackwallet/utilities/text_styles.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/wallets/isar/providers/wallet_info_provider.dart';
class DesktopWalletSummary extends ConsumerStatefulWidget { class DesktopWalletSummary extends ConsumerStatefulWidget {
@ -70,8 +71,7 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
.watch(prefsChangeNotifierProvider.select((value) => value.currency)); .watch(prefsChangeNotifierProvider.select((value) => value.currency));
final tokenContract = widget.isToken final tokenContract = widget.isToken
? ref ? ref.watch(pCurrentTokenWallet.select((value) => value!.tokenContract))
.watch(tokenServiceProvider.select((value) => value!.tokenContract))
: null; : null;
final priceTuple = widget.isToken final priceTuple = widget.isToken
@ -104,7 +104,8 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
} }
} else { } else {
Balance balance = widget.isToken Balance balance = widget.isToken
? ref.watch(tokenServiceProvider.select((value) => value!.balance)) ? ref.watch(pTokenBalance(
(walletId: walletId, contractAddress: tokenContract!.address)))
: ref.watch(pWalletBalance(walletId)); : ref.watch(pWalletBalance(walletId));
balanceToShow = _showAvailable ? balance.spendable : balance.total; 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()}"); return _routeError("${settings.name} invalid args: ${args.toString()}");
case AllTransactionsView.routeName: 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) { if (args is String) {
return getRoute( return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute, shouldUseMaterialRoute: useMaterialPageRoute,

View file

@ -8,29 +8,37 @@
* *
*/ */
import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/balance.dart'; import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/services/ethereum/ethereum_api.dart'; import 'package:stackwallet/services/ethereum/ethereum_api.dart';
import 'package:stackwallet/services/mixins/eth_token_cache.dart';
import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/isar/models/token_wallet_info.dart';
class CachedEthTokenBalance with EthTokenCache { class CachedEthTokenBalance {
final String walletId; final String walletId;
final EthContract token; final EthContract token;
CachedEthTokenBalance(this.walletId, this.token) { CachedEthTokenBalance(this.walletId, this.token);
initCache(walletId, token);
}
Future<void> fetchAndUpdateCachedBalance(String address) async { Future<void> fetchAndUpdateCachedBalance(
String address,
MainDB mainDB,
) async {
final response = await EthereumAPI.getWalletTokenBalance( final response = await EthereumAPI.getWalletTokenBalance(
address: address, address: address,
contractAddress: token.address, contractAddress: token.address,
); );
if (response.value != null) { final info = await mainDB.isar.tokenWalletInfo
await updateCachedBalance( .where()
.walletIdTokenAddressEqualTo(walletId, token.address)
.findFirst();
if (response.value != null && info != null) {
await info.updateCachedBalance(
Balance( Balance(
total: response.value!, total: response.value!,
spendable: response.value!, spendable: response.value!,
@ -43,6 +51,7 @@ class CachedEthTokenBalance with EthTokenCache {
fractionDigits: token.decimals, fractionDigits: token.decimals,
), ),
), ),
isar: mainDB.isar,
); );
} else { } else {
Logging.instance.log( Logging.instance.log(

View file

@ -1,611 +1,152 @@
/* // /*
* This file is part of Stack Wallet. // * This file is part of Stack Wallet.
* // *
* Copyright (c) 2023 Cypher Stack // * Copyright (c) 2023 Cypher Stack
* All Rights Reserved. // * All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details. // * The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26 // * Generated by Cypher Stack on 2023-05-26
* // *
*/ // */
//
import 'dart:async'; // import 'dart:async';
//
import 'package:ethereum_addresses/ethereum_addresses.dart'; // import 'package:ethereum_addresses/ethereum_addresses.dart';
import 'package:flutter/widgets.dart'; // import 'package:flutter/widgets.dart';
import 'package:http/http.dart'; // import 'package:http/http.dart';
import 'package:isar/isar.dart'; // import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.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_dto.dart';
import 'package:stackwallet/dto/ethereum/eth_token_tx_extra_dto.dart'; // import 'package:stackwallet/dto/ethereum/eth_token_tx_extra_dto.dart';
import 'package:stackwallet/models/balance.dart'; // import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart'; // import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/node_model.dart'; // import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart'; // import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/services/ethereum/ethereum_api.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/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/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart'; // import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/eth_token_cache.dart'; // import 'package:stackwallet/services/mixins/eth_token_cache.dart';
import 'package:stackwallet/services/node_service.dart'; // import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart'; // import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/amount/amount.dart'; // import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/default_nodes.dart'; // import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; // import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; // import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/eth_commons.dart'; // import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/utilities/extensions/extensions.dart'; // import 'package:stackwallet/utilities/extensions/extensions.dart';
import 'package:stackwallet/utilities/extensions/impl/contract_abi.dart'; // import 'package:stackwallet/utilities/extensions/impl/contract_abi.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; // import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart'; // import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/models/tx_data.dart'; // import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; // import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart';
import 'package:tuple/tuple.dart'; // import 'package:tuple/tuple.dart';
import 'package:web3dart/web3dart.dart' as web3dart; // import 'package:web3dart/web3dart.dart' as web3dart;
//
class EthTokenWallet extends ChangeNotifier with EthTokenCache { // class EthTokenWallet extends ChangeNotifier {
final EthereumWallet ethWallet; // final EthereumWallet ethWallet;
final TransactionNotificationTracker tracker; // final TransactionNotificationTracker tracker;
final SecureStorageInterface _secureStore; //
//
// late web3dart.EthereumAddress _contractAddress; // Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
late web3dart.EthPrivateKey _credentials; // final txid = txData["txid"] as String;
late web3dart.DeployedContract _deployedContract; // final addressString = checksumEthereumAddress(txData["address"] as String);
late web3dart.ContractFunction _sendFunction; // final response = await EthereumAPI.getEthTransactionByHash(txid);
late web3dart.Web3Client _client; //
// final transaction = Transaction(
static const _gasLimit = 200000; // walletId: ethWallet.walletId,
// txid: txid,
EthTokenWallet({ // timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
required EthContract token, // type: TransactionType.outgoing,
required this.ethWallet, // subType: TransactionSubType.ethToken,
required SecureStorageInterface secureStore, // // precision may be lost here hence the following amountString
required this.tracker, // amount: (txData["recipientAmt"] as Amount).raw.toInt(),
}) : _secureStore = secureStore, // amountString: (txData["recipientAmt"] as Amount).toJsonString(),
_tokenContract = token { // fee: (txData["fee"] as Amount).raw.toInt(),
// _contractAddress = web3dart.EthereumAddress.fromHex(token.address); // height: null,
initCache(ethWallet.walletId, token); // isCancelled: false,
} // isLelantus: false,
// otherData: tokenContract.address,
EthContract get tokenContract => _tokenContract; // slateId: null,
EthContract _tokenContract; // nonce: (txData["nonce"] as int?) ??
// response.value?.nonce.toBigIntFromHex.toInt(),
Balance get balance => _balance ??= getCachedBalance(); // inputs: [],
Balance? _balance; // outputs: [],
// numberOfMessages: null,
Coin get coin => Coin.ethereum; // );
//
Future<TxData> prepareSend({ // Address? address = await ethWallet.mainDB.getAddress(
required TxData txData, // ethWallet.walletId,
}) async { // addressString,
final feeRateType = txData.feeRateType!; // );
int fee = 0; //
final feeObject = await fees; // address ??= Address(
switch (feeRateType) { // walletId: ethWallet.walletId,
case FeeRateType.fast: // value: addressString,
fee = feeObject.fast; // publicKey: [],
break; // derivationIndex: -1,
case FeeRateType.average: // derivationPath: null,
fee = feeObject.medium; // type: AddressType.ethereum,
break; // subType: AddressSubType.nonWallet,
case FeeRateType.slow: // );
fee = feeObject.slow; //
break; // await ethWallet.mainDB.addNewTransactionData(
case FeeRateType.custom: // [
throw UnimplementedError("custom eth token fees"); // Tuple2(transaction, address),
} // ],
// ethWallet.walletId,
final feeEstimate = estimateFeeFor(fee); // );
// }
final client = await getEthClient(); //
//
final myAddress = await currentReceivingAddress; //
final myWeb3Address = web3dart.EthereumAddress.fromHex(myAddress); // bool get isRefreshing => _refreshLock;
//
final nonce = txData.nonce ?? // bool _refreshLock = false;
await client.getTransactionCount(myWeb3Address, //
atBlock: const web3dart.BlockNum.pending()); // Future<void> refresh() async {
// if (!_refreshLock) {
final amount = txData.recipients!.first.amount; // _refreshLock = true;
final address = txData.recipients!.first.address; // try {
// GlobalEventBus.instance.fire(
final tx = web3dart.Transaction.callContract( // WalletSyncStatusChangedEvent(
contract: _deployedContract, // WalletSyncStatus.syncing,
function: _sendFunction, // ethWallet.walletId + tokenContract.address,
parameters: [web3dart.EthereumAddress.fromHex(address), amount.raw], // coin,
maxGas: _gasLimit, // ),
gasPrice: web3dart.EtherAmount.fromUnitAndValue( // );
web3dart.EtherUnit.wei, //
fee, // await refreshCachedBalance();
), // await _refreshTransactions();
nonce: nonce, // } catch (e, s) {
); // Logging.instance.log(
// "Caught exception in ${tokenContract.name} ${ethWallet.info.name} ${ethWallet.walletId} refresh(): $e\n$s",
return txData.copyWith( // level: LogLevel.Warning,
fee: feeEstimate, // );
feeInWei: BigInt.from(fee), // } finally {
web3dartTransaction: tx, // _refreshLock = false;
chainId: await client.getChainId(), // GlobalEventBus.instance.fire(
nonce: tx.nonce, // WalletSyncStatusChangedEvent(
); // WalletSyncStatus.synced,
} // ethWallet.walletId + tokenContract.address,
// coin,
Future<String> confirmSend({required Map<String, dynamic> txData}) async { // ),
try { // );
final txid = await _client.sendTransaction( // notifyListeners();
_credentials, // }
txData["ethTx"] as web3dart.Transaction, // }
chainId: txData["chainId"] as int, // }
); //
//
try { //
txData["txid"] = txid; // Future<List<Transaction>> get transactions => ethWallet.mainDB
await updateSentCachedTxData(txData); // .getTransactions(ethWallet.walletId)
} catch (e, s) { // .filter()
// do not rethrow as that would get handled as a send failure further up // .otherDataEqualTo(tokenContract.address)
// also this is not critical code and transaction should show up on \ // .sortByTimestampDesc()
// refresh regardless // .findAll();
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());
}
}

View file

@ -1,70 +1,70 @@
/* // /*
* This file is part of Stack Wallet. // * This file is part of Stack Wallet.
* // *
* Copyright (c) 2023 Cypher Stack // * Copyright (c) 2023 Cypher Stack
* All Rights Reserved. // * All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details. // * The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26 // * Generated by Cypher Stack on 2023-05-26
* // *
*/ // */
//
import 'package:stackwallet/db/hive/db.dart'; // import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/models/balance.dart'; // import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; // import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/utilities/amount/amount.dart'; // import 'package:stackwallet/utilities/amount/amount.dart';
//
abstract class TokenCacheKeys { // abstract class TokenCacheKeys {
static String tokenBalance(String contractAddress) { // static String tokenBalance(String contractAddress) {
return "tokenBalanceCache_$contractAddress"; // return "tokenBalanceCache_$contractAddress";
} // }
} // }
//
mixin EthTokenCache { // mixin EthTokenCache {
late final String _walletId; // late final String _walletId;
late final EthContract _token; // late final EthContract _token;
//
void initCache(String walletId, EthContract token) { // void initCache(String walletId, EthContract token) {
_walletId = walletId; // _walletId = walletId;
_token = token; // _token = token;
} // }
//
// token balance cache // // token balance cache
Balance getCachedBalance() { // Balance getCachedBalance() {
final jsonString = DB.instance.get<dynamic>( // final jsonString = DB.instance.get<dynamic>(
boxName: _walletId, // boxName: _walletId,
key: TokenCacheKeys.tokenBalance(_token.address), // key: TokenCacheKeys.tokenBalance(_token.address),
) as String?; // ) as String?;
if (jsonString == null) { // if (jsonString == null) {
return Balance( // return Balance(
total: Amount( // total: Amount(
rawValue: BigInt.zero, // rawValue: BigInt.zero,
fractionDigits: _token.decimals, // fractionDigits: _token.decimals,
), // ),
spendable: Amount( // spendable: Amount(
rawValue: BigInt.zero, // rawValue: BigInt.zero,
fractionDigits: _token.decimals, // fractionDigits: _token.decimals,
), // ),
blockedTotal: Amount( // blockedTotal: Amount(
rawValue: BigInt.zero, // rawValue: BigInt.zero,
fractionDigits: _token.decimals, // fractionDigits: _token.decimals,
), // ),
pendingSpendable: Amount( // pendingSpendable: Amount(
rawValue: BigInt.zero, // rawValue: BigInt.zero,
fractionDigits: _token.decimals, // fractionDigits: _token.decimals,
), // ),
); // );
} // }
return Balance.fromJson( // return Balance.fromJson(
jsonString, // jsonString,
_token.decimals, // _token.decimals,
); // );
} // }
//
Future<void> updateCachedBalance(Balance balance) async { // Future<void> updateCachedBalance(Balance balance) async {
await DB.instance.put<dynamic>( // await DB.instance.put<dynamic>(
boxName: _walletId, // boxName: _walletId,
key: TokenCacheKeys.tokenBalance(_token.address), // key: TokenCacheKeys.tokenBalance(_token.address),
value: balance.toJsonIgnoreCoin(), // value: balance.toJsonIgnoreCoin(),
); // );
} // }
} // }

View file

@ -12,7 +12,6 @@ import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
import 'package:decimal/decimal.dart'; import 'package:decimal/decimal.dart';
import "package:hex/hex.dart"; import "package:hex/hex.dart";
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.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>); 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,93 @@
import 'package:isar/isar.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/wallets/isar/isar_id_interface.dart';
part 'token_wallet_info.g.dart';
@Collection(accessor: "tokenWalletInfo", inheritance: false)
class TokenWalletInfo implements IsarId {
@override
Id id = Isar.autoIncrement;
@Index(
unique: true,
replace: false,
composite: [
CompositeIndex("tokenAddress"),
],
)
final String walletId;
final String tokenAddress;
final int tokenFractionDigits;
final String? cachedBalanceJsonString;
TokenWalletInfo({
required this.walletId,
required this.tokenAddress,
required this.tokenFractionDigits,
this.cachedBalanceJsonString,
});
EthContract getContract(Isar isar) =>
isar.ethContracts.where().addressEqualTo(tokenAddress).findFirstSync()!;
// token balance cache
Balance getCachedBalance() {
if (cachedBalanceJsonString == null) {
return Balance(
total: Amount.zeroWith(
fractionDigits: tokenFractionDigits,
),
spendable: Amount.zeroWith(
fractionDigits: tokenFractionDigits,
),
blockedTotal: Amount.zeroWith(
fractionDigits: tokenFractionDigits,
),
pendingSpendable: Amount.zeroWith(
fractionDigits: tokenFractionDigits,
),
);
}
return Balance.fromJson(
cachedBalanceJsonString!,
tokenFractionDigits,
);
}
Future<void> updateCachedBalance(
Balance balance, {
required Isar isar,
}) async {
// // ensure we are updating using the latest entry of this in the db
final thisEntry = await isar.tokenWalletInfo
.where()
.walletIdEqualToTokenAddressNotEqualTo(walletId, tokenAddress)
.findFirst();
if (thisEntry == null) {
throw Exception(
"Attempted to update cached token balance before object was saved in db",
);
} else {
await isar.writeTxn(() async {
await isar.tokenWalletInfo.deleteByWalletIdTokenAddress(
walletId,
tokenAddress,
);
await isar.tokenWalletInfo.put(
TokenWalletInfo(
walletId: walletId,
tokenAddress: tokenAddress,
tokenFractionDigits: tokenFractionDigits,
cachedBalanceJsonString: balance.toJsonIgnoreCoin(),
),
);
});
}
}
}

File diff suppressed because it is too large Load diff

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

@ -0,0 +1,40 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/wallets/isar/models/token_wallet_info.dart';
import 'package:stackwallet/wallets/isar/providers/util/watcher.dart';
final _twiProvider = ChangeNotifierProvider.family<Watcher,
({String walletId, String contractAddress})>(
(ref, data) {
final collection = ref.watch(mainDBProvider).isar.tokenWalletInfo;
final watcher = Watcher(
collection
.where()
.walletIdTokenAddressEqualTo(data.walletId, data.contractAddress)
.findFirstSync()!,
collection: collection,
);
ref.onDispose(() => watcher.dispose());
return watcher;
},
);
final pTokenWalletInfo = Provider.family<TokenWalletInfo,
({String walletId, String contractAddress})>(
(ref, data) {
return ref.watch(_twiProvider(data)).value as TokenWalletInfo;
},
);
final pTokenBalance =
Provider.family<Balance, ({String walletId, String contractAddress})>(
(ref, data) {
return ref.watch(_twiProvider(data).select(
(value) => (value.value as TokenWalletInfo).getCachedBalance()));
},
);

View file

@ -82,3 +82,10 @@ final pWalletReceivingAddress = Provider.family<String, String>(
.select((value) => (value.value as WalletInfo).cachedReceivingAddress)); .select((value) => (value.value as WalletInfo).cachedReceivingAddress));
}, },
); );
final pWalletTokenAddresses = Provider.family<List<String>, String>(
(ref, walletId) {
return ref.watch(_wiProvider(walletId)
.select((value) => (value.value as WalletInfo).tokenContractAddresses));
},
);

View file

@ -1022,7 +1022,7 @@ class EpiccashWallet extends Bip39Wallet {
"isCancelled": "isCancelled":
tx.txType == epic_models.TransactionType.TxSentCancelled || tx.txType == epic_models.TransactionType.TxSentCancelled ||
tx.txType == epic_models.TransactionType.TxReceivedCancelled, tx.txType == epic_models.TransactionType.TxReceivedCancelled,
"anonFees": Amount( "overrideFee": Amount(
rawValue: BigInt.from(fee), rawValue: BigInt.from(fee),
fractionDigits: cryptoCurrency.fractionDigits, fractionDigits: cryptoCurrency.fractionDigits,
).toJsonString(), ).toJsonString(),

View file

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:decimal/decimal.dart';
import 'package:ethereum_addresses/ethereum_addresses.dart'; import 'package:ethereum_addresses/ethereum_addresses.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
@ -56,6 +57,24 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
return web3.Web3Client(node.host, client); 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 ============================================== // ==================== Private ==============================================
Future<void> _initCredentials( Future<void> _initCredentials(
@ -118,7 +137,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
@override @override
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async { Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
return estimateFee( return estimateEthFee(
feeRate, feeRate,
(cryptoCurrency as Ethereum).gasLimit, (cryptoCurrency as Ethereum).gasLimit,
cryptoCurrency.fractionDigits, cryptoCurrency.fractionDigits,
@ -249,7 +268,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
//Calculate fees (GasLimit * gasPrice) //Calculate fees (GasLimit * gasPrice)
// int txFee = element.gasPrice * element.gasUsed; // int txFee = element.gasPrice * element.gasUsed;
Amount txFee = element.gasCost; final Amount txFee = element.gasCost;
final transactionAmount = element.value; final transactionAmount = element.value;
final addressFrom = checksumEthereumAddress(element.from); final addressFrom = checksumEthereumAddress(element.from);
final addressTo = checksumEthereumAddress(element.to); final addressTo = checksumEthereumAddress(element.to);
@ -267,7 +286,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
continue; continue;
} }
// hack epic tx data into inputs and outputs // hack eth tx data into inputs and outputs
final List<OutputV2> outputs = []; final List<OutputV2> outputs = [];
final List<InputV2> inputs = []; final List<InputV2> inputs = [];
@ -308,7 +327,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
final otherData = { final otherData = {
"nonce": tuple.item2, "nonce": tuple.item2,
"isCancelled": txFailed, "isCancelled": txFailed,
"anonFees": txFee.toJsonString(), "overrideFee": txFee.toJsonString(),
}; };
final txn = TransactionV2( final txn = TransactionV2(

View file

@ -500,7 +500,7 @@ class FiroWallet extends Bip39HDWallet
if (anonFees != null) { if (anonFees != null) {
otherData = jsonEncode( otherData = jsonEncode(
{ {
"anonFees": anonFees.toJsonString(), "overrideFee": anonFees.toJsonString(),
}, },
); );
} }

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

@ -36,7 +36,7 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
derivePathType: DerivePathTypeExt.primaryFor(info.coin), derivePathType: DerivePathTypeExt.primaryFor(info.coin),
); );
await mainDB.putAddress(address); await mainDB.updateOrPutAddresses([address]);
await info.updateReceivingAddress( await info.updateReceivingAddress(
newAddress: address.value, newAddress: address.value,
isar: mainDB.isar, isar: mainDB.isar,
@ -58,9 +58,7 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
derivePathType: DerivePathTypeExt.primaryFor(info.coin), derivePathType: DerivePathTypeExt.primaryFor(info.coin),
); );
await mainDB.isar.writeTxn(() async { await mainDB.updateOrPutAddresses([address]);
await mainDB.isar.addresses.put(address);
});
} }
// ========== Subclasses may override ======================================== // ========== Subclasses may override ========================================

View file

@ -507,7 +507,7 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
subType: TransactionSubType.sparkSpend, subType: TransactionSubType.sparkSpend,
otherData: jsonEncode( otherData: jsonEncode(
{ {
"anonFees": fee.toJsonString(), "overrideFee": fee.toJsonString(),
}, },
), ),
height: null, height: null,

View file

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/models.dart'; import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.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/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/global/wallets_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_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/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/text_styles.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/wallets/wallet/impl/firo_wallet.dart';
import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/animated_text.dart';
import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/conditional_parent.dart';
@ -61,18 +61,27 @@ class _DesktopFeeDialogState extends ConsumerState<DesktopFeeDialog> {
final fee = await wallet.estimateFeeFor( final fee = await wallet.estimateFeeFor(
amount, MoneroTransactionPriority.fast.raw!); amount, MoneroTransactionPriority.fast.raw!);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) && } else if (coin == Coin.firo || coin == Coin.firoTestNet) {
ref.read(publicPrivateBalanceStateProvider.state).state == final Amount fee;
"Private") { switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
ref.read(feeSheetSessionCacheProvider).fast[amount] = case FiroType.spark:
await (wallet as FiroWallet).estimateFeeForLelantus(amount); fee =
await (wallet as FiroWallet).estimateFeeForSpark(amount);
case FiroType.lelantus:
fee = await (wallet as FiroWallet)
.estimateFeeForLelantus(amount);
case FiroType.public:
fee = await (wallet as FiroWallet)
.estimateFeeFor(amount, feeRate);
}
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else { } else {
ref.read(feeSheetSessionCacheProvider).fast[amount] = ref.read(feeSheetSessionCacheProvider).fast[amount] =
await wallet.estimateFeeFor(amount, feeRate); await wallet.estimateFeeFor(amount, feeRate);
} }
} else { } else {
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = tokenWallet.estimateFeeFor(feeRate); final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(tokenFeeSessionCacheProvider).fast[amount] = fee; ref.read(tokenFeeSessionCacheProvider).fast[amount] = fee;
} }
} }
@ -96,18 +105,27 @@ class _DesktopFeeDialogState extends ConsumerState<DesktopFeeDialog> {
final fee = await wallet.estimateFeeFor( final fee = await wallet.estimateFeeFor(
amount, MoneroTransactionPriority.regular.raw!); amount, MoneroTransactionPriority.regular.raw!);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee; ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) && } else if (coin == Coin.firo || coin == Coin.firoTestNet) {
ref.read(publicPrivateBalanceStateProvider.state).state == final Amount fee;
"Private") { switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
ref.read(feeSheetSessionCacheProvider).average[amount] = case FiroType.spark:
await (wallet as FiroWallet).estimateFeeForLelantus(amount); fee =
await (wallet as FiroWallet).estimateFeeForSpark(amount);
case FiroType.lelantus:
fee = await (wallet as FiroWallet)
.estimateFeeForLelantus(amount);
case FiroType.public:
fee = await (wallet as FiroWallet)
.estimateFeeFor(amount, feeRate);
}
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else { } else {
ref.read(feeSheetSessionCacheProvider).average[amount] = ref.read(feeSheetSessionCacheProvider).average[amount] =
await wallet.estimateFeeFor(amount, feeRate); await wallet.estimateFeeFor(amount, feeRate);
} }
} else { } else {
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = tokenWallet.estimateFeeFor(feeRate); final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(tokenFeeSessionCacheProvider).average[amount] = fee; ref.read(tokenFeeSessionCacheProvider).average[amount] = fee;
} }
} }
@ -131,18 +149,27 @@ class _DesktopFeeDialogState extends ConsumerState<DesktopFeeDialog> {
final fee = await wallet.estimateFeeFor( final fee = await wallet.estimateFeeFor(
amount, MoneroTransactionPriority.slow.raw!); amount, MoneroTransactionPriority.slow.raw!);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) && } else if (coin == Coin.firo || coin == Coin.firoTestNet) {
ref.read(publicPrivateBalanceStateProvider.state).state == final Amount fee;
"Private") { switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
ref.read(feeSheetSessionCacheProvider).slow[amount] = case FiroType.spark:
await (wallet as FiroWallet).estimateFeeForLelantus(amount); fee =
await (wallet as FiroWallet).estimateFeeForSpark(amount);
case FiroType.lelantus:
fee = await (wallet as FiroWallet)
.estimateFeeForLelantus(amount);
case FiroType.public:
fee = await (wallet as FiroWallet)
.estimateFeeFor(amount, feeRate);
}
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else { } else {
ref.read(feeSheetSessionCacheProvider).slow[amount] = ref.read(feeSheetSessionCacheProvider).slow[amount] =
await wallet.estimateFeeFor(amount, feeRate); await wallet.estimateFeeFor(amount, feeRate);
} }
} else { } else {
final tokenWallet = ref.read(tokenServiceProvider)!; final tokenWallet = ref.read(pCurrentTokenWallet)!;
final fee = tokenWallet.estimateFeeFor(feeRate); final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
ref.read(tokenFeeSessionCacheProvider).slow[amount] = fee; ref.read(tokenFeeSessionCacheProvider).slow[amount] = fee;
} }
} }

View file

@ -11,12 +11,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart'; import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart';
import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/expandable.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -40,17 +39,6 @@ class MasterWalletCard extends ConsumerStatefulWidget {
class _MasterWalletCardState extends ConsumerState<MasterWalletCard> { class _MasterWalletCardState extends ConsumerState<MasterWalletCard> {
final expandableController = ExpandableController(); final expandableController = ExpandableController();
final rotateIconController = RotateIconController(); final rotateIconController = RotateIconController();
late final List<String> tokenContractAddresses;
@override
void initState() {
final ethWallet =
ref.read(pWallets).getWallet(widget.walletId) as EthereumWallet;
tokenContractAddresses = ethWallet.info.tokenContractAddresses;
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -130,7 +118,7 @@ class _MasterWalletCardState extends ConsumerState<MasterWalletCard> {
popPrevious: true, popPrevious: true,
), ),
), ),
...tokenContractAddresses.map( ...ref.watch(pWalletTokenAddresses(widget.walletId)).map(
(e) => Padding( (e) => Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 7, left: 7,

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_token_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_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/db/main_db_provider.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/providers.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/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/util.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/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/wallets/wallet/wallet.dart';
import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart';
@ -55,17 +54,16 @@ class SimpleWalletCard extends ConsumerWidget {
Wallet wallet, Wallet wallet,
EthContract contract, EthContract contract,
) async { ) async {
final old = ref.read(tokenServiceStateProvider);
// exit previous if there is one
unawaited(old?.exit());
ref.read(tokenServiceStateProvider.state).state = EthTokenWallet( ref.read(tokenServiceStateProvider.state).state = EthTokenWallet(
token: contract, wallet as EthereumWallet,
secureStore: ref.read(secureStoreProvider), contract,
ethWallet: wallet as EthereumWallet,
tracker: TransactionNotificationTracker(
walletId: walletId,
),
); );
try { try {
await ref.read(tokenServiceProvider)!.initialize(); await ref.read(pCurrentTokenWallet)!.init();
return true; return true;
} catch (_) { } catch (_) {
await showDialog<void>( await showDialog<void>(