Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-06 17:16:32 +04:00
commit 19df6fefae
194 changed files with 5193 additions and 1945 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

@ -23,9 +23,11 @@ interface AppsFlyerStore {
enum class AppsFlyerDeeplinkSource {
TangemPayHotWalletOnboarding,
Referral,
;
fun toStoreKey() = when (this) {
TangemPayHotWalletOnboarding -> "tangem_pay_hot_wallet_onboarding"
Referral -> "referral"
}
}

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()
}
}