This commit is contained in:
fosse 2024-02-21 22:03:19 -05:00
parent 3ce93c9270
commit d8976182ca
36 changed files with 527 additions and 264 deletions

View file

@ -0,0 +1,90 @@
import 'dart:convert';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_lightning/lightning_transaction_info.dart';
import 'package:mobx/mobx.dart';
part 'lightning_transaction_history.g.dart';
const transactionsHistoryFileName = 'transactions.json';
class LightningTransactionHistory = LightningTransactionHistoryBase
with _$LightningTransactionHistory;
abstract class LightningTransactionHistoryBase
extends TransactionHistoryBase<LightningTransactionInfo> with Store {
LightningTransactionHistoryBase(
{required this.walletInfo, required String password})
: _password = password,
_height = 0 {
transactions = ObservableMap<String, LightningTransactionInfo>();
}
final WalletInfo walletInfo;
String _password;
int _height;
Future<void> init() async => await _load();
@override
void addOne(LightningTransactionInfo transaction) =>
transactions[transaction.id] = transaction;
@override
void addMany(Map<String, LightningTransactionInfo> transactions) =>
transactions.forEach((_, tx) => _update(tx));
@override
Future<void> save() async {
try {
final dirPath =
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final data =
json.encode({'height': _height, 'transactions': transactions});
await writeData(path: path, password: _password, data: data);
} catch (e) {
print('Error while save bitcoin transaction history: ${e.toString()}');
}
}
Future<void> changePassword(String password) async {
_password = password;
await save();
}
Future<Map<String, dynamic>> _read() async {
final dirPath =
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final content = await read(path: path, password: _password);
return json.decode(content) as Map<String, dynamic>;
}
Future<void> _load() async {
try {
final content = await _read();
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
txs.entries.forEach((entry) {
final val = entry.value;
if (val is Map<String, dynamic>) {
final tx = LightningTransactionInfo.fromJson(val, walletInfo.type);
_update(tx);
}
});
_height = content['height'] as int;
} catch (e) {
print(e);
}
}
void _update(LightningTransactionInfo transaction) =>
transactions[transaction.id] = transaction;
}

View file

@ -0,0 +1,122 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/wallet_type.dart';
class LightningTransactionInfo extends TransactionInfo {
LightningTransactionInfo(this.type,
{required String id,
required int height,
required int amount,
int? fee,
required TransactionDirection direction,
required bool isPending,
required DateTime date,
required int confirmations}) {
this.id = id;
this.height = height;
this.amount = amount;
this.fee = fee;
this.direction = direction;
this.date = date;
this.isPending = isPending;
this.confirmations = confirmations;
}
factory LightningTransactionInfo.fromHexAndHeader(WalletType type, String hex,
{List<String>? addresses, required int height, int? timestamp, required int confirmations}) {
final tx = bitcoin.Transaction.fromHex(hex);
var exist = false;
var amount = 0;
if (addresses != null) {
tx.outs.forEach((out) {
try {
final p2pkh = bitcoin.P2PKH(
data: PaymentData(output: out.script), network: bitcoin.bitcoin);
exist = addresses.contains(p2pkh.data.address);
if (exist) {
amount += out.value!;
}
} catch (_) {}
});
}
final date = timestamp != null
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
: DateTime.now();
return LightningTransactionInfo(type,
id: tx.getId(),
height: height,
isPending: false,
fee: null,
direction: TransactionDirection.incoming,
amount: amount,
date: date,
confirmations: confirmations);
}
factory LightningTransactionInfo.fromJson(
Map<String, dynamic> data, WalletType type) {
return LightningTransactionInfo(type,
id: data['id'] as String,
height: data['height'] as int,
amount: data['amount'] as int,
fee: data['fee'] as int,
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool,
confirmations: data['confirmations'] as int);
}
final WalletType type;
String? _fiatAmount;
@override
String amountFormatted() =>
'${formatAmount(bitcoinAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(type).title}';
@override
String? feeFormatted() => fee != null
? '${formatAmount(bitcoinAmountToString(amount: fee!))} ${walletTypeToCryptoCurrency(type).title}'
: '';
@override
String fiatAmount() => _fiatAmount ?? '';
@override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
LightningTransactionInfo updated(LightningTransactionInfo info) {
return LightningTransactionInfo(info.type,
id: id,
height: info.height,
amount: info.amount,
fee: info.fee,
direction: direction,
date: date,
isPending: isPending,
confirmations: info.confirmations);
}
Map<String, dynamic> toJson() {
final m = <String, dynamic>{};
m['id'] = id;
m['height'] = height;
m['amount'] = amount;
m['direction'] = direction.index;
m['date'] = date.millisecondsSinceEpoch;
m['isPending'] = isPending;
m['confirmations'] = confirmations;
m['fee'] = fee;
return m;
}
}

View file

@ -6,19 +6,20 @@ import 'package:breez_sdk/bridge_generated.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_wallet_keys.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_transaction_history.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_lightning/lightning_balance.dart';
import 'package:cw_lightning/lightning_transaction_history.dart';
import 'package:cw_lightning/lightning_transaction_info.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/foundation.dart';
@ -36,13 +37,13 @@ part 'lightning_wallet.g.dart';
class LightningWallet = LightningWalletBase with _$LightningWallet;
// abstract class LightningWalletBase extends ElectrumWallet with Store {
class LightningWalletBase
extends WalletBase<LightningBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
abstract class LightningWalletBase
extends WalletBase<LightningBalance, LightningTransactionHistory, LightningTransactionInfo>
with Store {
final bitcoin.HDWallet hd;
final String mnemonic;
String _password;
bool _isTransactionUpdating;
late ElectrumClient electrumClient;
@override
@ -77,12 +78,13 @@ class LightningWalletBase
syncStatus = NotConnectedSyncStatus(),
mnemonic = mnemonic,
_password = password,
_isTransactionUpdating = false,
balance = ObservableMap<CryptoCurrency, LightningBalance>.of({
CryptoCurrency.btc:
initialBalance ?? const LightningBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
}),
super(walletInfo) {
transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
transactionHistory = LightningTransactionHistory(walletInfo: walletInfo, password: password);
walletAddresses = BitcoinWalletAddresses(walletInfo,
electrumClient: electrumClient ?? ElectrumClient(),
initialAddresses: initialAddresses,
@ -182,10 +184,7 @@ class LightningWalletBase
}
sdk.nodeStateStream.listen((event) {
print("Node state: $event");
if (event == null) return;
int balanceSat = event.maxPayableMsat ~/ 1000;
print("sats: $balanceSat");
balance[CryptoCurrency.btc] = LightningBalance(
confirmed: event.maxPayableMsat ~/ 1000,
unconfirmed: event.maxReceivableMsat ~/ 1000,
@ -193,6 +192,9 @@ class LightningWalletBase
);
});
// final payments = await sdk.listPayments(req: ListPaymentsRequest());
// print("payments: $payments");
print("initialized breez: ${(await sdk.isInitialized())}");
}
@ -206,9 +208,7 @@ class LightningWalletBase
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
// TODO: CW-563 Implement sync
await updateTransactions();
syncStatus = SyncedSyncStatus();
} catch (e) {
print(e);
@ -230,6 +230,7 @@ class LightningWalletBase
Future<void> connectToNode({required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
await updateTransactions();
syncStatus = ConnectedSyncStatus();
} catch (e) {
print(e);
@ -242,35 +243,61 @@ class LightningWalletBase
throw UnimplementedError("createTransaction");
}
@override
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
// String address = _publicAddress!;
Future<bool> updateTransactions() async {
try {
if (_isTransactionUpdating) {
return false;
}
// final transactions = await _client.fetchTransactions(address);
// final Map<String, NanoTransactionInfo> result = {};
// for (var transactionModel in transactions) {
// final bool isSend = transactionModel.type == "send";
// result[transactionModel.hash] = NanoTransactionInfo(
// id: transactionModel.hash,
// amountRaw: transactionModel.amount,
// height: transactionModel.height,
// direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming,
// confirmed: transactionModel.confirmed,
// date: transactionModel.date ?? DateTime.now(),
// confirmations: transactionModel.confirmed ? 1 : 0,
// to: isSend ? transactionModel.account : address,
// from: isSend ? address : transactionModel.account,
// );
// }
// return result;
return {};
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
return true;
} catch (_) {
_isTransactionUpdating = false;
return false;
}
}
@override
Future<void> rescan({required int height}) async => throw UnimplementedError();
Future<Map<String, LightningTransactionInfo>> fetchTransactions() async {
final sdk = await BreezSDK();
final payments = await sdk.listPayments(req: ListPaymentsRequest());
final Map<String, LightningTransactionInfo> result = {};
for (var tx in payments) {
print(tx.details.data);
var details = tx.details.data as LnPaymentDetails;
bool isSend = false;
if (details.lnAddress?.isNotEmpty ?? false) {
isSend = true;
}
if (details.lnurlMetadata?.isNotEmpty ?? false) {
isSend = true;
}
result[tx.id] = LightningTransactionInfo(
WalletType.lightning,
isPending: false,
id: tx.id,
amount: tx.amountMsat ~/ 1000,
fee: tx.feeMsat,
date: DateTime.fromMillisecondsSinceEpoch(tx.paymentTime * 1000),
height: tx.paymentTime,
direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming,
confirmations: 1,
);
}
return result;
}
@override
Future<void> rescan({required int height}) async {
updateTransactions();
}
Future<void> init() async {
await walletAddresses.init();

View file

@ -247,8 +247,7 @@ class LightningInvoicePage extends BasePage {
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
// alertTitle: S.of(context).invoice_created,
alertTitle: "Invoice created TODO: CW-563",
alertTitle: S.of(context).invoice_created,
alertContent: state.payload as String,
rightButtonText: S.of(context).ok,
actionRightButton: () => Navigator.of(context).pop(),

View file

@ -1,50 +1,29 @@
import 'package:breez_sdk/breez_sdk.dart';
import 'package:breez_sdk/bridge_generated.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
import 'package:cake_wallet/src/widgets/add_template_button.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/template_tile.dart';
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/themes/extensions/seed_widget_theme.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/request_review_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:cw_core/crypto_currency.dart';
class LightningSendConfirmPage extends BasePage {
LightningSendConfirmPage({this.invoice}) : _formKey = GlobalKey<FormState>();
LightningSendConfirmPage({required this.invoice}) : _formKey = GlobalKey<FormState>();
final GlobalKey<FormState> _formKey;
final controller = PageController(initialPage: 0);
LNInvoice? invoice;
LNInvoice invoice;
final bolt11Controller = TextEditingController();
final _bolt11FocusNode = FocusNode();
@ -114,23 +93,6 @@ class LightningSendConfirmPage extends BasePage {
Navigator.of(context).pop();
}
// @override
// Widget? middle(BuildContext context) {
// final supMiddle = super.middle(context);
// return Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Padding(
// padding: const EdgeInsets.only(right: 8.0),
// child: Observer(
// builder: (_) => SyncIndicatorIcon(isSynced: sendViewModel.isReadyForSend),
// ),
// ),
// if (supMiddle != null) supMiddle
// ],
// );
// }
@override
Widget body(BuildContext context) {
_setEffects(context);
@ -176,23 +138,81 @@ class LightningSendConfirmPage extends BasePage {
child: Observer(builder: (_) {
return Padding(
padding: EdgeInsets.fromLTRB(24, 120, 24, 0),
child: BaseTextFormField(
controller: bolt11Controller,
focusNode: _bolt11FocusNode,
textInputAction: TextInputAction.next,
borderColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
hintText: S.of(context).invoice_details,
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
),
textStyle:
TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: null,
child: Column(
children: [
BaseTextFormField(
enabled: false,
borderColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
initialValue: "${S.of(context).invoice}: ${invoice.bolt11}",
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
),
textStyle: TextStyle(
fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: null,
),
SizedBox(height: 24),
BaseTextFormField(
enabled: false,
borderColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
initialValue:
"sats: ${bitcoinAmountToLightningString(amount: (invoice.amountMsat ?? 0) ~/ 1000)}",
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
),
textStyle: TextStyle(
fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: null,
),
SizedBox(height: 24),
BaseTextFormField(
enabled: false,
initialValue: "USD: ${invoice.amountMsat}",
borderColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
),
textStyle: TextStyle(
fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: null,
),
SizedBox(height: 24),
if (invoice.description?.isNotEmpty ?? false) ...[
BaseTextFormField(
enabled: false,
initialValue: "${S.of(context).description}: ${invoice.description}",
textInputAction: TextInputAction.next,
borderColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
),
textStyle: TextStyle(
fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: null,
),
SizedBox(height: 24),
],
],
),
);
}),
@ -201,27 +221,36 @@ class LightningSendConfirmPage extends BasePage {
bottomSection: Observer(builder: (_) {
return Column(
children: <Widget>[
// Padding(
// padding: EdgeInsets.only(bottom: 15),
// child: Center(
// child: Text(
// S.of(context).anonpay_description("an invoice", "pay"),
// textAlign: TextAlign.center,
// style: TextStyle(
// color: Theme.of(context)
// .extension<ExchangePageTheme>()!
// .receiveAmountColor,
// fontWeight: FontWeight.w500,
// fontSize: 12),
// ),
// ),
// ),
LoadingPrimaryButton(
text: S.of(context).send,
onPressed: () async {
FocusScope.of(context).unfocus();
// sendViewModel.send(bolt11Controller.text);
final sdk = await BreezSDK();
try {
final sdk = await BreezSDK();
await sdk.sendPayment(req: SendPaymentRequest(bolt11: invoice.bolt11));
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: '',
alertContent: S
.of(context)
.send_success(CryptoCurrency.btc.toString()),
buttonText: S.of(context).ok,
buttonAction: () {
Navigator.of(context).pop();
});
});
} catch (e) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
},
color: Theme.of(context).primaryColor,
textColor: Colors.white,
@ -246,80 +275,6 @@ class LightningSendConfirmPage extends BasePage {
return;
}
// reaction((_) => sendViewModel.state, (ExecutionState state) {
// if (state is FailureState) {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// showPopUp<void>(
// context: context,
// builder: (BuildContext context) {
// return AlertWithOneAction(
// alertTitle: S.of(context).error,
// alertContent: state.error,
// buttonText: S.of(context).ok,
// buttonAction: () => Navigator.of(context).pop());
// });
// });
// }
// if (state is ExecutedSuccessfullyState) {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// if (context.mounted) {
// showPopUp<void>(
// context: context,
// builder: (BuildContext _dialogContext) {
// return ConfirmSendingAlert(
// alertTitle: S.of(_dialogContext).confirm_sending,
// amount: S.of(_dialogContext).send_amount,
// amountValue: sendViewModel.pendingTransaction!.amountFormatted,
// fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted,
// fee: S.of(_dialogContext).send_fee,
// feeValue: sendViewModel.pendingTransaction!.feeFormatted,
// feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
// outputs: sendViewModel.outputs,
// rightButtonText: S.of(_dialogContext).send,
// leftButtonText: S.of(_dialogContext).cancel,
// actionRightButton: () {
// Navigator.of(_dialogContext).pop();
// sendViewModel.commitTransaction();
// showPopUp<void>(
// context: context,
// builder: (BuildContext _dialogContext) {
// return Observer(builder: (_) {
// final state = sendViewModel.state;
// if (state is FailureState) {
// Navigator.of(_dialogContext).pop();
// }
// if (state is TransactionCommitted) {
// return AlertWithOneAction(
// alertTitle: '',
// alertContent: S.of(_dialogContext).send_success(
// sendViewModel.selectedCryptoCurrency.toString()),
// buttonText: S.of(_dialogContext).ok,
// buttonAction: () {
// Navigator.of(_dialogContext).pop();
// RequestReviewHandler.requestReview();
// });
// }
// return Offstage();
// });
// });
// },
// actionLeftButton: () => Navigator.of(_dialogContext).pop());
// });
// }
// });
// }
// if (state is TransactionCommitted) {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// sendViewModel.clearOutputs();
// });
// }
// });
_effectsInstalled = true;
}
}

View file

@ -160,71 +160,57 @@ class LightningSendPage extends BasePage {
),
)
: null,
child: Observer(builder: (_) {
return Padding(
padding: EdgeInsets.fromLTRB(24, 120, 24, 0),
child: BaseTextFormField(
controller: bolt11Controller,
focusNode: _bolt11FocusNode,
textInputAction: TextInputAction.next,
borderColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
hintText: S.of(context).invoice_details,
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
child: Padding(
padding: EdgeInsets.fromLTRB(24, 120, 24, 0),
child: Column(
children: [
BaseTextFormField(
controller: bolt11Controller,
focusNode: _bolt11FocusNode,
textInputAction: TextInputAction.next,
borderColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
hintText: S.of(context).invoice_details,
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
),
textStyle:
TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: null,
),
textStyle:
TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: null,
),
);
}),
SizedBox(height: 24),
],
),
),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Observer(builder: (_) {
return Column(
children: <Widget>[
// Padding(
// padding: EdgeInsets.only(bottom: 15),
// child: Center(
// child: Text(
// S.of(context).anonpay_description("an invoice", "pay"),
// textAlign: TextAlign.center,
// style: TextStyle(
// color: Theme.of(context)
// .extension<ExchangePageTheme>()!
// .receiveAmountColor,
// fontWeight: FontWeight.w500,
// fontSize: 12),
// ),
// ),
// ),
LoadingPrimaryButton(
text: S.of(context).paste,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isLoading: false,
onPressed: () async {
processInput(context);
},
),
const SizedBox(height: 16),
LoadingPrimaryButton(
text: S.of(context).scan_qr_code,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isLoading: false,
onPressed: () async {
processInput(context);
},
),
],
);
}),
bottomSection: Column(
children: <Widget>[
LoadingPrimaryButton(
text: S.of(context).paste,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isLoading: false,
onPressed: () async {
processInput(context);
},
),
const SizedBox(height: 16),
LoadingPrimaryButton(
text: S.of(context).scan_qr_code,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isLoading: false,
onPressed: () async {
processInput(context);
},
),
],
),
),
),
),

View file

@ -142,7 +142,7 @@ abstract class BalanceViewModelBase with Store {
case WalletType.banano:
return S.current.receivable_balance;
case WalletType.lightning:
return "CW-563 Max Receivable";
return S.current.max_receivable;
default:
return S.current.unconfirmed;
}

View file

@ -92,10 +92,9 @@ abstract class LightningViewModelBase with Store {
amountMsat: 1000,
description: 'limits',
);
// final res = await sdk.receivePayment(req: req);
// print(res.lnInvoice.);
// return res.lnInvoice.bolt11;
final res = await sdk.receivePayment(req: req);
// sdk.
// TODO: CW-563 figure out how to get the limits
return ['0', '20000'];
return [(res.openingFeeMsat).toString(), '20000'];
}
}

View file

@ -377,8 +377,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.polygon:
return polygon!.createPolygonTransactionCredentials(outputs,
priority: priority!, currency: selectedCryptoCurrency);
case WalletType.lightning:
throw Exception("TODO: CW-563 Implement lightning tx send!");
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}

View file

@ -80,6 +80,16 @@ class BitcoinURI extends PaymentURI {
}
}
class LightningURI extends PaymentURI {
LightningURI({required String amount, required String address})
: super(amount: amount, address: address);
@override
String toString() {
throw Exception('TODO: Not implemented');
}
}
class LitecoinURI extends PaymentURI {
LitecoinURI({required String amount, required String address})
: super(amount: amount, address: address);
@ -238,8 +248,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}
if (wallet.type == WalletType.lightning) {
// TODO: CW-563
return BitcoinURI(amount: amount, address: address.address);
return LightningURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.litecoin) {

View file

@ -312,6 +312,8 @@
"incorrect_seed": "النص الذي تم إدخاله غير صالح.",
"introducing_cake_pay": "نقدم لكم Cake Pay!",
"invalid_input": "مدخل غير صالح",
"invoice": "فاتورة",
"invoice_created": "الفاتورة التي تم إنشاؤها",
"invoice_details": "تفاصيل الفاتورة",
"is_percentage": "يكون",
"last_30_days": "آخر 30 يومًا",
@ -329,6 +331,7 @@
"market_place": "منصة التجارة",
"matrix_green_dark_theme": "موضوع ماتريكس الأخضر الداكن",
"max_amount": "الحد الأقصى: ${value}",
"max_receivable": "ماكس القبض",
"max_value": "الحد الأقصى: ${value} ${currency}",
"memo": "مذكرة:",
"message": "ﺔﻟﺎﺳﺭ",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Въведеният текст е невалиден.",
"introducing_cake_pay": "Запознайте се с Cake Pay!",
"invalid_input": "Невалиден вход",
"invoice": "Фактура",
"invoice_created": "Създадена фактура",
"invoice_details": "IДанни за фактура",
"is_percentage": "е",
"last_30_days": "Последните 30 дни",
@ -329,6 +331,7 @@
"market_place": "Магазин",
"matrix_green_dark_theme": "Зелена тъмна тема Matrix",
"max_amount": "Макс: ${value}",
"max_receivable": "Макс вземания",
"max_value": "Макс: ${value} ${currency}",
"memo": "Мемо:",
"message": "Съобщение",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Zadaný text není správný.",
"introducing_cake_pay": "Představujeme Cake Pay!",
"invalid_input": "Neplatný vstup",
"invoice": "Faktura",
"invoice_created": "Faktura vytvořená",
"invoice_details": "detaily faktury",
"is_percentage": "je",
"last_30_days": "Posledních 30 dnů",
@ -329,6 +331,7 @@
"market_place": "Obchod",
"matrix_green_dark_theme": "Tmavé téma Matrix Green",
"max_amount": "Max: ${value}",
"max_receivable": "Max pohledávka",
"max_value": "Max: ${value} ${currency}",
"memo": "Memo:",
"message": "Zpráva",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Der eingegebene Text ist ungültig.",
"introducing_cake_pay": "Einführung von Cake Pay!",
"invalid_input": "Ungültige Eingabe",
"invoice": "Rechnung",
"invoice_created": "Rechnung erstellt",
"invoice_details": "Rechnungs-Details",
"is_percentage": "ist",
"last_30_days": "Letzte 30 Tage",
@ -329,6 +331,7 @@
"market_place": "Marktplatz",
"matrix_green_dark_theme": "Matrix Green Dark Theme",
"max_amount": "Max: ${value}",
"max_receivable": "Max. Forderung",
"max_value": "Max: ${value} ${currency}",
"memo": "Memo:",
"message": "Nachricht",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "The text entered is not valid.",
"introducing_cake_pay": "Introducing Cake Pay!",
"invalid_input": "Invalid input",
"invoice": "Invoice",
"invoice_created": "Invoice created",
"invoice_details": "Invoice details",
"is_percentage": "is",
"last_30_days": "Last 30 days",
@ -329,6 +331,7 @@
"market_place": "Marketplace",
"matrix_green_dark_theme": "Matrix Green Dark Theme",
"max_amount": "Max: ${value}",
"max_receivable": "Max Receivable",
"max_value": "Max: ${value} ${currency}",
"memo": "Memo:",
"message": "Message",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "El texto ingresado no es válido.",
"introducing_cake_pay": "¡Presentamos Cake Pay!",
"invalid_input": "Entrada inválida",
"invoice": "Factura",
"invoice_created": "Factura creada",
"invoice_details": "Detalles de la factura",
"is_percentage": "es",
"last_30_days": "Últimos 30 días",
@ -329,6 +331,7 @@
"market_place": "Mercado",
"matrix_green_dark_theme": "Matrix verde oscuro tema",
"max_amount": "Máx: ${value}",
"max_receivable": "Max por cobrar",
"max_value": "Max: ${value} ${currency}",
"memo": "Memorándum:",
"message": "Mensaje",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Le texte entré est invalide.",
"introducing_cake_pay": "Présentation de Cake Pay !",
"invalid_input": "Entrée invalide",
"invoice": "Facture",
"invoice_created": "Facture créée",
"invoice_details": "Détails de la facture",
"is_percentage": "est",
"last_30_days": "30 derniers jours",
@ -329,6 +331,7 @@
"market_place": "Place de marché",
"matrix_green_dark_theme": "Thème Matrix Green Dark",
"max_amount": "Max : ${value}",
"max_receivable": "Max à recevoir",
"max_value": "Max: ${value} ${currency}",
"memo": "Mémo :",
"message": "Message",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "rubutun da aka shigar ba shi da inganci.",
"introducing_cake_pay": "Gabatar da Cake Pay!",
"invalid_input": "Shigar da ba daidai ba",
"invoice": "Daftari",
"invoice_created": "Invoice ya kirkira",
"invoice_details": "Bayanin wadannan",
"is_percentage": "shine",
"last_30_days": "Kwanaki 30 na ƙarshe",
@ -329,6 +331,7 @@
"market_place": "Kasuwa",
"matrix_green_dark_theme": "Matrix Green Dark Jigo",
"max_amount": "Max: ${value}",
"max_receivable": "Max mai karba",
"max_value": "Max: ${value} ${currency}",
"memo": "Memo:",
"message": "Sako",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "दर्ज किया गया पाठ मान्य नहीं है।",
"introducing_cake_pay": "परिचय Cake Pay!",
"invalid_input": "अमान्य निवेश",
"invoice": "चालान",
"invoice_created": "चालान बनाया गया",
"invoice_details": "चालान विवरण",
"is_percentage": "है",
"last_30_days": "पिछले 30 दिन",
@ -329,6 +331,7 @@
"market_place": "मार्केटप्लेस",
"matrix_green_dark_theme": "मैट्रिक्स ग्रीन डार्क थीम",
"max_amount": "अधिकतम: ${value}",
"max_receivable": "अधिकतम प्राप्य",
"max_value": "मैक्स: ${value} ${currency}",
"memo": "ज्ञापन:",
"message": "संदेश",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Uneseni tekst nije valjan.",
"introducing_cake_pay": "Predstavljamo Cake Pay!",
"invalid_input": "Pogrešan unos",
"invoice": "Dostavnica",
"invoice_created": "Račun stvoren",
"invoice_details": "Podaci o fakturi",
"is_percentage": "je",
"last_30_days": "Zadnjih 30 dana",
@ -329,6 +331,7 @@
"market_place": "Tržnica",
"matrix_green_dark_theme": "Matrix Green Dark Theme",
"max_amount": "Maksimum: ${value}",
"max_receivable": "Maksimalno potraživanje",
"max_value": "Maks.: ${value} ${currency}",
"memo": "Memo:",
"message": "Poruka",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Teks yang dimasukkan tidak valid.",
"introducing_cake_pay": "Perkenalkan Cake Pay!",
"invalid_input": "Masukan tidak valid",
"invoice": "Faktur",
"invoice_created": "Faktur dibuat",
"invoice_details": "Detail faktur",
"is_percentage": "adalah",
"last_30_days": "30 hari terakhir",
@ -329,6 +331,7 @@
"market_place": "Pasar",
"matrix_green_dark_theme": "Tema Matrix Green Dark",
"max_amount": "Max: ${value}",
"max_receivable": "Piutang maksimum",
"max_value": "Max: ${value} ${currency}",
"memo": "Memo:",
"message": "Pesan",

View file

@ -313,6 +313,8 @@
"incorrect_seed": "Il testo inserito non è valido.",
"introducing_cake_pay": "Presentazione di Cake Pay!",
"invalid_input": "Inserimento non valido",
"invoice": "Fattura",
"invoice_created": "Fattura creata",
"invoice_details": "Dettagli della fattura",
"is_percentage": "è",
"last_30_days": "Ultimi 30 giorni",
@ -330,6 +332,7 @@
"market_place": "Mercato",
"matrix_green_dark_theme": "Tema Matrix verde scuro",
"max_amount": "Max: ${value}",
"max_receivable": "Ricevibile massimo",
"max_value": "Max: ${value} ${currency}",
"memo": "Memo:",
"message": "Messaggio",

View file

@ -313,6 +313,8 @@
"incorrect_seed": "入力されたテキストは無効です。",
"introducing_cake_pay": "序章Cake Pay",
"invalid_input": "無効入力",
"invoice": "請求書",
"invoice_created": "作成された請求書",
"invoice_details": "請求の詳細",
"is_percentage": "is",
"last_30_days": "過去30日",
@ -330,6 +332,7 @@
"market_place": "Marketplace",
"matrix_green_dark_theme": "マトリックスグリーンダークテーマ",
"max_amount": "最大: ${value}",
"max_receivable": "最大売掛金",
"max_value": "マックス: ${value} ${currency}",
"memo": "メモ:",
"message": "メッセージ",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "입력하신 텍스트가 유효하지 않습니다.",
"introducing_cake_pay": "소개 Cake Pay!",
"invalid_input": "잘못된 입력",
"invoice": "송장",
"invoice_created": "송장 생성",
"invoice_details": "인보이스 세부정보",
"is_percentage": "이다",
"last_30_days": "지난 30일",
@ -329,6 +331,7 @@
"market_place": "마켓플레이스",
"matrix_green_dark_theme": "매트릭스 그린 다크 테마",
"max_amount": "최대: ${value}",
"max_receivable": "MAX 미수금",
"max_value": "맥스: ${value} ${currency}",
"memo": "메모:",
"message": "메시지",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "ထည့်သွင်းထားသော စာသားသည် မမှန်ကန်ပါ။",
"introducing_cake_pay": "Cake Pay ကို မိတ်ဆက်ခြင်း။",
"invalid_input": "ထည့်သွင်းမှု မမှန်ကန်ပါ။",
"invoice": "ဝယ်ကုန်စာရင်း",
"invoice_created": "ငွေတောင်းခံလွှာကိုဖန်တီးခဲ့သည်",
"invoice_details": "ပြေစာအသေးစိတ်",
"is_percentage": "သည်",
"last_30_days": "လွန်ခဲ့သော ရက် 30",
@ -329,6 +331,7 @@
"market_place": "ဈေး",
"matrix_green_dark_theme": "Matrix Green Dark အပြင်အဆင်",
"max_amount": "အများဆုံး- ${value}",
"max_receivable": "max ကိုလက်ခံရရှိ",
"max_value": "အများဆုံး- ${value} ${currency}",
"memo": "မှတ်စုတို:",
"message": "မက်ဆေ့ချ်",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "De ingevoerde tekst is niet geldig.",
"introducing_cake_pay": "Introductie van Cake Pay!",
"invalid_input": "Ongeldige invoer",
"invoice": "Factuur",
"invoice_created": "Factuur gemaakt",
"invoice_details": "Factuurgegevens",
"is_percentage": "is",
"last_30_days": "Laatste 30 dagen",
@ -329,6 +331,7 @@
"market_place": "Marktplaats",
"matrix_green_dark_theme": "Matrix groen donker thema",
"max_amount": "Max: ${value}",
"max_receivable": "Max -vordering",
"max_value": "Max: ${value} ${currency}",
"memo": "Memo:",
"message": "Bericht",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Wprowadzony seed jest nieprawidłowy.",
"introducing_cake_pay": "Przedstawiamy Cake Pay!",
"invalid_input": "Nieprawidłowe dane wejściowe",
"invoice": "Faktura",
"invoice_created": "Utworzona faktura",
"invoice_details": "Dane do faktury",
"is_percentage": "jest",
"last_30_days": "Ostatnie 30 dni",
@ -329,6 +331,7 @@
"market_place": "Rynek",
"matrix_green_dark_theme": "Matrix Zielony ciemny motyw",
"max_amount": "Max: ${value}",
"max_receivable": "MAX NECTIVET",
"max_value": "Max: ${value} ${currency}",
"memo": "Notatka:",
"message": "Wiadomość",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "O texto digitado não é válido.",
"introducing_cake_pay": "Apresentando o Cake Pay!",
"invalid_input": "Entrada inválida",
"invoice": "Fatura",
"invoice_created": "Fatura criada",
"invoice_details": "Detalhes da fatura",
"is_percentage": "é",
"last_30_days": "Últimos 30 dias",
@ -330,6 +332,7 @@
"market_place": "Mercado",
"matrix_green_dark_theme": "Tema escuro verde matrix",
"max_amount": "Máx.: ${valor}",
"max_receivable": "Max a receber",
"max_value": "Máx: ${value} ${currency}",
"memo": "Memorando:",
"message": "Mensagem",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Введённый текст некорректный.",
"introducing_cake_pay": "Представляем Cake Pay!",
"invalid_input": "Неверный Ввод",
"invoice": "Счет",
"invoice_created": "Счет -фактор создан",
"invoice_details": "Детали счета",
"is_percentage": "есть",
"last_30_days": "Последние 30 дней",
@ -329,6 +331,7 @@
"market_place": "Торговая площадка",
"matrix_green_dark_theme": "Матрица Зеленая Темная Тема",
"max_amount": "Макс.: ${value}",
"max_receivable": "Максимальная дебиторская задолженность",
"max_value": "Макс: ${value} ${currency}",
"memo": "Памятка:",
"message": "Сообщение",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "ข้อความที่ป้อนไม่ถูกต้อง",
"introducing_cake_pay": "ยินดีต้อนรับสู่ Cake Pay!",
"invalid_input": "อินพุตไม่ถูกต้อง",
"invoice": "ใบแจ้งหนี้",
"invoice_created": "สร้างใบแจ้งหนี้",
"invoice_details": "รายละเอียดใบแจ้งหนี้",
"is_percentage": "เป็น",
"last_30_days": "30 วันล่าสุด",
@ -329,6 +331,7 @@
"market_place": "ตลาดพื้นที่",
"matrix_green_dark_theme": "ธีมเมทริกซ์สีเขียวเข้ม",
"max_amount": "จำนวนสูงสุด: ${value}",
"max_receivable": "ลูกหนี้สูงสุด",
"max_value": "ขั้นสูง: ${value} ${currency}",
"memo": "หมายเหตุ:",
"message": "ข้อความ",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Ang teksto na ipinasok ay hindi wasto.",
"introducing_cake_pay": "Ipinakikilala ang cake pay!",
"invalid_input": "Di -wastong input",
"invoice": "Invoice",
"invoice_created": "Nilikha ang Invoice",
"invoice_details": "Mga detalye ng invoice",
"is_percentage": "ay",
"last_30_days": "Huling 30 araw",
@ -329,6 +331,7 @@
"market_place": "Marketplace",
"matrix_green_dark_theme": "Matrix Green Madilim na Tema",
"max_amount": "Max: ${value}",
"max_receivable": "MAX RECEIVABLE",
"max_value": "Max: ${value} ${currency}",
"memo": "Memo:",
"message": "Mensahe",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Girilen metin geçerli değil.",
"introducing_cake_pay": "Cake Pay ile tanışın!",
"invalid_input": "Geçersiz Giriş",
"invoice": "Fatura",
"invoice_created": "Fatura Oluşturuldu",
"invoice_details": "fatura detayları",
"is_percentage": "is",
"last_30_days": "Son 30 gün",
@ -329,6 +331,7 @@
"market_place": "Pazar Alanı",
"matrix_green_dark_theme": "Matrix Yeşil Koyu Tema",
"max_amount": "Maks: ${value}",
"max_receivable": "Maksimum alacak",
"max_value": "En fazla: ${value} ${currency}",
"memo": "Memo:",
"message": "İleti",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "Введений текст невірний.",
"introducing_cake_pay": "Представляємо Cake Pay!",
"invalid_input": "Неправильні дані",
"invoice": "Рахунок -фактура",
"invoice_created": "Створений рахунок -фактури",
"invoice_details": "Реквізити рахунку-фактури",
"is_percentage": "є",
"last_30_days": "Останні 30 днів",
@ -329,6 +331,7 @@
"market_place": "Ринок",
"matrix_green_dark_theme": "Зелена темна тема Matrix",
"max_amount": "Макс: ${value}",
"max_receivable": "Максимальна дебіторська заборгованість",
"max_value": "Макс: ${value} ${currency}",
"memo": "Пам’ятка:",
"message": "повідомлення",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "درج کردہ متن درست نہیں ہے۔",
"introducing_cake_pay": "Cake پے کا تعارف!",
"invalid_input": "غلط ان پٹ",
"invoice": "انوائس",
"invoice_created": "انوائس تخلیق کیا گیا",
"invoice_details": "رسید کی تفصیلات",
"is_percentage": "ہے",
"last_30_days": "آخری 30 دن",
@ -329,6 +331,7 @@
"market_place": "بازار",
"matrix_green_dark_theme": "میٹرکس گرین ڈارک تھیم",
"max_amount": "زیادہ سے زیادہ: ${value}",
"max_receivable": "زیادہ سے زیادہ قابل وصول",
"max_value": "زیادہ سے زیادہ: ${value} ${currency}",
"memo": "میمو:",
"message": "ﻡﺎﻐﯿﭘ",

View file

@ -313,6 +313,8 @@
"incorrect_seed": "Ọ̀rọ̀ tí a tẹ̀ kì í ṣe èyí.",
"introducing_cake_pay": "Ẹ bá Cake Pay!",
"invalid_input": "Iṣawọle ti ko tọ",
"invoice": "Iye-owo ọja",
"invoice_created": "Risiti da",
"invoice_details": "Iru awọn ẹya ọrọ",
"is_percentage": "jẹ́",
"last_30_days": "Ọ̀jọ̀ mọ́gbọ̀n tó kọjà",
@ -330,6 +332,7 @@
"market_place": "Ọjà",
"matrix_green_dark_theme": "Matrix Green Dark Akori",
"max_amount": "kò tóbi ju: ${value}",
"max_receivable": "Max gba",
"max_value": "kò gbọ́dọ̀ tóbi ju ${value} ${currency}",
"memo": "Àkọsílẹ̀:",
"message": "Ifiranṣẹ",

View file

@ -312,6 +312,8 @@
"incorrect_seed": "输入的文字无效。",
"introducing_cake_pay": "介绍 Cake Pay!",
"invalid_input": "输入无效",
"invoice": "发票",
"invoice_created": "创建了发票",
"invoice_details": "发票明细",
"is_percentage": "是",
"last_30_days": "过去 30 天",
@ -329,6 +331,7 @@
"market_place": "市场",
"matrix_green_dark_theme": "矩阵绿暗主题",
"max_amount": "最大值: ${value}",
"max_receivable": "最大应收",
"max_value": "最大: ${value} ${currency}",
"memo": "备忘录:",
"message": "信息",