tezos and various tweaks

This commit is contained in:
julian 2023-11-22 12:30:09 -06:00
parent 700943ada4
commit 391304f6da
8 changed files with 330 additions and 123 deletions

View file

@ -443,7 +443,7 @@ class _ConfirmTransactionViewState
"Amount", "Amount",
style: STextStyles.smallMed12(context), style: STextStyles.smallMed12(context),
), ),
Text( SelectableText(
ref.watch(pAmountFormatter(coin)).format( ref.watch(pAmountFormatter(coin)).format(
widget.txData.amount!, widget.txData.amount!,
ethContract: ref ethContract: ref
@ -469,7 +469,7 @@ class _ConfirmTransactionViewState
"Transaction fee", "Transaction fee",
style: STextStyles.smallMed12(context), style: STextStyles.smallMed12(context),
), ),
Text( SelectableText(
ref ref
.watch(pAmountFormatter(coin)) .watch(pAmountFormatter(coin))
.format(widget.txData.fee!), .format(widget.txData.fee!),
@ -495,7 +495,7 @@ class _ConfirmTransactionViewState
const SizedBox( const SizedBox(
height: 4, height: 4,
), ),
Text( SelectableText(
"~${widget.txData.fee!.raw.toInt() ~/ widget.txData.vSize!}", "~${widget.txData.fee!.raw.toInt() ~/ widget.txData.vSize!}",
style: STextStyles.itemSubtitle12(context), style: STextStyles.itemSubtitle12(context),
), ),
@ -520,7 +520,7 @@ class _ConfirmTransactionViewState
const SizedBox( const SizedBox(
height: 4, height: 4,
), ),
Text( SelectableText(
widget.txData.noteOnChain!, widget.txData.noteOnChain!,
style: STextStyles.itemSubtitle12(context), style: STextStyles.itemSubtitle12(context),
), ),
@ -543,7 +543,7 @@ class _ConfirmTransactionViewState
const SizedBox( const SizedBox(
height: 4, height: 4,
), ),
Text( SelectableText(
widget.txData.note!, widget.txData.note!,
style: STextStyles.itemSubtitle12(context), style: STextStyles.itemSubtitle12(context),
), ),
@ -664,7 +664,7 @@ class _ConfirmTransactionViewState
return Row( return Row(
children: [ children: [
Text( SelectableText(
ref.watch(pAmountFormatter(coin)).format( ref.watch(pAmountFormatter(coin)).format(
amount, amount,
ethContract: ref ethContract: ref
@ -687,7 +687,7 @@ class _ConfirmTransactionViewState
context), context),
), ),
if (externalCalls) if (externalCalls)
Text( SelectableText(
"~$fiatAmount ${ref.watch(prefsChangeNotifierProvider.select( "~$fiatAmount ${ref.watch(prefsChangeNotifierProvider.select(
(value) => value.currency, (value) => value.currency,
))}", ))}",
@ -724,7 +724,7 @@ class _ConfirmTransactionViewState
const SizedBox( const SizedBox(
height: 2, height: 2,
), ),
Text( SelectableText(
widget.isPaynymTransaction widget.isPaynymTransaction
? widget.txData.paynymAccountLite!.nymName ? widget.txData.paynymAccountLite!.nymName
: widget.txData.recipients!.first.address, : widget.txData.recipients!.first.address,
@ -765,7 +765,7 @@ class _ConfirmTransactionViewState
builder: (context) { builder: (context) {
final fee = widget.txData.fee!; final fee = widget.txData.fee!;
return Text( return SelectableText(
ref ref
.watch(pAmountFormatter(coin)) .watch(pAmountFormatter(coin))
.format(fee), .format(fee),
@ -884,7 +884,7 @@ class _ConfirmTransactionViewState
const SizedBox( const SizedBox(
height: 12, height: 12,
), ),
Text( SelectableText(
(coin == Coin.epicCash) (coin == Coin.epicCash)
? "Local Note (optional)" ? "Local Note (optional)"
: "Note (optional)", : "Note (optional)",
@ -987,7 +987,7 @@ class _ConfirmTransactionViewState
builder: (context) { builder: (context) {
final fee = widget.txData.fee!; final fee = widget.txData.fee!;
return Text( return SelectableText(
ref.watch(pAmountFormatter(coin)).format(fee), ref.watch(pAmountFormatter(coin)).format(fee),
style: STextStyles.itemSubtitle(context), style: STextStyles.itemSubtitle(context),
); );
@ -1026,7 +1026,7 @@ class _ConfirmTransactionViewState
color: Theme.of(context) color: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.textFieldDefaultBG, .textFieldDefaultBG,
child: Text( child: SelectableText(
"~${widget.txData.fee!.raw.toInt() ~/ widget.txData.vSize!}", "~${widget.txData.fee!.raw.toInt() ~/ widget.txData.vSize!}",
style: STextStyles.itemSubtitle(context), style: STextStyles.itemSubtitle(context),
), ),
@ -1075,7 +1075,7 @@ class _ConfirmTransactionViewState
final fee = widget.txData.fee!; final fee = widget.txData.fee!;
final amount = widget.txData.amount!; final amount = widget.txData.amount!;
return Text( return SelectableText(
ref ref
.watch(pAmountFormatter(coin)) .watch(pAmountFormatter(coin))
.format(amount + fee), .format(amount + fee),

View file

@ -1486,7 +1486,7 @@ class _SendViewState extends ConsumerState<SendView> {
style: STextStyles.smallMed12(context), style: STextStyles.smallMed12(context),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
if (coin != Coin.ethereum) if (coin != Coin.ethereum && coin != Coin.tezos)
CustomTextButton( CustomTextButton(
text: "Send all ${coin.ticker}", text: "Send all ${coin.ticker}",
onTap: () async { onTap: () async {
@ -1898,7 +1898,8 @@ class _SendViewState extends ConsumerState<SendView> {
), ),
if (coin != Coin.epicCash && if (coin != Coin.epicCash &&
coin != Coin.nano && coin != Coin.nano &&
coin != Coin.banano) coin != Coin.banano &&
coin != Coin.tezos)
Text( Text(
"Transaction fee (estimated)", "Transaction fee (estimated)",
style: STextStyles.smallMed12(context), style: STextStyles.smallMed12(context),
@ -1906,13 +1907,15 @@ class _SendViewState extends ConsumerState<SendView> {
), ),
if (coin != Coin.epicCash && if (coin != Coin.epicCash &&
coin != Coin.nano && coin != Coin.nano &&
coin != Coin.banano) coin != Coin.banano &&
coin != Coin.tezos)
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
if (coin != Coin.epicCash && if (coin != Coin.epicCash &&
coin != Coin.nano && coin != Coin.nano &&
coin != Coin.banano) coin != Coin.banano &&
coin != Coin.tezos)
Stack( Stack(
children: [ children: [
TextField( TextField(

View file

@ -1062,7 +1062,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
), ),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
if (coin != Coin.ethereum) if (coin != Coin.ethereum && coin != Coin.tezos)
CustomTextButton( CustomTextButton(
text: "Send all ${coin.ticker}", text: "Send all ${coin.ticker}",
onTap: sendAllTapped, onTap: sendAllTapped,
@ -1481,7 +1481,8 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
const SizedBox( const SizedBox(
height: 20, height: 20,
), ),
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin))) if (!([Coin.nano, Coin.banano, Coin.epicCash, Coin.tezos]
.contains(coin)))
ConditionalParent( ConditionalParent(
condition: coin.isElectrumXCoin && condition: coin.isElectrumXCoin &&
!(((coin == Coin.firo || coin == Coin.firoTestNet) && !(((coin == Coin.firo || coin == Coin.firoTestNet) &&
@ -1532,11 +1533,13 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
), ),
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin))) if (!([Coin.nano, Coin.banano, Coin.epicCash, Coin.tezos]
.contains(coin)))
const SizedBox( const SizedBox(
height: 10, height: 10,
), ),
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin))) if (!([Coin.nano, Coin.banano, Coin.epicCash, Coin.tezos]
.contains(coin)))
if (!isCustomFee) if (!isCustomFee)
Padding( Padding(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),

View file

@ -117,7 +117,7 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
children: [ children: [
FittedBox( FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text( child: SelectableText(
ref ref
.watch(pAmountFormatter(coin)) .watch(pAmountFormatter(coin))
.format(balanceToShow, ethContract: tokenContract), .format(balanceToShow, ethContract: tokenContract),
@ -125,7 +125,7 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
), ),
), ),
if (externalCalls) if (externalCalls)
Text( SelectableText(
"${Amount.fromDecimal( "${Amount.fromDecimal(
priceTuple.item1 * balanceToShow.decimal, priceTuple.item1 * balanceToShow.decimal,
fractionDigits: 2, fractionDigits: 2,

View file

@ -0,0 +1,58 @@
class TezosAccount {
final int id;
final String type;
final String address;
final String? publicKey;
final bool revealed;
final int balance;
final int counter;
TezosAccount({
required this.id,
required this.type,
required this.address,
required this.publicKey,
required this.revealed,
required this.balance,
required this.counter,
});
TezosAccount copyWith({
int? id,
String? type,
String? address,
String? publicKey,
bool? revealed,
int? balance,
int? counter,
}) {
return TezosAccount(
id: id ?? this.id,
type: type ?? this.type,
address: address ?? this.address,
publicKey: publicKey ?? this.publicKey,
revealed: revealed ?? this.revealed,
balance: balance ?? this.balance,
counter: counter ?? this.counter,
);
}
factory TezosAccount.fromMap(Map<String, dynamic> map) {
return TezosAccount(
id: map['id'] as int,
type: map['type'] as String,
address: map['address'] as String,
publicKey: map['publicKey'] as String?,
revealed: map['revealed'] as bool,
balance: map['balance'] as int,
counter: map['counter'] as int,
);
}
@override
String toString() {
return 'UserData{id: $id, type: $type, address: $address, '
'publicKey: $publicKey, revealed: $revealed,'
' balance: $balance, counter: $counter}';
}
}

View file

@ -4,6 +4,7 @@ import 'package:stackwallet/networking/http.dart';
import 'package:stackwallet/services/tor_service.dart'; import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/wallets/api/tezos/tezos_account.dart';
import 'package:stackwallet/wallets/api/tezos/tezos_transaction.dart'; import 'package:stackwallet/wallets/api/tezos/tezos_transaction.dart';
abstract final class TezosAPI { abstract final class TezosAPI {
@ -32,6 +33,34 @@ abstract final class TezosAPI {
} }
} }
static Future<TezosAccount> getAccount(String address,
{String type = "user"}) async {
try {
final uriString = "$_baseURL/v1/accounts/$address?legacy=false";
final response = await _client.get(
url: Uri.parse(uriString),
headers: {'Content-Type': 'application/json'},
proxyInfo: Prefs.instance.useTor
? TorService.sharedInstance.getProxyInfo()
: null,
);
final result = jsonDecode(response.body) as Map;
final account = TezosAccount.fromMap(Map<String, dynamic>.from(result));
print("Get account =================== $account");
return account;
} catch (e, s) {
Logging.instance.log(
"Error occurred in TezosAPI while getting account for $address: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
static Future<List<TezosTransaction>> getTransactions(String address) async { static Future<List<TezosTransaction>> getTransactions(String address) async {
try { try {
final transactionsCall = final transactionsCall =

View file

@ -15,8 +15,9 @@ class Tezos extends Bip39Currency {
} }
@override @override
// TODO: implement genesisHash String get genesisHash => throw UnimplementedError(
String get genesisHash => throw UnimplementedError(); "Not used in tezos at the moment",
);
@override @override
int get minConfirms => 1; int get minConfirms => 1;

View file

@ -10,6 +10,7 @@ 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/extensions/impl/string.dart'; import 'package:stackwallet/utilities/extensions/impl/string.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/api/tezos/tezos_account.dart';
import 'package:stackwallet/wallets/api/tezos/tezos_api.dart'; import 'package:stackwallet/wallets/api/tezos/tezos_api.dart';
import 'package:stackwallet/wallets/api/tezos/tezos_rpc_api.dart'; import 'package:stackwallet/wallets/api/tezos/tezos_rpc_api.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/tezos.dart'; import 'package:stackwallet/wallets/crypto_currency/coins/tezos.dart';
@ -20,8 +21,8 @@ import 'package:tezart/tezart.dart' as tezart;
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
// const kDefaultTransactionStorageLimit = 496; // const kDefaultTransactionStorageLimit = 496;
const kDefaultTransactionGasLimit = 10600; // const kDefaultTransactionGasLimit = 10600;
//
// const kDefaultKeyRevealFee = 1270; // const kDefaultKeyRevealFee = 1270;
// const kDefaultKeyRevealStorageLimit = 0; // const kDefaultKeyRevealStorageLimit = 0;
// const kDefaultKeyRevealGasLimit = 1100; // const kDefaultKeyRevealGasLimit = 1100;
@ -53,45 +54,51 @@ class TezosWallet extends Bip39Wallet {
Future<tezart.OperationsList> _buildSendTransaction({ Future<tezart.OperationsList> _buildSendTransaction({
required Amount amount, required Amount amount,
required String address, required String address,
int? customGasLimit, required int counter,
Amount? customFee, // required bool reveal,
// int? customGasLimit,
// Amount? customFee,
// Amount? customRevealFee,
}) async { }) async {
try { try {
final sourceKeyStore = await _getKeyStore(); final sourceKeyStore = await _getKeyStore();
final server = (_xtzNode ?? getCurrentNode()).host;
// if (kDebugMode) {
// print("SERVER: $server");
// print("COUNTER: $counter");
// print("customFee: $customFee");
// }
final tezartClient = tezart.TezartClient( final tezartClient = tezart.TezartClient(
(_xtzNode ?? getCurrentNode()).host, server,
); );
final opList = await tezartClient.transferOperation( final opList = await tezartClient.transferOperation(
source: sourceKeyStore, source: sourceKeyStore,
destination: address, destination: address,
amount: amount.raw.toInt(), amount: amount.raw.toInt(),
customGasLimit: customGasLimit, // customFee: customFee?.raw.toInt(),
customFee: customFee?.raw.toInt(), // customGasLimit: customGasLimit,
// reveal: false,
); );
final counter = (await TezosAPI.getCounter( // if (reveal) {
(await getCurrentReceivingAddress())!.value, // opList.prependOperation(
)) + // tezart.RevealOperation(
1; // customGasLimit: customGasLimit,
// customFee: customRevealFee?.raw.toInt(),
// ),
// );
// }
for (final op in opList.operations) { for (final op in opList.operations) {
if (op is tezart.RevealOperation) {
// op.storageLimit = kDefaultKeyRevealStorageLimit;
// op.gasLimit = kDefaultKeyRevealGasLimit;
// op.fee = kDefaultKeyRevealFee;
op.counter = counter; op.counter = counter;
} else if (op is tezart.TransactionOperation) { counter++;
op.counter = counter + 1;
// op.storageLimit = kDefaultTransactionStorageLimit;
// op.gasLimit = kDefaultTransactionGasLimit;
}
} }
return opList; return opList;
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"Error in estimateFeeFor() in tezos_wallet.dart: $e\n$s}", "Error in _buildSendTransaction() in tezos_wallet.dart: $e\n$s}",
level: LogLevel.Error, level: LogLevel.Error,
); );
rethrow; rethrow;
@ -122,6 +129,7 @@ class TezosWallet extends Bip39Wallet {
@override @override
Future<TxData> prepareSend({required TxData txData}) async { Future<TxData> prepareSend({required TxData txData}) async {
try {
if (txData.recipients == null || txData.recipients!.length != 1) { if (txData.recipients == null || txData.recipients!.length != 1) {
throw Exception("$runtimeType prepareSend requires 1 recipient"); throw Exception("$runtimeType prepareSend requires 1 recipient");
} }
@ -131,31 +139,60 @@ class TezosWallet extends Bip39Wallet {
if (sendAmount > info.cachedBalance.spendable) { if (sendAmount > info.cachedBalance.spendable) {
throw Exception("Insufficient available balance"); throw Exception("Insufficient available balance");
} }
final account = await TezosAPI.getAccount(
final bool isSendAll = sendAmount == info.cachedBalance.spendable; (await getCurrentReceivingAddress())!.value,
Amount fee = await estimateFeeFor(sendAmount, -1);
int? customGasLimit;
if (isSendAll) {
//Fee guides for emptying a tz account
// https://github.com/TezTech/eztz/blob/master/PROTO_004_FEES.md
customGasLimit = kDefaultTransactionGasLimit + 320;
fee = Amount(
rawValue: BigInt.from(fee.raw.toInt() + 32),
fractionDigits: cryptoCurrency.fractionDigits,
); );
sendAmount = sendAmount - fee;
} // final bool isSendAll = sendAmount == info.cachedBalance.spendable;
//
// int? customGasLimit;
// Amount? fee;
// Amount? revealFee;
//
// if (isSendAll) {
// final fees = await _estimate(
// account,
// txData.recipients!.first.address,
// );
// //Fee guides for emptying a tz account
// // https://github.com/TezTech/eztz/blob/master/PROTO_004_FEES.md
// // customGasLimit = kDefaultTransactionGasLimit + 320;
// fee = Amount(
// rawValue: BigInt.from(fees.transfer + 32),
// fractionDigits: cryptoCurrency.fractionDigits,
// );
//
// BigInt rawAmount = sendAmount.raw - fee.raw;
//
// if (!account.revealed) {
// revealFee = Amount(
// rawValue: BigInt.from(fees.reveal + 32),
// fractionDigits: cryptoCurrency.fractionDigits,
// );
//
// rawAmount = rawAmount - revealFee.raw;
// }
//
// sendAmount = Amount(
// rawValue: rawAmount,
// fractionDigits: cryptoCurrency.fractionDigits,
// );
// }
final opList = await _buildSendTransaction( final opList = await _buildSendTransaction(
amount: sendAmount, amount: sendAmount,
address: txData.recipients!.first.address, address: txData.recipients!.first.address,
customFee: fee, counter: account.counter + 1,
customGasLimit: customGasLimit, // reveal: !account.revealed,
// customFee: isSendAll ? fee : null,
// customRevealFee: isSendAll ? revealFee : null,
// customGasLimit: customGasLimit,
); );
await opList.computeLimits();
await opList.computeFees();
await opList.simulate();
return txData.copyWith( return txData.copyWith(
recipients: [ recipients: [
( (
@ -163,41 +200,8 @@ class TezosWallet extends Bip39Wallet {
address: txData.recipients!.first.address, address: txData.recipients!.first.address,
) )
], ],
fee: fee, // fee: fee,
tezosOperationsList: opList, fee: Amount(
);
}
@override
Future<TxData> confirmSend({required TxData txData}) async {
await txData.tezosOperationsList!.executeAndMonitor();
return txData.copyWith(
txid: txData.tezosOperationsList!.result.id,
);
}
@override
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
if (amount.raw == BigInt.zero) {
amount = Amount(
rawValue: BigInt.one,
fractionDigits: cryptoCurrency.fractionDigits,
);
}
final myAddressForSimulation = (await getCurrentReceivingAddress())!.value;
try {
final opList = await _buildSendTransaction(
amount: amount,
address: myAddressForSimulation,
);
await opList.computeLimits();
await opList.computeFees();
await opList.simulate();
final fee = Amount(
rawValue: opList.operations rawValue: opList.operations
.map( .map(
(e) => BigInt.from(e.fee), (e) => BigInt.from(e.fee),
@ -207,12 +211,121 @@ class TezosWallet extends Bip39Wallet {
(p, e) => p + e, (p, e) => p + e,
), ),
fractionDigits: cryptoCurrency.fractionDigits, fractionDigits: cryptoCurrency.fractionDigits,
),
tezosOperationsList: opList,
);
} catch (e, s) {
Logging.instance.log(
"Error in prepareSend() in tezos_wallet.dart: $e\n$s}",
level: LogLevel.Error,
);
if (e
.toString()
.contains("(_operationResult['errors']): Must not be null")) {
throw Exception("Probably insufficient balance");
} else if (e.toString().contains(
"The simulation of the operation: \"transaction\" failed with error(s) :"
" contract.balance_too_low, tez.subtraction_underflow.",
)) {
throw Exception("Insufficient balance to pay fees");
}
rethrow;
}
}
@override
Future<TxData> confirmSend({required TxData txData}) async {
await txData.tezosOperationsList!.inject();
await txData.tezosOperationsList!.monitor();
return txData.copyWith(
txid: txData.tezosOperationsList!.result.id,
);
}
int _estCount = 0;
Future<({int reveal, int transfer})> _estimate(
TezosAccount account, String recipientAddress) async {
try {
final opList = await _buildSendTransaction(
amount: Amount(
rawValue: BigInt.one,
fractionDigits: cryptoCurrency.fractionDigits,
),
address: recipientAddress,
counter: account.counter + 1,
// reveal: !account.revealed,
);
await opList.computeLimits();
await opList.computeFees();
await opList.simulate();
int reveal = 0;
int transfer = 0;
for (final op in opList.operations) {
if (op is tezart.TransactionOperation) {
transfer += op.fee;
} else if (op is tezart.RevealOperation) {
reveal += op.fee;
}
}
return (reveal: reveal, transfer: transfer);
} catch (e, s) {
if (_estCount > 3) {
_estCount = 0;
Logging.instance.log(
" Error in _estimate in tezos_wallet.dart: $e\n$s}",
level: LogLevel.Error,
);
rethrow;
} else {
_estCount++;
Logging.instance.log(
"_estimate() retry _estCount=$_estCount",
level: LogLevel.Warning,
);
return await _estimate(
account,
recipientAddress,
);
}
}
}
@override
Future<Amount> estimateFeeFor(
Amount amount,
int feeRate, {
String recipientAddress = "tz1MXvDCyXSqBqXPNDcsdmVZKfoxL9FTHmp2",
}) async {
if (info.cachedBalance.spendable.raw == BigInt.zero) {
return Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
);
}
final account = await TezosAPI.getAccount(
(await getCurrentReceivingAddress())!.value,
);
try {
final fees = await _estimate(account, recipientAddress);
final fee = Amount(
rawValue: BigInt.from(fees.reveal + fees.transfer),
fractionDigits: cryptoCurrency.fractionDigits,
); );
return fee; return fee;
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"Error in estimateFeeFor() in tezos_wallet.dart: $e\n$s}", " Error in estimateFeeFor() in tezos_wallet.dart: $e\n$s}",
level: LogLevel.Error, level: LogLevel.Error,
); );
rethrow; rethrow;