Generic fixes (#1528)

* update target sdk for android

* make welcome page scrollable
fix moonpay url params

* fix null exception when restoring from backup

* fix ui issues

* hopefully fix the timeout exception error report [skip ci]

* validate electrum addresses

* disable silent payments for hardware wallets

* fixes and enhancements
This commit is contained in:
Omar Hatem
2024-07-21 03:46:43 +03:00
committed by GitHub
parent 7514d851ce
commit 311fff2c44
45 changed files with 280 additions and 271 deletions
+1 -1
View File
@@ -560,7 +560,7 @@ class CWBitcoin extends Bitcoin {
if (tweaksResponse != null) {
return true;
}
} on RequestFailedTimeoutException {
} on RequestFailedTimeoutException catch (_) {
return false;
} catch (_) {
rethrow;
+2 -3
View File
@@ -149,10 +149,9 @@ class MoonPayProvider extends BuyProvider {
'colorCode': settingsStore.currentTheme.type == ThemeType.dark
? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}'
: '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}',
'defaultCurrencyCode': _normalizeCurrency(currency),
'baseCurrencyCode': _normalizeCurrency(currency),
'baseCurrencyCode': settingsStore.fiatCurrency.title,
'baseCurrencyAmount': amount ?? '0',
'currencyCode': currencyCode,
'currencyCode': _normalizeCurrency(currency),
'walletAddress': walletAddress,
'lockAmount': 'false',
'showAllCurrencies': 'false',
+1 -2
View File
@@ -67,9 +67,8 @@ class ProvidersHelper {
];
case WalletType.litecoin:
case WalletType.bitcoinCash:
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, ProviderType.moonpay];
case WalletType.solana:
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood];
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, ProviderType.moonpay];
case WalletType.tron:
return [
ProviderType.askEachTime,
@@ -340,7 +340,7 @@ class CryptoBalanceWidget extends StatelessWidget {
builder: (BuildContext context) => AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: S.of(context).confirm_silent_payments_switch_node,
rightButtonText: S.of(context).ok,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
dashboardViewModel.setSilentPaymentsScanning(newValue);
+35 -31
View File
@@ -22,38 +22,42 @@ class RescanPage extends BasePage {
@override
Widget body(BuildContext context) {
return Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Observer(
builder: (_) => BlockchainHeightWidget(
key: _blockchainHeightWidgetKey,
onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
doSingleScan: _rescanViewModel.doSingleScan,
toggleSingleScan: () =>
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
walletType: _rescanViewModel.wallet.type,
)),
Observer(
builder: (_) => LoadingPrimaryButton(
isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
text: S.of(context).rescan,
onPressed: () async {
if (_rescanViewModel.isSilentPaymentsScan) {
return _toggleSilentPaymentsScanning(context);
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => FocusScope.of(context).unfocus(),
child: Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Observer(
builder: (_) => BlockchainHeightWidget(
key: _blockchainHeightWidgetKey,
onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
doSingleScan: _rescanViewModel.doSingleScan,
toggleSingleScan: () =>
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
walletType: _rescanViewModel.wallet.type,
)),
Observer(
builder: (_) => LoadingPrimaryButton(
isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
text: S.of(context).rescan,
onPressed: () async {
if (_rescanViewModel.isSilentPaymentsScan) {
return _toggleSilentPaymentsScanning(context);
}
_rescanViewModel.rescanCurrentWallet(
restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
_rescanViewModel.rescanCurrentWallet(
restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
Navigator.of(context).pop();
},
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isDisabled: !_rescanViewModel.isButtonEnabled,
))
]),
Navigator.of(context).pop();
},
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isDisabled: !_rescanViewModel.isButtonEnabled,
))
]),
),
);
}
@@ -71,7 +75,7 @@ class RescanPage extends BasePage {
builder: (BuildContext _dialogContext) => AlertWithTwoActions(
alertTitle: S.of(_dialogContext).change_current_node_title,
alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node,
rightButtonText: S.of(_dialogContext).ok,
rightButtonText: S.of(_dialogContext).confirm,
leftButtonText: S.of(_dialogContext).cancel,
actionRightButton: () async {
Navigator.of(_dialogContext).pop();
+93 -94
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
@@ -49,115 +50,113 @@ class WelcomePage extends BasePage {
@override
Widget body(BuildContext context) {
final welcomeImage = currentTheme.type == ThemeType.dark
? welcomeImageDark
: welcomeImageLight;
final welcomeImage = currentTheme.type == ThemeType.dark ? welcomeImageDark : welcomeImageLight;
final newWalletImage = Image.asset('assets/images/new_wallet.png',
height: 12,
width: 12,
color: Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor);
final restoreWalletImage = Image.asset('assets/images/restore_wallet.png',
height: 12,
width: 12,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor);
height: 12, width: 12, color: Theme.of(context).extension<CakeTextTheme>()!.titleColor);
return WillPopScope(
onWillPop: () async => false,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
children: <Widget>[
AspectRatio(
aspectRatio: aspectRatioImage,
child: FittedBox(
child: welcomeImage, fit: BoxFit.contain),
),
Padding(
padding: EdgeInsets.only(top: 24),
child: Text(
S.of(context).welcome,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<NewWalletTheme>()!.hintTextColor,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
appTitle(context),
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
appDescription(context),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<NewWalletTheme>()!.hintTextColor,
),
textAlign: TextAlign.center,
),
),
],
),
Column(
children: <Widget>[
Text(
S.of(context).please_make_selection,
onWillPop: () async => false,
child: ScrollableWithBottomSection(
content: Container(
alignment: Alignment.center,
padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24),
child: ConstrainedBox(
constraints:
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
children: <Widget>[
AspectRatio(
aspectRatio: aspectRatioImage,
child: FittedBox(child: welcomeImage, fit: BoxFit.contain),
),
Padding(
padding: EdgeInsets.only(top: 24),
child: Text(
S.of(context).welcome,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
fontSize: 18,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<NewWalletTheme>()!.hintTextColor,
),
textAlign: TextAlign.center,
),
Padding(
padding: EdgeInsets.only(top: 24),
child: PrimaryImageButton(
onPressed: () => Navigator.pushNamed(
context, Routes.newWalletFromWelcome),
image: newWalletImage,
text: S.of(context).create_new,
color: Theme.of(context).extension<WalletListTheme>()!.createNewWalletButtonBackgroundColor,
textColor: Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor,
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
appTitle(context),
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
textAlign: TextAlign.center,
),
Padding(
padding: EdgeInsets.only(top: 10),
child: PrimaryImageButton(
onPressed: () {
Navigator.pushNamed(
context, Routes.restoreOptions,
arguments: true);
},
image: restoreWalletImage,
text: S.of(context).restore_wallet,
color: Theme.of(context).cardColor,
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
)
],
)
],
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
appDescription(context),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<NewWalletTheme>()!.hintTextColor,
),
textAlign: TextAlign.center,
),
),
],
),
],
),
),
),
bottomSection: Column(
children: <Widget>[
Text(
S.of(context).please_make_selection,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<NewWalletTheme>()!.hintTextColor,
),
)));
textAlign: TextAlign.center,
),
Padding(
padding: EdgeInsets.only(top: 24),
child: PrimaryImageButton(
onPressed: () => Navigator.pushNamed(context, Routes.newWalletFromWelcome),
image: newWalletImage,
text: S.of(context).create_new,
color: Theme.of(context)
.extension<WalletListTheme>()!
.createNewWalletButtonBackgroundColor,
textColor:
Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor,
),
),
Padding(
padding: EdgeInsets.only(top: 10),
child: PrimaryImageButton(
onPressed: () {
Navigator.pushNamed(context, Routes.restoreOptions, arguments: true);
},
image: restoreWalletImage,
text: S.of(context).restore_wallet,
color: Theme.of(context).cardColor,
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
)
],
),
),
);
}
}
+76 -72
View File
@@ -65,89 +65,93 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: BaseTextFormField(
focusNode: widget.focusNode,
controller: restoreHeightController,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_height
: S.of(context).widgets_restore_from_blockheight,
)))
],
),
if (widget.hasDatePicker) ...[
Padding(
padding: EdgeInsets.only(top: 15, bottom: 15),
child: Text(
S.of(context).widgets_or,
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
),
),
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => FocusScope.of(context).unfocus(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Flexible(
child: Container(
child: InkWell(
onTap: () => _selectDate(context),
child: IgnorePointer(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: BaseTextFormField(
controller: dateController,
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date
: S.of(context).widgets_restore_from_date,
)),
),
))
focusNode: widget.focusNode,
controller: restoreHeightController,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_height
: S.of(context).widgets_restore_from_blockheight,
)))
],
),
if (widget.isSilentPaymentsScan)
if (widget.hasDatePicker) ...[
Padding(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).scan_one_block,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
Padding(
padding: const EdgeInsets.only(right: 8),
child: StandardSwitch(
value: widget.doSingleScan,
onTaped: () => widget.toggleSingleScan?.call(),
),
)
],
padding: EdgeInsets.only(top: 15, bottom: 15),
child: Text(
S.of(context).widgets_or,
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
),
),
Padding(
padding: EdgeInsets.only(left: 40, right: 40, top: 24),
child: Text(
widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date_or_blockheight
: S.of(context).restore_from_date_or_blockheight,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12, fontWeight: FontWeight.normal, color: Theme.of(context).hintColor),
Row(
children: <Widget>[
Flexible(
child: Container(
child: InkWell(
onTap: () => _selectDate(context),
child: IgnorePointer(
child: BaseTextFormField(
controller: dateController,
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date
: S.of(context).widgets_restore_from_date,
)),
),
))
],
),
)
]
],
if (widget.isSilentPaymentsScan)
Padding(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).scan_one_block,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
Padding(
padding: const EdgeInsets.only(right: 8),
child: StandardSwitch(
value: widget.doSingleScan,
onTaped: () => widget.toggleSingleScan?.call(),
),
)
],
),
),
Padding(
padding: EdgeInsets.only(left: 40, right: 40, top: 24),
child: Text(
widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date_or_blockheight
: S.of(context).restore_from_date_or_blockheight,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12, fontWeight: FontWeight.normal, color: Theme.of(context).hintColor),
),
)
]
],
),
);
}
+5 -3
View File
@@ -1184,9 +1184,11 @@ abstract class SettingsStoreBase with Store {
raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
priority[WalletType.monero]!;
priority[WalletType.wownero] = wownero?.deserializeWowneroTransactionPriority(
raw: sharedPreferences.getInt(PreferencesKey.wowneroTransactionPriority)!) ??
priority[WalletType.wownero]!;
if (wownero != null &&
sharedPreferences.getInt(PreferencesKey.wowneroTransactionPriority) != null) {
priority[WalletType.wownero] = wownero!.deserializeWowneroTransactionPriority(
raw: sharedPreferences.getInt(PreferencesKey.wowneroTransactionPriority)!);
}
if (bitcoin != null &&
sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority) != null) {
@@ -61,7 +61,7 @@ abstract class BalanceViewModelBase with Store {
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
@computed
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
bool get hasSilentPayments => wallet.type == WalletType.bitcoin && !wallet.isHardwareWallet;
@computed
double get price {
@@ -308,7 +308,7 @@ abstract class DashboardViewModelBase with Store {
wallet.type == WalletType.haven;
@computed
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
bool get hasSilentPayments => wallet.type == WalletType.bitcoin && !wallet.isHardwareWallet;
@computed
bool get showSilentPaymentsCard => hasSilentPayments && settingsStore.silentPaymentsCardDisplay;
@@ -86,7 +86,7 @@ abstract class TransactionDetailsViewModelBase with Store {
if (showRecipientAddress && !isRecipientAddressShown) {
try {
final recipientAddress = transactionDescriptionBox.values
.firstWhere((val) => val.id == transactionInfo.id)
.firstWhere((val) => val.id == transactionInfo.txHash)
.recipientAddress;
if (recipientAddress?.isNotEmpty ?? false) {
@@ -105,14 +105,14 @@ abstract class TransactionDetailsViewModelBase with Store {
value: _explorerDescription(type),
onTap: () async {
try {
final uri = Uri.parse(_explorerUrl(type, tx.id));
final uri = Uri.parse(_explorerUrl(type, tx.txHash));
if (await canLaunchUrl(uri)) await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (e) {}
}));
final description = transactionDescriptionBox.values.firstWhere(
(val) => val.id == transactionInfo.id,
orElse: () => TransactionDescription(id: transactionInfo.id));
(val) => val.id == transactionInfo.txHash,
orElse: () => TransactionDescription(id: transactionInfo.txHash));
items.add(TextFieldListItem(
title: S.current.note_tap_to_change,
@@ -214,7 +214,7 @@ abstract class TransactionDetailsViewModelBase with Store {
final addressIndex = tx.additionalInfo['addressIndex'] as int;
final feeFormatted = tx.feeFormatted();
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txhash),
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
@@ -250,7 +250,7 @@ abstract class TransactionDetailsViewModelBase with Store {
void _addElectrumListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
@@ -265,7 +265,7 @@ abstract class TransactionDetailsViewModelBase with Store {
void _addHavenListItems(TransactionInfo tx, DateFormat dateFormat) {
items.addAll([
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
@@ -277,7 +277,7 @@ abstract class TransactionDetailsViewModelBase with Store {
void _addEthereumListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
@@ -296,7 +296,7 @@ abstract class TransactionDetailsViewModelBase with Store {
void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
if (showRecipientAddress && tx.to != null)
StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
if (showRecipientAddress && tx.from != null)
@@ -313,7 +313,7 @@ abstract class TransactionDetailsViewModelBase with Store {
void _addPolygonListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
@@ -332,7 +332,7 @@ abstract class TransactionDetailsViewModelBase with Store {
void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
@@ -396,7 +396,7 @@ abstract class TransactionDetailsViewModelBase with Store {
void _addTronListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
@@ -455,7 +455,7 @@ abstract class TransactionDetailsViewModelBase with Store {
final addressIndex = tx.additionalInfo['addressIndex'] as int;
final feeFormatted = tx.feeFormatted();
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txhash),
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),