Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions lib/common/transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class TransactionHelper {
//无 nonce、有 remark(成块补零)
static const int padZerosNoNonceHasRemark = 18;

static String getTransaction(String fromAddress, String toAddress, String remark, double value, bip32.BIP32 wallet, String nonce) {
static String getTransaction(String fromAddress, String toAddress, String remark, double value, bip32.BIP32 wallet, String nonce, double fee) {
// print('getTransaction: $fromAddress, $toAddress, $remark, $value, $nonce');
bool isPubKeyEven = wallet.publicKey[0] % 2 == 0;
String from = checkBase58Address(fromAddress);
Expand Down Expand Up @@ -71,7 +71,8 @@ class TransactionHelper {
}
sb += HEX.encode(timeBytes.buffer.asUint8List());

sb += "0000000000000000";
// 添加可变手续费
sb += fee2Bytes(fee);
// print('header: $sb');
// nonce:前面补 48 个 0
// 由于rpc查询出来的nonce(rpc查出来的到的结果是String类型),会放在该32字节的后八个字段,然后前面24个字节的零,这后八个字节存放nonce的方式是小端序存放。
Expand Down Expand Up @@ -146,6 +147,19 @@ class TransactionHelper {
return res + amount;
}

static String fee2Bytes(double fee) {
final amountValue = (fee * 1000000000).round();

final valBytes = Uint8List(8);
ByteData.view(valBytes.buffer).setUint64(0, amountValue, Endian.little);

final hexString = valBytes.map((byte) {
return byte.toRadixString(16).padLeft(2, '0');
}).join();

return hexString;
}

static Int64 getCurrentTimestamp() {
var t0 = DateTime.now().toUtc().millisecondsSinceEpoch * 1000000;
Int64 t = Int64(t0);
Expand Down
4 changes: 2 additions & 2 deletions lib/desktop/desktop_transaction_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class DesktopTransactionDetailPageWidgetState extends State<DesktopTransactionDe
Helper.showToast(context, AppLocalizations.of(context)!.copied_to_clipboard);
}),
const SizedBox(height: 1),
TransactionButton(showCopy: false, title: AppLocalizations.of(context)!.fee, value: '$fee XDAG', borderRadius: transaction.remark.isNotEmpty ? BorderRadius.zero : const BorderRadius.only(bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8))),
TransactionButton(showCopy: false, title: AppLocalizations.of(context)!.fee, value: '${transaction.fee + 0.1} XDAG', borderRadius: transaction.remark.isNotEmpty ? BorderRadius.zero : const BorderRadius.only(bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8))),
const SizedBox(height: 1),
if (transaction.remark.isNotEmpty)
TransactionButton(
Expand Down Expand Up @@ -233,7 +233,7 @@ class DesktopTransactionDetail extends StatelessWidget {
value: transaction.from,
),
const SizedBox(height: 1),
TransactionButton(showCopy: false, title: AppLocalizations.of(context)!.fee, value: '0.00 XDAG', borderRadius: transaction.remark.isNotEmpty ? BorderRadius.zero : const BorderRadius.only(bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8))),
TransactionButton(showCopy: false, title: AppLocalizations.of(context)!.fee, value: '${transaction.fee + 0.1} XDAG', borderRadius: transaction.remark.isNotEmpty ? BorderRadius.zero : const BorderRadius.only(bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8))),
const SizedBox(height: 1),
if (transaction.remark.isNotEmpty)
TransactionButton(
Expand Down
190 changes: 182 additions & 8 deletions lib/desktop/main_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -510,10 +510,17 @@ class _SendCardState extends State<SendCard> {
final TextEditingController controller0 = TextEditingController();
final TextEditingController controller1 = TextEditingController();
final TextEditingController controller2 = TextEditingController();
final TextEditingController controller3 = TextEditingController();

String address = "";
String amount = "";
String remark = "";
String fee = "";
String averageFee = "0.1 XDAG"; // 平均手续费

bool load = false;
bool showFeeBox = false; // 控制是否显示自定义手续费输入框
bool isLoadingAverageFee = false; // 控制平均手续费加载状态

Isolate? isolate;
final dio = Dio();
Expand All @@ -522,6 +529,7 @@ class _SendCardState extends State<SendCard> {
@override
void initState() {
super.initState();
fetchAverageFee(); // 调用 RPC 方法(页面初始化时触发)
}

@override
Expand All @@ -532,6 +540,45 @@ class _SendCardState extends State<SendCard> {
super.dispose();
}

Future<void> fetchAverageFee() async {
setState(() {
isLoadingAverageFee = true;
});
try {
ConfigModal config = Provider.of<ConfigModal>(context, listen: false);
String rpcURL = config.getCurrentRpc();
Response response = await dio.post(
rpcURL,
cancelToken: cancelToken,
data: {
"jsonrpc": "2.0",
"method": "xdag_getAverageFee",
"params": [],
"id": 1
},
);
if (response.data != null && response.data['result'] != null) {
double feeValue = double.tryParse(response.data['result'].toString()) ?? 0.0;
setState(() {
averageFee = "${feeValue.toStringAsFixed(2)} XDAG";
});
} else {
setState(() {
averageFee = "0.10 XDAG";
});
}
} catch (e) {
debugPrint("获取平均手续费失败:$e");
setState(() {
averageFee = "0.10 XDAG";
});
} finally {
setState(() {
isLoadingAverageFee = false;
});
}
}

static void isolateFunction(SendPort sendPort) async {
final receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
Expand All @@ -543,9 +590,11 @@ class _SendCardState extends State<SendCard> {
String fromAddress = data[3] as String;
String remark = data[4] as String;
String nonce = data[5] as String;
String fee = data[6] as String;

bool isPrivateKey = res.trim().split(' ').length == 1;
bip32.BIP32 wallet = Helper.createWallet(isPrivate: isPrivateKey, content: res);
String result = TransactionHelper.getTransaction(fromAddress, toAddress, remark, double.parse(amount), wallet, nonce);
String result = TransactionHelper.getTransaction(fromAddress, toAddress, remark, double.parse(amount), wallet, nonce, double.parse(fee));
sendPort.send(['success', result]);
});
}
Expand All @@ -569,9 +618,11 @@ class _SendCardState extends State<SendCard> {
receivePort.listen((data) async {
var sendAmount = amount;
var sendRemark = remark;
var sendFee = fee;

if (data is SendPort) {
var subSendPort = data;
subSendPort.send([res, toAddress, amount, fromAddress, remark, nonce]);
subSendPort.send([res, toAddress, amount, fromAddress, remark, nonce, fee]);
} else if (data is List<String>) {
String result = data[1];
try {
Expand All @@ -587,15 +638,18 @@ class _SendCardState extends State<SendCard> {
var res = response.data['result'] as String;
// print(res);
if (res.length == 32 && res.trim().split(' ').length == 1) {
var transactionItem = Transaction(time: '', amount: Helper.removeTrailingZeros(sendAmount.toString()), address: fromAddress, status: 'pending', from: fromAddress, to: toAddress, type: 0, hash: '', fee: 0, blockAddress: res, remark: sendRemark);
var transactionItem = Transaction(time: '', amount: Helper.removeTrailingZeros(sendAmount.toString()), address: fromAddress, status: 'pending', from: fromAddress, to: toAddress, type: 0, hash: '', fee: double.parse(sendFee), blockAddress: res, remark: sendRemark);

controller0.clear();
controller1.clear();
controller2.clear();
controller3.clear();
setState(() {
load = false;
amount = '';
remark = '';
address = '';
fee = '';
});

showDialog(context: context, builder: (BuildContext context) => DesktopTransactionDetailPageWidget(transaction: transactionItem, address: fromAddress));
Expand All @@ -610,11 +664,13 @@ class _SendCardState extends State<SendCard> {
controller0.clear();
controller1.clear();
controller2.clear();
controller3.clear();
setState(() {
load = false;
amount = '';
remark = '';
address = '';
fee = '';
});
}
}
Expand Down Expand Up @@ -656,10 +712,12 @@ class _SendCardState extends State<SendCard> {
),
Expanded(
flex: 1,
child: Padding(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 10),
Text(AppLocalizations.of(context)!.to, style: Helper.fitChineseFont(context, const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w500))),
Expand Down Expand Up @@ -723,14 +781,113 @@ class _SendCardState extends State<SendCard> {
],
decoration: InputDecoration(
filled: true,
contentPadding: const EdgeInsets.fromLTRB(15, 40, 15, 40),
contentPadding: const EdgeInsets.fromLTRB(15, 30, 15, 30),
fillColor: DarkColors.bgColor,
hintText: 'XDAG',
hintStyle: Helper.fitChineseFont(context, const TextStyle(decoration: TextDecoration.none, fontSize: 32, fontWeight: FontWeight.w500, color: Colors.white54)),
enabledBorder: const OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.all(Radius.circular(10))),
focusedBorder: const OutlineInputBorder(borderSide: BorderSide(color: DarkColors.mainColor, width: 1), borderRadius: BorderRadius.all(Radius.circular(10))),
),
),
// 控制手续费框显示
const SizedBox(height: 15),
Row(
children: [
Checkbox(
value: showFeeBox,
onChanged: (value) {
setState(() {
showFeeBox = value ?? false;
if (!showFeeBox) {
fee = "";
controller3.clear();
}
});
},
activeColor: DarkColors.mainColor,
checkColor: Colors.white,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.express_fee,
style: Helper.fitChineseFont(context, const TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w500,
)),
),
const SizedBox(height: 4),
Text(
"${AppLocalizations.of(context)!.average_fee}: $averageFee",
style: Helper.fitChineseFont(context, const TextStyle(
fontSize: 14,
color: Colors.white54,
)),
),
],
),
],
),
// 手续费框
if (showFeeBox) ...[
const SizedBox(height: 10),
Container(
constraints: const BoxConstraints(maxHeight: 150),
decoration: BoxDecoration(
color: DarkColors.bgColor,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: DarkColors.mainColor, width: 1),
),
child: SingleChildScrollView(
padding: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
AutoSizeTextField(
controller: controller3,
onChanged: (value) {
setState(() => fee = value);
},
minFontSize: 16,
maxLines: null,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
keyboardAppearance: Brightness.dark,
style: Helper.fitChineseFont(context, const TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w500,
)),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}')),
],
decoration: InputDecoration(
filled: true,
fillColor: DarkColors.blockColor,
hintText: AppLocalizations.of(context)!.fee,
hintStyle: const TextStyle(color: Colors.white54),
border: const OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
),
),
const SizedBox(height: 10),
Text(
"${AppLocalizations.of(context)!.fee_explanation}",
style: Helper.fitChineseFont(context, const TextStyle(
fontSize: 14,
color: Colors.white70,
height: 1.5,
)),
),
],
),
),
),
],
const SizedBox(height: 15),
Text(AppLocalizations.of(context)!.remark, style: Helper.fitChineseFont(context, const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w500))),
const SizedBox(height: 10),
Expand Down Expand Up @@ -767,7 +924,7 @@ class _SendCardState extends State<SendCard> {
focusedBorder: const OutlineInputBorder(borderSide: BorderSide(color: DarkColors.mainColor, width: 1), borderRadius: BorderRadius.all(Radius.circular(10))),
),
),
const Spacer(),
const SizedBox(height: 30),
Row(
children: [
const Spacer(),
Expand All @@ -783,9 +940,26 @@ class _SendCardState extends State<SendCard> {
showDialog(context: context, builder: (context) => DesktopAlertModal(title: AppLocalizations.of(context)!.error, content: AppLocalizations.of(context)!.walletAddressError));
return;
}
if (showFeeBox) {
// 必须输入有效数字
if (fee == '' || fee.isEmpty || double.tryParse(fee) == null) {
if (context.mounted) {
showDialog(
context: context,
builder: (context) => DesktopAlertModal(
title: AppLocalizations.of(context)!.error,
content: AppLocalizations.of(context)!.invalid_fee,
),
);
}
return;
}
} else {
fee = '0';
}
WalletModal walletModal = Provider.of<WalletModal>(context, listen: false);
Wallet wallet = walletModal.getWallet();
var transactionItem = Transaction(time: '', amount: Helper.removeTrailingZeros(amount.toString()), address: wallet.address, status: 'pending', from: wallet.address, to: address, type: 0, hash: '', fee: 0, blockAddress: "", remark: remark);
var transactionItem = Transaction(time: '', amount: Helper.removeTrailingZeros(amount.toString()), address: wallet.address, status: 'pending', from: wallet.address, to: address, type: 0, hash: '', fee: double.parse(fee), blockAddress: "", remark: remark);
var f = await showDialog(
context: context,
builder: (context) => DesktopTransactionDetail(transaction: transactionItem),
Expand Down Expand Up @@ -829,4 +1003,4 @@ class _SendCardState extends State<SendCard> {
],
));
}
}
}
6 changes: 5 additions & 1 deletion lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,9 @@
"transactions": "Transaktionen",
"forget_the_password": "Passwort vergessen",
"time_zone": "Zeitzone",
"transactions_in_progress": "Transaktionen in Arbeit"
"transactions_in_progress": "Transaktionen in Arbeit",
"express_fee": "Variable Gebühr",
"invalid_fee": "Bitte geben Sie eine gültige Gebühr ein",
"average_fee": "Durchschnittliche Gebühr",
"fee_explanation": "Gebührenerklärung:\n1. Zur Grundgebühr von 0,1 XDAG müssen Eingabegebühren hinzugerechnet werden\n2. Je höher die Gebühr, desto schneller die Transaktionsbestätigung"
}
6 changes: 5 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,9 @@
"transactions": "Transactions",
"forget_the_password": "forget_the_password",
"time_zone": "Time Zone",
"transactions_in_progress": "Transactions in progress"
"transactions_in_progress": "Transactions in progress",
"express_fee": "Expression Fee",
"invalid_fee": "Please enter a invalid fee",
"average_fee": "Average Fee",
"fee_explanation": "Fee Explanation:\n1. Input fees must be added to the base fee of 0.1 XDAG\n2. The higher the fee, the faster the transaction confirmation speed"
}
Loading