Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-01 21:31:10 +04:00
commit 9f5099b7fd
14 changed files with 400 additions and 50 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.datasource.api.ethpool
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolAccountsListRequest
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolBroadcastRequest
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolTransactionRequest
import com.tangem.datasource.api.ethpool.models.response.*
@ -94,4 +95,20 @@ interface P2PEthPoolApi {
@Path("delegatorAddress") delegatorAddress: String,
@Path("vaultAddress") vaultAddress: String,
): ApiResponse<P2PEthPoolResponse<P2PEthPoolAccountResponse>>
/**
* Get account summaries for multiple delegators in a vault (batch).
*
* Designed to be called once per client to avoid rate-limit bursts.
*
* @param network Ethereum pool network: "mainnet" or "hoodi"
* @param vaultAddress Ethereum address of the vault
* @param body Delegator addresses to fetch (up to 255)
*/
@POST("api/v1/staking/pool/{network}/vaults/{vaultAddress}/accounts/list")
suspend fun getAccountsList(
@Path("network") network: String,
@Path("vaultAddress") vaultAddress: String,
@Body body: P2PEthPoolAccountsListRequest,
): ApiResponse<P2PEthPoolResponse<P2PEthPoolAccountsListResponse>>
}

View file

@ -0,0 +1,16 @@
package com.tangem.datasource.api.ethpool.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Request body for POST /api/v1/staking/pool/{network}/vaults/{vaultAddress}/accounts/list
*
* Batch fetch of staking balances for multiple delegator addresses within a single vault.
* Limit: up to 255 addresses per request. Addresses are deduplicated server-side.
*/
@JsonClass(generateAdapter = true)
data class P2PEthPoolAccountsListRequest(
@Json(name = "delegatorAddresses")
val delegatorAddresses: List<String>,
)

View file

@ -0,0 +1,26 @@
package com.tangem.datasource.api.ethpool.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Response for POST /api/v1/staking/pool/{network}/vaults/{vaultAddress}/accounts/list
*
* Each item is keyed by delegatorAddress and carries either a non-null [account]
* or a per-address [error] (e.g. code 127108 invalid delegator address).
*/
@JsonClass(generateAdapter = true)
data class P2PEthPoolAccountsListResponse(
@Json(name = "list")
val list: List<P2PEthPoolAccountListItem>,
)
@JsonClass(generateAdapter = true)
data class P2PEthPoolAccountListItem(
@Json(name = "delegatorAddress")
val delegatorAddress: String,
@Json(name = "account")
val account: P2PEthPoolAccountResponse?,
@Json(name = "error")
val error: P2PEthPoolErrorDetailsDTO?,
)

View file

@ -23,7 +23,7 @@ data class P2PEthPoolErrorDetailsDTO(
@Json(name = "message")
val message: String, // Human-readable error message
@Json(name = "name")
val name: String, // Error name/type
val name: String?, // Error name/type
@Json(name = "errors")
val errors: List<String>? = null, // Optional validation errors array
)

View file

@ -0,0 +1,68 @@
package com.tangem.datasource.api.ethpool
import com.google.common.truth.Truth.assertThat
import com.squareup.moshi.Types
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountsListResponse
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolResponse
import org.junit.jupiter.api.Test
import java.math.BigDecimal
internal class P2PEthPoolAccountsListResponseTest {
private val adapter = MoshiConverter.networkMoshi.adapter<P2PEthPoolResponse<P2PEthPoolAccountsListResponse>>(
Types.newParameterizedType(
P2PEthPoolResponse::class.java,
P2PEthPoolAccountsListResponse::class.java,
),
)
@Test
fun `decode batch payload with valid account and per-address error`() {
val response = requireNotNull(adapter.fromJson(SAMPLE_JSON))
val list = requireNotNull(response.result).list
assertThat(list).hasSize(2)
val good = list.first { it.account != null }
val account = requireNotNull(good.account)
assertThat(account.stake.assets.compareTo(BigDecimal("1.2345"))).isEqualTo(0)
assertThat(account.availableToWithdraw).isGreaterThan(BigDecimal(15049))
assertThat(account.exitQueue.requests).isEmpty()
val bad = list.first { it.account == null }
assertThat(requireNotNull(bad.error).code).isEqualTo(127108)
}
private companion object {
private val SAMPLE_JSON = """
{
"error": null,
"result": {
"list": [
{
"delegatorAddress": "0x008d3cd3e349Cd3D5F7c287b3BaF9e4f3E4ba99b",
"account": {
"delegatorAddress": "0x008d3cd3e349Cd3D5F7c287b3BaF9e4f3E4ba99b",
"vaultAddress": "0x4c09BC47db288F998b33CD63BCc1b6ddCCe13F33",
"stake": { "assets": "1.234500000000000000", "totalEarnedAssets": 0.0191 },
"availableToUnstake": "0.000000000000000005",
"availableToWithdraw": 15049.547647281135,
"exitQueue": { "total": 0, "requests": [] }
},
"error": null
},
{
"delegatorAddress": "0xBADADDRESS",
"account": null,
"error": {
"code": 127108,
"message": "The provided delegator address is invalid or not properly formatted."
}
}
]
}
}
""".trimIndent()
}
}

View file

@ -566,7 +566,7 @@
<string name="dynamic_addresses_disable_main_button_title">Dynamische Adressen deaktivieren</string>
<string name="dynamic_addresses_disable_title">Dynamische Adressen deaktivieren</string>
<string name="dynamic_addresses_disabled_popup_title">Dynamische Adressen deaktiviert</string>
<string name="dynamic_addresses_enabled_toast_title">Einige Adressen fehlen</string>
<string name="dynamic_addresses_enabled_toast_title">Dynamische Adressen aktiviert</string>
<string name="dynamic_addresses_enter_features_privacy_description">Verwenden Sie für jede Transaktion eine neue Adresse, um die Rückverfolgbarkeit zu verringern und den Datenschutz in der Kette zu verbessern.</string>
<string name="dynamic_addresses_enter_features_privacy_title">Verbesserter Datenschutz</string>
<string name="dynamic_addresses_enter_features_receving_description">Einfacher Geldempfang in UTXO-basierten Netzwerken mit automatischer Adressgenerierung - keine manuelle Adressverwaltung erforderlich.</string>
@ -1550,6 +1550,7 @@
<string name="staking_enabled">Staking aktiviert</string>
<string name="staking_error_no_validators_message">Zurzeit sind keine Validierer verfügbar. Bitte versuche es später noch einmal.</string>
<string name="staking_error_no_validators_title">Staking nicht verfügbar</string>
<string name="staking_error_unavailable_region">Staking ist in Ihrer Region nicht verfügbar.</string>
<string name="staking_give_permission_fee_footer">Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Du die Verwendung Deines Tokens für das Staking genehmigst.</string>
<string name="staking_legal">Indem Du die Staking-Funktionalität nutzt, stimmst Du den %1$s und %2$s des Anbieters zu.</string>
<string name="staking_locked">Gesperrt</string>

View file

@ -1421,6 +1421,7 @@
<string name="staking_enabled">Staking activé</string>
<string name="staking_error_no_validators_message">Aucun validateur disponible pour le moment. Veuillez réessayer plus tard.</string>
<string name="staking_error_no_validators_title">Staking indisponible</string>
<string name="staking_error_unavailable_region">Le staking est indisponible dans votre région</string>
<string name="staking_give_permission_fee_footer">Le réseau facturera des frais dapprobation de jeton pour vérifier que vous autorisez lutilisation de votre jeton pour le jalonnement.</string>
<string name="staking_legal">En utilisant la fonctionnalité de staking, vous acceptez les %1$s et %2$s du fournisseur</string>
<string name="staking_locked">Bloqué</string>

View file

@ -1520,6 +1520,7 @@
<string name="staking_enabled">ステーキングが有効です</string>
<string name="staking_error_no_validators_message">現在、利用可能なバリデーターは見つかりません。しばらくしてからもう一度お試しください。</string>
<string name="staking_error_no_validators_title">ステーキングは利用できません</string>
<string name="staking_error_unavailable_region">お住まいの地域ではステーキングをご利用いただけません</string>
<string name="staking_give_permission_fee_footer">ネットワークは、ステーキングのためにトークンの使用を承認していることを確認するために、トークン承認手数料を請求します。</string>
<string name="staking_legal">ステーキング機能を使用すると、プロバイダーの%1$sと%2$sに同意したことになります</string>
<string name="staking_locked">ロック中</string>

View file

@ -1512,6 +1512,7 @@
<string name="staking_enabled">已启用质押</string>
<string name="staking_error_no_validators_message">目前没有可用的验证节点。请稍后再试。</string>
<string name="staking_error_no_validators_title">质押功能不可用</string>
<string name="staking_error_unavailable_region">您所在的地区暂不支持质押功能</string>
<string name="staking_give_permission_fee_footer">网络将收取代币批准费,以验证您是否授权使用您的代币进行质押。</string>
<string name="staking_legal">使用质押功能即表示您同意提供商的 %1$s 和 %2$s</string>
<string name="staking_locked">已锁定</string>

View file

@ -115,10 +115,17 @@
<string name="address_book_enter_address">Enter address</string>
<string name="address_book_invalid_address_error">Invalid address</string>
<string name="address_book_keep_editing">Keep editing</string>
<string name="address_book_max_networks_alert_description">You can not create more than 20 addresses. Delete one to add new.</string>
<string name="address_book_max_networks_alert_title">Can\'t add new address </string>
<string name="address_book_name_empty_error">Contact name is required</string>
<string name="address_book_name_invalid_chars_error">Contact name contains invalid characters</string>
<string name="address_book_name_max_chars_error">Contact name must not exceed 50 characters</string>
<string name="address_book_name_taken_error">That name is already taken on this wallet</string>
<string name="address_book_new_contact">New contact</string>
<string name="address_book_no_contacts">No contacts yet</string>
<string name="address_book_no_contacts_description">Contacts added will appear here</string>
<string name="address_book_remove_address">Remove address</string>
<string name="address_book_save_to_wallet_title">Save to Wallet</string>
<string name="address_book_save_wallet_to_description">This contact will be linked to this wallets address book.</string>
<string name="address_book_select_network">Select network</string>
<string name="address_book_title">Address book</string>
@ -352,6 +359,7 @@
<string name="common_get_token">Get token</string>
<string name="common_go_to_provider">Go to provider</string>
<string name="common_go_to_token">Go to token</string>
<string name="common_go_to_verification">Go to verification</string>
<string name="common_got_it">Got it</string>
<string name="common_hide">Hide</string>
<string name="common_hold_to">Hold to %s</string>
@ -410,6 +418,7 @@
<string name="common_privacy_policy">Privacy Policy</string>
<string name="common_range">%1$s-%2$s</string>
<string name="common_range_with_space">%1$s — %2$s</string>
<string name="common_rate">Rate</string>
<string name="common_read_more">Read more</string>
<string name="common_receive">Receive</string>
<string name="common_received">Received</string>
@ -701,6 +710,8 @@
<string name="feedback_subject_support_tangem">Tangem feedback</string>
<string name="feedback_subject_tx_failed">Can\'t send a transaction</string>
<string name="feedback_token_description_error">Coin description error</string>
<string name="for_you_description">Review portfolio and explore earn opportunities</string>
<string name="for_you_title">For You</string>
<string name="force_update_action">Update now</string>
<string name="force_update_banner_message">Update the app to its latest version to ensure proper functionality</string>
<string name="force_update_banner_title">Update needed</string>
@ -732,6 +743,8 @@
<string name="hardware_wallet_key_feature_title">Key Generation</string>
<string name="hardware_wallet_security_feature_description">All cryptographic operations happen inside the secure chip, certified against cloning and physical tampering.</string>
<string name="hardware_wallet_security_feature_title">Hardware-Level Security</string>
<string name="high_fee_warning_description">Network activity is high. You can continue now or try again later when fees may be lower.</string>
<string name="high_fee_warning_title">Network fee is higher than usual</string>
<string name="home_button_add_existing_wallet">Add Existing Wallet</string>
<string name="home_button_create_new_wallet">Create New Wallet</string>
<string name="home_button_order">Order Tangem</string>
@ -1402,6 +1415,7 @@
<string name="send_extras_hint_memo">Memo</string>
<string name="send_fee_unreachable_error_text">Check your network connection</string>
<string name="send_fee_unreachable_error_title">Network fee info unreachable</string>
<string name="send_from_account_in_wallet_name">from %1$s in %2$s</string>
<string name="send_from_title">You send</string>
<string name="send_from_wallet_name">From %s</string>
<string name="send_gas_limit">Gas limit</string>

View file

@ -8,7 +8,6 @@ import com.tangem.data.common.api.safeApiCall
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
import com.tangem.data.staking.store.StakeKitBalancesStore
import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
import com.tangem.datasource.api.stakekit.StakeKitApi
@ -25,7 +24,6 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -65,6 +63,11 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
) : MultiStakingBalanceFetcher {
private val p2pAccountsFetcher = P2PEthPoolAccountsFetcher(
p2pEthPoolApi = p2pEthPoolApi,
dispatchers = dispatchers,
)
override suspend fun invoke(params: MultiStakingBalanceFetcher.Params): Either<Throwable, Unit> {
TangemLogger.i("Start fetching staking balances for params:\n$params")
@ -195,50 +198,7 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
vaults: List<P2PEthPoolVault>,
addresses: Set<String>,
): Set<P2PEthPoolAccountResponse> {
val responses = mutableSetOf<P2PEthPoolAccountResponse>()
for (vault in vaults) {
for (address in addresses) {
runSuspendCatching {
val response = p2pEthPoolApi.getAccountInfo(
network = P2PEthPoolStakingConfig.activeNetwork.value,
delegatorAddress = address,
vaultAddress = vault.vaultAddress,
)
when (response) {
is ApiResponse.Success -> {
val data = response.data
if (data.error != null) {
TangemLogger.w(
"P2PEthPool API returned error for vault ${vault.vaultAddress}, " +
"address $address: ${data.error ?: "error"}",
)
} else {
val result = requireNotNull(data.result) {
"Result is null in successful response"
}
responses.add(result)
}
}
is ApiResponse.Error -> {
TangemLogger.w(
"Failed to fetch P2PEthPool balance for vault ${vault.vaultAddress}, " +
"address $address",
response.cause,
)
}
}
}.onFailure { error ->
TangemLogger.w(
"Failed to fetch P2PEthPool balance for vault ${vault.vaultAddress}, address $address",
error,
)
}
}
}
return responses
return p2pAccountsFetcher.fetchBatch(vaults = vaults, addresses = addresses)
}
private inline fun checkIsSupportedByWalletOrElse(userWalletId: UserWalletId, ifNotSupported: (Throwable) -> Unit) {

View file

@ -0,0 +1,102 @@
package com.tangem.data.staking.multi
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolAccountsListRequest
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountsListResponse
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolResponse
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
/**
* Fetches P2P ETH Pool account responses via the batch strategy:
* one POST per vault sending all delegator addresses at once.
*
* @property p2pEthPoolApi P2PEthPool API
* @property dispatchers coroutine dispatcher provider
*
[REDACTED_AUTHOR]
*/
internal class P2PEthPoolAccountsFetcher(
private val p2pEthPoolApi: P2PEthPoolApi,
private val dispatchers: CoroutineDispatcherProvider,
) {
suspend fun fetchBatch(vaults: List<P2PEthPoolVault>, addresses: Set<String>): Set<P2PEthPoolAccountResponse> =
coroutineScope {
val request = P2PEthPoolAccountsListRequest(delegatorAddresses = addresses.toList())
vaults
.map { vault ->
async(dispatchers.io) {
runSuspendCatching {
val response = p2pEthPoolApi.getAccountsList(
network = P2PEthPoolStakingConfig.activeNetwork.value,
vaultAddress = vault.vaultAddress,
body = request,
)
mapBatchVaultResponse(vault = vault, response = response)
}.getOrElse { error ->
TangemLogger.w(
"Failed to fetch P2PEthPool batch balances for vault ${vault.vaultAddress}",
error,
)
emptyList()
}
}
}
.awaitAll()
.flatten()
.toSet()
}
private fun mapBatchVaultResponse(
vault: P2PEthPoolVault,
response: ApiResponse<P2PEthPoolResponse<P2PEthPoolAccountsListResponse>>,
): List<P2PEthPoolAccountResponse> {
return when (response) {
is ApiResponse.Success -> {
val data = response.data
if (data.error != null) {
TangemLogger.w(
"P2PEthPool batch API returned error for vault " +
"${vault.vaultAddress}: ${data.error}",
)
emptyList()
} else {
val result = requireNotNull(data.result) {
"Result is null in successful response"
}
result.list.mapNotNull { item ->
if (item.error != null) {
TangemLogger.w(
"P2PEthPool batch item error for vault " +
"${vault.vaultAddress}, address " +
"${item.delegatorAddress}: ${item.error}",
)
null
} else {
item.account
}
}
}
}
is ApiResponse.Error -> {
TangemLogger.w(
"Failed to fetch P2PEthPool batch balances for vault " +
"${vault.vaultAddress}",
response.cause,
)
emptyList()
}
}
}
}

View file

@ -10,12 +10,15 @@ import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolAccountsListRequest
import com.tangem.datasource.api.ethpool.models.response.*
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.token.P2PEthPoolVaultsStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.test.core.assertEitherLeft
import com.tangem.test.core.assertEitherRight
@ -26,6 +29,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
@ -54,7 +58,14 @@ internal class DefaultMultiStakingBalanceFetcherTest {
@BeforeEach
fun resetMocks() {
clearMocks(userWalletsListRepository, stakingYieldsStore, stakeKitBalancesStore, stakeKitApi)
clearMocks(
userWalletsListRepository,
stakingYieldsStore,
stakeKitBalancesStore,
stakeKitApi,
p2pEthPoolApi,
p2pEthPoolVaultsStore,
)
}
@Test
@ -359,6 +370,88 @@ internal class DefaultMultiStakingBalanceFetcherTest {
assertEitherLeft(actual, expected)
}
@Test
fun `fetch P2P balances via batch endpoint`() = runTest {
// Arrange
val params = MultiStakingBalanceFetcher.Params(userWalletId, setOf(p2pId1, p2pId2))
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(userWallet))
coEvery { p2pEthPoolVaultsStore.getSync() } returns listOf(vault(VAULT_A), vault(VAULT_B))
coEvery { p2pEthPoolApi.getAccountsList(any(), VAULT_A, any()) } returns
accountsListSuccess(accountResponse(ADDR_1, VAULT_A))
coEvery { p2pEthPoolApi.getAccountsList(any(), VAULT_B, any()) } returns
accountsListSuccess(accountResponse(ADDR_2, VAULT_B))
// Actual
val actual = fetcher.invoke(params)
// Assert
coVerify(exactly = 1) {
p2pEthPoolApi.getAccountsList(
network = any(),
vaultAddress = VAULT_A,
body = match { it.delegatorAddresses.containsAll(listOf(ADDR_1, ADDR_2)) },
)
}
coVerify(exactly = 1) {
p2pEthPoolApi.getAccountsList(network = any(), vaultAddress = VAULT_B, body = any())
}
coVerify(inverse = true) { p2pEthPoolApi.getAccountInfo(any(), any(), any()) }
coVerify { p2PEthPoolBalancesStore.storeActual(userWalletId = userWalletId, values = any()) }
assertEitherRight(actual)
}
@Test
fun `fetch P2P batch maps per-item error to missing stakingId`() = runTest {
// Arrange
val params = MultiStakingBalanceFetcher.Params(userWalletId, setOf(p2pId1, p2pId2))
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(userWallet))
coEvery { p2pEthPoolVaultsStore.getSync() } returns listOf(vault(VAULT_A))
coEvery { p2pEthPoolApi.getAccountsList(any(), VAULT_A, any()) } returns
ApiResponse.Success(
P2PEthPoolResponse(
error = null,
result = P2PEthPoolAccountsListResponse(
list = listOf(
P2PEthPoolAccountListItem(
delegatorAddress = ADDR_1,
account = accountResponse(ADDR_1, VAULT_A),
error = null,
),
P2PEthPoolAccountListItem(
delegatorAddress = ADDR_2,
account = null,
error = P2PEthPoolErrorDetailsDTO(
code = 127108,
message = "invalid",
name = null,
errors = null,
),
),
),
),
),
)
// Actual
val actual = fetcher.invoke(params)
// Assert
coVerify { p2PEthPoolBalancesStore.storeActual(userWalletId = userWalletId, values = any()) }
coVerify {
p2PEthPoolBalancesStore.storeError(
userWalletId = userWalletId,
stakingIds = match { it == setOf(p2pId2) },
)
}
assertEitherRight(actual)
}
private companion object {
val userWallet = MockUserWalletFactory.create()
val userWalletId = userWallet.walletId
@ -370,5 +463,55 @@ internal class DefaultMultiStakingBalanceFetcherTest {
)
val tonAndSolanaIds = setOf(tonId, solanaId)
const val ADDR_1 = "0x1111111111111111111111111111111111111111"
const val ADDR_2 = "0x2222222222222222222222222222222222222222"
const val VAULT_A = "0xVaultAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
const val VAULT_B = "0xVaultBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
val p2pId1 = StakingID(integrationId = "p2p-ethereum-pooled", address = ADDR_1)
val p2pId2 = StakingID(integrationId = "p2p-ethereum-pooled", address = ADDR_2)
fun vault(address: String) = P2PEthPoolVault(
vaultAddress = address,
displayName = "Vault",
apy = BigDecimal("4.5"),
baseApy = BigDecimal("4.0"),
capacity = BigDecimal("1000"),
totalAssets = BigDecimal("100"),
feePercent = BigDecimal("10"),
isPrivate = false,
isGenesis = false,
isSmoothingPool = true,
isErc20 = false,
tokenName = null,
tokenSymbol = null,
createdAt = 0L,
)
fun accountResponse(address: String, vaultAddress: String) = P2PEthPoolAccountResponse(
delegatorAddress = address,
vaultAddress = vaultAddress,
stake = P2PEthPoolStakeDTO(assets = BigDecimal("1.5"), totalEarnedAssets = BigDecimal("0.1")),
availableToUnstake = BigDecimal.ZERO,
availableToWithdraw = BigDecimal.ZERO,
exitQueue = P2PEthPoolExitQueueDTO(total = BigDecimal.ZERO, requests = emptyList()),
)
fun accountsListSuccess(vararg accounts: P2PEthPoolAccountResponse) =
ApiResponse.Success(
P2PEthPoolResponse(
error = null,
result = P2PEthPoolAccountsListResponse(
list = accounts.map {
P2PEthPoolAccountListItem(
delegatorAddress = it.delegatorAddress,
account = it,
error = null,
)
},
),
),
)
}
}

View file

@ -57,7 +57,7 @@ internal class WcSolanaSignAndSendTransactionUseCase @AssistedInject constructor
val formattedHash = getFormattedHash(hash) // uses for flow sendLargeSolanaTransaction
if (context.session.wallet is UserWallet.Cold && isLargeHash(formattedHash)) {
// workaround for large transactions that cannot be signed directly by card
TangemLogger.w("The transaction hash is too large to be signed directly: ${formattedHash.size} bytes")
TangemLogger.i("The transaction hash is too large to be signed directly: ${formattedHash.size} bytes")
sendLargeSolanaTransactionUseCase(context.session.wallet as UserWallet.Cold, context.network, formattedHash)
.fold(
ifLeft = { error ->