Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-22 18:17:15 +03:00
commit 9b53fd4f0c
909 changed files with 14900 additions and 6085 deletions

View file

@ -22,6 +22,9 @@ enum class ApiEnvironment {
@Json(name = "STAGE")
STAGE,
@Json(name = "STAGE_2")
STAGE_2,
@Json(name = "MOCK")
MOCK,

View file

@ -30,6 +30,7 @@ internal class Express(
createDev2Environment(),
createDev3Environment(),
createStageEnvironment(),
createStage2Environment(),
createMockedEnvironment(),
createProdEnvironment(),
)
@ -73,6 +74,12 @@ internal class Express(
headers = createHeaders(isProd = false),
)
private fun createStage2Environment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.STAGE_2,
baseUrl = "[REDACTED_ENV_URL]",
headers = createHeaders(isProd = false),
)
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.MOCK,
baseUrl = "[REDACTED_ENV_URL]",

View file

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

View file

@ -31,7 +31,7 @@ internal class TangemPay(
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.DEV,
baseUrl = "https://api.dev.us.paera.com/bff/",
baseUrl = "https://api.dev.us.paera.com/bff-v2/",
headers = createHeaders(),
)
@ -43,7 +43,7 @@ internal class TangemPay(
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.PROD,
baseUrl = "https://api.us.paera.com/bff/",
baseUrl = "https://api.us.paera.com/bff-v2/",
headers = createHeaders(),
)

View file

@ -77,6 +77,7 @@ internal class YieldSupply(
ApiEnvironment.DEV_2,
ApiEnvironment.DEV_3,
ApiEnvironment.STAGE,
ApiEnvironment.STAGE_2,
-> environmentConfigStorage.getConfigSync().yieldModuleApiKeyDev
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().yieldModuleApiKey
} ?: error("No tangem tech api config provided")

View file

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

View file

@ -26,6 +26,7 @@ data class TokenMarketListResponse(
@Json(name = "market_cap") val marketCap: BigDecimal?,
@Json(name = "is_under_market_cap_limit") val isUnderMarketCapLimit: Boolean?,
@Json(name = "staking_opportunities") val stakingOpportunities: List<StakingOpportunities>?,
@Json(name = "max_yield_apy") val maxYieldApy: BigDecimal?,
) {
@JsonClass(generateAdapter = true)

View file

@ -38,6 +38,6 @@ interface NewsApi {
private companion object {
private const val NEWS_PATH = "api/v1/news"
private const val NEWS_PATH = "v1/news"
}
}

View file

@ -5,11 +5,5 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class NewsTrendingResponse(
@Json(name = "meta") val meta: NewsTrendingMetaDto,
@Json(name = "items") val items: List<NewsArticleDto>,
)
@JsonClass(generateAdapter = true)
data class NewsTrendingMetaDto(
@Json(name = "limit") val limit: Int,
)

View file

@ -3,109 +3,13 @@ package com.tangem.datasource.api.pay
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.pay.models.request.*
import com.tangem.datasource.api.pay.models.response.*
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.*
private const val TX_HISTORY_PAGING_DEFAULT_LIMIT = 20
@Suppress("TooManyFunctions")
interface TangemPayApi {
// region: auth
@POST("v1/auth/challenge")
suspend fun generateNonceByCardId(@Body request: GenerateNoneByCardIdRequest): ApiResponse<GenerateNonceResponse>
@POST("v1/auth/challenge")
suspend fun generateNonceByCardWallet(
@Body request: GenerateNoneByCardWalletRequest,
): ApiResponse<GenerateNonceResponse>
@POST("v1/auth/token")
suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): ApiResponse<JWTResponse>
@POST("v1/auth/token")
suspend fun getTokenByCustomerWallet(@Body request: GetTokenByCustomerWalletRequest): ApiResponse<JWTResponse>
@POST("v1/auth/token/refresh")
suspend fun refreshCustomerWalletAccessToken(
@Body request: RefreshCustomerWalletAccessTokenRequest,
): ApiResponse<JWTResponse>
@POST("v1/auth/token")
suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): ApiResponse<JWTResponse>
@POST("v1/auth/token/refresh")
suspend fun refreshCardIdAccessToken(@Body request: RefreshTokenByCardIdRequest): ApiResponse<JWTResponse>
@POST("v1/auth/token/refresh")
suspend fun refreshCardWalletAccessToken(@Body request: RefreshTokenByCardWalletRequest): ApiResponse<JWTResponse>
@POST("v1/auth/token/exchange")
suspend fun exchangeAccessToken(@Body request: ExchangeAccessTokenRequest): ApiResponse<JWTResponse>
// endregion
// region: activation
@POST("v1/activation/status")
suspend fun getRemoteActivationStatus(
@Header("Authorization") authHeader: String,
@Body request: ActivationStatusRequest,
): ApiResponse<CardActivationRemoteStateResponse>
@POST("v1/activation/acceptance/message")
suspend fun getCardWalletAcceptance(
@Header("Authorization") authHeader: String,
@Body request: GetCardWalletAcceptanceRequest,
): ApiResponse<VisaDataToSignResponse>
@POST("v1/activation/acceptance/message")
suspend fun getCustomerWalletAcceptance(
@Header("Authorization") authHeader: String,
@Body request: GetCustomerWalletAcceptanceRequest,
): ApiResponse<VisaDataToSignResponse>
@POST("v1/activation/data")
suspend fun activateByCardWallet(
@Header("Authorization") authHeader: String,
@Body body: ActivationByCardWalletRequest,
): ApiResponse<Unit>
@POST("v1/activation/data")
suspend fun activateByCustomerWallet(
@Header("Authorization") authHeader: String,
@Body body: ActivationByCustomerWalletRequest,
): ApiResponse<Unit>
@POST("v1/activation/pin")
suspend fun setPinCode(
@Header("Authorization") authHeader: String,
@Body body: SetPinCodeRequest,
): ApiResponse<Unit>
// endregion
@GET("customer/info")
suspend fun getCustomerInfo(
@Header("Authorization") authHeader: String,
@Query("card_id") cardId: String,
): ApiResponse<VisaCustomerInfo>
@GET("product_instance/transactions")
suspend fun getTxHistory(
@Header("Authorization") authHeader: String,
@Query("customer_id") customerId: String,
@Query("product_instance_id") productInstanceId: String,
@Query("limit") limit: Int,
@Query("offset") offset: Int,
): ApiResponse<VisaTxHistoryResponse>
@GET("v1/customer/transactions")
suspend fun getTangemPayTxHistory(
@Header("Authorization") authHeader: String,
@ -128,6 +32,9 @@ interface TangemPayApi {
@POST("v1/deeplink/validate")
suspend fun validateDeeplink(@Body body: DeeplinkValidityRequest): ApiResponse<DeeplinkValidityResponse>
@GET("v1/customer/eligibility")
suspend fun checkCustomerEligibility(): ApiResponse<CustomerEligibilityResponse>
@GET("v1/order/{order_id}")
suspend fun getOrder(
@Header("Authorization") authHeader: String,

View file

@ -4,5 +4,4 @@ import com.squareup.moshi.Json
data class CardBalanceResponse(
@Json(name = "result") val result: BalanceResponse?,
@Json(name = "error") val error: String?,
)

View file

@ -5,7 +5,6 @@ import com.squareup.moshi.JsonClass
data class CardDetailsResponse(
@Json(name = "result") val result: Result?,
@Json(name = "error") val error: String?,
) {
@JsonClass(generateAdapter = true)
data class Result(
@ -18,6 +17,7 @@ data class CardDetailsResponse(
@Json(name = "card_number_end") val cardNumberEnd: String,
@Json(name = "pan") val pan: Secret,
@Json(name = "cvv") val cvv: Secret,
@Json(name = "pin") val pin: Secret?,
)
@JsonClass(generateAdapter = true)

View file

@ -5,5 +5,9 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class CheckCustomerWalletResponse(
@Json(name = "id") val id: String?,
)
@Json(name = "result") val result: Result?,
) {
data class Result(
@Json(name = "id") val id: String?,
)
}

View file

@ -0,0 +1,14 @@
package com.tangem.datasource.api.pay.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class CustomerEligibilityResponse(
@Json(name = "result") val result: Result?,
) {
@JsonClass(generateAdapter = true)
data class Result(
@Json(name = "is_tangem_pay_available") val isTangemPayAvailable: Boolean,
)
}

View file

@ -6,17 +6,16 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class CustomerMeResponse(
@Json(name = "result") val result: Result?,
@Json(name = "error") val error: String?,
) {
@JsonClass(generateAdapter = true)
data class Result(
@Json(name = "id") val id: String,
@Json(name = "state") val state: String,
@Json(name = "createdAt") val createdAt: String,
@Json(name = "created_at") val createdAt: String,
@Json(name = "product_instance") val productInstance: ProductInstance?,
@Json(name = "payment_account") val paymentAccount: PaymentAccount?,
@Json(name = "kyc") val kyc: Kyc?,
@Json(name = "depositAddress") val depositAddress: String?,
@Json(name = "deposit_address") val depositAddress: String?,
@Json(name = "card") val card: Card?,
@Json(name = "balance") val balance: BalanceResponse?,
)
@ -24,49 +23,49 @@ data class CustomerMeResponse(
@JsonClass(generateAdapter = true)
data class ProductInstance(
@Json(name = "id") val id: String,
@Json(name = "cid") val cid: String,
@Json(name = "cid") val cid: String?,
@Json(name = "card_id") val cardId: String,
@Json(name = "card_wallet_address") val cardWalletAddress: String,
@Json(name = "card_wallet_address") val cardWalletAddress: String?,
@Json(name = "status") val status: Status,
@Json(name = "updated_at") val updatedAt: String,
@Json(name = "payment_account_id") val paymentAccountId: String,
) {
@JsonClass(generateAdapter = false)
enum class Status {
@Json(name = "new")
@Json(name = "NEW")
NEW,
@Json(name = "ready_for_manufacturing")
@Json(name = "READY_FOR_MANUFACTURING")
READY_FOR_MANUFACTURING,
@Json(name = "manufacturing")
@Json(name = "MANUFACTURING")
MANUFACTURING,
@Json(name = "sent_to_delivery")
@Json(name = "SENT_TO_DELIVERY")
SENT_TO_DELIVERY,
@Json(name = "delivered")
@Json(name = "DELIVERED")
DELIVERED,
@Json(name = "activating")
@Json(name = "ACTIVATING")
ACTIVATING,
@Json(name = "active")
@Json(name = "ACTIVE")
ACTIVE,
@Json(name = "blocked")
@Json(name = "BLOCKED")
BLOCKED,
@Json(name = "deactivating")
@Json(name = "DEACTIVATING")
DEACTIVATING,
@Json(name = "deactivated")
@Json(name = "DEACTIVATED")
DEACTIVATED,
@Json(name = "canceled")
@Json(name = "CANCELED")
CANCELED,
@Json(name = "unknown")
@Json(name = "UNKNOWN")
UNKNOWN,
}
}
@ -91,11 +90,12 @@ data class CustomerMeResponse(
@JsonClass(generateAdapter = true)
data class Card(
@Json(name = "token") val token: String,
@Json(name = "expiration_month") val expirationMonth: Int,
@Json(name = "expiration_year") val expirationYear: Int,
@Json(name = "expiration_month") val expirationMonth: String,
@Json(name = "expiration_year") val expirationYear: String,
@Json(name = "emboss_name") val embossName: String,
@Json(name = "card_type") val cardType: String,
@Json(name = "card_status") val cardStatus: String,
@Json(name = "card_number_end") val cardNumberEnd: String,
@Json(name = "is_pin_set") val isPinSet: Boolean?,
)
}

View file

@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class DeeplinkValidityResponse(
@Json(name = "result") val result: Result?,
@Json(name = "error") val error: String?,
) {
@JsonClass(generateAdapter = true)
data class Result(

View file

@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class FreezeUnfreezeCardResponse(
@Json(name = "result") val result: Result?,
@Json(name = "error") val error: String?,
) {
@JsonClass(generateAdapter = true)
data class Result(

View file

@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OrderResponse(
@Json(name = "result") val result: Result?,
@Json(name = "error") val error: String?,
) {
@JsonClass(generateAdapter = true)
data class Result(

View file

@ -4,7 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class VisaErrorResponse(
data class TangemPayErrorResponse(
@Json(name = "error") val error: Error,
) {
@JsonClass(generateAdapter = true)

View file

@ -7,7 +7,6 @@ import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class TangemPayTxHistoryResponse(
@Json(name = "error") val error: String?,
@Json(name = "result") val result: Result,
) {
@JsonClass(generateAdapter = true)

View file

@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class WithdrawDataResponse(
@Json(name = "result") val result: Result?,
@Json(name = "error") val error: String?,
) {
@JsonClass(generateAdapter = true)
data class Result(

View file

@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class WithdrawResponse(
@Json(name = "result") val result: Result?,
@Json(name = "error") val error: String?,
) {
@JsonClass(generateAdapter = true)
data class Result(

View file

@ -50,6 +50,12 @@ interface TangemTechApi {
@Body userTokens: UserTokensResponse,
): ApiResponse<Unit>
@PUT("/v1/wallets/{walletId}/tokens")
suspend fun saveTokens(
@Path(value = "walletId") userId: String,
@Body userTokens: UserTokensResponse,
): ApiResponse<Unit>
/** Returns referral status by [walletId] */
@GET("v1/referral/{walletId}")
suspend fun getReferralStatus(@Path("walletId") walletId: String): ApiResponse<ReferralResponse>
@ -129,6 +135,12 @@ interface TangemTechApi {
@Body body: List<WalletIdBody>,
): ApiResponse<Unit>
@PUT("/v1/user-wallets/applications/{application_id}/wallets")
suspend fun associateApplicationIdWithWalletsV2(
@Path("application_id") applicationId: String,
@Body body: AssociateApplicationIdWithWalletsBody,
): ApiResponse<Unit>
@GET("v1/user-wallets/wallets/{wallet_id}")
suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse<WalletResponse>

View file

@ -0,0 +1,9 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class AssociateAppWithWalletsErrorResponse(
@Json(name = "missingWalletIds") val missingWalletIds: List<String>,
)

View file

@ -0,0 +1,9 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class AssociateApplicationIdWithWalletsBody(
@Json(name = "walletIds") val walletIds: List<String>,
)

View file

@ -19,6 +19,7 @@ data class GetWalletAccountsResponse(
@Json(name = "group") val group: GroupType?,
@Json(name = "sort") val sort: SortType?,
@Json(name = "totalAccounts") val totalAccounts: Int,
@Json(name = "totalArchivedAccounts") val totalArchivedAccounts: Int,
)
}

View file

@ -0,0 +1,104 @@
package com.tangem.datasource.api.visa
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardIdRequest
import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardWalletRequest
import com.tangem.datasource.api.pay.models.request.SetPinCodeRequest
import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest
import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest
import com.tangem.datasource.api.visa.models.request.ActivationStatusRequest
import com.tangem.datasource.api.visa.models.request.ExchangeAccessTokenRequest
import com.tangem.datasource.api.visa.models.request.GenerateNoneByCardIdRequest
import com.tangem.datasource.api.visa.models.request.GenerateNoneByCardWalletRequest
import com.tangem.datasource.api.visa.models.request.GetAccessTokenByCardIdRequest
import com.tangem.datasource.api.visa.models.request.GetAccessTokenByCardWalletRequest
import com.tangem.datasource.api.visa.models.request.GetCardWalletAcceptanceRequest
import com.tangem.datasource.api.visa.models.request.GetCustomerWalletAcceptanceRequest
import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse
import com.tangem.datasource.api.visa.models.response.GenerateNonceResponse
import com.tangem.datasource.api.visa.models.response.JWTResponse
import com.tangem.datasource.api.visa.models.response.VisaCustomerInfo
import com.tangem.datasource.api.visa.models.response.VisaDataToSignResponse
import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Query
interface VisaApi {
@POST("v1/auth/token/refresh")
suspend fun refreshCardWalletAccessToken(@Body request: RefreshTokenByCardWalletRequest): ApiResponse<JWTResponse>
@POST("v1/auth/challenge")
suspend fun generateNonceByCardId(@Body request: GenerateNoneByCardIdRequest): ApiResponse<GenerateNonceResponse>
@POST("v1/auth/challenge")
suspend fun generateNonceByCardWallet(
@Body request: GenerateNoneByCardWalletRequest,
): ApiResponse<GenerateNonceResponse>
@POST("v1/auth/token")
suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): ApiResponse<JWTResponse>
@POST("v1/auth/token")
suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): ApiResponse<JWTResponse>
@POST("v1/auth/token/refresh")
suspend fun refreshCardIdAccessToken(@Body request: RefreshTokenByCardIdRequest): ApiResponse<JWTResponse>
@POST("v1/auth/token/exchange")
suspend fun exchangeAccessToken(@Body request: ExchangeAccessTokenRequest): ApiResponse<JWTResponse>
@POST("v1/activation/status")
suspend fun getRemoteActivationStatus(
@Header("Authorization") authHeader: String,
@Body request: ActivationStatusRequest,
): ApiResponse<CardActivationRemoteStateResponse>
@POST("v1/activation/acceptance/message")
suspend fun getCardWalletAcceptance(
@Header("Authorization") authHeader: String,
@Body request: GetCardWalletAcceptanceRequest,
): ApiResponse<VisaDataToSignResponse>
@POST("v1/activation/acceptance/message")
suspend fun getCustomerWalletAcceptance(
@Header("Authorization") authHeader: String,
@Body request: GetCustomerWalletAcceptanceRequest,
): ApiResponse<VisaDataToSignResponse>
@POST("v1/activation/data")
suspend fun activateByCardWallet(
@Header("Authorization") authHeader: String,
@Body body: ActivationByCardWalletRequest,
): ApiResponse<Unit>
@POST("v1/activation/data")
suspend fun activateByCustomerWallet(
@Header("Authorization") authHeader: String,
@Body body: ActivationByCustomerWalletRequest,
): ApiResponse<Unit>
@POST("v1/activation/pin")
suspend fun setPinCode(
@Header("Authorization") authHeader: String,
@Body body: SetPinCodeRequest,
): ApiResponse<Unit>
@GET("customer/info")
suspend fun getCustomerInfo(
@Header("Authorization") authHeader: String,
@Query("card_id") cardId: String,
): ApiResponse<VisaCustomerInfo>
@GET("product_instance/transactions")
suspend fun getTxHistory(
@Header("Authorization") authHeader: String,
@Query("customer_id") customerId: String,
@Query("product_instance_id") productInstanceId: String,
@Query("limit") limit: Int,
@Query("offset") offset: Int,
): ApiResponse<VisaTxHistoryResponse>
}

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.request
package com.tangem.datasource.api.visa.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.request
package com.tangem.datasource.api.visa.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.request
package com.tangem.datasource.api.visa.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.request
package com.tangem.datasource.api.visa.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.request
package com.tangem.datasource.api.visa.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.request
package com.tangem.datasource.api.visa.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.request
package com.tangem.datasource.api.visa.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.request
package com.tangem.datasource.api.visa.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.request
package com.tangem.datasource.api.visa.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.request
package com.tangem.datasource.api.visa.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.response
package com.tangem.datasource.api.visa.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.response
package com.tangem.datasource.api.visa.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.response
package com.tangem.datasource.api.visa.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.response
package com.tangem.datasource.api.visa.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.response
package com.tangem.datasource.api.visa.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.api.pay.models.response
package com.tangem.datasource.api.visa.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -15,4 +15,7 @@ interface AppCurrencyResponseStore {
/** Get [CurrenciesResponse.Currency] synchronously or null */
suspend fun getSyncOrNull(): CurrenciesResponse.Currency?
/** Store [CurrenciesResponse.Currency] */
suspend fun store(currency: CurrenciesResponse.Currency)
}

View file

@ -5,6 +5,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObject
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.preferences.utils.storeObject
import kotlinx.coroutines.flow.Flow
/**
@ -25,4 +26,11 @@ internal class DefaultAppCurrencyResponseStore(
PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
)
}
override suspend fun store(currency: CurrenciesResponse.Currency) {
appPreferencesStore.storeObject(
PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
currency,
)
}
}

View file

@ -5,7 +5,6 @@ import com.tangem.datasource.api.common.blockaid.BlockAidApi
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfigs
import com.tangem.datasource.api.common.config.MoonPay
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.api.common.config.managers.DevApiConfigsManager
import com.tangem.datasource.api.common.config.managers.MockApiConfigsManager
@ -21,6 +20,7 @@ import com.tangem.datasource.api.pay.TangemPayAuthApi
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
import com.tangem.datasource.api.visa.VisaApi
import com.tangem.datasource.di.utils.RetrofitApiBuilder
import com.tangem.datasource.di.utils.RetrofitApiBuilder.Timeouts
import com.tangem.datasource.local.preferences.AppPreferencesStore
@ -129,7 +129,16 @@ internal object NetworkModule {
@Provides
@Singleton
fun provideTangemVisaApi(retrofitApiBuilder: RetrofitApiBuilder): TangemPayApi {
fun provideTangemPayApi(retrofitApiBuilder: RetrofitApiBuilder): TangemPayApi {
return retrofitApiBuilder.build(
apiConfigId = ApiConfig.ID.TangemPay,
applyTimeoutAnnotations = false,
)
}
@Provides
@Singleton
fun provideVisaApi(retrofitApiBuilder: RetrofitApiBuilder): VisaApi {
return retrofitApiBuilder.build(
apiConfigId = ApiConfig.ID.TangemPay,
applyTimeoutAnnotations = false,

View file

@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
import com.tangem.datasource.local.datastore.RuntimeDataStore
@ -77,6 +78,24 @@ internal object StakingStoreModule {
return DefaultStakingActionsStore(dataStore = RuntimeDataStore())
}
@Provides
@Singleton
fun provideP2PBalancesPersistenceStore(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
dispatchers: CoroutineDispatcherProvider,
): DataStore<Map<String, Set<P2PEthPoolAccountResponse>>> {
return DataStoreFactory.create(
serializer = MoshiDataStoreSerializer(
moshi = moshi,
types = mapWithStringKeyTypes(valueTypes = setTypes<P2PEthPoolAccountResponse>()),
defaultValue = emptyMap(),
),
produceFile = { context.dataStoreFile(fileName = "p2p_balances") },
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
)
}
@Provides
@Singleton
fun provideP2PEthPoolVaultsStore(

View file

@ -1,25 +1,25 @@
package com.tangem.datasource.local.news.trending
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.domain.models.news.ShortArticle
import com.tangem.domain.models.news.TrendingNews
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
private typealias TrendingCache = Map<String, List<ShortArticle>>
private typealias TrendingCache = Map<String, TrendingNews>
internal class DefaultTrendingNewsStore(
private val store: RuntimeSharedStore<TrendingCache>,
) : TrendingNewsStore {
override fun get(key: String): Flow<List<ShortArticle>> {
return store.get().map { it[key].orEmpty() }
override fun get(key: String): Flow<TrendingNews> {
return store.get().map { it[key] ?: TrendingNews.Data(emptyList()) }
}
override suspend fun getSyncOrNull(key: String): List<ShortArticle>? {
override suspend fun getSyncOrNull(key: String): TrendingNews? {
return store.getSyncOrNull()?.get(key)
}
override suspend fun store(key: String, value: List<ShortArticle>) {
override suspend fun store(key: String, value: TrendingNews) {
store.update(emptyMap()) { current ->
current + (key to value)
}

View file

@ -1,15 +1,15 @@
package com.tangem.datasource.local.news.trending
import com.tangem.domain.models.news.ShortArticle
import com.tangem.domain.models.news.TrendingNews
import kotlinx.coroutines.flow.Flow
interface TrendingNewsStore {
fun get(key: String): Flow<List<ShortArticle>>
fun get(key: String): Flow<TrendingNews>
suspend fun getSyncOrNull(key: String): List<ShortArticle>?
suspend fun getSyncOrNull(key: String): TrendingNews?
suspend fun store(key: String, value: List<ShortArticle>)
suspend fun store(key: String, value: TrendingNews)
suspend fun clear()
}

View file

@ -27,6 +27,8 @@ object PreferencesKeys {
val SAVE_USER_WALLETS_KEY by lazy { booleanPreferencesKey(name = "saveUserWallets") }
val ROOT_DETECTED_WARNING_SHOWN_KEY by lazy { booleanPreferencesKey(name = "rootDetectedWarningShown") }
val SHOULD_SHOW_ASK_BIOMETRY_KEY by lazy { booleanPreferencesKey("saveUserWalletShown") }
val APP_LAUNCH_COUNT_KEY by lazy { intPreferencesKey(name = "launchCount") }
@ -77,8 +79,8 @@ object PreferencesKeys {
val SHOULD_SHOW_MARKETS_TOOLTIP_KEY by lazy { booleanPreferencesKey(name = "shouldShowMarketsTooltip") }
val MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY by lazy {
booleanPreferencesKey(name = "marketsStakingNotificationHideClicked")
val MARKETS_YIELD_SUPPLY_NOTIFICATION_HIDE_CLICKED_KEY by lazy {
booleanPreferencesKey(name = "marketsYieldSupplyNotificationHideClicked")
}
val WALLET_FIRST_USAGE_DATE_KEY by lazy { longPreferencesKey(name = "walletFirstUsageDate") }
@ -108,6 +110,8 @@ object PreferencesKeys {
val ONRAMP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "onrampTransactionsStatuses") }
val ONRAMP_HANDLED_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "onrampHandledTransactions") }
val ONBOARDING_FINALIZE_SCAN_RESPONSE_KEY by lazy { stringPreferencesKey(name = "onboardingFinalizeScanResponse") }
val IS_GOOGLE_SERVICES_AVAILABLE_KEY by lazy { booleanPreferencesKey(name = "isGoogleServicesAvailable") }
@ -192,6 +196,9 @@ object PreferencesKeys {
fun getTangemPayCheckCustomerByWalletId(userWalletId: UserWalletId) =
booleanPreferencesKey("tangem_pay_check_customer_by_wallet_id_$userWalletId")
fun getTangemPayHideOnboardingKey(userWalletId: UserWalletId) =
booleanPreferencesKey("tangem_pay_hide_onboarding_key_$userWalletId")
// endregion
}

View file

@ -4,28 +4,32 @@ import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.staking.BalanceItem
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.models.staking.YieldBalanceItem
import com.tangem.utils.converter.Converter
import kotlinx.datetime.Instant
class YieldBalanceConverter(
/**
* Converts StakeKit DTO to [StakingBalance].
* Returns [StakingBalance.Data.StakeKit] for non-empty balances, [StakingBalance.Empty] otherwise.
*/
class StakingBalanceConverter(
private val source: StatusSource,
) : Converter<YieldBalanceWrapperDTO, YieldBalance?> {
) : Converter<YieldBalanceWrapperDTO, StakingBalance?> {
constructor(isCached: Boolean) : this(source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL)
override fun convert(value: YieldBalanceWrapperDTO): YieldBalance? {
override fun convert(value: YieldBalanceWrapperDTO): StakingBalance? {
val stakingId = StakingID(
integrationId = value.integrationId ?: return null,
address = value.addresses.address,
)
return if (value.balances.isEmpty()) {
YieldBalance.Empty(stakingId = stakingId, source = source)
StakingBalance.Empty(stakingId = stakingId, source = source)
} else {
YieldBalance.Data(
StakingBalance.Data.StakeKit(
stakingId = stakingId,
balance = YieldBalanceItem(
items = value.balances

View file

@ -30,5 +30,9 @@ interface TangemPayStorage {
suspend fun deleteWithdrawOrder(userWalletId: UserWalletId)
suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean
suspend fun storeHideOnboardingBanner(userWalletId: UserWalletId, hide: Boolean)
suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String)
}

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.managers.MockEnvironmentConfigStorage.Companion.BLOCK_AID_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.P2PEthPoolAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider
@ -250,7 +251,7 @@ internal class ProdApiConfigsManagerTest {
id = ApiConfig.ID.TangemPay,
expected = ApiEnvironmentConfig(
environment = ApiEnvironment.DEV,
baseUrl = "https://api.dev.us.paera.com/bff/",
baseUrl = "https://api.dev.us.paera.com/bff-v2/",
headers = mapOf(
"version" to ProviderSuspend { VERSION_NAME },
"platform" to ProviderSuspend { "Android" },
@ -299,11 +300,17 @@ internal class ProdApiConfigsManagerTest {
}
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(
id = ApiConfig.ID.P2PEthPool,
expected = ApiEnvironmentConfig(
environment = ApiEnvironment.PROD,
baseUrl = "https://api.p2p.org/",
environment = environment,
baseUrl = baseUrl,
headers = mapOf(
"Authorization" to ProviderSuspend { "Bearer $P2P_API_KEY" },
"accept" to ProviderSuspend { "application/json" },