Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-08 10:46:19 +02:00
parent 81a7b760ae
commit 7a1b1ff5a3
15 changed files with 62 additions and 52 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.tap.network.auth package com.tangem.tap.network.auth
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
import com.tangem.lib.auth.P2PEthPoolAuthProvider import com.tangem.lib.auth.P2PEthPoolAuthProvider
internal class DefaultP2PEthPoolAuthProvider( internal class DefaultP2PEthPoolAuthProvider(
@ -11,6 +12,6 @@ internal class DefaultP2PEthPoolAuthProvider(
val keys = environmentConfigStorage.getConfigSync().p2pApiKey val keys = environmentConfigStorage.getConfigSync().p2pApiKey
?: error("No P2P api keys provided") ?: error("No P2P api keys provided")
return keys.mainnet return if (P2PStakingConfig.USE_TESTNET) keys.hoodi else keys.mainnet
} }
} }

View file

@ -1,6 +1,7 @@
package com.tangem.datasource.api.common.config package com.tangem.datasource.api.common.config
import com.tangem.datasource.BuildConfig import com.tangem.datasource.BuildConfig
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
import com.tangem.lib.auth.P2PEthPoolAuthProvider import com.tangem.lib.auth.P2PEthPoolAuthProvider
import com.tangem.utils.ProviderSuspend import com.tangem.utils.ProviderSuspend
@ -22,12 +23,7 @@ internal class P2PEthPool(
private fun getInitialEnvironment(): ApiEnvironment { private fun getInitialEnvironment(): ApiEnvironment {
return when (BuildConfig.BUILD_TYPE) { return when (BuildConfig.BUILD_TYPE) {
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
DEBUG_BUILD_TYPE, else -> if (P2PStakingConfig.USE_TESTNET) ApiEnvironment.DEV else ApiEnvironment.PROD
INTERNAL_BUILD_TYPE,
EXTERNAL_BUILD_TYPE,
RELEASE_BUILD_TYPE,
-> ApiEnvironment.PROD
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
} }
} }

View file

@ -23,9 +23,7 @@ interface P2PEthPoolApi {
* @param network Ethereum pool network: "mainnet" or "hoodi" (testnet) * @param network Ethereum pool network: "mainnet" or "hoodi" (testnet)
*/ */
@GET("api/v1/staking/pool/{network}/vaults") @GET("api/v1/staking/pool/{network}/vaults")
suspend fun getVaults( suspend fun getVaults(@Path("network") network: String): ApiResponse<P2PEthPoolResponse<P2PEthPoolVaultsResponse>>
@Path("network") network: String = "mainnet",
): ApiResponse<P2PEthPoolResponse<P2PEthPoolVaultsResponse>>
/** /**
* Prepare deposit transaction * Prepare deposit transaction

View file

@ -12,6 +12,7 @@ import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_
import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.BLOCK_AID_API_KEY import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.BLOCK_AID_API_KEY
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.TANGEM_API_KEY import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.TANGEM_API_KEY
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.P2PEthPoolAuthProvider import com.tangem.lib.auth.P2PEthPoolAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider import com.tangem.lib.auth.StakeKitAuthProvider
@ -299,11 +300,17 @@ internal class ProdApiConfigsManagerTest {
} }
private fun createP2PModel(): TestModel { private fun createP2PModel(): TestModel {
val (environment, baseUrl) = if (P2PStakingConfig.USE_TESTNET) {
ApiEnvironment.DEV to "https://api-test.p2p.org/"
} else {
ApiEnvironment.PROD to "https://api.p2p.org/"
}
return TestModel( return TestModel(
id = ApiConfig.ID.P2PEthPool, id = ApiConfig.ID.P2PEthPool,
expected = ApiEnvironmentConfig( expected = ApiEnvironmentConfig(
environment = ApiEnvironment.PROD, environment = environment,
baseUrl = "https://api.p2p.org/", baseUrl = baseUrl,
headers = mapOf( headers = mapOf(
"Authorization" to ProviderSuspend { "Bearer $P2P_API_KEY" }, "Authorization" to ProviderSuspend { "Bearer $P2P_API_KEY" },
"accept" to ProviderSuspend { "application/json" }, "accept" to ProviderSuspend { "application/json" },

View file

@ -223,30 +223,18 @@ internal class DefaultP2PEthPoolRepository(
.map { vaults -> .map { vaults ->
if (vaults.isEmpty()) { if (vaults.isEmpty()) {
return@map StakingAvailability.TemporaryUnavailable return@map StakingAvailability.TemporaryUnavailable
}
val vault = findPublicVault(vaults = vaults)
if (vault != null) {
StakingAvailability.Available(StakingOption.P2P(vault))
} else { } else {
StakingAvailability.TemporaryUnavailable StakingAvailability.Available(StakingOption.P2P(vaults))
} }
} }
} }
override suspend fun getStakingAvailabilitySync(): StakingAvailability { override suspend fun getStakingAvailabilitySync(): StakingAvailability {
val vaults = getVaultsSync() val vaults = getVaultsSync()
if (vaults.isEmpty()) { return if (vaults.isEmpty()) {
return StakingAvailability.TemporaryUnavailable
}
val vault = findPublicVault(vaults = vaults)
return if (vault != null) {
StakingAvailability.Available(StakingOption.P2P(vault))
} else {
StakingAvailability.TemporaryUnavailable StakingAvailability.TemporaryUnavailable
} else {
StakingAvailability.Available(StakingOption.P2P(vaults))
} }
} }
@ -257,8 +245,4 @@ internal class DefaultP2PEthPoolRepository(
private fun getVaultsFlow(): Flow<List<P2PEthPoolVault>> { private fun getVaultsFlow(): Flow<List<P2PEthPoolVault>> {
return p2pEthPoolVaultsStore.get() return p2pEthPoolVaultsStore.get()
} }
private fun findPublicVault(vaults: List<P2PEthPoolVault>): P2PEthPoolVault? {
return vaults.firstOrNull { vault -> !vault.isPrivate }
}
} }

View file

@ -24,7 +24,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.staking.model.StakingIntegrationID import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching import com.tangem.utils.coroutines.runSuspendCatching
@ -155,7 +155,7 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
for (address in addresses) { for (address in addresses) {
runSuspendCatching { runSuspendCatching {
val response = p2pApi.getAccountInfo( val response = p2pApi.getAccountInfo(
network = P2PEthPoolNetwork.MAINNET.value, network = P2PStakingConfig.activeNetwork.value,
delegatorAddress = address, delegatorAddress = address,
vaultAddress = vault.vaultAddress, vaultAddress = vault.vaultAddress,
) )

View file

@ -2,11 +2,6 @@ package com.tangem.domain.models.staking
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
enum class StakingProvider {
STAKEKIT,
P2P,
}
@Serializable @Serializable
data class StakingID( data class StakingID(
val integrationId: String, val integrationId: String,

View file

@ -39,11 +39,11 @@ sealed interface StakingOption {
* P2P pooled staking option * P2P pooled staking option
* Wraps P2P ETH Pool vault information * Wraps P2P ETH Pool vault information
*/ */
data class P2P(val vault: P2PEthPoolVault) : StakingOption { data class P2P(val vaults: List<P2PEthPoolVault>) : StakingOption {
override val integrationId: String = "p2p-ethereum-pooled" override val integrationId: String = "p2p-ethereum-pooled"
override val apy: SerializedBigDecimal = vault.apy override val apy: SerializedBigDecimal = vaults.maxOf { it.apy }
override val token: YieldToken = createEthToken() override val token: YieldToken = createEthToken()
override val isAvailable: Boolean = !vault.isPrivate override val isAvailable: Boolean = vaults.isNotEmpty()
private fun createEthToken(): YieldToken { // TODO private fun createEthToken(): YieldToken { // TODO
return YieldToken( return YieldToken(

View file

@ -11,6 +11,8 @@ enum class P2PEthPoolNetwork(
val value: String, val value: String,
val displayName: String, val displayName: String,
val chainId: Int, val chainId: Int,
val stakingNetworkId: String,
val isTestnet: Boolean,
) { ) {
/** /**
* Ethereum mainnet * Ethereum mainnet
@ -20,16 +22,20 @@ enum class P2PEthPoolNetwork(
value = "mainnet", value = "mainnet",
displayName = "Ethereum", displayName = "Ethereum",
chainId = 1, chainId = 1,
stakingNetworkId = "ethereum",
isTestnet = false,
), ),
/** /**
* Ethereum testnet (Holesky) * Ethereum testnet (Hoodi)
* Chain ID: 17000 * Chain ID: 17000
*/ */
TESTNET( TESTNET(
value = "hoodi", value = "hoodi",
displayName = "Holesky Testnet", displayName = "Hoodi Testnet",
chainId = 17000, chainId = 17000,
stakingNetworkId = "ethereum/test",
isTestnet = true,
), ),
; ;

View file

@ -0,0 +1,14 @@
package com.tangem.domain.staking.model.ethpool
/**
* Configuration for P2P Ethereum staking network.
*
* Change [USE_TESTNET] to switch between testnet and mainnet.
*/
object P2PStakingConfig {
const val USE_TESTNET: Boolean = true
val activeNetwork: P2PEthPoolNetwork
get() = if (USE_TESTNET) P2PEthPoolNetwork.TESTNET else P2PEthPoolNetwork.MAINNET
}

View file

@ -5,6 +5,7 @@ import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toMigratedCoinId import com.tangem.blockchainsdk.utils.toMigratedCoinId
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
/** /**
* Represents a staking integration identifier. * Represents a staking integration identifier.
@ -101,8 +102,10 @@ sealed interface StakingIntegrationID {
enum class P2P : StakingIntegrationID { enum class P2P : StakingIntegrationID {
EthereumPooled { EthereumPooled {
override val value: String = "p2p-ethereum-pooled" override val value: String = "p2p-ethereum-pooled"
override val blockchain: Blockchain = Blockchain.Ethereum override val blockchain: Blockchain
override val networkId: String = "ethereum" get() = if (P2PStakingConfig.USE_TESTNET) Blockchain.EthereumTestnet else Blockchain.Ethereum
override val networkId: String
get() = P2PStakingConfig.activeNetwork.stakingNetworkId
}, },
} }

View file

@ -2,7 +2,13 @@ package com.tangem.domain.staking.repositories
import arrow.core.Either import arrow.core.Either
import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.ethpool.* import com.tangem.domain.staking.model.ethpool.P2PEthPoolAccount
import com.tangem.domain.staking.model.ethpool.P2PEthPoolBroadcastResult
import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork
import com.tangem.domain.staking.model.ethpool.P2PEthPoolReward
import com.tangem.domain.staking.model.ethpool.P2PEthPoolUnsignedTx
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.StakingError
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -13,7 +19,7 @@ interface P2PEthPoolRepository {
* *
* @param network P2P network (MAINNET or TESTNET) * @param network P2P network (MAINNET or TESTNET)
*/ */
suspend fun fetchVaults(network: P2PEthPoolNetwork = P2PEthPoolNetwork.MAINNET) suspend fun fetchVaults(network: P2PEthPoolNetwork = P2PStakingConfig.activeNetwork)
/** /**
* Get list of available staking vaults * Get list of available staking vaults
@ -22,7 +28,7 @@ interface P2PEthPoolRepository {
* @return Either error or list of vaults with APY, capacity, fees * @return Either error or list of vaults with APY, capacity, fees
*/ */
suspend fun getVaults( suspend fun getVaults(
network: P2PEthPoolNetwork = P2PEthPoolNetwork.MAINNET, network: P2PEthPoolNetwork = P2PStakingConfig.activeNetwork,
): Either<StakingError, List<P2PEthPoolVault>> ): Either<StakingError, List<P2PEthPoolVault>>
/** /**

View file

@ -149,7 +149,7 @@ internal class StakingIdFactoryTest {
expected = createStakingId(integrationId = StakingIntegrationID.StakeKit.Coin.Cardano), expected = createStakingId(integrationId = StakingIntegrationID.StakeKit.Coin.Cardano),
), ),
CreateModel( CreateModel(
currencyId = createCurrencyId(blockchain = Blockchain.Ethereum), currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2P.EthereumPooled.blockchain),
expected = createStakingId(integrationId = StakingIntegrationID.P2P.EthereumPooled), expected = createStakingId(integrationId = StakingIntegrationID.P2P.EthereumPooled),
), ),
CreateModel( CreateModel(

View file

@ -151,7 +151,7 @@ class StakingIntegrationIDTest {
expected = StakingIntegrationID.StakeKit.Coin.Cardano, expected = StakingIntegrationID.StakeKit.Coin.Cardano,
), ),
CreateModel( CreateModel(
currencyId = createCurrencyId(blockchain = Blockchain.Ethereum), currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2P.EthereumPooled.blockchain),
expected = StakingIntegrationID.P2P.EthereumPooled, expected = StakingIntegrationID.P2P.EthereumPooled,
), ),
CreateModel( CreateModel(

View file

@ -5,7 +5,7 @@
# https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/tangem-sdk-android/
# https://github.com/tangem/vico # https://github.com/tangem/vico
tangemBlockchainSdk = "develop-1309" tangemBlockchainSdk = "develop-1317"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-568" tangemCardSdk = "develop-568"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^