mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-19 01:04:43 +00:00
Fee estimation
This commit is contained in:
parent
404672f10f
commit
4abe70062f
5 changed files with 100 additions and 30 deletions
|
@ -331,14 +331,19 @@ class ElectrumClient {
|
||||||
// "height": 520481,
|
// "height": 520481,
|
||||||
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
||||||
// }
|
// }
|
||||||
Future<int?> getCurrentBlockChainTip() =>
|
BehaviorSubject<Map<String, dynamic>>? tipListener;
|
||||||
call(method: 'blockchain.headers.subscribe').then((result) {
|
int? currentTip;
|
||||||
if (result is Map<String, dynamic>) {
|
|
||||||
return result["height"] as int;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
Future<int?> getCurrentBlockChainTip() async {
|
||||||
});
|
final method = 'blockchain.headers.subscribe';
|
||||||
|
final cb = (result) => currentTip = result['height'] as int;
|
||||||
|
if (tipListener == null) {
|
||||||
|
tipListener = subscribe(id: method, method: method);
|
||||||
|
tipListener?.listen(cb);
|
||||||
|
cb(await call(method: method));
|
||||||
|
}
|
||||||
|
return currentTip;
|
||||||
|
}
|
||||||
|
|
||||||
BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
|
BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
|
||||||
_id += 1;
|
_id += 1;
|
||||||
|
@ -424,6 +429,12 @@ class ElectrumClient {
|
||||||
|
|
||||||
void _methodHandler({required String method, required Map<String, dynamic> request}) {
|
void _methodHandler({required String method, required Map<String, dynamic> request}) {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
|
case 'blockchain.headers.subscribe':
|
||||||
|
final params = request['params'] as List<dynamic>;
|
||||||
|
final id = 'blockchain.headers.subscribe';
|
||||||
|
|
||||||
|
_tasks[id]?.subject?.add(params.last);
|
||||||
|
break;
|
||||||
case 'blockchain.scripthash.subscribe':
|
case 'blockchain.scripthash.subscribe':
|
||||||
final params = request['params'] as List<dynamic>;
|
final params = request['params'] as List<dynamic>;
|
||||||
final scripthash = params.first as String?;
|
final scripthash = params.first as String?;
|
||||||
|
|
|
@ -392,24 +392,13 @@ abstract class ElectrumWalletBase
|
||||||
value: BigInt.from(amountLeftForChangeAndFee),
|
value: BigInt.from(amountLeftForChangeAndFee),
|
||||||
));
|
));
|
||||||
|
|
||||||
int estimatedSize;
|
int fee = await calcFee(
|
||||||
if (network is BitcoinCashNetwork) {
|
utxos: utxos,
|
||||||
estimatedSize = ForkedTransactionBuilder.estimateTransactionSize(
|
outputs: outputs,
|
||||||
utxos: utxos,
|
network: network,
|
||||||
outputs: outputs,
|
memo: memo,
|
||||||
network: network as BitcoinCashNetwork,
|
feeRate: feeRate,
|
||||||
memo: memo,
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
|
|
||||||
utxos: utxos,
|
|
||||||
outputs: outputs,
|
|
||||||
network: network,
|
|
||||||
memo: memo,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int fee = feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize);
|
|
||||||
|
|
||||||
if (fee == 0) {
|
if (fee == 0) {
|
||||||
throw BitcoinTransactionNoFeeException();
|
throw BitcoinTransactionNoFeeException();
|
||||||
|
@ -499,6 +488,33 @@ abstract class ElectrumWalletBase
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> calcFee({
|
||||||
|
required List<UtxoWithAddress> utxos,
|
||||||
|
required List<BitcoinBaseOutput> outputs,
|
||||||
|
required BasedUtxoNetwork network,
|
||||||
|
String? memo,
|
||||||
|
required int feeRate}) async {
|
||||||
|
|
||||||
|
int estimatedSize;
|
||||||
|
if (network is BitcoinCashNetwork) {
|
||||||
|
estimatedSize = ForkedTransactionBuilder.estimateTransactionSize(
|
||||||
|
utxos: utxos,
|
||||||
|
outputs: outputs,
|
||||||
|
network: network,
|
||||||
|
memo: memo,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
|
||||||
|
utxos: utxos,
|
||||||
|
outputs: outputs,
|
||||||
|
network: network,
|
||||||
|
memo: memo,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
import 'package:convert/convert.dart';
|
import 'package:convert/convert.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
import 'package:cw_core/sync_status.dart';
|
import 'package:cw_core/sync_status.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
@ -279,4 +282,45 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> calcFee({
|
||||||
|
required List<UtxoWithAddress> utxos,
|
||||||
|
required List<BitcoinBaseOutput> outputs,
|
||||||
|
required BasedUtxoNetwork network,
|
||||||
|
String? memo,
|
||||||
|
required int feeRate}) async {
|
||||||
|
|
||||||
|
final txb = BitcoinTransactionBuilder(utxos: utxos,
|
||||||
|
outputs: outputs, fee: BigInt.zero, network: network);
|
||||||
|
final scanSecret = mwebHd.derive(0x80000000).privKey!;
|
||||||
|
final spendSecret = mwebHd.derive(0x80000001).privKey!;
|
||||||
|
final stub = await CwMweb.stub();
|
||||||
|
final resp = await stub.create(CreateRequest(
|
||||||
|
rawTx: txb.buildTransaction((a, b, c, d) => '').toBytes(),
|
||||||
|
scanSecret: hex.decode(scanSecret),
|
||||||
|
spendSecret: hex.decode(spendSecret),
|
||||||
|
feeRatePerKb: Int64(feeRate * 1000),
|
||||||
|
dryRun: true));
|
||||||
|
final tx = BtcTransaction.fromRaw(hex.encode(resp.rawTx));
|
||||||
|
final posUtxos = utxos.where((utxo) => tx.inputs.any((input) =>
|
||||||
|
input.txId == utxo.utxo.txHash && input.txIndex == utxo.utxo.vout)).toList();
|
||||||
|
final preOutputSum = outputs.fold<int>(0, (acc, output) => acc + output.toOutput.amount.toInt());
|
||||||
|
final posOutputSum = tx.outputs.fold<int>(0, (acc, output) => acc + output.amount.toInt());
|
||||||
|
final mwebInputSum = utxos.sumOfUtxosValue() - posUtxos.sumOfUtxosValue();
|
||||||
|
final expectedPegin = max(0, preOutputSum - mwebInputSum.toInt());
|
||||||
|
var fee = posOutputSum - expectedPegin;
|
||||||
|
if (expectedPegin > 0) {
|
||||||
|
fee += await super.calcFee(utxos: posUtxos, outputs: tx.outputs.map((output) =>
|
||||||
|
BitcoinScriptOutput(script: output.scriptPubKey, value: output.amount)).toList(),
|
||||||
|
network: network, memo: memo, feeRate: feeRate) + feeRate * 41;
|
||||||
|
}
|
||||||
|
return fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
|
final tx = await super.createTransaction(credentials);
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
|
||||||
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
||||||
/// when the Flutter Engine is detached from the Activity
|
/// when the Flutter Engine is detached from the Activity
|
||||||
private lateinit var channel : MethodChannel
|
private lateinit var channel : MethodChannel
|
||||||
|
private var port: Long? = null
|
||||||
|
|
||||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cw_mweb")
|
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cw_mweb")
|
||||||
|
@ -26,7 +27,8 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
|
||||||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
|
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||||
if (call.method == "start") {
|
if (call.method == "start") {
|
||||||
val dataDir = call.argument("dataDir") ?: ""
|
val dataDir = call.argument("dataDir") ?: ""
|
||||||
result.success(Mwebd.newServer("", dataDir, "").start(0))
|
port = port ?: Mwebd.newServer("", dataDir, "").start(0)
|
||||||
|
result.success(port)
|
||||||
} else {
|
} else {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,10 @@ import 'cw_mweb_platform_interface.dart';
|
||||||
import 'mwebd.pbgrpc.dart';
|
import 'mwebd.pbgrpc.dart';
|
||||||
|
|
||||||
class CwMweb {
|
class CwMweb {
|
||||||
static Future<int?>? port;
|
|
||||||
|
|
||||||
static Future<RpcClient> stub() async {
|
static Future<RpcClient> stub() async {
|
||||||
final appDir = await getApplicationSupportDirectory();
|
final appDir = await getApplicationSupportDirectory();
|
||||||
port ??= CwMwebPlatform.instance.start(appDir.path);
|
|
||||||
return RpcClient(ClientChannel('127.0.0.1',
|
return RpcClient(ClientChannel('127.0.0.1',
|
||||||
port: await port ?? 0,
|
port: await CwMwebPlatform.instance.start(appDir.path) ?? 0,
|
||||||
options: const ChannelOptions(
|
options: const ChannelOptions(
|
||||||
credentials: ChannelCredentials.insecure())));
|
credentials: ChannelCredentials.insecure())));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue