mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2024-12-24 23:19:23 -05:00
lib: add functions for retrieving base fees
This commit is contained in:
parent
f91af7aabd
commit
44223c962b
@ -11,5 +11,6 @@ interface IWallet {
|
||||
oneway void cancelRefresh();
|
||||
oneway void setRefreshSince(long heightOrTimestamp);
|
||||
oneway void commit(in IWalletCallbacks callback);
|
||||
oneway void requestFees(in IWalletCallbacks callback);
|
||||
void close();
|
||||
}
|
||||
|
@ -5,4 +5,5 @@ import im.molly.monero.BlockchainTime;
|
||||
oneway interface IWalletCallbacks {
|
||||
void onRefreshResult(in BlockchainTime blockchainTime, int status);
|
||||
void onCommitResult(boolean success);
|
||||
void onFeesReceived(in long[] fees);
|
||||
}
|
||||
|
@ -213,10 +213,22 @@ ScopedJvmLocalRef<jbyteArray> nativeToJvmByteArray(JNIEnv* env,
|
||||
return {env, j_array};
|
||||
}
|
||||
|
||||
ScopedJvmLocalRef<jbyteArray> nativeToJvmByteArray(
|
||||
JNIEnv* env,
|
||||
const std::vector<char>& bytes) {
|
||||
return nativeToJvmByteArray(env, bytes.data(), bytes.size());
|
||||
ScopedJvmLocalRef<jlongArray> nativeToJvmLongArray(JNIEnv* env,
|
||||
const int64_t* longs,
|
||||
size_t len) {
|
||||
LOG_FATAL_IF(len > INT_MAX);
|
||||
auto j_len = (jsize) len;
|
||||
jlongArray j_array = env->NewLongArray(j_len);
|
||||
LOG_FATAL_IF(checkException(env));
|
||||
env->SetLongArrayRegion(j_array, 0, j_len, longs);
|
||||
LOG_FATAL_IF(checkException(env));
|
||||
return {env, j_array};
|
||||
}
|
||||
|
||||
ScopedJvmLocalRef<jlongArray> nativeToJvmLongArray(JNIEnv* env,
|
||||
const uint64_t* longs,
|
||||
size_t len) {
|
||||
return nativeToJvmLongArray(env, reinterpret_cast<const int64_t*>(longs), len);
|
||||
}
|
||||
|
||||
jlong nativeToJvmPointer(void* ptr) {
|
||||
@ -225,7 +237,7 @@ jlong nativeToJvmPointer(void* ptr) {
|
||||
}
|
||||
|
||||
std::string jvmToStdString(JNIEnv* env, const JvmRef<jstring>& j_string) {
|
||||
const char* chars = env->GetStringUTFChars(j_string.obj(), nullptr);
|
||||
const char* chars = env->GetStringUTFChars(j_string.obj(), /*isCopy=*/nullptr);
|
||||
LOG_FATAL_IF(checkException(env));
|
||||
const jsize len = env->GetStringUTFLength(j_string.obj());
|
||||
LOG_FATAL_IF(checkException(env));
|
||||
@ -250,6 +262,4 @@ std::vector<char> jvmToNativeByteArray(JNIEnv* env,
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace monero
|
||||
|
@ -270,13 +270,9 @@ ScopedJvmLocalRef<jclass> findClass(JNIEnv* env, const char* name);
|
||||
// Methods for converting native types to Java types.
|
||||
ScopedJvmLocalRef<jstring> nativeToJvmString(JNIEnv* env, const char* str);
|
||||
ScopedJvmLocalRef<jstring> nativeToJvmString(JNIEnv* env, const std::string& str);
|
||||
ScopedJvmLocalRef<jbyteArray> nativeToJvmByteArray(
|
||||
JNIEnv* env,
|
||||
const char* bytes,
|
||||
size_t len);
|
||||
ScopedJvmLocalRef<jbyteArray> nativeToJvmByteArray(
|
||||
JNIEnv* env,
|
||||
const std::vector<char>& bytes);
|
||||
ScopedJvmLocalRef<jbyteArray> nativeToJvmByteArray(JNIEnv* env, const char* bytes, size_t len);
|
||||
ScopedJvmLocalRef<jlongArray> nativeToJvmLongArray(JNIEnv* env, const int64_t* longs, size_t len);
|
||||
ScopedJvmLocalRef<jlongArray> nativeToJvmLongArray(JNIEnv* env, const uint64_t* longs, size_t len);
|
||||
jlong nativeToJvmPointer(void* ptr);
|
||||
|
||||
// Helper function for converting std::vector<T> into a Java array.
|
||||
|
@ -21,11 +21,17 @@ namespace monero {
|
||||
using namespace std::chrono_literals;
|
||||
using namespace epee::string_tools;
|
||||
|
||||
static_assert(COIN == 1e12, "Monero atomic unit must be 1e-12 XMR");
|
||||
// Ensure constant values match the expected values in Kotlin.
|
||||
static_assert(COIN == 1e12,
|
||||
"Monero atomic unit must be 1e-12 XMR");
|
||||
static_assert(CRYPTONOTE_MAX_BLOCK_NUMBER == 500000000,
|
||||
"Min timestamp must be higher than max block height");
|
||||
static_assert(CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE == 10, ""); // TODO
|
||||
static_assert(DIFFICULTY_TARGET_V2 == 120, "");
|
||||
static_assert(CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE == 10,
|
||||
"CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE mismatch");
|
||||
static_assert(DIFFICULTY_TARGET_V2 == 120,
|
||||
"DIFFICULTY_TARGET_V2 mismatch");
|
||||
static_assert(PER_KB_FEE_QUANTIZATION_DECIMALS == 8,
|
||||
"PER_KB_FEE_QUANTIZATION_DECIMALS mismatch");
|
||||
|
||||
Wallet::Wallet(
|
||||
JNIEnv* env,
|
||||
@ -109,7 +115,7 @@ bool Wallet::parseFrom(std::istream& input) {
|
||||
|
||||
bool Wallet::writeTo(std::ostream& output) {
|
||||
return suspendRefreshAndRunLocked([&]() -> bool {
|
||||
binary_archive < true > ar(output);
|
||||
binary_archive<true> ar(output);
|
||||
if (!serialization::serialize_noeof(ar, *this))
|
||||
return false;
|
||||
if (!serialization::serialize_noeof(ar, require_account()))
|
||||
@ -126,6 +132,10 @@ void Wallet::withTxHistory(Consumer consumer) {
|
||||
consumer(m_tx_history);
|
||||
}
|
||||
|
||||
std::vector<uint64_t> Wallet::fetchBaseFeeEstimate() {
|
||||
return m_wallet.get_dynamic_base_fee_scaling_estimate();
|
||||
}
|
||||
|
||||
std::string Wallet::public_address() const {
|
||||
auto account = const_cast<Wallet*>(this)->require_account();
|
||||
return account.get_public_address_str(m_wallet.nettype());
|
||||
@ -191,7 +201,7 @@ void Wallet::captureTxHistorySnapshot(std::vector<TxInfo>& snapshot) {
|
||||
snapshot.emplace_back(td.m_txid, TxInfo::INCOMING);
|
||||
TxInfo& recv = snapshot.back();
|
||||
recv.m_public_key = td.get_public_key();
|
||||
recv.m_public_key_known = true;
|
||||
recv.m_public_key_known = true;
|
||||
recv.m_key_image = td.m_key_image;
|
||||
recv.m_key_image_known = td.m_key_image_known;
|
||||
recv.m_subaddress_major = td.m_subaddr_index.major;
|
||||
@ -235,7 +245,7 @@ void Wallet::captureTxHistorySnapshot(std::vector<TxInfo>& snapshot) {
|
||||
spent.m_state = TxInfo::ON_CHAIN;
|
||||
}
|
||||
|
||||
for (const auto& ring : tx.m_rings) {
|
||||
for (const auto& ring: tx.m_rings) {
|
||||
snapshot.emplace_back(pair.first, TxInfo::OUTGOING);
|
||||
TxInfo& spent = snapshot.back();
|
||||
spent.m_key_image = ring.first;
|
||||
@ -254,8 +264,8 @@ void Wallet::captureTxHistorySnapshot(std::vector<TxInfo>& snapshot) {
|
||||
const auto& utx = pair.second;
|
||||
uint64_t fee = utx.m_amount_in - utx.m_amount_out;
|
||||
auto state = (utx.m_state == unconfirmed_transfer_details::pending)
|
||||
? TxInfo::PENDING
|
||||
: TxInfo::FAILED;
|
||||
? TxInfo::PENDING
|
||||
: TxInfo::FAILED;
|
||||
|
||||
for (const auto& dest: utx.m_dests) {
|
||||
if (const auto dest_subaddr_idx = m_wallet.get_subaddress_index(dest.addr)) {
|
||||
@ -297,7 +307,7 @@ void Wallet::captureTxHistorySnapshot(std::vector<TxInfo>& snapshot) {
|
||||
change.m_state = state;
|
||||
}
|
||||
|
||||
for (const auto& ring : utx.m_rings) {
|
||||
for (const auto& ring: utx.m_rings) {
|
||||
snapshot.emplace_back(pair.first, TxInfo::OUTGOING);
|
||||
TxInfo& spent = snapshot.back();
|
||||
spent.m_key_image = ring.first;
|
||||
@ -636,4 +646,15 @@ Java_im_molly_monero_WalletNative_nativeGetTxHistory(
|
||||
return j_array.Release();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlongArray JNICALL
|
||||
Java_im_molly_monero_WalletNative_nativeFetchBaseFeeEstimate(
|
||||
JNIEnv* env,
|
||||
jobject thiz,
|
||||
jlong handle) {
|
||||
auto* wallet = reinterpret_cast<Wallet*>(handle);
|
||||
std::vector<uint64_t> fees = wallet->fetchBaseFeeEstimate();
|
||||
return nativeToJvmLongArray(env, fees.data(), fees.size()).Release();
|
||||
}
|
||||
|
||||
} // namespace monero
|
||||
|
@ -47,7 +47,7 @@ struct TxInfo {
|
||||
ON_CHAIN = 3,
|
||||
} m_state;
|
||||
|
||||
TxInfo(crypto::hash tx_hash, TxType type):
|
||||
TxInfo(crypto::hash tx_hash, TxType type) :
|
||||
m_tx_hash(tx_hash),
|
||||
m_public_key(crypto::public_key{}),
|
||||
m_key_image(crypto::key_image{}),
|
||||
@ -96,6 +96,8 @@ class Wallet : tools::i_wallet2_callback {
|
||||
template<typename Consumer>
|
||||
void withTxHistory(Consumer consumer);
|
||||
|
||||
std::vector<uint64_t> fetchBaseFeeEstimate();
|
||||
|
||||
std::string public_address() const;
|
||||
|
||||
uint32_t current_blockchain_height() const { return static_cast<uint32_t>(m_last_block_height); }
|
||||
|
@ -0,0 +1,13 @@
|
||||
package im.molly.monero
|
||||
|
||||
import im.molly.monero.internal.constants.PER_KB_FEE_QUANTIZATION_DECIMALS
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class DynamicFeeRate(val feePerByte: Map<FeePriority, MoneroAmount>) {
|
||||
|
||||
val quantizationMask: MoneroAmount = BigDecimal.TEN.pow(PER_KB_FEE_QUANTIZATION_DECIMALS).xmr
|
||||
|
||||
fun estimateFee(tx: PendingTransaction): Map<FeePriority, MoneroAmount> {
|
||||
TODO()
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package im.molly.monero
|
||||
|
||||
enum class FeePriority(val priority: Int) {
|
||||
LOW(1),
|
||||
MEDIUM(2),
|
||||
HIGH(3),
|
||||
URGENT(4),
|
||||
}
|
@ -5,10 +5,12 @@ import im.molly.monero.internal.consolidateTransactions
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.onFailure
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import java.time.Instant
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class MoneroWallet internal constructor(
|
||||
private val wallet: IWallet,
|
||||
@ -74,6 +76,40 @@ class MoneroWallet internal constructor(
|
||||
})
|
||||
}
|
||||
|
||||
fun dynamicFeeRate(): Flow<DynamicFeeRate> = flow {
|
||||
while (true) {
|
||||
val fees = requestFees() ?: emptyList()
|
||||
val feePerByte = when (fees.size) {
|
||||
1 -> mapOf(FeePriority.MEDIUM to fees[0])
|
||||
4 -> mapOf(
|
||||
FeePriority.LOW to fees[0],
|
||||
FeePriority.MEDIUM to fees[1],
|
||||
FeePriority.HIGH to fees[2],
|
||||
FeePriority.URGENT to fees[3],
|
||||
)
|
||||
|
||||
else -> {
|
||||
logger.e("Unexpected number of fees received: ${fees.size}")
|
||||
null
|
||||
}
|
||||
}
|
||||
feePerByte?.let { emit(DynamicFeeRate(it)) }
|
||||
// RPC client caches fees for 30 secs, wait before re-requesting fees
|
||||
delay(30.seconds)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private suspend fun requestFees(): List<MoneroAmount>? =
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
wallet.requestFees(object : BaseWalletCallbacks() {
|
||||
override fun onFeesReceived(fees: LongArray?) {
|
||||
val feeAmounts = fees?.map { MoneroAmount(atomicUnits = it) }
|
||||
continuation.resume(feeAmounts) {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun close() = wallet.close()
|
||||
}
|
||||
|
||||
@ -81,6 +117,8 @@ private abstract class BaseWalletCallbacks : IWalletCallbacks.Stub() {
|
||||
override fun onRefreshResult(blockchainTime: BlockchainTime, status: Int) = Unit
|
||||
|
||||
override fun onCommitResult(success: Boolean) = Unit
|
||||
|
||||
override fun onFeesReceived(fees: LongArray?) = Unit
|
||||
}
|
||||
|
||||
class RefreshResult(val blockchainTime: BlockchainTime, private val status: Int) {
|
||||
|
@ -0,0 +1,3 @@
|
||||
package im.molly.monero
|
||||
|
||||
class PendingTransaction
|
@ -1,3 +1,12 @@
|
||||
package im.molly.monero
|
||||
|
||||
enum class ProtocolInfo
|
||||
interface ProtocolInfo {
|
||||
val version: Int
|
||||
val perByteFee: Boolean
|
||||
val feeScaling2021: Boolean
|
||||
}
|
||||
|
||||
data class MoneroReleaseInfo(override val version: Int) : ProtocolInfo {
|
||||
override val perByteFee = version >= 8
|
||||
override val feeScaling2021 = version >= 15
|
||||
}
|
||||
|
@ -168,6 +168,13 @@ class WalletNative private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestFees(callback: IWalletCallbacks?) {
|
||||
scope.launch(ioDispatcher) {
|
||||
val fees = nativeFetchBaseFeeEstimate(handle)
|
||||
callback?.onFeesReceived(fees)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Also replays the last known balance whenever a new listener registers.
|
||||
*/
|
||||
@ -281,6 +288,7 @@ class WalletNative private constructor(
|
||||
private external fun nativeGetTxHistory(handle: Long): Array<TxInfo>
|
||||
private external fun nativeGetAccountPrimaryAddress(handle: Long): String
|
||||
// private external fun nativeGetAccountSubAddress(handle: Long, accountIndex: Int, subAddressIndex: Int): String
|
||||
private external fun nativeFetchBaseFeeEstimate(handle: Long): LongArray
|
||||
private external fun nativeLoad(handle: Long, fd: Int): Boolean
|
||||
private external fun nativeNonReentrantRefresh(handle: Long, skipCoinbase: Boolean): Int
|
||||
private external fun nativeRestoreAccount(
|
||||
|
@ -5,3 +5,5 @@ internal const val CRYPTONOTE_MAX_BLOCK_NUMBER: Int = 500_000_000
|
||||
internal const val CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE: Int = 10
|
||||
internal const val DIFFICULTY_TARGET_V1: Long = 60
|
||||
internal const val DIFFICULTY_TARGET_V2: Long = 120
|
||||
internal const val PER_KB_FEE_QUANTIZATION_DECIMALS: Int = 8
|
||||
internal const val BULLETPROOF_PLUS_MAX_OUTPUTS: Int = 16
|
||||
|
2
vendor/monero
vendored
2
vendor/monero
vendored
@ -1 +1 @@
|
||||
Subproject commit 04dc1bf8f16e6c6345cabaff15ecc5f55ff648d9
|
||||
Subproject commit 6063fbeb1414eab1c027ee57b4f2834bb178af7e
|
Loading…
Reference in New Issue
Block a user