Refactor gas handling and fix some issues (#3272)

pull/3276/head
James Brown 1 year ago committed by GitHub
parent 730448cd27
commit ed094eb78b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/build.gradle
  2. 11
      app/src/main/cpp/keys.c
  3. 10
      app/src/main/java/com/alphawallet/app/entity/EIP1559FeeOracleResult.java
  4. 86
      app/src/main/java/com/alphawallet/app/entity/GasPriceSpread.java
  5. 89
      app/src/main/java/com/alphawallet/app/entity/SuggestEIP1559.kt
  6. 25
      app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java
  7. 1
      app/src/main/java/com/alphawallet/app/repository/EthereumNetworkRepositoryType.java
  8. 2
      app/src/main/java/com/alphawallet/app/repository/KeyProvider.java
  9. 2
      app/src/main/java/com/alphawallet/app/repository/KeyProviderJNIImpl.java
  10. 166
      app/src/main/java/com/alphawallet/app/service/BlockNativeGasAPI.java
  11. 94
      app/src/main/java/com/alphawallet/app/service/GasService.java
  12. 231
      app/src/main/java/com/alphawallet/app/ui/GasSettingsActivity.java
  13. 91
      app/src/main/java/com/alphawallet/app/ui/widget/entity/GasSpeed.java
  14. 65
      app/src/main/java/com/alphawallet/app/ui/widget/entity/GasSpeed2.java
  15. 54
      app/src/main/java/com/alphawallet/app/util/BalanceUtils.java
  16. 8
      app/src/main/java/com/alphawallet/app/widget/GasSliderView.java
  17. 17
      app/src/main/java/com/alphawallet/app/widget/GasWidget.java
  18. 22
      app/src/main/java/com/alphawallet/app/widget/GasWidget2.java
  19. 27
      app/src/main/res/layout/activity_gas_settings.xml
  20. 254
      app/src/main/res/layout/item_gas_speed.xml
  21. 2
      app/src/main/res/values-es/strings.xml
  22. 2
      app/src/main/res/values-fr/strings.xml
  23. 2
      app/src/main/res/values-id/strings.xml
  24. 2
      app/src/main/res/values-my/strings.xml
  25. 2
      app/src/main/res/values-vi/strings.xml
  26. 2
      app/src/main/res/values-zh/strings.xml
  27. 6
      app/src/main/res/values/strings.xml
  28. 6
      app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockImpl.java

@ -77,8 +77,6 @@ repositories {
}
android {
compileSdkVersion 33
buildToolsVersion '33.0.0'
sourceSets {
main {
@ -91,6 +89,7 @@ android {
applicationId "io.stormbird.wallet"
minSdkVersion 24
targetSdkVersion 32
compileSdk 33
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
testInstrumentationRunnerArguments clearPackageData: 'true'
def XInfuraAPI = "XInfuraAPI"

@ -259,3 +259,14 @@ Java_com_alphawallet_app_repository_KeyProviderJNIImpl_getBlockPiCypressKey( JNI
return (*env)->NewStringUTF(env, key);
#endif
}
JNIEXPORT jstring JNICALL
Java_com_alphawallet_app_repository_KeyProviderJNIImpl_getBlockNativeKey( JNIEnv* env, jclass thiz )
{
#if (HAS_KEYS == 1)
return getDecryptedKey(env, blockNative);
#else
const jstring key = "";
return (*env)->NewStringUTF(env, key);
#endif
}

@ -14,27 +14,27 @@ import java.math.BigInteger;
public class EIP1559FeeOracleResult implements Parcelable
{
public final BigInteger maxFeePerGas;
public final BigInteger maxPriorityFeePerGas;
public final BigInteger priorityFee;
public final BigInteger baseFee;
public EIP1559FeeOracleResult(BigInteger maxFee, BigInteger maxPriority, BigInteger base)
{
maxFeePerGas = fixGasPriceReturn(maxFee); // Some chains (eg Phi) have a gas price lower than 1Gwei.
maxPriorityFeePerGas = fixGasPriceReturn(maxPriority);
priorityFee = fixGasPriceReturn(maxPriority);
baseFee = base;
}
public EIP1559FeeOracleResult(EIP1559FeeOracleResult r)
{
maxFeePerGas = r.maxFeePerGas;
maxPriorityFeePerGas = r.maxPriorityFeePerGas;
priorityFee = r.priorityFee;
baseFee = r.baseFee;
}
protected EIP1559FeeOracleResult(Parcel in)
{
maxFeePerGas = new BigInteger(in.readString(), 16);
maxPriorityFeePerGas = new BigInteger(in.readString(), 16);
priorityFee = new BigInteger(in.readString(), 16);
baseFee = new BigInteger(in.readString(), 16);
}
@ -60,7 +60,7 @@ public class EIP1559FeeOracleResult implements Parcelable
public void writeToParcel(Parcel dest, int flags)
{
dest.writeString(maxFeePerGas.toString(16));
dest.writeString(maxPriorityFeePerGas.toString(16));
dest.writeString(priorityFee.toString(16));
dest.writeString(baseFee.toString(16));
}

@ -7,7 +7,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import com.alphawallet.app.R;
import com.alphawallet.app.ui.widget.entity.GasSpeed2;
import com.alphawallet.app.ui.widget.entity.GasSpeed;
import org.json.JSONException;
import org.json.JSONObject;
@ -30,7 +30,7 @@ public class GasPriceSpread implements Parcelable
private final boolean hasLockedGas;
private BigInteger baseFee = BigInteger.ZERO;
public GasSpeed2 getSelectedGasFee(TXSpeed currentGasSpeedIndex)
public GasSpeed getSelectedGasFee(TXSpeed currentGasSpeedIndex)
{
return fees.get(currentGasSpeedIndex);
}
@ -40,11 +40,11 @@ public class GasPriceSpread implements Parcelable
return fees.size();
}
public GasSpeed2 getQuickestGasSpeed()
public GasSpeed getQuickestGasSpeed()
{
for (TXSpeed txs : TXSpeed.values())
{
GasSpeed2 gs = fees.get(txs);
GasSpeed gs = fees.get(txs);
if (gs != null) return gs;
}
@ -52,12 +52,12 @@ public class GasPriceSpread implements Parcelable
return null;
}
public GasSpeed2 getSlowestGasSpeed()
public GasSpeed getSlowestGasSpeed()
{
TXSpeed slowest = TXSpeed.STANDARD;
for (TXSpeed txs : TXSpeed.values())
{
GasSpeed2 gs = fees.get(txs);
GasSpeed gs = fees.get(txs);
if (gs != null)
{
if (gs.gasPrice.maxFeePerGas.compareTo(fees.get(slowest).gasPrice.maxFeePerGas) < 0)
@ -75,7 +75,7 @@ public class GasPriceSpread implements Parcelable
boolean begin = false;
for (TXSpeed txs : TXSpeed.values())
{
GasSpeed2 gs = fees.get(txs);
GasSpeed gs = fees.get(txs);
if (gs != null)
{
if (txs == speed)
@ -97,7 +97,7 @@ public class GasPriceSpread implements Parcelable
int index = 0;
for (TXSpeed txs : TXSpeed.values())
{
GasSpeed2 gs = fees.get(txs);
GasSpeed gs = fees.get(txs);
if (gs == null) continue;
else if (absoluteAdapterPosition == index) return txs;
index++;
@ -109,7 +109,7 @@ public class GasPriceSpread implements Parcelable
public final long timeStamp;
public TXSpeed speedIndex = TXSpeed.STANDARD;
private final Map<TXSpeed, GasSpeed2> fees = new HashMap<>();
private final Map<TXSpeed, GasSpeed> fees = new HashMap<>();
public GasPriceSpread(Context ctx, Map<Integer, EIP1559FeeOracleResult> result)
{
@ -118,7 +118,7 @@ public class GasPriceSpread implements Parcelable
if (result == null || result.size() == 0) return;
setComponents(ctx, result);
fees.put(TXSpeed.CUSTOM, new GasSpeed2(ctx.getString(R.string.speed_custom), STANDARD_SECONDS, fees.get(TXSpeed.STANDARD).gasPrice));
fees.put(TXSpeed.CUSTOM, new GasSpeed(ctx.getString(R.string.speed_custom), STANDARD_SECONDS, fees.get(TXSpeed.STANDARD).gasPrice));
}
public GasPriceSpread(Context ctx, GasPriceSpread gs, Map<Integer, EIP1559FeeOracleResult> result)
@ -128,9 +128,9 @@ public class GasPriceSpread implements Parcelable
if (result == null || result.size() == 0) return;
setComponents(ctx, result);
GasSpeed2 custom = gs.getSelectedGasFee(TXSpeed.CUSTOM);
GasSpeed custom = gs.getSelectedGasFee(TXSpeed.CUSTOM);
fees.put(TXSpeed.CUSTOM, new GasSpeed2(ctx.getString(R.string.speed_custom), custom.seconds, custom.gasPrice));
fees.put(TXSpeed.CUSTOM, new GasSpeed(ctx.getString(R.string.speed_custom), custom.seconds, custom.gasPrice));
}
//This is a fallback method, it should never be used
@ -140,8 +140,8 @@ public class GasPriceSpread implements Parcelable
BigInteger baseFeeApprox = maxFeePerGas.subtract(maxPriorityFeePerGas.divide(BigInteger.valueOf(2)));
fees.put(TXSpeed.STANDARD, new GasSpeed2(ctx.getString(R.string.speed_average), STANDARD_SECONDS, new EIP1559FeeOracleResult(maxFeePerGas, maxPriorityFeePerGas, baseFeeApprox)));
fees.put(TXSpeed.CUSTOM, new GasSpeed2(ctx.getString(R.string.speed_custom), STANDARD_SECONDS, new EIP1559FeeOracleResult(maxFeePerGas, maxPriorityFeePerGas, baseFeeApprox)));
fees.put(TXSpeed.STANDARD, new GasSpeed(ctx.getString(R.string.speed_average), STANDARD_SECONDS, new EIP1559FeeOracleResult(maxFeePerGas, maxPriorityFeePerGas, baseFeeApprox)));
fees.put(TXSpeed.CUSTOM, new GasSpeed(ctx.getString(R.string.speed_custom), STANDARD_SECONDS, new EIP1559FeeOracleResult(maxFeePerGas, maxPriorityFeePerGas, baseFeeApprox)));
hasLockedGas = false;
}
@ -149,8 +149,8 @@ public class GasPriceSpread implements Parcelable
{
timeStamp = System.currentTimeMillis();
fees.put(TXSpeed.FAST, new GasSpeed2("", FAST_SECONDS, new BigDecimal(currentAvGasPrice).multiply(BigDecimal.valueOf(1.2)).toBigInteger()));
fees.put(TXSpeed.STANDARD, new GasSpeed2("", STANDARD_SECONDS, currentAvGasPrice));
fees.put(TXSpeed.FAST, new GasSpeed("", FAST_SECONDS, new BigDecimal(currentAvGasPrice).multiply(BigDecimal.valueOf(1.2)).toBigInteger()));
fees.put(TXSpeed.STANDARD, new GasSpeed("", STANDARD_SECONDS, currentAvGasPrice));
hasLockedGas = lockedGas;
}
@ -158,8 +158,8 @@ public class GasPriceSpread implements Parcelable
{
timeStamp = System.currentTimeMillis();
fees.put(TXSpeed.STANDARD, new GasSpeed2(ctx.getString(R.string.speed_average), STANDARD_SECONDS, gasPrice));
fees.put(TXSpeed.CUSTOM, new GasSpeed2(ctx.getString(R.string.speed_custom), STANDARD_SECONDS, gasPrice));
fees.put(TXSpeed.STANDARD, new GasSpeed(ctx.getString(R.string.speed_average), STANDARD_SECONDS, gasPrice));
fees.put(TXSpeed.CUSTOM, new GasSpeed(ctx.getString(R.string.speed_custom), STANDARD_SECONDS, gasPrice));
hasLockedGas = false;
}
@ -193,10 +193,10 @@ public class GasPriceSpread implements Parcelable
}
//convert to wei
fees.put(TXSpeed.RAPID, new GasSpeed2("", RAPID_SECONDS, gweiToWei(rRapid)));
fees.put(TXSpeed.FAST, new GasSpeed2("", FAST_SECONDS, gweiToWei(rFast)));
fees.put(TXSpeed.STANDARD, new GasSpeed2("", STANDARD_SECONDS, gweiToWei(rStandard)));
fees.put(TXSpeed.SLOW, new GasSpeed2("", SLOW_SECONDS, gweiToWei(rSlow)));
fees.put(TXSpeed.RAPID, new GasSpeed("", RAPID_SECONDS, gweiToWei(rRapid)));
fees.put(TXSpeed.FAST, new GasSpeed("", FAST_SECONDS, gweiToWei(rFast)));
fees.put(TXSpeed.STANDARD, new GasSpeed("", STANDARD_SECONDS, gweiToWei(rStandard)));
fees.put(TXSpeed.SLOW, new GasSpeed("", SLOW_SECONDS, gweiToWei(rSlow)));
baseFee = gweiToWei(rBaseFee);
hasLockedGas = false;
@ -213,16 +213,16 @@ public class GasPriceSpread implements Parcelable
if (gasSpread != null)
{
GasSpeed2 custom = gasSpread.getSelectedGasFee(TXSpeed.CUSTOM);
GasSpeed custom = gasSpread.getSelectedGasFee(TXSpeed.CUSTOM);
if (custom != null)
{
fees.put(TXSpeed.CUSTOM, new GasSpeed2(ctx.getString(R.string.speed_custom), custom.seconds, custom.gasPrice.maxFeePerGas));
fees.put(TXSpeed.CUSTOM, new GasSpeed(ctx.getString(R.string.speed_custom), custom.seconds, custom.gasPrice.maxFeePerGas));
}
}
else
{
fees.put(TXSpeed.CUSTOM, new GasSpeed2(ctx.getString(R.string.speed_custom), STANDARD_SECONDS, feeMap.get(TXSpeed.STANDARD)));
fees.put(TXSpeed.CUSTOM, new GasSpeed(ctx.getString(R.string.speed_custom), STANDARD_SECONDS, feeMap.get(TXSpeed.STANDARD)));
}
hasLockedGas = locked;
}
@ -232,25 +232,25 @@ public class GasPriceSpread implements Parcelable
BigInteger gasPrice = feeMap.get(speed);
if (gasPrice != null && gasPrice.compareTo(BigInteger.ZERO) > 0)
{
fees.put(speed, new GasSpeed2(speedName, seconds, gasPrice));
fees.put(speed, new GasSpeed(speedName, seconds, gasPrice));
}
}
private void setComponents(Context ctx, Map<Integer, EIP1559FeeOracleResult> result)
{
int third = result.size()/3;
int quarter = result.size()/4;
fees.put(TXSpeed.RAPID, new GasSpeed2(ctx.getString(R.string.speed_rapid), RAPID_SECONDS, new EIP1559FeeOracleResult(result.get(0))));
fees.put(TXSpeed.FAST, new GasSpeed2(ctx.getString(R.string.speed_fast), FAST_SECONDS, new EIP1559FeeOracleResult(result.get(third))));
fees.put(TXSpeed.STANDARD, new GasSpeed2(ctx.getString(R.string.speed_average), STANDARD_SECONDS, new EIP1559FeeOracleResult(result.get(third*2))));
fees.put(TXSpeed.SLOW, new GasSpeed2(ctx.getString(R.string.speed_slow), SLOW_SECONDS, new EIP1559FeeOracleResult(result.get(result.size()-1))));
fees.put(TXSpeed.RAPID, new GasSpeed(ctx.getString(R.string.speed_rapid), RAPID_SECONDS, new EIP1559FeeOracleResult(result.get(0))));
fees.put(TXSpeed.FAST, new GasSpeed(ctx.getString(R.string.speed_fast), FAST_SECONDS, new EIP1559FeeOracleResult(result.get(quarter))));
fees.put(TXSpeed.STANDARD, new GasSpeed(ctx.getString(R.string.speed_average), STANDARD_SECONDS, new EIP1559FeeOracleResult(result.get(quarter*2))));
fees.put(TXSpeed.SLOW, new GasSpeed(ctx.getString(R.string.speed_slow), SLOW_SECONDS, new EIP1559FeeOracleResult(result.get(result.size()-1))));
//now de-duplicate
for (TXSpeed txs : TXSpeed.values())
{
GasSpeed2 gs = fees.get(txs);
GasSpeed gs = fees.get(txs);
if (txs == TXSpeed.STANDARD || gs == null) continue;
if (gs.gasPrice.maxPriorityFeePerGas.equals(fees.get(TXSpeed.STANDARD).gasPrice.maxPriorityFeePerGas)
if (gs.gasPrice.priorityFee.equals(fees.get(TXSpeed.STANDARD).gasPrice.priorityFee)
&& gs.gasPrice.maxFeePerGas.equals(fees.get(TXSpeed.STANDARD).gasPrice.maxFeePerGas))
{
fees.remove(txs);
@ -260,15 +260,15 @@ public class GasPriceSpread implements Parcelable
public void setCustom(BigInteger maxFeePerGas, BigInteger maxPriorityFeePerGas, long fastSeconds)
{
GasSpeed2 gsCustom = fees.get(TXSpeed.CUSTOM);
GasSpeed gsCustom = fees.get(TXSpeed.CUSTOM);
BigInteger baseFee = gsCustom.gasPrice.baseFee;
fees.put(TXSpeed.CUSTOM, new GasSpeed2(gsCustom.speed, fastSeconds, new EIP1559FeeOracleResult(maxFeePerGas, maxPriorityFeePerGas, baseFee)));
fees.put(TXSpeed.CUSTOM, new GasSpeed(gsCustom.speed, fastSeconds, new EIP1559FeeOracleResult(maxFeePerGas, maxPriorityFeePerGas, baseFee)));
}
public void setCustom(BigInteger gasPrice, long fastSeconds)
{
GasSpeed2 gsCustom = fees.get(TXSpeed.CUSTOM);
fees.put(TXSpeed.CUSTOM, new GasSpeed2(gsCustom.speed, fastSeconds, gasPrice));
GasSpeed gsCustom = fees.get(TXSpeed.CUSTOM);
fees.put(TXSpeed.CUSTOM, new GasSpeed(gsCustom.speed, fastSeconds, gasPrice));
}
protected GasPriceSpread(Parcel in)
@ -282,7 +282,7 @@ public class GasPriceSpread implements Parcelable
for (int i = 0; i < feeCount; i++)
{
int entry = in.readInt();
GasSpeed2 r = in.readParcelable(GasSpeed2.class.getClassLoader());
GasSpeed r = in.readParcelable(GasSpeed.class.getClassLoader());
fees.put(TXSpeed.values()[entry], r);
}
}
@ -313,7 +313,7 @@ public class GasPriceSpread implements Parcelable
dest.writeInt(speedIndex.ordinal());
dest.writeByte(hasLockedGas ? (byte) 1 : (byte) 0);
for (Map.Entry<TXSpeed, GasSpeed2> entry : fees.entrySet())
for (Map.Entry<TXSpeed, GasSpeed> entry : fees.entrySet())
{
dest.writeInt(entry.getKey().ordinal());
dest.writeParcelable(entry.getValue(), flags);
@ -322,9 +322,9 @@ public class GasPriceSpread implements Parcelable
public void addCustomGas(long seconds, EIP1559FeeOracleResult fee)
{
GasSpeed2 currentCustom = fees.get(TXSpeed.CUSTOM);
GasSpeed currentCustom = fees.get(TXSpeed.CUSTOM);
fees.put(TXSpeed.CUSTOM,
new GasSpeed2(currentCustom.speed, seconds, fee));
new GasSpeed(currentCustom.speed, seconds, fee));
}
public EIP1559FeeOracleResult getCurrentGasFee()
@ -337,7 +337,7 @@ public class GasPriceSpread implements Parcelable
return fees.get(this.speedIndex).seconds;
}
public GasSpeed2 getGasSpeed()
public GasSpeed getGasSpeed()
{
return fees.get(this.speedIndex);
}
@ -355,7 +355,7 @@ public class GasPriceSpread implements Parcelable
{
for (TXSpeed txs : TXSpeed.values())
{
GasSpeed2 gs = fees.get(txs);
GasSpeed gs = fees.get(txs);
if (gs != null && gs.gasPrice.maxFeePerGas.compareTo(BigInteger.ZERO) > 0) return true;
}

@ -27,9 +27,10 @@ private const val rewardBlockPercentile = 40 // suggested priority fee to
private const val maxTimeFactor = 15 // highest timeFactor index in the returned list of suggestion
private const val extraPriorityFeeRatio = 0.25 // extra priority fee offered in case of expected baseFee rise
private const val fallbackPriorityFee = 2000000000L // priority fee offered when there are no recent transactions
private const val minPriorityFee = 100000000L // Minimum priority fee in Wei, 0.1 Gwei
fun SuggestEIP1559(gasService: GasService, feeHistory: FeeHistory): Single<MutableMap<Int, EIP1559FeeOracleResult>> {
return suggestPriorityFee(parseLong(feeHistory.oldestBlock.removePrefix("0x"), 16), feeHistory.gasUsedRatio, gasService)
return suggestPriorityFee(parseLong(feeHistory.oldestBlock.removePrefix("0x"), 16), feeHistory, gasService)
.flatMap { priorityFee -> calculateResult(priorityFee, feeHistory) }
}
@ -40,7 +41,20 @@ private fun calculateResult(priorityFee: BigInteger, feeHistory: FeeHistory): Si
Numeric.toBigInt(it)
}.toTypedArray()
baseFee[baseFee.size - 1] = (baseFee[baseFee.size - 1].toBigDecimal() * BigDecimal(9 / 8.0)).toBigInteger()
var usePriorityFee = priorityFee;
var consistentBaseFee = false;
//spot consistent base fees
if (checkConsistentFees(baseFee)) {
consistentBaseFee = true
baseFee[baseFee.size - 1] = baseFee[0];
if (priorityFee.toLong() == fallbackPriorityFee && priorityFee > baseFee[0]) {
usePriorityFee = baseFee[0];
}
} else {
baseFee[baseFee.size - 1] = (baseFee[baseFee.size - 1].toBigDecimal() * BigDecimal(9 / 8.0)).toBigInteger()
}
((feeHistory.gasUsedRatio.size - 1) downTo 0).forEach { i ->
if (feeHistory.gasUsedRatio[i] > 0.9) {
@ -53,8 +67,8 @@ private fun calculateResult(priorityFee: BigInteger, feeHistory: FeeHistory): Si
var maxBaseFee = ZERO
val result = mutableMapOf<Int, EIP1559FeeOracleResult>()
(maxTimeFactor downTo 0).forEach { timeFactor ->
var bf = predictMinBaseFee(baseFee, order, timeFactor.toDouble())
var t = BigDecimal(priorityFee)
var bf = predictMinBaseFee(baseFee, order, timeFactor.toDouble(), consistentBaseFee)
var t = BigDecimal(usePriorityFee)
if (bf > maxBaseFee) {
maxBaseFee = bf
} else {
@ -71,7 +85,26 @@ private fun calculateResult(priorityFee: BigInteger, feeHistory: FeeHistory): Si
}
}
internal fun predictMinBaseFee(baseFee: Array<BigInteger>, order: List<Int>, timeFactor: Double): BigInteger {
fun checkConsistentFees(baseFee: Array<BigInteger>): Boolean {
if (baseFee.isEmpty()) {
return false
}
val firstVal: BigInteger = baseFee[0]
var isConsistent = true;
baseFee.forEach { element ->
run {
if (element != firstVal) {
isConsistent = false;
}
}
}
return isConsistent;
}
internal fun predictMinBaseFee(baseFee: Array<BigInteger>, order: List<Int>, timeFactor: Double, consistentBaseFee: Boolean): BigInteger {
if (timeFactor < 1e-6) {
return baseFee.last()
}
@ -79,20 +112,25 @@ internal fun predictMinBaseFee(baseFee: Array<BigInteger>, order: List<Int>, tim
var sumWeight = .0
var result = ZERO
var samplingCurveLast = .0
order.indices.forEach { i ->
sumWeight += pendingWeight * exp((order[i] - baseFee.size + 1) / timeFactor)
val samplingCurveValue = samplingCurve(sumWeight * 100.0)
result += ((samplingCurveValue - samplingCurveLast) * baseFee[order[i]].toDouble()).toBigDecimal().toBigInteger()
if (samplingCurveValue >= 1) {
return result
if (consistentBaseFee) {
result = baseFee[0]
} else {
order.indices.forEach { i ->
sumWeight += pendingWeight * exp((order[i] - baseFee.size + 1) / timeFactor)
val samplingCurveValue = samplingCurve(sumWeight * 100.0)
result += ((samplingCurveValue - samplingCurveLast) * baseFee[order[i]].toDouble()).toBigDecimal().toBigInteger()
if (samplingCurveValue >= 1) {
return result
}
samplingCurveLast = samplingCurveValue
}
samplingCurveLast = samplingCurveValue
}
return result
}
internal fun suggestPriorityFee(firstBlock: Long, gasUsedRatio: DoubleArray, gasService: GasService): Single<BigInteger> {
internal fun suggestPriorityFee(firstBlock: Long, feeHistory: FeeHistory, gasService: GasService): Single<BigInteger> {
return Single.fromCallable {
val gasUsedRatio = feeHistory.gasUsedRatio
var ptr = gasUsedRatio.size - 1
var needBlocks = 5
val rewards = mutableListOf<BigInteger>()
@ -100,11 +138,11 @@ internal fun suggestPriorityFee(firstBlock: Long, gasUsedRatio: DoubleArray, gas
val blockCount = maxBlockCount(gasUsedRatio, ptr, needBlocks)
if (blockCount > 0) {
// feeHistory API call with reward percentile specified is expensive and therefore is only requested for a few non-full recent blocks.
val feeHistory = gasService.getChainFeeHistory(blockCount, "0x" + (firstBlock + ptr).toString(16), rewardPercentile.toString()).blockingGet();
val feeHistoryFetch = gasService.getChainFeeHistory(blockCount, "0x" + (firstBlock + ptr).toString(16), rewardPercentile.toString()).blockingGet();
val rewardSize = feeHistory?.reward?.size ?: 0
val rewardSize = feeHistoryFetch?.reward?.size ?: 0
(0 until rewardSize).forEach {
rewards.add(BigInteger(Numeric.cleanHexPrefix(feeHistory.reward[it][0].removePrefix("0x")), 16))
rewards.add(BigInteger(Numeric.cleanHexPrefix(feeHistoryFetch.reward[it][0].removePrefix("0x")), 16))
}
if (rewardSize < blockCount) break
needBlocks -= blockCount
@ -113,13 +151,28 @@ internal fun suggestPriorityFee(firstBlock: Long, gasUsedRatio: DoubleArray, gas
}
if (rewards.isEmpty()) {
return@fromCallable BigInteger.valueOf(fallbackPriorityFee)
return@fromCallable calculatePriorityFee(feeHistory)
}
rewards.sort()
return@fromCallable rewards[floor((rewards.size - 1) * rewardBlockPercentile / 100.0).toInt()]
}
}
fun calculatePriorityFee(feeHistory: FeeHistory): BigInteger? {
var priorityFee = BigInteger.valueOf(minPriorityFee)
feeHistory.baseFeePerGas.forEach { element ->
run {
val elementVal = Numeric.toBigInt(element)
if (elementVal > priorityFee && elementVal <= BigInteger.valueOf(fallbackPriorityFee)) {
priorityFee = elementVal
}
}
}
return priorityFee
}
internal fun maxBlockCount(gasUsedRatio: DoubleArray, _ptr: Int, _needBlocks: Int): Int {
var blockCount = 0
var ptr = _ptr
@ -139,4 +192,4 @@ internal fun samplingCurve(percentile: Double): Double = when {
(percentile <= sampleMinPercentile) -> 0.0
(percentile >= sampleMaxPercentile) -> 1.0
else -> (1 - cos((percentile - sampleMinPercentile) * 2 * Math.PI / (sampleMaxPercentile - sampleMinPercentile))) / 2
}
}

@ -492,10 +492,9 @@ public abstract class EthereumNetworkBase implements EthereumNetworkRepositoryTy
// <etherscanAPI from the above list> + GAS_API
//If the gas oracle you're adding doesn't follow this spec then you'll have to change the getGasOracle method
private static final List<Long> hasGasOracleAPI = Arrays.asList(MAINNET_ID, HECO_ID, BINANCE_MAIN_ID, POLYGON_ID);
private static final List<Long> hasBlockNativeGasOracleAPI = Arrays.asList(MAINNET_ID, POLYGON_ID);
//These chains don't allow custom gas
private static final List<Long> hasLockedGas = Arrays.asList(OPTIMISTIC_MAIN_ID, ARBITRUM_MAIN_ID, KLAYTN_ID, KLAYTN_BAOBAB_ID);
private static final List<Long> hasLockedGas = Arrays.asList(KLAYTN_ID, KLAYTN_BAOBAB_ID);
private static final List<Long> hasOpenSeaAPI = Arrays.asList(MAINNET_ID, POLYGON_ID, ARBITRUM_GOERLI_TEST_ID, AVALANCHE_ID, KLAYTN_ID, OPTIMISM_GOERLI_TEST_ID, GOERLI_ID);
private static final LongSparseArray<BigInteger> blockGasLimit = new LongSparseArray<BigInteger>()
@ -519,6 +518,20 @@ public abstract class EthereumNetworkBase implements EthereumNetworkRepositoryTy
}
}
private static final String BLOCKNATIVE_GAS_API = "https://api.blocknative.com/gasprices/blockprices?chainid=";
public static String getBlockNativeOracle(long chainId)
{
if (hasBlockNativeGasOracleAPI.contains(chainId) && networkMap.indexOfKey(chainId) >= 0)
{
return BLOCKNATIVE_GAS_API + chainId;
}
else
{
return "";
}
}
/**
* This function determines the order in which chains appear in the main wallet view
*
@ -597,6 +610,12 @@ public abstract class EthereumNetworkBase implements EthereumNetworkRepositoryTy
return hasLockedGas.contains(chainId);
}
@Override
public boolean hasBlockNativeGasAPI(long chainId)
{
return hasBlockNativeGasOracleAPI.contains(chainId);
}
static final Map<Long, String> addressOverride = new HashMap<Long, String>()
{
{

@ -58,6 +58,7 @@ public interface EthereumNetworkRepositoryType {
boolean isChainContract(long chainId, String address);
boolean hasLockedGas(long chainId);
boolean hasBlockNativeGasAPI(long chainId);
NetworkInfo getBuiltInNetwork(long chainId);

@ -41,4 +41,6 @@ public interface KeyProvider
String getBlockPiBaobabKey();
String getBlockPiCypressKey();
String getBlockNativeKey();
}

@ -46,4 +46,6 @@ public class KeyProviderJNIImpl implements KeyProvider
public native String getBlockPiBaobabKey();
public native String getBlockPiCypressKey();
public native String getBlockNativeKey();
}

@ -0,0 +1,166 @@
package com.alphawallet.app.service;
import android.text.TextUtils;
import com.alphawallet.app.entity.EIP1559FeeOracleResult;
import com.alphawallet.app.repository.EthereumNetworkBase;
import com.alphawallet.app.repository.KeyProviderFactory;
import com.alphawallet.app.util.BalanceUtils;
import com.alphawallet.app.util.JsonUtils;
import com.google.gson.Gson;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import io.reactivex.Single;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import timber.log.Timber;
public class BlockNativeGasAPI
{
public static BlockNativeGasAPI instance;
private final OkHttpClient httpClient;
public static BlockNativeGasAPI get(OkHttpClient httpClient)
{
if (instance == null)
{
instance = new BlockNativeGasAPI(httpClient);
}
return instance;
}
public BlockNativeGasAPI(OkHttpClient httpClient)
{
this.httpClient = httpClient;
}
private Request buildRequest(String api)
{
Request.Builder requestB = new Request.Builder()
.url(api)
.header("Content-Type", "application/json")
.addHeader("Authorization", KeyProviderFactory.get().getBlockNativeKey())
.get();
return requestB.build();
}
public Single<Map<Integer, EIP1559FeeOracleResult>> fetchGasEstimates(long chainId)
{
String oracleAPI = EthereumNetworkBase.getBlockNativeOracle(chainId);
return Single.fromCallable(() -> buildOracleResult(executeRequest(oracleAPI))); // any kind of error results in blank mapping,
// if blank, fall back to calculation method
}
private Map<Integer, EIP1559FeeOracleResult> buildOracleResult(String oracleReturn)
{
Map<Integer, EIP1559FeeOracleResult> results = new HashMap<>();
try
{
JSONObject prices = new JSONObject(oracleReturn);
//get base fee per gas
JSONArray blockPrices = prices.getJSONArray("blockPrices");
JSONObject blockPrice0 = blockPrices.getJSONObject(0);
String baseFeePerGasStr = blockPrice0.getString("baseFeePerGas");
BigDecimal baseFeePerGas = new BigDecimal(baseFeePerGasStr);
BigInteger baseFeePerGasWei = BalanceUtils.gweiToWei(baseFeePerGas);
//get the array
String estimatedPrices = blockPrice0.getJSONArray("estimatedPrices").toString();
PriceElement[] priceElements = new Gson().fromJson(estimatedPrices, PriceElement[].class);
results.put(0, new EIP1559FeeOracleResult(priceElements[0].getFeeOracleResult(baseFeePerGasWei)));
results.put(1, new EIP1559FeeOracleResult(priceElements[2].getFeeOracleResult(baseFeePerGasWei)));
results.put(2, new EIP1559FeeOracleResult(priceElements[3].getFeeOracleResult(baseFeePerGasWei)));
results.put(3, new EIP1559FeeOracleResult(priceElements[4].getFeeOracleResult(baseFeePerGasWei)));
}
catch (JSONException e)
{
// map will be empty; default to using backup calculation method
Timber.w(e);
}
return results;
}
private String executeRequest(String api)
{
if (!TextUtils.isEmpty(api))
{
try (okhttp3.Response response = httpClient.newCall(buildRequest(api)).execute())
{
if (response.isSuccessful())
{
ResponseBody responseBody = response.body();
if (responseBody != null)
{
return responseBody.string();
}
}
else
{
return Objects.requireNonNull(response.body()).string();
}
}
catch (Exception e)
{
Timber.e(e);
return e.getMessage();
}
}
return JsonUtils.EMPTY_RESULT;
}
private static class PriceElement
{
public String confidence;
public String price;
public String maxPriorityFeePerGas;
public String maxFeePerGas;
public BigInteger getMaxPriorityFeePerGasWei()
{
return elementToWei(maxPriorityFeePerGas);
}
public BigInteger getMaxFeePerGasWei()
{
return elementToWei(maxFeePerGas);
}
private BigInteger elementToWei(String value)
{
try
{
BigDecimal gweiValue = new BigDecimal(value);
return BalanceUtils.gweiToWei(gweiValue);
}
catch (Exception e)
{
return BigInteger.ZERO;
}
}
/*
public EIP1559FeeOracleResult(BigInteger maxFee, BigInteger maxPriority, BigInteger base)
{
maxFeePerGas = fixGasPriceReturn(maxFee); // Some chains (eg Phi) have a gas price lower than 1Gwei.
maxPriorityFeePerGas = fixGasPriceReturn(maxPriority);
baseFee = base;
}
*/
public EIP1559FeeOracleResult getFeeOracleResult(BigInteger baseFee)
{
return new EIP1559FeeOracleResult(getMaxFeePerGasWei(), getMaxPriorityFeePerGasWei(), baseFee);
}
}
}

@ -13,7 +13,6 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.alphawallet.app.C;
import com.alphawallet.app.entity.EIP1559FeeOracleResult;
import com.alphawallet.app.entity.FeeHistory;
import com.alphawallet.app.entity.GasEstimate;
@ -44,14 +43,19 @@ import org.web3j.protocol.core.methods.response.EthGasPrice;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.gas.ContractGasProvider;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.SingleSource;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import io.realm.Realm;
import okhttp3.OkHttpClient;
@ -71,7 +75,6 @@ public class GasService implements ContractGasProvider
private static final String BLOCK_COUNT = "[BLOCK_COUNT]";
private static final String NEWEST_BLOCK = "[NEWEST_BLOCK]";
private static final String REWARD_PERCENTILES = "[REWARD_PERCENTILES]";
private static final String FEE_HISTORY = "{\"jsonrpc\":\"2.0\",\"method\":\"eth_feeHistory\",\"params\":[\""+ BLOCK_COUNT +"\", \""+ NEWEST_BLOCK +"\",["+ REWARD_PERCENTILES +"]],\"id\":1}";
private final String WHALE_ACCOUNT = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; //used for calculating gas estimate where a tx would exceed the limits with default gas settings
private final EthereumNetworkRepositoryType networkRepository;
@ -80,10 +83,12 @@ public class GasService implements ContractGasProvider
private long currentChainId;
private Web3j web3j;
private BigInteger currentGasPrice;
private long currentGasPriceTime;
private BigInteger currentLowGasPrice = BigInteger.ZERO;
private final String ETHERSCAN_API_KEY;
private final String POLYGONSCAN_API_KEY;
private boolean keyFail;
private static final List<Long> noEIP1559 = new ArrayList<>();
@Nullable
private Disposable gasFetchDisposable;
@ -101,6 +106,8 @@ public class GasService implements ContractGasProvider
ETHERSCAN_API_KEY = "&apikey=" + keyProvider.getEtherscanKey();
POLYGONSCAN_API_KEY = "&apikey=" + keyProvider.getPolygonScanKey();
keyFail = false;
currentGasPrice = BigInteger.ZERO;
currentGasPriceTime = 0;
}
public void startGasPriceCycle(long chainId)
@ -129,6 +136,8 @@ public class GasService implements ContractGasProvider
}
else if (web3j == null || web3j.ethChainId().getId() != chainId)
{
currentGasPrice = BigInteger.ZERO;
currentGasPriceTime = 0;
currentChainId = chainId;
web3j = getWeb3jService(chainId);
}
@ -137,9 +146,8 @@ public class GasService implements ContractGasProvider
private void fetchCurrentGasPrice()
{
currentLowGasPrice = BigInteger.ZERO;
currentGasPrice = BigInteger.ZERO;
updateCurrentGasPrices()
.flatMap(this::useNodeFallback)
.flatMap(this::useNodeEstimate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(updated -> {
@ -147,20 +155,16 @@ public class GasService implements ContractGasProvider
}, Throwable::printStackTrace)
.isDisposed();
//also update EIP1559
getEIP1559FeeStructure()
.map(result -> updateEIP1559Realm(result, currentChainId))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(r -> { if (!r) Timber.d("Fail to update fees"); }, this::handleError).isDisposed();
}
private Single<Boolean> useNodeFallback(Boolean updated)
{
if (updated) return Single.fromCallable(() -> true);
else
//also update EIP1559 if required and we haven't previously determined there's no EIP1559 support
if (!noEIP1559.contains(currentChainId))
{
return useNodeEstimate();
getEIP1559FeeStructure()
.map(result -> updateEIP1559Realm(result, currentChainId))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(r -> {
if (!r) Timber.d("Fail to update fees");
}, this::handleError).isDisposed();
}
}
@ -188,6 +192,11 @@ public class GasService implements ContractGasProvider
return new BigInteger(DEFAULT_GAS_LIMIT_FOR_NONFUNGIBLE_TOKENS);
}
private boolean nodeFetchValid()
{
return (System.currentTimeMillis() + FETCH_GAS_PRICE_INTERVAL_SECONDS * 1000) <= currentGasPriceTime;
}
private Single<Boolean> updateCurrentGasPrices()
{
String gasOracleAPI = EthereumNetworkRepository.getGasOracle(currentChainId);
@ -200,31 +209,39 @@ public class GasService implements ContractGasProvider
else
{
//use node to get chain price
return useNodeEstimate();
return useNodeEstimate(false);
}
}
private Single<Boolean> useNodeEstimate()
private Single<Boolean> useNodeEstimate(boolean updated)
{
if (EthereumNetworkRepository.hasGasOverride(currentChainId))
if (nodeFetchValid())
{
return Single.fromCallable(() -> true);
}
else if (EthereumNetworkRepository.hasGasOverride(currentChainId))
{
updateRealm(new GasPriceSpread(EthereumNetworkRepository.gasOverrideValue(currentChainId),
networkRepository.hasLockedGas(currentChainId)), currentChainId);
currentGasPriceTime = System.currentTimeMillis();
currentGasPrice = EthereumNetworkRepository.gasOverrideValue(currentChainId);
return Single.fromCallable(() -> true);
}
else
{
final long nodeId = currentChainId;
return Single.fromCallable(() -> web3j.ethGasPrice().send())
.map(price -> updateGasPrice(price, nodeId));
.map(price -> updateGasPrice(price, currentChainId, updated));
}
}
private Boolean updateGasPrice(EthGasPrice ethGasPrice, long chainId)
private Boolean updateGasPrice(EthGasPrice ethGasPrice, long chainId, boolean databaseUpdated)
{
currentGasPrice = ethGasPrice.getGasPrice();
updateRealm(new GasPriceSpread(currentGasPrice, networkRepository.hasLockedGas(chainId)), chainId);
currentGasPriceTime = System.currentTimeMillis();
if (!databaseUpdated)
{
updateRealm(new GasPriceSpread(currentGasPrice, networkRepository.hasLockedGas(chainId)), chainId);
}
return true;
}
@ -249,7 +266,6 @@ public class GasService implements ContractGasProvider
if (gps.isResultValid())
{
update = true;
currentGasPrice = gps.getSelectedGasFee(TXSpeed.STANDARD).gasPrice.maxFeePerGas;
currentLowGasPrice = gps.getBaseFee();
}
else
@ -321,15 +337,8 @@ public class GasService implements ContractGasProvider
BigInteger amount, Wallet wallet, final BigInteger defaultLimit)
{
updateChainId(chainId);
if (currentGasPrice == null)
{
return useNodeEstimate()
return useNodeEstimate(true)
.flatMap(com -> calculateGasEstimateInternal(transactionBytes, chainId, toAddress, amount, wallet, defaultLimit));
}
else
{
return calculateGasEstimateInternal(transactionBytes, chainId, toAddress, amount, wallet, defaultLimit);
}
}
public Single<GasEstimate> calculateGasEstimateInternal(byte[] transactionBytes, long chainId, String toAddress,
@ -423,6 +432,24 @@ public class GasService implements ContractGasProvider
}
private Single<Map<Integer, EIP1559FeeOracleResult>> getEIP1559FeeStructure()
{
return BlockNativeGasAPI.get(httpClient).fetchGasEstimates(currentChainId)
.flatMap(this::useCalculationIfRequired); //if interface doesn't have blocknative API then use calculation method
}
private Single<Map<Integer, EIP1559FeeOracleResult>> useCalculationIfRequired(Map<Integer, EIP1559FeeOracleResult> resultMap)
{
if (resultMap.size() > 0)
{
return Single.fromCallable(() -> resultMap);
}
else
{
return getEIP1559FeeStructureCalculation();
}
}
private Single<Map<Integer, EIP1559FeeOracleResult>> getEIP1559FeeStructureCalculation()
{
return getChainFeeHistory(100, "latest", "")
.flatMap(feeHistory -> SuggestEIP1559Kt.SuggestEIP1559(this, feeHistory));
@ -487,6 +514,7 @@ public class GasService implements ContractGasProvider
catch (org.json.JSONException j)
{
Timber.e("Note: " + info.getShortName() + " does not appear to have EIP1559 support");
noEIP1559.add(info.chainId);
}
catch (Exception e)
{

@ -1,6 +1,5 @@
package com.alphawallet.app.ui;
import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID;
import android.content.Context;
@ -8,6 +7,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@ -38,7 +38,7 @@ import com.alphawallet.app.repository.entity.RealmGasSpread;
import com.alphawallet.app.repository.entity.RealmTokenTicker;
import com.alphawallet.app.service.TickerService;
import com.alphawallet.app.ui.widget.entity.GasSettingsCallback;
import com.alphawallet.app.ui.widget.entity.GasSpeed2;
import com.alphawallet.app.ui.widget.entity.GasSpeed;
import com.alphawallet.app.ui.widget.entity.GasWarningLayout;
import com.alphawallet.app.util.BalanceUtils;
import com.alphawallet.app.util.Utils;
@ -61,16 +61,12 @@ import io.realm.RealmQuery;
public class GasSettingsActivity extends BaseActivity implements GasSettingsCallback
{
public static final int GAS_PRECISION = 5; //5 dp for gas
GasSettingsViewModel viewModel;
private GasSliderView gasSliderView;
private CustomAdapter adapter;
private GasPriceSpread gasSpread;
private Realm1559Gas realmGasSpread;
private RealmGasSpread realmLegacyGasSpread;
private TXSpeed currentGasSpeedIndex = TXSpeed.STANDARD;
private long chainId;
private BigDecimal presetGasLimit;
@ -82,6 +78,7 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
private long minGasPrice;
private boolean gasWarningShown;
private boolean isUsing1559;
private TextView baseGasFeeText;
private enum Warning
{
@ -106,6 +103,7 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
RecyclerView recyclerView = findViewById(R.id.list);
gasWarning = findViewById(R.id.gas_warning_bubble);
insufficientWarning = findViewById(R.id.insufficient_bubble);
baseGasFeeText = findViewById(R.id.text_base_price);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
viewModel = new ViewModelProvider(this)
@ -135,15 +133,18 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
recyclerView.setAdapter(adapter);
gasSliderView.setCallback(this);
LinearLayout baseGasFeeLayout = findViewById(R.id.text_base_price_layout);
// start listening for gas price updates
if (isUsing1559)
{
startGasListener();
baseGasFeeLayout.setVisibility(View.VISIBLE);
}
else
{
startLegacyGasListener();
gasSliderView.usingLegacyGas();
baseGasFeeLayout.setVisibility(View.GONE);
}
gasWarningShown = false;
@ -192,10 +193,14 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
{
gasSpread = new GasPriceSpread(this, gasSpread, gs.getResult());
gasSliderView.initGasPriceMax(gasSpread.getQuickestGasSpeed().gasPrice);
GasSpeed2 custom = gasSpread.getSelectedGasFee(TXSpeed.CUSTOM);
updateCustomElement(custom.gasPrice.maxFeePerGas, custom.gasPrice.maxPriorityFeePerGas, customGasLimit.toBigInteger());
GasSpeed custom = gasSpread.getSelectedGasFee(TXSpeed.CUSTOM);
updateCustomElement(custom.gasPrice.maxFeePerGas, custom.gasPrice.priorityFee, customGasLimit.toBigInteger());
gasSliderView.initGasPrice(custom);
//update base price
String baseFeePrice = getCleanAmount(gasSpread.getQuickestGasSpeed().gasPrice.baseFee);
baseGasFeeText.setText(baseFeePrice);
//if we have mainnet then show timings, otherwise no timing, if the token has fiat value, show fiat value of gas, so we need the ticker
adapter.notifyDataSetChanged();
}
@ -204,8 +209,8 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
{
gasSpread = new GasPriceSpread(this, gasSpread, gs.getTimeStamp(), gs.getGasFees(), gs.isLocked());
gasSliderView.initGasPriceMax(gasSpread.getQuickestGasSpeed().gasPrice);
GasSpeed2 custom = gasSpread.getSelectedGasFee(TXSpeed.CUSTOM);
updateCustomElement(custom.gasPrice.maxFeePerGas, custom.gasPrice.maxPriorityFeePerGas, customGasLimit.toBigInteger());
GasSpeed custom = gasSpread.getSelectedGasFee(TXSpeed.CUSTOM);
updateCustomElement(custom.gasPrice.maxFeePerGas, custom.gasPrice.priorityFee, customGasLimit.toBigInteger());
gasSliderView.initGasPrice(custom);
//if we have mainnet then show timings, otherwise no timing, if the token has fiat value, show fiat value of gas, so we need the ticker
@ -232,16 +237,16 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
public void onBackPressed()
{
Intent result = new Intent();
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
result.putExtra(C.EXTRA_SINGLE_ITEM, currentGasSpeedIndex.ordinal());
result.putExtra(C.EXTRA_GAS_LIMIT, customGasLimit.toString());
result.putExtra(C.EXTRA_NONCE, gasSliderView.getNonce());
result.putExtra(C.EXTRA_AMOUNT, gs.seconds);
GasSpeed2 custom = gasSpread.getSelectedGasFee(TXSpeed.CUSTOM);
GasSpeed custom = gasSpread.getSelectedGasFee(TXSpeed.CUSTOM);
result.putExtra(C.EXTRA_GAS_PRICE, custom.gasPrice.maxFeePerGas.toString());
result.putExtra(C.EXTRA_MIN_GAS_PRICE, custom.gasPrice.maxPriorityFeePerGas.toString());
result.putExtra(C.EXTRA_MIN_GAS_PRICE, custom.gasPrice.priorityFee.toString());
setResult(RESULT_OK, result);
finish();
@ -277,6 +282,27 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
adapter.notifyItemChanged(TXSpeed.CUSTOM.ordinal());
}
private String getCleanAmount(BigInteger amountInWei)
{
String convertedAmount;
BigDecimal ethAmount = Convert.fromWei(new BigDecimal(amountInWei), Convert.Unit.ETHER);
BigDecimal gweiAmount = BalanceUtils.weiToGweiBI(amountInWei);
if (BalanceUtils.requiresSmallGweiValueSuffix(ethAmount))
{
convertedAmount = BalanceUtils.getSlidingBaseValue(new BigDecimal(amountInWei), 18, GAS_PRECISION);
}
else if (gweiAmount.compareTo(BigDecimal.valueOf(2)) < 0)
{
convertedAmount = BalanceUtils.weiToGwei(new BigDecimal(amountInWei), 2);
}
else
{
convertedAmount = BalanceUtils.weiToGwei(new BigDecimal(amountInWei), 2);
}
return convertedAmount;
}
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.GasSpeedHolder>
{
private final Token baseCurrency;
@ -295,15 +321,20 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
{
final MaterialRadioButton radio;
final TextView speedName;
final TextView speedGwei;
final TextView basePriceText;
final TextView speedCostEth;
final TextView speedCostFiat;
final TextView speedTime;
final TextView priorityFee;
final TextView currencySymbol;
final View itemLayout;
final LinearLayout warning;
final TextView warningText;
final LinearLayout baseFeeLayout;
final LinearLayout maxFeeLayout;
final LinearLayout priorityFeeLayout;
final TextView baseFeeValue;
final TextView maxFeeValue;
final TextView priorityFeeValue;
GasSpeedHolder(View view)
{
@ -314,10 +345,30 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
speedCostEth = view.findViewById(R.id.text_speed_cost_eth);
speedTime = view.findViewById(R.id.text_speed_time);
itemLayout = view.findViewById(R.id.layout_list_item);
speedGwei = view.findViewById(R.id.text_gwei);
priorityFee = view.findViewById(R.id.text_priority_fee);
if (!isUsing1559) priorityFee.setVisibility(View.GONE);
basePriceText = view.findViewById(R.id.text_base_fee);
currencySymbol = view.findViewById(R.id.text_currency);
baseFeeLayout = view.findViewById(R.id.base_fee_layout);
maxFeeLayout = view.findViewById(R.id.max_fee_layout);
priorityFeeLayout = view.findViewById(R.id.priority_fee_layout);
baseFeeValue = view.findViewById(R.id.text_base_fee_value);
maxFeeValue = view.findViewById(R.id.text_max_fee_value);
priorityFeeValue = view.findViewById(R.id.text_priority_value);
if (!isUsing1559)
{
priorityFeeLayout.setVisibility(View.GONE);
maxFeeLayout.setVisibility(View.GONE);
baseFeeLayout.setVisibility(View.VISIBLE);
basePriceText.setText(R.string.label_gas_price);
}
else
{
priorityFeeLayout.setVisibility(View.VISIBLE);
maxFeeLayout.setVisibility(View.VISIBLE);
basePriceText.setText(R.string.base_fee);
baseFeeLayout.setVisibility(View.GONE);
((TextView)view.findViewById(R.id.text_max_fee_currency)).setText(TickerService.getCurrencySymbol());
}
warning = view.findViewById(R.id.layout_speed_warning);
warningText = view.findViewById(R.id.text_speed_warning);
@ -334,6 +385,46 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
speedName.setTypeface(ResourcesCompat.getFont(getApplicationContext(), R.font.font_regular));
}
}
public Pair<String, BigDecimal> handleCustomSpeed(GasSpeed gs, TXSpeed position)
{
String baseGasAmount = "";
BigDecimal useGasLimit = BigDecimal.ZERO;
if (gs.seconds == 0)
{
blankCustomHolder(this);
setCustomGasDetails(position);
}
else
{
//recalculate the custom speed every time it's updated
gs.seconds = getExpectedTransactionTime(gs.gasPrice.maxFeePerGas);
baseGasAmount = context.getString(R.string.bracketed, context.getString(R.string.set_your_speed));
useGasLimit = customGasLimit;
speedName.setVisibility(View.GONE);
warning.setVisibility(View.VISIBLE);
switch (warningType)
{
case OFF:
warning.setVisibility(View.GONE);
speedName.setVisibility(View.VISIBLE);
break;
case LOW:
warningText.setText(R.string.speed_too_low);
break;
case HIGH:
warningText.setText(R.string.speed_high_gas);
break;
case INSUFFICIENT:
warningText.setText(R.string.insufficient_gas);
break;
}
}
return new Pair<>(baseGasAmount, useGasLimit);
}
}
private CustomAdapter(Context ctx)
@ -347,7 +438,7 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
{
BigDecimal useGasLimit = presetGasLimit;
TXSpeed position = gasSpread.getSelectedPosition(holder.getAbsoluteAdapterPosition());// TXSpeed.values()[holder.getAbsoluteAdapterPosition()];
GasSpeed2 gs = gasSpread.getSelectedGasFee(position);
GasSpeed gs = gasSpread.getSelectedGasFee(position);
holder.speedName.setText(gs.speed);
holder.speedName.setVisibility(View.VISIBLE);
@ -374,90 +465,52 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
notifyItemChanged(holder.getAbsoluteAdapterPosition());
});
BigDecimal maxGas = BalanceUtils.weiToGweiBI(gs.gasPrice.maxFeePerGas);
String speedGwei;
BigDecimal ethAmount = Convert.fromWei(new BigDecimal(gs.gasPrice.maxFeePerGas), Convert.Unit.ETHER);
if (BalanceUtils.requiresSmallGweiValueSuffix(ethAmount))
{
speedGwei = context.getString(R.string.token_balance,
BalanceUtils.getSlidingBaseValue(new BigDecimal(gs.gasPrice.maxFeePerGas), 18, GAS_PRECISION),
baseCurrency.getSymbol());
}
else if (maxGas.compareTo(BigDecimal.valueOf(2)) < 0)
{
speedGwei = BalanceUtils.weiToGwei(new BigDecimal(gs.gasPrice.maxFeePerGas), 2);
}
else
{
speedGwei = BalanceUtils.weiToGweiBI(gs.gasPrice.maxFeePerGas).toBigInteger().toString();
}
String priorityFee = BalanceUtils.weiToGwei(new BigDecimal(gs.gasPrice.maxPriorityFeePerGas), 2);
String baseGasAmount = getCleanAmount(gs.gasPrice.baseFee);
String maxGasAmount = getCleanAmount(gs.gasPrice.maxFeePerGas);
String priorityFee = BalanceUtils.displayDigitPrecisionValue(new BigDecimal(gs.gasPrice.priorityFee), Convert.Unit.GWEI.getFactor(), 2); //convert wei to Gwei then truncate to 2 digits
if (position == TXSpeed.CUSTOM)
{
if (gs.seconds == 0)
{
blankCustomHolder(holder);
setCustomGasDetails(position);
return;
}
else
{
//recalculate the custom speed every time it's updated
gs.seconds = getExpectedTransactionTime(gs.gasPrice.maxFeePerGas);
speedGwei = context.getString(R.string.bracketed, context.getString(R.string.set_your_speed));
useGasLimit = customGasLimit;
}
holder.speedName.setVisibility(View.GONE);
holder.warning.setVisibility(View.VISIBLE);
switch (warningType)
{
case OFF:
holder.warning.setVisibility(View.GONE);
holder.speedName.setVisibility(View.VISIBLE);
break;
case LOW:
holder.warningText.setText(R.string.speed_too_low);
break;
case HIGH:
holder.warningText.setText(R.string.speed_high_gas);
break;
case INSUFFICIENT:
holder.warningText.setText(R.string.insufficient_gas);
break;
}
Pair<String, BigDecimal> customReturn = holder.handleCustomSpeed(gs, position);
if (!TextUtils.isEmpty(customReturn.first) && !customReturn.second.equals(BigDecimal.ZERO))
{
baseGasAmount = customReturn.first;
useGasLimit = customReturn.second;
}
}
BigDecimal gasFee = new BigDecimal(gs.gasPrice.maxFeePerGas).multiply(useGasLimit);
//Calculate estimate gas: for 1559 it's base fee + max priority, or if legacy, it's just the gas price
BigDecimal gasFee = gs.calculateGasFee(useGasLimit, isUsing1559);
BigDecimal gasFeeMax = gs.calculateMaxGasFee(useGasLimit);
String gasAmountInBase = BalanceUtils.getSlidingBaseValue(gasFee, baseCurrency.tokenInfo.decimals, GAS_PRECISION);
String gasAmountInBase = BalanceUtils.getScaledValue(gasFee, baseCurrency.tokenInfo.decimals, 18);
String gasMaxAmountInBase = BalanceUtils.getScaledValue(gasFeeMax, baseCurrency.tokenInfo.decimals, 18);
if (gasAmountInBase.equals("0"))
gasAmountInBase = "0.00001"; //NB no need to allow for zero gas chains; this activity wouldn't appear
String displayStr = context.getString(R.string.gas_amount, gasAmountInBase, baseCurrency.getSymbol());
String displayTime = context.getString(R.string.gas_time_suffix,
Utils.shortConvertTimePeriodInSeconds(gs.seconds, context));
String fiatStr = getGasCost(gasAmountInBase);
String fiatMaxStr = getGasCost(gasMaxAmountInBase);
String buildGasMax = (isUsing1559 ? context.getString(R.string.gas_max) : context.getString(R.string.label_gas_price)) + ": " + context.getString(R.string.delete_session, speedGwei);
holder.speedGwei.setText(buildGasMax);
holder.speedCostEth.setText(context.getString(R.string.gas_fiat_suffix, gasAmountInBase, baseCurrency.getSymbol()));
String baseFeeStr = isUsing1559 ? baseGasAmount : maxGasAmount;
holder.baseFeeValue.setText(baseFeeStr);
holder.speedCostEth.setText(context.getString(R.string.gas_fiat_suffix,
BalanceUtils.displayDigitPrecisionValue(gasFee, baseCurrency.tokenInfo.decimals, 2), baseCurrency.getSymbol()));
holder.speedTime.setText(displayTime);
String buildPriorityFee = context.getString(R.string.priority_fee) + ": " + context.getString(R.string.delete_session, priorityFee);
holder.priorityFee.setText(buildPriorityFee);
holder.priorityFeeValue.setText(priorityFee);
if (fiatStr.length() > 0)
{
holder.speedCostFiat.setVisibility(View.VISIBLE);
holder.speedCostFiat.setText(fiatStr);
holder.currencySymbol.setText(TickerService.getCurrencySymbol());
holder.maxFeeValue.setText(fiatMaxStr);
}
else
{
holder.currencySymbol.setVisibility(View.GONE);
holder.speedCostFiat.setVisibility(View.GONE);
holder.maxFeeLayout.setVisibility(View.GONE);
}
setCustomGasDetails(position);
@ -482,7 +535,7 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
private void blankCustomHolder(GasSpeedHolder holder)
{
holder.speedGwei.setText(context.getString(R.string.bracketed, context.getString(R.string.set_your_speed)));
holder.basePriceText.setText(context.getString(R.string.bracketed, context.getString(R.string.set_your_speed)));
holder.speedCostEth.setText("");
holder.speedCostFiat.setText("");
holder.speedTime.setText("");
@ -505,7 +558,8 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
//calculate equivalent fiat
double cryptoRate = Double.parseDouble(rtt.getPrice());
double cryptoAmount = Double.parseDouble(gasAmountInBase);
costStr = TickerService.getCurrencyString(cryptoAmount * cryptoRate);
//costStr = TickerService.getCurrencyString(cryptoAmount * cryptoRate);
costStr = BalanceUtils.genCurrencyString(cryptoAmount * cryptoRate, "");
}
}
catch (Exception e)
@ -528,7 +582,7 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
}
else
{
GasSpeed2 gs = gasSpread.getSelectedGasFee(position);
GasSpeed gs = gasSpread.getSelectedGasFee(position);
gasSliderView.initGasPriceMax(gs.gasPrice);
gasSliderView.setVisibility(View.GONE);
hideGasWarning();
@ -572,8 +626,8 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
TXSpeed nextSpeed = gasSpread.getNextSpeed(speed);
if (nextSpeed == TXSpeed.CUSTOM) break;
GasSpeed2 ug = gasSpread.getSelectedGasFee(speed);
GasSpeed2 lg = gasSpread.getSelectedGasFee(nextSpeed);
GasSpeed ug = gasSpread.getSelectedGasFee(speed);
GasSpeed lg = gasSpread.getSelectedGasFee(nextSpeed);
double lowerBound = lg.gasPrice.maxFeePerGas.doubleValue();
double upperBound = ug.gasPrice.maxFeePerGas.doubleValue();
if (lowerBound <= dGasPrice && (upperBound >= dGasPrice))
@ -705,5 +759,4 @@ public class GasSettingsActivity extends BaseActivity implements GasSettingsCall
gasWarning.setVisibility(View.GONE);
}
}
}

@ -0,0 +1,91 @@
package com.alphawallet.app.ui.widget.entity;
import android.os.Parcel;
import android.os.Parcelable;
import com.alphawallet.app.entity.EIP1559FeeOracleResult;
import com.alphawallet.app.util.BalanceUtils;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* Created by JB on 20/01/2022.
*/
public class GasSpeed implements Parcelable
{
public final String speed;
public long seconds;
public final EIP1559FeeOracleResult gasPrice;
public GasSpeed(String speed, long seconds, EIP1559FeeOracleResult gasPrice)
{
this.speed = speed;
this.seconds = seconds;
this.gasPrice = gasPrice;
}
public GasSpeed(Parcel in)
{
speed = in.readString();
seconds = in.readLong();
gasPrice = in.readParcelable(EIP1559FeeOracleResult.class.getClassLoader());
}
public GasSpeed(String speed, long seconds, BigInteger gasPrice)
{
this.speed = speed;
this.seconds = seconds;
this.gasPrice = new EIP1559FeeOracleResult(gasPrice, BigInteger.ZERO, BigInteger.ZERO);
}
@Override
public int describeContents()
{
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeString(speed);
dest.writeLong(seconds);
dest.writeParcelable(gasPrice, flags);
}
public static final Creator<GasSpeed> CREATOR = new Creator<GasSpeed>() {
@Override
public GasSpeed createFromParcel(Parcel in) {
return new GasSpeed(in);
}
@Override
public GasSpeed[] newArray(int size) {
return new GasSpeed[size];
}
};
public BigDecimal calculateGasFee(BigDecimal useGasLimit, boolean isUsing1559)
{
if (isUsing1559)
{
return new BigDecimal(gasPrice.baseFee.add(gasPrice.priorityFee)).multiply(useGasLimit);
}
else
{
return new BigDecimal(gasPrice.maxFeePerGas).multiply(useGasLimit);
}
}
public BigDecimal calculateMaxGasFee(BigDecimal useGasLimit)
{
if (gasPrice.maxFeePerGas != null && gasPrice.maxFeePerGas.compareTo(BigInteger.ZERO) > 0)
{
return new BigDecimal(gasPrice.maxFeePerGas).multiply(useGasLimit);
}
else
{
return BigDecimal.ZERO;
}
}
}

@ -1,65 +0,0 @@
package com.alphawallet.app.ui.widget.entity;
import android.os.Parcel;
import android.os.Parcelable;
import com.alphawallet.app.entity.EIP1559FeeOracleResult;
import java.math.BigInteger;
/**
* Created by JB on 20/01/2022.
*/
public class GasSpeed2 implements Parcelable
{
public final String speed;
public long seconds;
public final EIP1559FeeOracleResult gasPrice;
public GasSpeed2(String speed, long seconds, EIP1559FeeOracleResult gasPrice)
{
this.speed = speed;
this.seconds = seconds;
this.gasPrice = gasPrice;
}
public GasSpeed2(Parcel in)
{
speed = in.readString();
seconds = in.readLong();
gasPrice = in.readParcelable(EIP1559FeeOracleResult.class.getClassLoader());
}
public GasSpeed2(String speed, long seconds, BigInteger gasPrice)
{
this.speed = speed;
this.seconds = seconds;
this.gasPrice = new EIP1559FeeOracleResult(gasPrice, BigInteger.ZERO, BigInteger.ZERO);
}
@Override
public int describeContents()
{
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeString(speed);
dest.writeLong(seconds);
dest.writeParcelable(gasPrice, flags);
}
public static final Creator<GasSpeed2> CREATOR = new Creator<GasSpeed2>() {
@Override
public GasSpeed2 createFromParcel(Parcel in) {
return new GasSpeed2(in);
}
@Override
public GasSpeed2[] newArray(int size) {
return new GasSpeed2[size];
}
};
}

@ -16,13 +16,11 @@ public class BalanceUtils
private static final String weiInEth = "1000000000000000000";
private static final int showDecimalPlaces = 5;
private static final int slidingDecimalPlaces = 2;
private static final BigDecimal displayThresholdMillis = BigDecimal.ONE.divide(BigDecimal.valueOf(1000000), 18, RoundingMode.DOWN);
private static final BigDecimal oneGwei = BigDecimal.ONE.divide(Convert.Unit.GWEI.getWeiFactor(), 18, RoundingMode.DOWN); //BigDecimal.valueOf(0.000000001);
private static final BigDecimal displayThresholdEth = Convert.fromWei(Convert.toWei(BigDecimal.valueOf(0.01), Convert.Unit.GWEI), Convert.Unit.ETHER); //Convert. toWei(BigDecimal.valueOf(0.01), Convert.Unit.ETHER);
private static final BigDecimal oneGwei = BigDecimal.ONE.divide(Convert.Unit.GWEI.getWeiFactor(), 18, RoundingMode.DOWN); // BigDecimal.valueOf(0.000000001);
public static final String MACRO_PATTERN = "###,###,###,###,##0";
public static final String CURRENCY_PATTERN = MACRO_PATTERN + ".00";
private static final double ONE_BILLION = 1000000000.0;
private static String getDigitalPattern(int precision)
{
return getDigitalPattern(precision, 0);
@ -39,6 +37,42 @@ public class BalanceUtils
return new DecimalFormat(pattern, standardisedNumericFormat);
}
/**
* This method displays at least `numberOfDigits` of decimals
* @param value
* @param numberOfDigits
* @return
*/
public static String displayDigitPrecisionValue(BigDecimal value, int decimalReduction, int numberOfDigits)
{
//divide down to value
value = value.divide(BigDecimal.valueOf(10).pow(decimalReduction));
if (value.compareTo(BigDecimal.ONE) > 0)
{
return getScaledValue(value, 18, 2);
}
else
{
//how many zeros?
int zeros = calcZeros(value.doubleValue());
//scale to zeros + numberOfDigits
String pattern = getDigitalPattern(zeros + numberOfDigits - 1);
return scaledValue(value, pattern, 0, 0);
}
}
private static int calcZeros(double value)
{
int zeros = 0;
while (value < 1.0 && value > 0.0)
{
value *= 10.0;
zeros++;
}
return zeros;
}
private static String getDigitalPattern(int precision, int fixed)
{
StringBuilder sb = new StringBuilder();
@ -197,12 +231,12 @@ public class BalanceUtils
public static boolean requiresSmallValueSuffix(BigDecimal correctedValue)
{
return correctedValue.compareTo(displayThresholdMillis) < 0;
return correctedValue.compareTo(displayThresholdEth) < 0;
}
public static boolean requiresSmallGweiValueSuffix(BigDecimal ethAmount)
{
return ethAmount.compareTo(oneGwei) < 0;
return ethAmount.compareTo(displayThresholdEth) < 0;
}
private static boolean requiresSuffix(BigDecimal correctedValue, int dPlaces)
@ -357,9 +391,9 @@ public class BalanceUtils
{
return smallSuffixValue(correctedValue);
}
else if (correctedValue.compareTo(displayThresholdMillis) < 0)
else if (correctedValue.compareTo(displayThresholdEth) < 0)
{
returnValue = correctedValue.divide(displayThresholdMillis, slidingDecimalPlaces, RoundingMode.DOWN) + " m";
returnValue = correctedValue.divide(displayThresholdEth, slidingDecimalPlaces, RoundingMode.DOWN) + " m";
}
else if (requiresSuffix(correctedValue, dPlaces))
{
@ -378,8 +412,8 @@ public class BalanceUtils
private static String smallSuffixValue(BigDecimal correctedValue)
{
final BigDecimal displayThresholdMicro = BigDecimal.ONE.divide(BigDecimal.valueOf(1000000000), 18, RoundingMode.DOWN);
final BigDecimal displayThresholdNano = BigDecimal.ONE.divide(BigDecimal.valueOf(1000000000000L), 18, RoundingMode.DOWN);
final BigDecimal displayThresholdMicro = BigDecimal.ONE.divide(BigDecimal.valueOf(100000000L), 18, RoundingMode.DOWN);
final BigDecimal displayThresholdNano = BigDecimal.ONE.divide(BigDecimal.valueOf(100000000000L), 18, RoundingMode.DOWN);
BigDecimal weiAmount = Convert.toWei(correctedValue, Convert.Unit.ETHER);

@ -20,7 +20,7 @@ import com.alphawallet.app.C;
import com.alphawallet.app.R;
import com.alphawallet.app.entity.EIP1559FeeOracleResult;
import com.alphawallet.app.ui.widget.entity.GasSettingsCallback;
import com.alphawallet.app.ui.widget.entity.GasSpeed2;
import com.alphawallet.app.ui.widget.entity.GasSpeed;
import com.alphawallet.app.util.BalanceUtils;
import java.math.BigDecimal;
@ -270,12 +270,12 @@ public class GasSliderView extends RelativeLayout
}
@SuppressLint("SetTextI18n")
public void initGasPrice(GasSpeed2 gs)
public void initGasPrice(GasSpeed gs)
{
if (!limitInit)
{
BigDecimal gweiPrice = BalanceUtils.weiToGweiBI(gs.gasPrice.maxFeePerGas);
BigDecimal gweiPriorityFee = BalanceUtils.weiToGweiBI(gs.gasPrice.maxPriorityFeePerGas);
BigDecimal gweiPriorityFee = BalanceUtils.weiToGweiBI(gs.gasPrice.priorityFee);
gasPriceValue.setText(gweiPrice.setScale(1, RoundingMode.HALF_DOWN).toString());
setPriceSlider(gweiPrice);
priorityFeeValue.setText(gweiPriorityFee.setScale(2, RoundingMode.HALF_DOWN).toString());
@ -290,7 +290,7 @@ public class GasSliderView extends RelativeLayout
{
BigDecimal gweiPrice = new BigDecimal(gasPriceStr);
BigDecimal maxDefault = BalanceUtils.weiToGweiBI(maxPrice.maxFeePerGas).multiply(BigDecimal.valueOf(15.0));
BigDecimal gweiPriorityFee = BalanceUtils.weiToGweiBI(maxPrice.maxPriorityFeePerGas);
BigDecimal gweiPriorityFee = BalanceUtils.weiToGweiBI(maxPrice.priorityFee);
if (gweiPriorityFee.compareTo(BigDecimal.valueOf(4.0)) > 0)
{
maxPriorityFee = gweiPriorityFee.floatValue();

@ -24,12 +24,11 @@ import com.alphawallet.app.entity.tokens.Token;
import com.alphawallet.app.repository.TokensRealmSource;
import com.alphawallet.app.repository.entity.RealmGasSpread;
import com.alphawallet.app.repository.entity.RealmTokenTicker;
import com.alphawallet.app.walletconnect.AWWalletConnectClient;
import com.alphawallet.app.service.GasService;
import com.alphawallet.app.service.TickerService;
import com.alphawallet.app.service.TokensService;
import com.alphawallet.app.ui.GasSettingsActivity;
import com.alphawallet.app.ui.widget.entity.GasSpeed2;
import com.alphawallet.app.ui.widget.entity.GasSpeed;
import com.alphawallet.app.ui.widget.entity.GasWidgetInterface;
import com.alphawallet.app.util.BalanceUtils;
import com.alphawallet.app.util.Utils;
@ -214,7 +213,7 @@ public class GasWidget extends LinearLayout implements Runnable, GasWidgetInterf
BigInteger useGasLimit = getUseGasLimit();
boolean sufficientGas = true;
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
BigDecimal networkFee = new BigDecimal(gs.gasPrice.maxFeePerGas.multiply(useGasLimit));
Token base = tokensService.getTokenOrBase(token.tokenInfo.chainId, token.getWallet());
@ -258,7 +257,7 @@ public class GasWidget extends LinearLayout implements Runnable, GasWidgetInterf
private BigInteger calculateSendAllValue()
{
BigInteger sendAllValue;
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
BigDecimal networkFee = new BigDecimal(gs.gasPrice.maxFeePerGas.multiply(getUseGasLimit()));
if (isSendingAll)
@ -324,7 +323,7 @@ public class GasWidget extends LinearLayout implements Runnable, GasWidgetInterf
@Override
public void run()
{
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
if (gs == null || gs.gasPrice == null || gs.gasPrice.maxFeePerGas == null)
{
@ -389,7 +388,7 @@ public class GasWidget extends LinearLayout implements Runnable, GasWidgetInterf
@Override
public BigInteger getGasPrice(BigInteger defaultPrice)
{
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
return gs.gasPrice.maxFeePerGas;
}
@ -410,8 +409,8 @@ public class GasWidget extends LinearLayout implements Runnable, GasWidgetInterf
{
double dGasPrice = customGasPrice.doubleValue();
GasSpeed2 ug = gasSpread.getQuickestGasSpeed();
GasSpeed2 lg = gasSpread.getSlowestGasSpeed();
GasSpeed ug = gasSpread.getQuickestGasSpeed();
GasSpeed lg = gasSpread.getSlowestGasSpeed();
double lowerBound = lg.gasPrice.maxFeePerGas.doubleValue();
double upperBound = ug.gasPrice.maxFeePerGas.doubleValue();
@ -498,7 +497,7 @@ public class GasWidget extends LinearLayout implements Runnable, GasWidgetInterf
public long getExpectedTransactionTime()
{
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
return gs.seconds;
}

@ -28,7 +28,7 @@ import com.alphawallet.app.service.GasService;
import com.alphawallet.app.service.TickerService;
import com.alphawallet.app.service.TokensService;
import com.alphawallet.app.ui.GasSettingsActivity;
import com.alphawallet.app.ui.widget.entity.GasSpeed2;
import com.alphawallet.app.ui.widget.entity.GasSpeed;
import com.alphawallet.app.ui.widget.entity.GasWidgetInterface;
import com.alphawallet.app.util.BalanceUtils;
import com.alphawallet.app.util.Utils;
@ -188,7 +188,7 @@ public class GasWidget2 extends LinearLayout implements Runnable, GasWidgetInter
{
BigInteger useGasLimit = getUseGasLimit();
boolean sufficientGas = true;
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
//Calculate total network fee here:
BigDecimal networkFee = new BigDecimal(gs.gasPrice.maxFeePerGas.multiply(useGasLimit));
@ -289,11 +289,11 @@ public class GasWidget2 extends LinearLayout implements Runnable, GasWidgetInter
@Override
public void run()
{
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
if (gs == null) return;
Token baseCurrency = tokensService.getTokenOrBase(token.tokenInfo.chainId, token.getWallet());
BigInteger networkFee = gs.gasPrice.maxFeePerGas.multiply(getUseGasLimit());
BigInteger networkFee = (gs.gasPrice.baseFee.add(gs.gasPrice.priorityFee)).multiply(getUseGasLimit());
String gasAmountInBase = BalanceUtils.getSlidingBaseValue(new BigDecimal(networkFee), baseCurrency.tokenInfo.decimals, GasSettingsActivity.GAS_PRECISION);
if (gasAmountInBase.equals("0")) gasAmountInBase = "0.0001";
String displayStr = context.getString(R.string.gas_amount, gasAmountInBase, baseCurrency.getSymbol());
@ -357,22 +357,22 @@ public class GasWidget2 extends LinearLayout implements Runnable, GasWidgetInter
@Override
public BigInteger getGasPrice(BigInteger defaultPrice)
{
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
return gs.gasPrice.maxFeePerGas;
}
@Override
public BigInteger getGasMax()
{
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
return gs.gasPrice.maxFeePerGas;
}
@Override
public BigInteger getPriorityFee()
{
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
return gs.gasPrice.maxPriorityFeePerGas;
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
return gs.gasPrice.priorityFee;
}
@Override
@ -390,8 +390,8 @@ public class GasWidget2 extends LinearLayout implements Runnable, GasWidgetInter
private void checkCustomGasPrice(BigInteger customGasPrice)
{
double dGasPrice = customGasPrice.doubleValue();
GasSpeed2 ug = gasSpread.getQuickestGasSpeed();
GasSpeed2 lg = gasSpread.getSlowestGasSpeed();
GasSpeed ug = gasSpread.getQuickestGasSpeed();
GasSpeed lg = gasSpread.getSlowestGasSpeed();
if (resendGasPrice.compareTo(BigInteger.ZERO) > 0)
{
@ -484,7 +484,7 @@ public class GasWidget2 extends LinearLayout implements Runnable, GasWidgetInter
public long getExpectedTransactionTime()
{
GasSpeed2 gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
GasSpeed gs = gasSpread.getSelectedGasFee(currentGasSpeedIndex);
return gs.seconds;
}

@ -12,6 +12,31 @@
<include layout="@layout/layout_simple_toolbar" />
<LinearLayout
android:id="@+id/text_base_price_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
android:layout_margin="6dp">
<TextView
android:id="@+id/text_base_price_title"
style="@style/Aw.Typography.Title.SemiBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/base_fee" />
<TextView
android:id="@+id/text_base_price"
android:layout_marginStart="6dp"
style="@style/Aw.Typography.Control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="14.92" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
@ -170,4 +195,4 @@
</LinearLayout>
</ScrollView>
</ScrollView>

@ -1,126 +1,204 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content">
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/layout_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical"
android:paddingStart="15dp"
android:paddingTop="15dp"
android:paddingBottom="15dp">
android:id="@+id/layout_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical"
android:paddingStart="15dp"
android:paddingTop="15dp"
android:paddingBottom="15dp">
<LinearLayout
android:id="@+id/layout_speed_warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="3.1"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
android:id="@+id/layout_speed_warning"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="3.1"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:src="@drawable/ic_red_warning" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:src="@drawable/ic_red_warning" />
<TextView
android:id="@+id/text_speed_warning"
style="@style/Aw.Typography.Title.SemiBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/speed_too_low"
android:textColor="?colorError" />
android:id="@+id/text_speed_warning"
style="@style/Aw.Typography.Title.SemiBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/speed_too_low"
android:textColor="?colorError" />
</LinearLayout>
<TextView
android:id="@+id/text_speed"
style="@style/Aw.Typography.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Average" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal">
<TextView
android:id="@+id/text_speed_cost"
style="@style/Aw.Typography.Caption.SemiBold"
android:id="@+id/text_speed"
style="@style/Aw.Typography.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="3dp"
android:lineSpacingExtra="7sp"
tools:text="$0.55" />
tools:text="Average" />
<TextView
android:id="@+id/text_speed_cost_eth"
style="@style/Aw.Typography.Sub"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="(0.00362 ETH)" />
android:layout_marginTop="4dp"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/text_currency"
style="@style/Aw.Typography.Caption.SemiBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingExtra="7sp"
tools:text="$" />
<TextView
android:id="@+id/text_gwei"
style="@style/Aw.Typography.Sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
tools:text="Gas Price/Max Fee: 45" />
android:id="@+id/text_speed_cost"
style="@style/Aw.Typography.Caption.SemiBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="3dp"
android:lineSpacingExtra="7sp"
tools:text="0.55" />
<TextView
android:id="@+id/text_priority_fee"
style="@style/Aw.Typography.Sub"
android:layout_width="wrap_content"
android:id="@+id/text_speed_cost_eth"
style="@style/Aw.Typography.Sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="0.00362ETH" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="3dp"
tools:text="Priority Fee: 2.00" />
android:orientation="horizontal">
<LinearLayout
android:id="@+id/base_fee_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="3dp"
android:gravity="center">
<TextView
android:id="@+id/text_base_fee"
style="@style/Aw.Typography.Sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Gas Price/Base Fee"
tools:visibility="visible" />
<TextView
android:id="@+id/text_base_fee_value"
style="@style/Aw.Typography.Caption"
android:layout_marginStart="2dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="12.03" />
</LinearLayout>
<LinearLayout
android:id="@+id/max_fee_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="3dp"
android:visibility="gone"
tools:visibility="visible"
android:gravity="center">
<TextView
android:id="@+id/text_max_fee"
style="@style/Aw.Typography.Sub"
android:text="@string/gas_max"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text_max_fee_currency"
style="@style/Aw.Typography.Caption"
android:layout_marginStart="2dp"
tools:text="$"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text_max_fee_value"
style="@style/Aw.Typography.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="0.59" />
</LinearLayout>
<LinearLayout
android:id="@+id/priority_fee_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="3dp"
android:visibility="gone"
tools:visibility="visible"
android:gravity="center">
<TextView
android:id="@+id/text_priority_fee"
android:text="@string/priority_fee"
style="@style/Aw.Typography.Sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text_priority_value"
style="@style/Aw.Typography.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
tools:text="0.18" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:orientation="horizontal"
android:paddingEnd="@dimen/tiny_8">
<TextView
android:id="@+id/text_speed_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/tiny_8"
tools:text=" ≈ 2 minutes" />
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:orientation="horizontal"
android:paddingEnd="@dimen/tiny_8">
<TextView
android:id="@+id/text_speed_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/tiny_8"
tools:text=" ≈ 2 minutes" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:clickable="false"
android:focusable="false" />
android:id="@+id/radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:clickable="false"
android:focusable="false" />
</LinearLayout>
</RelativeLayout>
</RelativeLayout>

@ -675,7 +675,7 @@
<string name="gas_price_widget">Precio del gas: %1$s</string>
<string name="gas_max">Max Fee</string>
<string name="priority_fee">Priority Fee</string>
<string name="base_fee">Base Fee: %1$s</string>
<string name="base_fee">Base Fee</string>
<string name="gas_message">Fees are based on current Ethereum blockchain network load. They are not up to AlphaWallet. AlphaWallet uses the %1$s live oracle and updates the gas prices every 15 seconds.</string>
<string name="set_your_speed">Establece tu propio</string>
<string name="nonce_optional">Nonce (opcional)</string>

@ -690,7 +690,7 @@
<string name="gas_price_widget">Prix du Gas: %1$s</string>
<string name="gas_max">Max Fee</string>
<string name="priority_fee">Priority Fee</string>
<string name="base_fee">Base Fee: %1$s</string>
<string name="base_fee">Base Fee</string>
<string name="gas_message">Les frais sont basés sur la charge actuelle du réseau de la blockchain Ethereum. Ils ne dépendent pas d\'AlphaWallet. AlphaWallet utilise l\'oracle en direct de %1$s et met à jour les prix du gas toutes les 15 secondes.</string>
<string name="set_your_speed">Définir Le Votre</string>
<string name="nonce_optional">Nonce (optionnel)</string>

@ -690,7 +690,7 @@
<string name="gas_price_widget">Harga Gas: %1$s</string>
<string name="gas_max">Biaya Maksimum</string>
<string name="priority_fee">Biaya Prioritas</string>
<string name="base_fee">Biaya Dasar: %1$s</string>
<string name="base_fee">Biaya Dasar</string>
<string name="gas_message">Biaya didasarkan pada beban jaringan blockchain Ethereum saat ini. Mereka tidak bergantung pada AlphaWallet. AlphaWallet menggunakan oracle langsung %1$s dan memperbarui harga gas setiap 15 detik.</string>
<string name="set_your_speed">Tetapkan Milik Anda Sendiri</string>
<string name="nonce_optional">Nonce (pilihan)</string>

@ -702,7 +702,7 @@
<string name="gas_price_widget">Gas Price: %1$s</string>
<string name="gas_max">Max Fee</string>
<string name="priority_fee">Priority Fee</string>
<string name="base_fee">Base Fee: %1$s</string>
<string name="base_fee">Base Fee</string>
<string name="gas_message">ငနကခမသည AlphaWallet နမသက Etheruem Blockchain ၏ကရကကအပတညသည။ AlphaWallet သညငနကခက ၁၅စကကနတက %1$s မကက၍ အသသည</string>
<string name="set_your_speed">ကအမသတမည</string>
<string name="nonce_optional">Nonce (optional)</string>

@ -693,7 +693,7 @@
<string name="gas_price_widget">Gas Price: %1$s</string>
<string name="gas_max">Max Fee</string>
<string name="priority_fee">Priority Fee</string>
<string name="base_fee">Base Fee: %1$s</string>
<string name="base_fee">Base Fee</string>
<string name="gas_message">Fees are based on current Ethereum blockchain network load. They are not up to AlphaWallet. AlphaWallet uses the %1$s live oracle and updates the gas prices every 15 seconds.</string>
<string name="set_your_speed">Set Your Own</string>
<string name="nonce_optional">Nonce (optional)</string>

@ -676,7 +676,7 @@
<string name="gas_price_widget">燃料价格: %1$s</string>
<string name="gas_max">Max Fee</string>
<string name="priority_fee">Priority Fee</string>
<string name="base_fee">Base Fee: %1$s</string>
<string name="base_fee">Base Fee</string>
<string name="gas_message">Fees are based on current Ethereum blockchain network load. They are not up to AlphaWallet. AlphaWallet uses the %1$s live oracle and updates the gas prices every 15 seconds.</string>
<string name="set_your_speed">自己设定</string>
<string name="nonce_optional">Nonce (可选的)</string>

@ -724,8 +724,8 @@
<string name="gas_time_suffix" translatable="false"> ≈ %1$s</string>
<string name="confirm_transaction">Confirm Transaction</string>
<string name="balance">Balance</string>
<string name="speed_average">Average</string>
<string name="speed_rapid">Rapid</string>
<string name="speed_average">Standard</string>
<string name="speed_rapid">Aggressive</string>
<string name="speed_fast">Fast</string>
<string name="speed_slow">Slow</string>
<string name="speed_custom">Custom</string>
@ -737,7 +737,7 @@
<string name="gas_price_widget">Gas Price: %1$s</string>
<string name="gas_max">Max Fee</string>
<string name="priority_fee">Priority Fee</string>
<string name="base_fee">Base Fee: %1$s</string>
<string name="base_fee">Base Fee</string>
<string name="gas_message">Fees are based on current Ethereum blockchain network load. They are not up to AlphaWallet. AlphaWallet uses the %1$s live oracle and updates the gas prices every 15 seconds.</string>
<string name="set_your_speed">Set Your Own</string>
<string name="nonce_optional">Nonce (optional)</string>

@ -125,4 +125,10 @@ public class KeyProviderMockImpl implements KeyProvider
{
return FAKE_KEY_FOR_TESTING;
}
@Override
public String getBlockNativeKey()
{
return FAKE_KEY_FOR_TESTING;
}
}

Loading…
Cancel
Save