() {}.type, LongTypeAdapter())
.registerTypeAdapter(Int::class.java, IntTypeAdapter())
diff --git a/core/src/main/java/org/p2p/core/rpc/Interfaces.kt b/core/src/main/java/org/p2p/core/rpc/Interfaces.kt
index 71975a08f8..4ac11001f6 100644
--- a/core/src/main/java/org/p2p/core/rpc/Interfaces.kt
+++ b/core/src/main/java/org/p2p/core/rpc/Interfaces.kt
@@ -6,7 +6,7 @@ import io.reactivex.Single
data class RpcResponse(
@SerializedName("id")
- val id: Int,
+ val id: String,
@SerializedName("result")
val result: JsonElement?,
@SerializedName("error")
diff --git a/core/src/main/java/org/p2p/core/rpc/JsonRpc.kt b/core/src/main/java/org/p2p/core/rpc/JsonRpc.kt
index 18cd385906..44a3bb23a7 100644
--- a/core/src/main/java/org/p2p/core/rpc/JsonRpc.kt
+++ b/core/src/main/java/org/p2p/core/rpc/JsonRpc.kt
@@ -5,24 +5,25 @@ import com.google.gson.JsonElement
import com.google.gson.annotations.SerializedName
import timber.log.Timber
import java.lang.reflect.Type
+import java.util.UUID
private const val TAG = "JsonRpcParser"
-abstract class JsonRpc(
+abstract class JsonRpc
(
@SerializedName("method")
val method: String,
@SerializedName("params")
- val params: P
+ val params: Body,
) {
@SerializedName("jsonrpc")
val version: String = "2.0"
@SerializedName("id")
- var id: Int = 1
+ val id: String = UUID.randomUUID().toString()
protected abstract val typeOfResult: Type
- fun parseResponse(response: RpcResponse, gson: Gson): T {
+ fun parseResponse(response: RpcResponse, gson: Gson): Response {
if (response.error != null) {
val error = ResponseError.RpcError(response.error)
Timber.tag(TAG).i(error, "RPC error returned")
@@ -31,9 +32,9 @@ abstract class JsonRpc(
return parseResult(response.result, gson)
}
- fun parseResult(result: JsonElement?, gson: Gson): T {
+ private fun parseResult(result: JsonElement?, gson: Gson): Response {
return try {
- gson.fromJson(result, typeOfResult) as T
+ gson.fromJson(result, typeOfResult) as Response
} catch (error: Throwable) {
Timber.tag(TAG).i(result.toString())
Timber.tag(TAG).i(error)
diff --git a/core/src/main/java/org/p2p/core/token/Token.kt b/core/src/main/java/org/p2p/core/token/Token.kt
index 67ec130c2e..a1e44f5c2c 100644
--- a/core/src/main/java/org/p2p/core/token/Token.kt
+++ b/core/src/main/java/org/p2p/core/token/Token.kt
@@ -8,10 +8,9 @@ import kotlinx.parcelize.Parcelize
import org.p2p.core.crypto.Base58String
import org.p2p.core.crypto.toBase58Instance
import org.p2p.core.utils.Constants
-import org.p2p.core.utils.Constants.REN_BTC_SYMBOL
import org.p2p.core.utils.Constants.SOL_NAME
-import org.p2p.core.utils.Constants.USDC_SYMBOL
-import org.p2p.core.utils.Constants.USDT_SYMBOL
+import org.p2p.core.utils.Constants.USDC_MINT
+import org.p2p.core.utils.Constants.USDT_MINT
import org.p2p.core.utils.Constants.WRAPPED_ETH_MINT
import org.p2p.core.utils.Constants.WRAPPED_SOL_MINT
import org.p2p.core.utils.asCurrency
@@ -20,8 +19,8 @@ import org.p2p.core.utils.formatFiat
import org.p2p.core.utils.formatToken
import org.p2p.core.utils.isZero
import org.p2p.core.utils.orZero
-import org.p2p.core.utils.scaleLong
-import org.p2p.core.utils.scaleShort
+import org.p2p.core.utils.scaleNine
+import org.p2p.core.utils.scaleTwo
import org.p2p.core.utils.toLamports
import org.p2p.core.utils.toPowerValue
import org.p2p.core.wrapper.eth.EthAddress
@@ -78,7 +77,7 @@ sealed class Token constructor(
@IgnoredOnParcel
val totalInUsdScaled: BigDecimal?
- get() = totalInUsd?.scaleShort()
+ get() = totalInUsd?.scaleTwo()
@IgnoredOnParcel
val isZero: Boolean
@@ -202,35 +201,31 @@ sealed class Token constructor(
@IgnoredOnParcel
val isSOL: Boolean
- get() = mintAddress == WRAPPED_SOL_MINT
+ get() = mintAddress.equals(WRAPPED_SOL_MINT, ignoreCase = true)
@IgnoredOnParcel
val isSpl: Boolean
- get() = mintAddress != WRAPPED_SOL_MINT
+ get() = !isSOL
@IgnoredOnParcel
val isToken2022: Boolean
get() = programId == Constants.SOLANA_TOKEN_2022_PROGRAM_ID
- @IgnoredOnParcel
- val isRenBTC: Boolean
- get() = tokenSymbol == REN_BTC_SYMBOL
-
@IgnoredOnParcel
val isUSDC: Boolean
- get() = tokenSymbol.equals(USDC_SYMBOL, ignoreCase = true)
+ get() = mintAddress.equals(USDC_MINT, ignoreCase = true)
@IgnoredOnParcel
val isUSDT: Boolean
- get() = tokenSymbol == USDT_SYMBOL
+ get() = mintAddress.equals(USDT_MINT, ignoreCase = true)
@IgnoredOnParcel
val usdRateOrZero: BigDecimal
- get() = rate ?: BigDecimal.ZERO
+ get() = rate.orZero()
@IgnoredOnParcel
val currencyFormattedRate: String
- get() = (rate ?: BigDecimal.ZERO).asCurrency(currencySymbol)
+ get() = usdRateOrZero.asCurrency(currencySymbol)
@IgnoredOnParcel
val currencySymbol: String
@@ -264,7 +259,7 @@ sealed class Token constructor(
tokenName = SOL_NAME,
iconUrl = tokenMetadata.iconUrl,
totalInUsd = if (amount == 0L) null else solPrice?.let { total.multiply(it) },
- total = total.scaleLong(tokenMetadata.decimals),
+ total = total.scaleNine(tokenMetadata.decimals),
rate = solPrice,
tokenServiceAddress = Constants.TOKEN_SERVICE_NATIVE_SOL_TOKEN,
visibility = TokenVisibility.DEFAULT,
@@ -289,9 +284,9 @@ fun List.findByMintAddress(mintAddress: String?): Token.Active? =
fun List.sortedWithPreferredStableCoins(): List {
return sortedWith(
compareBy {
- when (it.tokenSymbol) {
- USDC_SYMBOL -> 1
- USDT_SYMBOL -> 2
+ when (it.mintAddress) {
+ USDC_MINT -> 1
+ USDT_MINT -> 2
else -> 3
}
}
diff --git a/core/src/main/java/org/p2p/core/utils/AmountExtensions.kt b/core/src/main/java/org/p2p/core/utils/AmountExtensions.kt
index 343cf899b6..4c3fe4d80a 100644
--- a/core/src/main/java/org/p2p/core/utils/AmountExtensions.kt
+++ b/core/src/main/java/org/p2p/core/utils/AmountExtensions.kt
@@ -3,72 +3,71 @@ package org.p2p.core.utils
import java.math.BigDecimal
import java.math.BigInteger
import java.math.RoundingMode
-import kotlin.math.pow
import org.p2p.core.token.Token
import org.p2p.core.utils.Constants.FIAT_FRACTION_LENGTH
-private const val POWER_VALUE = 10.0
-const val DEFAULT_DECIMAL = 9
+private val BASE_TEN = BigDecimal.TEN
+const val SOL_DECIMALS = 9
const val MOONPAY_DECIMAL = 2
const val STRIGA_FIAT_DECIMALS = 2
-private const val SCALE_VALUE_SHORT = 2
-private const val SCALE_VALUE_MEDIUM = 6
-private const val SCALE_VALUE_LONG = 9
+private const val SCALE_VALUE_TWO = 2
+private const val SCALE_VALUE_SIX = 6
+private const val SCALE_VALUE_NINE = 9
private const val AMOUNT_MIN_VALUE = 0.01
fun String?.toBigDecimalOrZero(): BigDecimal {
val removedZeros = this?.replace("(?<=\\d)\\.?0+(?![\\d\\.])", emptyString())
- return removedZeros?.toBigDecimalOrNull() ?: BigDecimal.ZERO
+ return removedZeros?.toBigDecimalOrNull().orZero()
}
fun String?.toBigIntegerOrZero(): BigInteger {
- return this?.toBigIntegerOrNull() ?: BigInteger.ZERO
+ return this?.toBigIntegerOrNull().orZero()
}
-fun Int.toPowerValue(): BigDecimal =
- BigDecimal(POWER_VALUE.pow(this))
+fun Int.toPowerValue(): BigDecimal = BASE_TEN.pow(this)
-fun BigDecimal.scaleShortOrFirstNotZero(): BigDecimal {
- return if (isZero()) {
- this
+fun BigDecimal.scaleTwoOrFirstNotZero(): BigDecimal {
+ if (isZero()) {
+ return this
+ }
+ val scale = if (scale() > SCALE_VALUE_TWO) {
+ scale() - (unscaledValue().toString().length - SCALE_VALUE_TWO)
} else {
- val scale = if (scale() > SCALE_VALUE_SHORT) {
- scale() - (unscaledValue().toString().length - SCALE_VALUE_SHORT)
- } else {
- SCALE_VALUE_SHORT
- }
- // removing zeros, case: 0.02000 -> 0.2
- setScale(scale, RoundingMode.DOWN).stripTrailingZeros()
+ SCALE_VALUE_TWO
}
+ // removing zeros, case: 0.02000 -> 0.2
+ return setScale(scale, RoundingMode.DOWN).stripTrailingZeros()
}
-fun BigDecimal.scaleShort(): BigDecimal =
- this.setScale(SCALE_VALUE_SHORT, RoundingMode.DOWN)
+fun BigDecimal.scaleTwo(): BigDecimal =
+ this.setScale(SCALE_VALUE_TWO, RoundingMode.DOWN)
.stripTrailingZeros() // removing zeros, case: 0.02000 -> 0.02
-fun BigDecimal.scaleMedium(): BigDecimal =
- if (this.isZero()) this else this.setScale(SCALE_VALUE_MEDIUM, RoundingMode.DOWN)
+fun BigDecimal.scaleSix(): BigDecimal =
+ if (this.isZero()) this else this.setScale(SCALE_VALUE_SIX, RoundingMode.DOWN)
.stripTrailingZeros() // removing zeros, case: 0.02000 -> 0.02
-fun BigDecimal.scaleLong(decimals: Int = SCALE_VALUE_LONG): BigDecimal =
+fun BigDecimal.scaleNine(decimals: Int = SCALE_VALUE_NINE): BigDecimal =
if (this.isZero()) this else this.setScale(decimals, RoundingMode.DOWN)
.stripTrailingZeros() // removing zeros, case: 0.02000 -> 0.02
-fun BigInteger.fromLamports(decimals: Int = DEFAULT_DECIMAL): BigDecimal =
- BigDecimal(this.toDouble() / (POWER_VALUE.pow(decimals)))
+// do not use BigDecimal(double) sometimes it makes the amount less
+// example: pass 0.030, get 0.029
+fun BigInteger.fromLamports(decimals: Int): BigDecimal =
+ (this.toBigDecimal().divide(BASE_TEN.pow(decimals), 18, RoundingMode.HALF_DOWN))
.stripTrailingZeros() // removing zeros, case: 0.02000 -> 0.02
- .scaleLong(decimals)
+ .scaleNine(decimals)
fun BigDecimal.toLamports(decimals: Int): BigInteger =
this.multiply(decimals.toPowerValue()).toBigInteger()
fun BigDecimal.toUsd(usdRate: BigDecimal?): BigDecimal? =
- usdRate?.let { this.multiply(it).scaleShort() }
+ usdRate?.let { this.multiply(it).scaleTwo() }
fun BigDecimal.toUsd(token: Token): BigDecimal? =
- token.rate?.let { this.multiply(it).scaleShort() }
+ token.rate?.let { this.multiply(it).scaleTwo() }
// case: 1000.023000 -> 1 000.02
fun BigDecimal.formatFiat(): String = formatWithDecimals(FIAT_FRACTION_LENGTH)
@@ -77,7 +76,7 @@ fun BigDecimal.formatFiat(): String = formatWithDecimals(FIAT_FRACTION_LENGTH)
// case 2: 1.0 -> 1 - default behavior
// case 3: 1.0 -> 1.0 -> with keepInitialDecimals=true
fun BigDecimal.formatToken(
- decimals: Int = DEFAULT_DECIMAL,
+ decimals: Int = SOL_DECIMALS,
exactDecimals: Boolean = false,
keepInitialDecimals: Boolean = false,
): String = formatWithDecimals(
@@ -88,7 +87,7 @@ fun BigDecimal.formatToken(
fun BigDecimal.formatTokenWithSymbol(
tokenSymbol: String,
- decimals: Int = DEFAULT_DECIMAL,
+ decimals: Int,
exactDecimals: Boolean = false,
keepInitialDecimals: Boolean = false,
): String {
@@ -100,6 +99,17 @@ fun BigDecimal.formatTokenWithSymbol(
return "$formattedAmount $tokenSymbol"
}
+fun BigDecimal.formatTokenWithSymbol(
+ token: Token.Active,
+ exactDecimals: Boolean = false,
+ keepInitialDecimals: Boolean = false,
+): String = formatTokenWithSymbol(
+ token.tokenSymbol,
+ token.decimals,
+ exactDecimals,
+ keepInitialDecimals
+)
+
// case: 10000.000000007900 -> 100 000.00
fun BigDecimal.formatTokenForMoonpay(): String = formatWithDecimals(MOONPAY_DECIMAL)
@@ -133,20 +143,20 @@ fun BigDecimal.formatWithDecimals(
}
fun BigDecimal?.isNullOrZero(): Boolean = this == null || this.compareTo(BigDecimal.ZERO) == 0
-fun BigDecimal.isZero() = this.compareTo(BigDecimal.ZERO) == 0
-fun BigDecimal.isNotZero() = this.compareTo(BigDecimal.ZERO) != 0
-fun BigDecimal.isMoreThan(value: BigDecimal) = this.compareTo(value) == 1
-fun BigDecimal.isLessThan(value: BigDecimal) = this.compareTo(value) == -1
-fun BigDecimal.isZeroOrLess() = isZero() || isLessThan(BigDecimal.ZERO)
+fun BigDecimal.isZero(): Boolean = this.compareTo(BigDecimal.ZERO) == 0
+fun BigDecimal.isNotZero(): Boolean = this.compareTo(BigDecimal.ZERO) != 0
+fun BigDecimal.isMoreThan(value: BigDecimal): Boolean = this.compareTo(value) == 1
+fun BigDecimal.isLessThan(value: BigDecimal): Boolean = this.compareTo(value) == -1
+fun BigDecimal.isZeroOrLess(): Boolean = isZero() || isLessThan(BigDecimal.ZERO)
fun BigDecimal?.orZero(): BigDecimal = this ?: BigDecimal.ZERO
fun BigInteger?.orZero(): BigInteger = this ?: BigInteger.ZERO
-fun BigInteger.isZero() = this.compareTo(BigInteger.ZERO) == 0
-fun BigInteger.isNotZero() = this.compareTo(BigInteger.ZERO) != 0
+fun BigInteger.isZero(): Boolean = this.compareTo(BigInteger.ZERO) == 0
+fun BigInteger.isNotZero(): Boolean = this.compareTo(BigInteger.ZERO) != 0
fun BigInteger.isLessThan(value: BigInteger) = this.compareTo(value) == -1
fun BigInteger.isMoreThan(value: BigInteger) = this.compareTo(value) == 1
-fun BigInteger.isZeroOrLess() = isZero() || isLessThan(BigInteger.ZERO)
+fun BigInteger.isZeroOrLess(): Boolean = isZero() || isLessThan(BigInteger.ZERO)
fun BigDecimal.asCurrency(currencyUiSymbol: String): String = when {
isZero() -> "${currencyUiSymbol}0"
@@ -174,7 +184,11 @@ fun BigDecimal.asPositiveUsdTransaction(): String = asUsdTransaction("+")
fun BigDecimal.asNegativeUsdTransaction(): String = asUsdTransaction("-")
fun BigDecimal.asUsdTransaction(
transactionSymbol: String
-): String = if (lessThenMinValue()) "<$0.01" else "$transactionSymbol$ ${formatFiat()}"
+): String {
+ if (lessThenMinValue()) return "<$0.01"
+ val fiatAmountWithSymbol = "$${formatFiat()}"
+ return "$transactionSymbol $fiatAmountWithSymbol"
+}
fun BigDecimal.asUsdSwap(): String = when {
isZero() -> "0 USD"
@@ -185,5 +199,6 @@ fun BigDecimal.asUsdSwap(): String = when {
fun Int?.orZero(): Int = this ?: 0
// value is in (0..0.01)
-fun BigDecimal.lessThenMinValue() = !isZero() && isLessThan(AMOUNT_MIN_VALUE.toBigDecimal())
-fun BigDecimal.moreThenMinValue() = isMoreThan(AMOUNT_MIN_VALUE.toBigDecimal())
+fun BigDecimal.lessThenMinValue(): Boolean = !isZero() && isLessThan(AMOUNT_MIN_VALUE.toBigDecimal())
+fun BigDecimal.moreThenMinValue(): Boolean = isMoreThan(AMOUNT_MIN_VALUE.toBigDecimal())
+fun BigDecimal.divideByInt(byInt: Int): BigDecimal = divide(byInt.toBigDecimal())
diff --git a/core/src/main/java/org/p2p/core/utils/Constants.kt b/core/src/main/java/org/p2p/core/utils/Constants.kt
index 8a47bdc880..5010d1c355 100644
--- a/core/src/main/java/org/p2p/core/utils/Constants.kt
+++ b/core/src/main/java/org/p2p/core/utils/Constants.kt
@@ -1,5 +1,7 @@
package org.p2p.core.utils
+import org.p2p.core.crypto.toBase58Instance
+
object Constants {
const val MIN_REQUIRED_ACCOUNT_INFO_DATA_LENGTH = 0
@@ -26,13 +28,16 @@ object Constants {
const val TOKEN_SERVICE_NATIVE_ETH_TOKEN = "native"
const val WRAPPED_SOL_MINT = "So11111111111111111111111111111111111111112"
+ val WRAPPED_SOL_MINT_B58 = WRAPPED_SOL_MINT.toBase58Instance()
const val WRAPPED_ETH_MINT = "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs"
const val WRAPPED_BTC_MINT = "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh"
const val FEE_RELAYER_ACCOUNT = "FG4Y3yX4AAchp1HvNZ7LfzFTewF2f6nDoMDCohTFrdpT"
+ const val FEE_RELAYER_ACCOUNT_V2 = "9U8gVazjmW87Ax5j1yFG7PBfD4ZzckFPLhdPhmoWc2xD"
const val SWAP_FEE_RELAYER_ACCOUNT = "JdYkwaUrvoeYsCbPgnt3AAa1qzjV2CtoRqU3bzuAvQu"
val FEE_RELAYER_ACCOUNTS = listOf(
FEE_RELAYER_ACCOUNT,
+ FEE_RELAYER_ACCOUNT_V2,
SWAP_FEE_RELAYER_ACCOUNT
)
diff --git a/core/src/main/java/org/p2p/core/utils/DecimalFormatter.kt b/core/src/main/java/org/p2p/core/utils/DecimalFormatter.kt
index 3885e48461..64509284ae 100644
--- a/core/src/main/java/org/p2p/core/utils/DecimalFormatter.kt
+++ b/core/src/main/java/org/p2p/core/utils/DecimalFormatter.kt
@@ -48,6 +48,8 @@ object DecimalFormatter {
fmt.minimumFractionDigits = if (keepInitialDecimals) clamp(value.scale(), 0, decimals) else 0
}
fmt.decimalFormatSymbols = symbols
+ // HALF_DOWN to we won't round, for example, 1.505 to 1.51 which is not valid
+ fmt.roundingMode = RoundingMode.DOWN
return fmt.format(value)
}
}
diff --git a/core/src/main/java/org/p2p/core/utils/MathUtils.kt b/core/src/main/java/org/p2p/core/utils/MathUtils.kt
index 97cf4a4a3b..900d75369f 100644
--- a/core/src/main/java/org/p2p/core/utils/MathUtils.kt
+++ b/core/src/main/java/org/p2p/core/utils/MathUtils.kt
@@ -14,8 +14,25 @@ fun BigInteger.divideSafe(value: BigInteger): BigInteger =
else this / value
// scales by default to the decimals
-fun BigDecimal.divideSafe(value: BigDecimal, decimals: Int = DEFAULT_DECIMALS_VALUE): BigDecimal =
- if (this.isZero() || value.isZero()) BigDecimal.ZERO
- else this.divide(value, decimals, RoundingMode.HALF_EVEN).stripTrailingZeros()
+fun BigDecimal.divideSafe(
+ value: BigDecimal,
+ decimals: Int = DEFAULT_DECIMALS_VALUE,
+ roundingMode: RoundingMode = RoundingMode.HALF_EVEN
+): BigDecimal =
+ if (this.isZero() || value.isZero()) {
+ BigDecimal.ZERO
+ } else {
+ this.divide(value, decimals, roundingMode).stripTrailingZeros()
+ }
fun clamp(value: Int, min: Int, max: Int): Int = min(max(min, value), max)
+
+/**
+ * @param ratio - ratio in range (0.0 .. 1.0)
+ */
+fun BigDecimal.subtractRatio(ratio: BigDecimal): BigDecimal {
+ require(ratio.toDouble() in 0.0..1.0) {
+ "Expected ratio between 0.0 and 1.0, but given is $ratio"
+ }
+ return this.subtract(this.multiply(ratio))
+}
diff --git a/core/src/main/java/org/p2p/core/wrapper/eth/EthereumExt.kt b/core/src/main/java/org/p2p/core/wrapper/eth/EthereumExt.kt
index 18e20bf1ad..ac9560cdb2 100644
--- a/core/src/main/java/org/p2p/core/wrapper/eth/EthereumExt.kt
+++ b/core/src/main/java/org/p2p/core/wrapper/eth/EthereumExt.kt
@@ -77,6 +77,10 @@ fun String.hexStringToBigIntegerOrNull(): BigInteger? {
return this.stripHexPrefix().toBigIntegerOrNull(16)
}
+fun String.hexStringToBigInteger(): BigInteger {
+ return this.stripHexPrefix().toBigInteger(16)
+}
+
// Converts positive long values to a byte array without leading zero byte (for sign bit)
fun Long.toByteArray(): ByteArray {
var array = this.toBigInteger().toByteArray()
diff --git a/core/src/main/res/drawable/ic_placeholder_v2.xml b/core/src/main/res/drawable/ic_placeholder_v2.xml
new file mode 100644
index 0000000000..d67fabde43
--- /dev/null
+++ b/core/src/main/res/drawable/ic_placeholder_v2.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/core/src/main/res/values/environment_config.xml b/core/src/main/res/values/environment_config.xml
index 199d6cbcdf..91f0e64941 100644
--- a/core/src/main/res/values/environment_config.xml
+++ b/core/src/main/res/values/environment_config.xml
@@ -3,8 +3,8 @@
https://api.coingecko.com/api/v3/
https://btc.com/btc/address/%s
- https://explorer.solana.com/tx/%s
- https://explorer.solana.com/address/%s
+ https://solscan.io/tx/%s
+ https://solscan.io/account/%s
https://raw.githubusercontent.com/p2p-org/token-list/main/src/tokens/
@@ -24,6 +24,7 @@
https://orca.key.app/
https://swap.key.app/
+ https://swap-v6.key.app/
https://push-service.key.app/
diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/EthereumNetworkModule.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/EthereumNetworkModule.kt
index 76c2db63cf..d387f33feb 100644
--- a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/EthereumNetworkModule.kt
+++ b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/EthereumNetworkModule.kt
@@ -9,7 +9,6 @@ import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import org.p2p.core.rpc.RPC_JSON_QUALIFIER
-import org.p2p.ethereumkit.external.api.coingecko.CoinGeckoService
import org.p2p.ethereumkit.external.api.interceptor.EthereumApiLoggingInterceptor
import org.p2p.ethereumkit.external.core.EthereumNetworkEnvironment
@@ -22,11 +21,11 @@ internal object EthereumNetworkModule {
single(named(QUALIFIER_ETH_HTTP_CLIENT)) { getOkHttpClient() }
single(named(QUALIFIER_ETH_RETROFIT)) {
getRetrofit(
+ baseUrl = EthereumNetworkEnvironment.ALCHEMY.baseUrl,
httpClient = get(named(QUALIFIER_ETH_HTTP_CLIENT)),
gson = get(named(RPC_JSON_QUALIFIER))
)
}
- single { get(named(QUALIFIER_ETH_RETROFIT)).create(CoinGeckoService::class.java) }
}
private fun getOkHttpClient(): OkHttpClient {
@@ -35,9 +34,9 @@ internal object EthereumNetworkModule {
return httpClient.build()
}
- private fun getRetrofit(httpClient: OkHttpClient, gson: Gson): Retrofit {
+ private fun getRetrofit(httpClient: OkHttpClient, gson: Gson, baseUrl: String): Retrofit {
return Retrofit.Builder()
- .baseUrl(EthereumNetworkEnvironment.ALCHEMY.baseUrl)
+ .baseUrl(baseUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(httpClient)
diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/alchemy/request/GetBalanceJsonRpc.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/alchemy/request/GetBalanceJsonRpc.kt
index 91704e36c2..6a78e4bf74 100644
--- a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/alchemy/request/GetBalanceJsonRpc.kt
+++ b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/alchemy/request/GetBalanceJsonRpc.kt
@@ -1,18 +1,17 @@
package org.p2p.ethereumkit.external.api.alchemy.request
-import org.p2p.core.rpc.JsonRpc
-import org.p2p.core.wrapper.eth.EthAddress
import java.lang.reflect.Type
-import java.math.BigInteger
import org.p2p.core.model.DefaultBlockParameter
+import org.p2p.core.rpc.JsonRpc
+import org.p2p.core.wrapper.eth.EthAddress
internal class GetBalanceJsonRpc(
@Transient val address: EthAddress,
@Transient val defaultBlockParameter: DefaultBlockParameter
-) : JsonRpc, BigInteger>(
+) : JsonRpc, String>(
method = "eth_getBalance",
params = listOf(address, defaultBlockParameter)
) {
@Transient
- override val typeOfResult: Type = BigInteger::class.java
+ override val typeOfResult: Type = String::class.java
}
diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/alchemy/response/TokenBalancesResponse.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/alchemy/response/TokenBalancesResponse.kt
index f03cc3ce9a..69949603d9 100644
--- a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/alchemy/response/TokenBalancesResponse.kt
+++ b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/alchemy/response/TokenBalancesResponse.kt
@@ -1,7 +1,6 @@
package org.p2p.ethereumkit.external.api.alchemy.response
import com.google.gson.annotations.SerializedName
-import java.math.BigInteger
import org.p2p.core.wrapper.eth.EthAddress
internal data class TokenBalancesResponse(
@@ -15,7 +14,7 @@ internal data class TokenBalanceResponse(
@SerializedName("contractAddress")
val contractAddress: EthAddress,
@SerializedName("tokenBalance")
- val tokenBalance: BigInteger
+ val tokenBalanceHex: String
)
internal data class TokenMetadataResponse(
diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/coingecko/CoinGeckoService.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/coingecko/CoinGeckoService.kt
deleted file mode 100644
index 3cd9f4af7a..0000000000
--- a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/coingecko/CoinGeckoService.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.p2p.ethereumkit.external.api.coingecko
-
-import org.p2p.ethereumkit.external.api.coingecko.response.CoinGeckoPriceResponse
-import retrofit2.http.GET
-import retrofit2.http.Query
-import retrofit2.http.Url
-
-internal interface CoinGeckoService {
-
- @GET
- suspend fun getEthereumTokenPrices(
- @Url url: String = "https://api.coingecko.com/api/v3/simple/token_price/ethereum",
- @Query("contract_addresses") tokenAddresses: String,
- @Query("vs_currencies") targetCurrency: String,
- ): Map
-}
diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/coingecko/response/CoinGeckoPriceResponse.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/coingecko/response/CoinGeckoPriceResponse.kt
deleted file mode 100644
index 0ea1ff680c..0000000000
--- a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/api/coingecko/response/CoinGeckoPriceResponse.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.p2p.ethereumkit.external.api.coingecko.response
-
-import com.google.gson.annotations.SerializedName
-import java.math.BigDecimal
-
-internal data class CoinGeckoPriceResponse(
- @SerializedName("usd")
- val priceInUsd: BigDecimal
-)
diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/repository/EthereumKitRepository.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/repository/EthereumKitRepository.kt
index 9bc23c7e81..501da14c62 100644
--- a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/repository/EthereumKitRepository.kt
+++ b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/repository/EthereumKitRepository.kt
@@ -9,14 +9,15 @@ import org.p2p.core.utils.Constants.TOKEN_SERVICE_NATIVE_ETH_TOKEN
import org.p2p.core.utils.orZero
import org.p2p.core.wrapper.HexString
import org.p2p.core.wrapper.eth.EthAddress
+import org.p2p.core.wrapper.eth.hexStringToBigInteger
import org.p2p.ethereumkit.external.api.alchemy.response.TokenBalanceResponse
import org.p2p.ethereumkit.external.model.ERC20Tokens
import org.p2p.ethereumkit.external.model.EthTokenConverter
import org.p2p.ethereumkit.external.model.EthTokenKeyProvider
import org.p2p.ethereumkit.external.model.EthTokenMetadata
import org.p2p.ethereumkit.external.model.EthereumClaimToken
-import org.p2p.ethereumkit.external.token.EthereumTokensLocalRepository
import org.p2p.ethereumkit.external.token.EthereumTokenRepository
+import org.p2p.ethereumkit.external.token.EthereumTokensLocalRepository
import org.p2p.ethereumkit.internal.core.TransactionSignerEip1559
import org.p2p.ethereumkit.internal.core.TransactionSignerLegacy
import org.p2p.ethereumkit.internal.core.signer.Signer
@@ -42,7 +43,7 @@ internal class EthereumKitRepository(
privateKey = Signer.privateKey(words = seedPhrase, chain = Chain.Ethereum)
)
- val ethAddress = tokenKeyProvider?.publicKey ?: return
+ tokenKeyProvider?.publicKey ?: return
}
override fun isInitialized(): Boolean {
@@ -131,7 +132,7 @@ internal class EthereumKitRepository(
this += erc20TokensAddresses
}
// Fetch tokens metadata
- val tokensMetadata = tokenServiceRepository.loadMetadataForTokens(
+ val tokensMetadata = tokenServiceRepository.fetchMetadataForTokens(
chain = TokenServiceNetwork.ETHEREUM,
tokenAddresses = allTokensAddresses
)
@@ -142,7 +143,8 @@ internal class EthereumKitRepository(
val erc20TokensMetadata = tokensMetadata.map { metadata ->
val tokenBalance = tokensBalances
.firstOrNull { it.contractAddress.hex == metadata.address }
- ?.tokenBalance
+ ?.tokenBalanceHex
+ ?.hexStringToBigInteger()
.orZero()
converter.toEthTokenMetadata(
diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokenRemoteRepository.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokenRemoteRepository.kt
index 23512c0552..501deeb473 100644
--- a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokenRemoteRepository.kt
+++ b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokenRemoteRepository.kt
@@ -1,17 +1,18 @@
package org.p2p.ethereumkit.external.token
import com.google.gson.Gson
-import org.p2p.ethereumkit.external.core.EthereumNetworkEnvironment
+import java.math.BigInteger
+import java.net.URI
+import org.p2p.core.model.DefaultBlockParameter
+import org.p2p.core.rpc.RpcApi
+import org.p2p.core.wrapper.eth.EthAddress
+import org.p2p.core.wrapper.eth.hexStringToBigInteger
import org.p2p.ethereumkit.external.api.alchemy.request.GetBalanceJsonRpc
import org.p2p.ethereumkit.external.api.alchemy.request.GetTokenBalancesJsonRpc
import org.p2p.ethereumkit.external.api.alchemy.request.GetTokenMetadataJsonRpc
import org.p2p.ethereumkit.external.api.alchemy.response.TokenBalancesResponse
import org.p2p.ethereumkit.external.api.alchemy.response.TokenMetadataResponse
-import org.p2p.core.wrapper.eth.EthAddress
-import java.math.BigInteger
-import java.net.URI
-import org.p2p.core.rpc.RpcApi
-import org.p2p.core.model.DefaultBlockParameter
+import org.p2p.ethereumkit.external.core.EthereumNetworkEnvironment
internal class EthereumTokenRemoteRepository(
private val alchemyService: RpcApi,
@@ -29,7 +30,7 @@ internal class EthereumTokenRemoteRepository(
uri = URI.create(networkEnvironment.baseUrl),
jsonRpc = requestGson
)
- return request.parseResponse(response, gson)
+ return request.parseResponse(response, gson).hexStringToBigInteger()
}
override suspend fun getTokenBalances(
diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokensInMemoryRepository.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokensInMemoryRepository.kt
index a5a79a78f1..24f25c866e 100644
--- a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokensInMemoryRepository.kt
+++ b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokensInMemoryRepository.kt
@@ -11,9 +11,9 @@ class EthereumTokensInMemoryRepository : EthereumTokensLocalRepository {
private val cachedTokens = MutableStateFlow>(emptyList())
- override suspend fun cacheTokens(newTokens: List) {
+ override suspend fun cacheTokens(tokens: List) {
val oldTokens = cachedTokens.value
- val validatedTokens = newTokens.map { newToken ->
+ val validatedTokens = tokens.map { newToken ->
val oldToken = oldTokens.firstOrNull { newToken.tokenServiceAddress == it.tokenServiceAddress }
val newTokenRate = newToken.rate ?: oldToken?.rate
@@ -25,14 +25,14 @@ class EthereumTokensInMemoryRepository : EthereumTokensLocalRepository {
override suspend fun updateTokensRate(tokensRate: List) {
val newTokenRates = buildList {
- val nativePrice = tokensRate.firstOrNull { it.address == ERC20Tokens.ETH.contractAddress }
+ val nativePrice = tokensRate.firstOrNull { it.tokenAddress == ERC20Tokens.ETH.contractAddress }
if (nativePrice != null) {
- this += nativePrice.copy(address = Constants.TOKEN_SERVICE_NATIVE_SOL_TOKEN)
+ this += nativePrice.copy(tokenAddress = Constants.TOKEN_SERVICE_NATIVE_SOL_TOKEN)
}
this += tokensRate
}
cachedTokens.value = cachedTokens.value.map { token ->
- val foundTokenRate = newTokenRates.firstOrNull { it.address == token.tokenServiceAddress }
+ val foundTokenRate = newTokenRates.firstOrNull { it.tokenAddress == token.tokenServiceAddress }
val totalInUsd = foundTokenRate?.usdRate?.let { token.total.times(it) }
token.copy(totalInUsd = totalInUsd, rate = foundTokenRate?.usdRate)
}
diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/internal/core/TransactionSignerLegacy.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/internal/core/TransactionSignerLegacy.kt
index e4534832c4..f9e1cbb6e3 100644
--- a/ethereumkit/src/main/java/org/p2p/ethereumkit/internal/core/TransactionSignerLegacy.kt
+++ b/ethereumkit/src/main/java/org/p2p/ethereumkit/internal/core/TransactionSignerLegacy.kt
@@ -12,8 +12,8 @@ class TransactionSignerLegacy(
private val chainId: Int
) {
- fun signatureLegacy(encodedTransaction: HexString): Signature {
- val decodedTransaction = TransactionDecoder.decode(encodedTransaction.rawValue)
+ fun signatureLegacy(encodedTransactionHex: HexString): Signature {
+ val decodedTransaction = TransactionDecoder.decode(encodedTransactionHex.rawValue)
val encodedTransaction = TransactionEncoder.encode(decodedTransaction, chainId.toLong())
val rawTransactionHash = CryptoUtils.sha3(encodedTransaction)
val sign = CryptoUtils.ellipticSign(rawTransactionHash, privateKey)
diff --git a/solana/src/main/java/org/p2p/solanaj/kits/AccountInfoTokenExtension.kt b/solana/src/main/java/org/p2p/solanaj/kits/AccountInfoTokenExtension.kt
index b95bafd763..33ef5b8b9d 100644
--- a/solana/src/main/java/org/p2p/solanaj/kits/AccountInfoTokenExtension.kt
+++ b/solana/src/main/java/org/p2p/solanaj/kits/AccountInfoTokenExtension.kt
@@ -22,7 +22,7 @@ sealed interface AccountInfoTokenExtensionConfig {
data class TransferFeeConfig(
@SerializedName("withheldAmount")
- val withheldAmount: Long,
+ val withheldAmount: String,
@SerializedName("newerTransferFee")
val newerTransferFee: TransferFeeConfigData? = null,
@SerializedName("olderTransferFee")
@@ -33,22 +33,37 @@ sealed interface AccountInfoTokenExtensionConfig {
const val NAME = "transferFeeConfig"
}
- data class TransferFeeConfigData(
+ class TransferFeeConfigData(
+ @SerializedName("epoch")
val epoch: BigInteger,
+ @SerializedName("maximumFee")
val maximumFee: BigInteger,
- val transferFeeBasisPoints: Int,
+ @SerializedName("transferFeeBasisPoints")
+ val transferFeeBasisPoints: Long,
) {
+ /**
+ * Value in percents (e.g. 6.65) in range [0, 100]
+ * todo: I think we should use ratio instead of percent, it's useless except for UI
+ */
val transferFeePercent: BigDecimal
get() = transferFeeBasisPoints.toBigDecimal().setScale(4)
.divideSafe("10000".toBigDecimal().setScale(4))
.multiply("100".toBigDecimal().setScale(4))
}
+ /**
+ * Each transfer fee has it's "turn on" epoch field
+ * when epoch comes - we should use the turned on transfer fee
+ */
fun getActualTransferFee(currentEpoch: BigInteger): TransferFeeConfigData? {
- if (currentEpoch > (newerTransferFee?.epoch.orZero()) || currentEpoch.isZero()) {
- return newerTransferFee ?: olderTransferFee
+ if (currentEpoch.isZero()) return newerTransferFee ?: olderTransferFee
+
+ val isNewerTransferFeeEnabled = currentEpoch >= newerTransferFee?.epoch.orZero()
+ return if (isNewerTransferFeeEnabled) {
+ newerTransferFee
+ } else {
+ olderTransferFee
}
- return olderTransferFee
}
}
@@ -58,9 +73,9 @@ sealed interface AccountInfoTokenExtensionConfig {
@SerializedName("preUpdateAverageRate")
val preUpdateAverageRate: Double,
@SerializedName("initializationTimestamp")
- val initializationTimestamp: Long,
+ val initializationTimestamp: BigInteger,
@SerializedName("lastUpdateTimestamp")
- val lastUpdateTimestamp: Long,
+ val lastUpdateTimestamp: BigInteger,
@SerializedName("rateAuthority")
val rateAuthority: String,
) : AccountInfoTokenExtensionConfig {
diff --git a/solana/src/main/java/org/p2p/solanaj/utils/SolanaMessageSigner.kt b/solana/src/main/java/org/p2p/solanaj/utils/SolanaMessageSigner.kt
new file mode 100644
index 0000000000..5e55d3b7d3
--- /dev/null
+++ b/solana/src/main/java/org/p2p/solanaj/utils/SolanaMessageSigner.kt
@@ -0,0 +1,16 @@
+package org.p2p.solanaj.utils
+
+import org.p2p.core.crypto.Base64String
+import org.p2p.core.crypto.toBase64Instance
+
+class SolanaMessageSigner {
+ fun signMessage(message: ByteArray, keyPair: ByteArray): Base64String {
+ return TweetNaclFast.Signature(byteArrayOf(), keyPair.copyOf())
+ .detached(message.copyOf())
+ .toBase64Instance()
+ }
+
+ fun signMessage(message: String, keyPair: ByteArray): Base64String {
+ return signMessage(message.toByteArray(), keyPair)
+ }
+}
diff --git a/solana/src/main/java/org/p2p/solanaj/ws/SocketStateListener.kt b/solana/src/main/java/org/p2p/solanaj/ws/SocketStateListener.kt
index 4a679f90fe..fb31ff5357 100644
--- a/solana/src/main/java/org/p2p/solanaj/ws/SocketStateListener.kt
+++ b/solana/src/main/java/org/p2p/solanaj/ws/SocketStateListener.kt
@@ -1,10 +1,8 @@
package org.p2p.solanaj.ws
-import java.lang.Exception
-
interface SocketStateListener {
fun onConnected()
fun onWebSocketPong()
fun onFailed(exception: Exception)
- fun onClosed(code: Int, message: String)
+ fun onClientClosed(code: Int, message: String)
}
diff --git a/solana/src/main/java/org/p2p/solanaj/ws/impl/SubscriptionWebSocketClient.kt b/solana/src/main/java/org/p2p/solanaj/ws/impl/SubscriptionWebSocketClient.kt
index 2d4c51d37f..502e39c5a6 100644
--- a/solana/src/main/java/org/p2p/solanaj/ws/impl/SubscriptionWebSocketClient.kt
+++ b/solana/src/main/java/org/p2p/solanaj/ws/impl/SubscriptionWebSocketClient.kt
@@ -130,7 +130,7 @@ internal class SubscriptionWebSocketClient internal constructor(
override fun onClose(code: Int, reason: String, remote: Boolean) {
val closedFrom = if (remote) "remote peer" else "us"
- stateListener.onClosed(
+ stateListener.onClientClosed(
code = code,
message = "Connection closed by $closedFrom Code: $code Reason: $reason"
)
diff --git a/token-service/src/main/java/org/p2p/token/service/TokenServiceModule.kt b/token-service/src/main/java/org/p2p/token/service/TokenServiceModule.kt
index 932acd4522..2110972f9b 100644
--- a/token-service/src/main/java/org/p2p/token/service/TokenServiceModule.kt
+++ b/token-service/src/main/java/org/p2p/token/service/TokenServiceModule.kt
@@ -11,20 +11,25 @@ import org.p2p.core.common.di.InjectionModule
import org.p2p.core.network.NetworkCoreModule.getRetrofit
import org.p2p.core.network.environment.NetworkServicesUrlProvider
import org.p2p.core.rpc.RPC_JSON_QUALIFIER
-import org.p2p.token.service.api.TokenServiceRemoteDataSource
+import org.p2p.token.service.api.coingecko.CoinGeckoDataSource
+import org.p2p.token.service.api.coingecko.CoinGeckoTokenPriceRepository
import org.p2p.token.service.api.events.manager.TokenServiceEventManager
import org.p2p.token.service.api.events.manager.TokenServiceEventPublisher
-import org.p2p.token.service.api.mapper.TokenServiceMapper
+import org.p2p.token.service.api.jupiter.JupiterPricesDataSource
+import org.p2p.token.service.api.tokenservice.TokenServiceDataSource
+import org.p2p.token.service.api.tokenservice.TokenServiceRemoteDataSource
import org.p2p.token.service.converter.TokenServiceAmountsConverter
import org.p2p.token.service.converter.TokenServiceAmountsRemoteConverter
import org.p2p.token.service.database.TokenServiceDatabaseModule
import org.p2p.token.service.database.mapper.TokenServiceDatabaseMapper
import org.p2p.token.service.repository.TokenServiceRepository
import org.p2p.token.service.repository.TokenServiceRepositoryImpl
+import org.p2p.token.service.repository.mapper.TokenServiceMapper
import org.p2p.token.service.repository.metadata.TokenMetadataInMemoryRepository
import org.p2p.token.service.repository.metadata.TokenMetadataLocalRepository
import org.p2p.token.service.repository.metadata.TokenMetadataRemoteRepository
import org.p2p.token.service.repository.metadata.TokenMetadataRepository
+import org.p2p.token.service.repository.price.JupiterTokenPriceRepository
import org.p2p.token.service.repository.price.TokenPriceDatabaseRepository
import org.p2p.token.service.repository.price.TokenPriceLocalRepository
import org.p2p.token.service.repository.price.TokenPriceRemoteRepository
@@ -35,7 +40,7 @@ object TokenServiceModule : InjectionModule {
override fun create() = module {
includes(TokenServiceDatabaseModule.create())
- single {
+ single {
TokenServiceRemoteDataSource(
api = get(named(TOKEN_SERVICE_RETROFIT_QUALIFIER)).create(),
gson = get(named(RPC_JSON_QUALIFIER)),
@@ -43,6 +48,18 @@ object TokenServiceModule : InjectionModule {
)
}
+ single {
+ getRetrofit(baseUrl = "https://price.jup.ag/", interceptor = null).create()
+ }
+
+ single {
+ getRetrofit(
+ baseUrl = "https://api.coingecko.com/api/v3/",
+ tag = "CoinGeckoApi",
+ interceptor = null,
+ ).create()
+ }
+
single(named(TOKEN_SERVICE_RETROFIT_QUALIFIER)) {
val url = get()
getRetrofit(
@@ -52,9 +69,12 @@ object TokenServiceModule : InjectionModule {
)
}
- single