Updated on 2026-08-14

This commit is contained in:
Tangem 2025-01-29 21:44:25 +03:00
commit bf0cb06cb3
297 changed files with 4771 additions and 2376 deletions

View file

@ -40,6 +40,7 @@ sealed class ApiConfig {
companion object {
internal const val DEBUG_BUILD_TYPE = "debug"
internal const val DEBUG_PG_BUILD_TYPE = "debugPG"
internal const val INTERNAL_BUILD_TYPE = "internal"
internal const val MOCKED_BUILD_TYPE = "mocked"
internal const val EXTERNAL_BUILD_TYPE = "external"

View file

@ -1,10 +1,21 @@
package com.tangem.datasource.api.common.config
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Api environment
*
[REDACTED_AUTHOR]
*/
@JsonClass(generateAdapter = false)
enum class ApiEnvironment {
DEV, STAGE, PROD
@Json(name = "DEV")
DEV,
@Json(name = "STAGE")
STAGE,
@Json(name = "PROD")
PROD,
}

View file

@ -68,7 +68,9 @@ internal class Express(
fun getInitialEnvironment(): ApiEnvironment {
return when (BuildConfig.BUILD_TYPE) {
DEBUG_BUILD_TYPE -> ApiEnvironment.DEV
DEBUG_BUILD_TYPE,
DEBUG_PG_BUILD_TYPE,
-> ApiEnvironment.DEV
INTERNAL_BUILD_TYPE,
MOCKED_BUILD_TYPE,
-> ApiEnvironment.STAGE

View file

@ -1,11 +1,8 @@
package com.tangem.datasource.api.common.config
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
import com.tangem.utils.ProviderSuspend
internal class TangemVisa(
private val authProvider: TangemVisaAuthProvider,
) : ApiConfig() {
internal class TangemVisa : ApiConfig() {
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
@ -19,7 +16,5 @@ internal class TangemVisa(
headers = createHeaders(),
)
private fun createHeaders() = mapOf(
"Authorization" to ProviderSuspend { authProvider.getAuthHeader() },
)
private fun createHeaders() = mapOf<String, ProviderSuspend<String>>()
}

View file

@ -2,5 +2,5 @@ package com.tangem.datasource.api.common.visa
interface TangemVisaAuthProvider {
suspend fun getAuthHeader(): String
suspend fun getAuthHeader(cardId: String): String
}

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.express.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class AssetsRequestBody(
@Json(name = "tokensList") val tokensList: List<LeastTokenInfo>?,
)

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.express.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ExchangeSentRequestBody(
@Json(name = "txId")
val txId: String,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.express.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class LeastTokenInfo(
@Json(name = "contractAddress")
val contractAddress: String,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.express.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PairsRequestBody(
@Json(name = "from")
val from: List<LeastTokenInfo>,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class Asset(
@Json(name = "contractAddress")
val contractAddress: String,

View file

@ -1,11 +1,17 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ExchangeDataResponseWithTxDetails(
@Json(name = "dataResponse")
val dataResponse: ExchangeDataResponse,
@Json(name = "txDetails")
val txDetails: TxDetails,
)
@JsonClass(generateAdapter = true)
data class ExchangeDataResponse(
@Json(name = "fromAmount")
val fromAmount: String,
@ -29,6 +35,7 @@ data class ExchangeDataResponse(
val signature: String,
)
@JsonClass(generateAdapter = true)
data class TxDetails(
@Json(name = "payoutAddress")
val payoutAddress: String,
@ -70,6 +77,7 @@ data class TxDetails(
val gas: String?,
)
@JsonClass(generateAdapter = false)
enum class TxType {
@Json(name = "send")
SEND,

View file

@ -1,8 +1,10 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class ExchangeProvider(
@Json(name = "id")
val id: String,
@ -32,6 +34,7 @@ data class ExchangeProvider(
val slippage: BigDecimal?,
)
@JsonClass(generateAdapter = false)
enum class ExchangeProviderType {
@Json(name = "dex")
DEX,

View file

@ -1,8 +1,10 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class ExchangeQuoteResponse(
@Json(name = "fromAmount")

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ExchangeSentResponseBody(
@Json(name = "txId")
val txId: String,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ExchangeStatusResponse(
@Json(name = "providerId")
@ -26,6 +28,7 @@ data class ExchangeStatusResponse(
val refundContractAddress: String? = null,
)
@JsonClass(generateAdapter = false)
enum class ExchangeStatus {
@Json(name = "new")
@ -71,6 +74,7 @@ enum class ExchangeStatus {
Unknown,
}
@JsonClass(generateAdapter = true)
data class ExchangeStatusError(
@Json(name = "code")
val code: Int,

View file

@ -1,13 +1,16 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class ExpressErrorResponse(
@Json(name = "error")
val error: ExpressError,
)
@JsonClass(generateAdapter = true)
data class ExpressError(
@Json(name = "code")
val code: Int,
@ -19,6 +22,7 @@ data class ExpressError(
val value: ExpressErrorValue?,
)
@JsonClass(generateAdapter = true)
data class ExpressErrorValue(
@Json(name = "minAmount")
val minAmount: String?,

View file

@ -1,8 +1,10 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
@JsonClass(generateAdapter = true)
data class SwapPair(
@Json(name = "from")
val from: LeastTokenInfo,
@ -15,6 +17,7 @@ data class SwapPair(
)
@JsonClass(generateAdapter = true)
data class SwapPairProvider(
@Json(name = "providerId")
val providerId: String,
@ -23,6 +26,7 @@ data class SwapPairProvider(
val rateTypes: List<RateType>,
)
@JsonClass(generateAdapter = false)
enum class RateType {
@Json(name = "float")
FLOAT,

View file

@ -3,8 +3,11 @@ package com.tangem.datasource.api.onramp.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampDataResponseWithTxDetails(
@Json(name = "dataResponse")
val dataResponse: OnrampDataResponse,
@Json(name = "txDetails")
val txDetails: OnrampTxDetails,
)

View file

@ -60,6 +60,7 @@ data class OnrampStatusResponse(
val countryCode: String,
)
@JsonClass(generateAdapter = false)
enum class Status {
@Json(name = "created")
Created,

View file

@ -1,11 +1,13 @@
package com.tangem.datasource.api.stakekit.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.datasource.api.stakekit.models.request.ConstructTransactionRequestBody.GasArgs
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
@JsonClass(generateAdapter = true)
data class PendingActionRequestBody(
@Json(name = "type")
val type: StakingActionType,
@ -19,6 +21,7 @@ data class PendingActionRequestBody(
val gasArgs: GasArgs? = null, // used only in estimate_gas request
)
@JsonClass(generateAdapter = true)
data class ActionRequestBody(
@Json(name = "integrationId")
val integrationId: String,
@ -32,6 +35,7 @@ data class ActionRequestBody(
val gasArgs: GasArgs? = null, // used only in estimate_gas request
)
@JsonClass(generateAdapter = true)
data class ActionRequestBodyArgs(
@Json(name = "amount")
val amount: String,
@ -55,6 +59,7 @@ data class ActionRequestBodyArgs(
val inputToken: TokenDTO? = null,
)
@JsonClass(generateAdapter = true)
data class SignatureVerification(
@Json(name = "message")
val message: String,
@ -62,6 +67,7 @@ data class SignatureVerification(
val signed: String,
)
@JsonClass(generateAdapter = false)
enum class TronResource {
@Json(name = "ENERGY")
ENERGY,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.stakekit.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class Address(
@Json(name = "address")
val address: String,
@ -11,6 +13,7 @@ data class Address(
val explorerUrl: String? = null,
) {
@JsonClass(generateAdapter = true)
data class AdditionalAddresses(
// cosmos-specific
@Json(name = "cosmosPubKey")

View file

@ -1,14 +1,17 @@
package com.tangem.datasource.api.stakekit.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class ConstructTransactionRequestBody(
@Json(name = "gasArgs")
val gasArgs: GasArgs? = null,
@Json(name = "ledgerWalletAPICompatible")
val ledgerWalletAPICompatible: Boolean? = null,
) {
@JsonClass(generateAdapter = true)
data class GasArgs(
// cosmos-specific
@Json(name = "gasPrice")

View file

@ -1,19 +1,23 @@
package com.tangem.datasource.api.stakekit.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class MultipleYieldBalancesRequestBody(
@Json(name = "addresses") val addresses: Address,
@Json(name = "args") val args: MultipleYieldBalancesRequestArgs,
@Json(name = "integrationId") val integrationId: String,
) {
@JsonClass(generateAdapter = true)
data class Address(
@Json(name = "address") val address: String,
@Json(name = "additionalAddresses") val additionalAddresses: AdditionalAddresses? = null,
@Json(name = "explorerUrl") val explorerUrl: String,
) {
@JsonClass(generateAdapter = true)
data class AdditionalAddresses(
@Json(name = "cosmosPubKey") val cosmosPubKey: String? = null,
@Json(name = "binanceBeaconAddress") val binanceBeaconAddress: String? = null,
@ -25,6 +29,7 @@ data class MultipleYieldBalancesRequestBody(
)
}
@JsonClass(generateAdapter = true)
data class MultipleYieldBalancesRequestArgs(
@Json(name = "validatorAddresses") val validatorAddresses: List<String>,
)

View file

@ -1,8 +1,13 @@
package com.tangem.datasource.api.stakekit.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class RevenueOption(val value: String) {
SUPPORTS_FEE("supportsFee"),
SUPPORTS_REV_SHARE("supportsRevShare"),
@Json(name = "SUPPORTS_FEE") SUPPORTS_FEE("supportsFee"),
@Json(name = "SUPPORTS_REV_SHARE") SUPPORTS_REV_SHARE("supportsRevShare"),
;
override fun toString(): String {

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.stakekit.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SubmitTransactionHashRequestBody(
@Json(name = "hash")
val hash: String,

View file

@ -1,13 +1,16 @@
package com.tangem.datasource.api.stakekit.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class YieldBalanceRequestBody(
@Json(name = "addresses") val addresses: Address,
@Json(name = "args") val args: YieldBalanceRequestArgs,
@Json(name = "integrationId") val integrationId: String,
) {
@JsonClass(generateAdapter = true)
data class YieldBalanceRequestArgs(
@Json(name = "validatorAddresses") val validatorAddresses: List<String>,
)

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.stakekit.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class NetworkTypeDTO {
@Json(name = "avalanche-c")
AVALANCHE_C,

View file

@ -41,6 +41,7 @@ data class BalanceDTO(
val providerId: String?,
) {
@JsonClass(generateAdapter = false)
enum class BalanceTypeDTO {
@Json(name = "available")
AVAILABLE,

View file

@ -85,7 +85,7 @@ data class YieldDTO(
@Json(name = "preferred")
val preferred: Boolean?,
) {
@JsonClass(generateAdapter = true)
@JsonClass(generateAdapter = false)
enum class ValidatorStatusDTO {
@Json(name = "active")
ACTIVE,
@ -153,6 +153,7 @@ data class YieldDTO(
val enabled: Boolean?,
)
@JsonClass(generateAdapter = false)
enum class RewardScheduleDTO {
@Json(name = "block")
BLOCK,
@ -178,6 +179,7 @@ data class YieldDTO(
UNKNOWN,
}
@JsonClass(generateAdapter = false)
enum class RewardClaimingDTO {
@Json(name = "auto")
AUTO,
@ -189,6 +191,7 @@ data class YieldDTO(
}
}
@JsonClass(generateAdapter = false)
enum class RewardTypeDTO {
@Json(name = "apy")
APY, // compound rate

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.stakekit.models.response.model.action
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class StakingActionStatusDTO {
@Json(name = "CANCELED")
CANCELED,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.stakekit.models.response.model.action
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class StakingActionTypeDTO {
@Json(name = "STAKE")
STAKE,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.stakekit.models.response.model.error
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class StakeKitErrorResponse(
@Json(name = "message")
val message: String? = null,
@ -26,6 +28,7 @@ class StakeKitErrorResponse(
val tags: List<String>? = null,
)
@JsonClass(generateAdapter = true)
data class StakeKitErrorDetailsDTO(
@Json(name = "arguments")
val arguments: String? = null,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.stakekit.models.response.model.transaction
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class StakingTransactionStatusDTO {
@Json(name = "NOT_FOUND")
NOT_FOUND,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.stakekit.models.response.model.transaction
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class StakingTransactionTypeDTO {
@Json(name = "SWAP")
SWAP,

View file

@ -122,6 +122,9 @@ interface TangemTechApi {
@Body body: SeedPhraseNotificationDTO,
): ApiResponse<Unit>
@GET("hot_crypto")
suspend fun getHotCrypto(@Query("currency") currencyId: String): ApiResponse<HotCryptoResponse>
companion object {
val marketsQuoteFields = listOf(
"price",

View file

@ -1,14 +1,17 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class CoinsResponse(
@Json(name = "imageHost") val imageHost: String?,
@Json(name = "coins") val coins: List<Coin>,
@Json(name = "total") val total: Int,
) {
@JsonClass(generateAdapter = true)
data class Coin(
@Json(name = "id") val id: String,
@Json(name = "name") val name: String,
@ -17,6 +20,7 @@ data class CoinsResponse(
@Json(name = "networks") val networks: List<Network> = listOf(),
) {
@JsonClass(generateAdapter = true)
data class Network(
@Json(name = "networkId") val networkId: String,
@Json(name = "contractAddress") val contractAddress: String? = null,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class CreateUserTokensAccountBody(
@Json(name = "accountId")
val id: Int,

View file

@ -17,7 +17,7 @@ data class CurrenciesResponse(
@Json(name = "rateBTC") val rateBTC: String,
@Json(name = "unit") val unit: String, // $, €, ₽
@Json(name = "type") val type: String,
val iconSmallUrl: String? = null,
val iconMediumUrl: String? = null,
@Json(name = "iconSmallUrl") val iconSmallUrl: String? = null,
@Json(name = "iconMediumUrl") val iconMediumUrl: String? = null,
)
}

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class FeaturesResponse(
@Json(name = "send") val isNewSendEnabled: Boolean,
)

View file

@ -0,0 +1,24 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class HotCryptoResponse(
@Json(name = "imageHost") val imageHost: String? = null,
@Json(name = "tokens") val tokens: List<Token>,
) {
@JsonClass(generateAdapter = true)
data class Token(
@Json(name = "id") val id: String,
@Json(name = "symbol") val symbol: String,
@Json(name = "name") val name: String,
@Json(name = "network_id") val networkId: String,
@Json(name = "current_price") val currentPrice: BigDecimal,
@Json(name = "price_change_percentage_24h") val priceChangePercentage: BigDecimal,
@Json(name = "contractAddress") val contractAddress: String? = null, // specific for token
@Json(name = "decimal_count") val decimalCount: Int? = null, // specific for token
)
}

View file

@ -1,13 +1,16 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class QuotesResponse(
@Json(name = "quotes")
val quotes: Map<String, Quote>,
) {
@JsonClass(generateAdapter = true)
data class Quote(
@Json(name = "price")
val price: BigDecimal?,

View file

@ -1,8 +1,10 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
// rates.keys = networkId's
@JsonClass(generateAdapter = true)
data class RatesResponse(
@Json(name = "rates") val rates: Map<String, Double>,
)

View file

@ -1,30 +1,36 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.joda.time.LocalDate
@JsonClass(generateAdapter = true)
data class ReferralResponse(
@Json(name = "conditions") val conditions: Conditions,
@Json(name = "referral") val referral: Referral?,
@Json(name = "expectedAwards") val expectedAwards: ExpectedAwards?,
) {
@JsonClass(generateAdapter = true)
data class Conditions(
@Json(name = "discount") val discount: Discount,
@Json(name = "awards") val awards: List<Award>,
@Json(name = "tosLink") val tosLink: String,
) {
@JsonClass(generateAdapter = true)
data class Discount(
@Json(name = "amount") val amount: Int,
@Json(name = "type") val discountType: String,
)
@JsonClass(generateAdapter = true)
data class Award(
@Json(name = "amount") val amount: Int,
@Json(name = "token") val token: Token,
) {
@JsonClass(generateAdapter = true)
data class Token(
@Json(name = "id") val id: String,
@Json(name = "name") val name: String,
@ -36,6 +42,7 @@ data class ReferralResponse(
}
}
@JsonClass(generateAdapter = true)
data class Referral(
@Json(name = "shareLink") val shareLink: String,
@Json(name = "address") val address: String,
@ -44,11 +51,13 @@ data class ReferralResponse(
@Json(name = "termsAcceptedAt") val termsAcceptedAt: String?,
)
@JsonClass(generateAdapter = true)
data class ExpectedAwards(
@Json(name = "numberOfWallets") val numberOfWallets: Int,
@Json(name = "list") val list: List<AwardItem>,
) {
@JsonClass(generateAdapter = true)
data class AwardItem(
@Json(name = "currency") val currency: String,
@Json(name = "paymentDate") val paymentDate: LocalDate,

View file

@ -1,10 +1,12 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Sales response
*/
@JsonClass(generateAdapter = true)
data class SalesResponse(
@Json(name = "sales") val sales: List<Sales>,
)
@ -17,6 +19,7 @@ data class SalesResponse(
* @property product product that is sales
* @property notification optional notification for product
*/
@JsonClass(generateAdapter = true)
data class Sales(
@Json(name = "id") val id: String,
@Json(name = "state") val state: String,
@ -31,12 +34,14 @@ data class Sales(
* @property code code that shows what product it is
* @property name product name
*/
@JsonClass(generateAdapter = true)
data class Product(
@Json(name = "id") val id: String,
@Json(name = "code") val code: String,
@Json(name = "name") val name: String,
)
@JsonClass(generateAdapter = true)
data class Notification(
@Json(name = "type") val type: String,
@Json(name = "title") val title: String,

View file

@ -1,6 +1,7 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Shop response
@ -9,6 +10,7 @@ import com.squareup.moshi.Json
*
[REDACTED_AUTHOR]
*/
@JsonClass(generateAdapter = true)
data class ShopResponse(
@Json(name = "canOrder") val isOrderingAvailable: Boolean,
)

View file

@ -1,8 +1,10 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/** Body for make user referral */
@JsonClass(generateAdapter = true)
data class StartReferralBody(
@Json(name = "walletId") val walletId: String,
@Json(name = "networkId") val networkId: String,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class UpdateUserTokensAccountBody(
@Json(name = "accountTitle")
val title: String,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class UserTokensAccountResponse(
@Json(name = "accountId")
val id: Int,

View file

@ -1,8 +1,10 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.common.extensions.calculateHashCode
@JsonClass(generateAdapter = true)
data class UserTokensResponse(
@Json(name = "version") val version: Int = 0,
@Json(name = "group") val group: GroupType,
@ -10,6 +12,7 @@ data class UserTokensResponse(
@Json(name = "tokens") val tokens: List<Token> = emptyList(),
) {
@JsonClass(generateAdapter = true)
data class Token(
@Json(name = "id") val id: String? = null,
@Json(name = "networkId") val networkId: String,
@ -36,6 +39,7 @@ data class UserTokensResponse(
)
}
@JsonClass(generateAdapter = false)
enum class GroupType {
@Json(name = "none")
NONE,
@ -47,6 +51,7 @@ data class UserTokensResponse(
NETWORK,
}
@JsonClass(generateAdapter = false)
enum class SortType {
@Json(name = "balance")
BALANCE,

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class UserTokensSettingsResponse(
@Json(name = "group")
val group: UserTokensResponse.GroupType,

View file

@ -1,13 +1,16 @@
package com.tangem.datasource.api.tangemTech.models.v2
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
@JsonClass(generateAdapter = true)
data class UserTokensResponseV2(
@Json(name = "accounts")
val accounts: List<TokensAccount>,
) {
@JsonClass(generateAdapter = true)
data class TokensAccount(
@Json(name = "id")
val id: Int,

View file

@ -1,10 +1,21 @@
package com.tangem.datasource.api.visa
import com.tangem.datasource.api.utils.ReadTimeout
import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse
import retrofit2.http.GET
import retrofit2.http.Header
import java.util.concurrent.TimeUnit
interface TangemVisaApi {
@GET("activation-status")
suspend fun getRemoteActivationStatus(): CardActivationRemoteStateResponse
suspend fun getRemoteActivationStatus(
@Header("Authorization") authHeader: String,
): CardActivationRemoteStateResponse
@ReadTimeout(duration = 20, TimeUnit.MINUTES)
@GET("activation-status")
suspend fun getRemoteActivationStatusLongPoll(
@Header("Authorization") authHeader: String,
): CardActivationRemoteStateResponse
}

View file

@ -5,7 +5,6 @@ import com.tangem.datasource.api.common.config.*
import com.tangem.datasource.api.common.config.Express
import com.tangem.datasource.api.common.config.StakeKit
import com.tangem.datasource.api.common.config.TangemTech
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider
@ -47,6 +46,5 @@ internal object ApiConfigsModule {
@Provides
@IntoSet
fun provideTangemVisaConfig(tangemVisaAuthProvider: TangemVisaAuthProvider): ApiConfig =
TangemVisa(tangemVisaAuthProvider)
fun provideTangemVisaConfig(): ApiConfig = TangemVisa()
}

View file

@ -194,7 +194,7 @@ internal object NetworkModule {
clientBuilder = {
addInterceptor(
NetworkLogsSaveInterceptor(appLogsStore),
)
).applyTimeoutAnnotations()
},
)
}
@ -215,7 +215,7 @@ internal object NetworkModule {
clientBuilder = {
addInterceptor(
NetworkLogsSaveInterceptor(appLogsStore),
)
).applyTimeoutAnnotations()
},
)
}

View file

@ -0,0 +1,18 @@
package com.tangem.datasource.di.exchangeservice
import com.tangem.datasource.exchangeservice.hotcrypto.DefaultHotCryptoLoader
import com.tangem.datasource.exchangeservice.hotcrypto.HotCryptoLoader
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface HotCryptoLoaderModule {
@Binds
@Singleton
fun bindExpressServiceLoader(defaultHotCryptoLoader: DefaultHotCryptoLoader): HotCryptoLoader
}

View file

@ -0,0 +1,81 @@
package com.tangem.datasource.exchangeservice.hotcrypto
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import com.tangem.datasource.api.tangemTech.models.HotCryptoResponse
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import timber.log.Timber
import javax.inject.Inject
/**
* Default implementation of [HotCryptoLoader]
*
* @property tangemTechApi tangem tech api
* @property hotCryptoResponseStore store of [HotCryptoResponse]
* @property appPreferencesStore app preferences store
* @property dispatchers dispatchers
* @property onrampFeatureToggles onramp feature toggles
*
[REDACTED_AUTHOR]
*/
internal class DefaultHotCryptoLoader @Inject constructor(
private val tangemTechApi: TangemTechApi,
private val hotCryptoResponseStore: HotCryptoResponseStore,
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
private val onrampFeatureToggles: OnrampFeatureToggles,
) : HotCryptoLoader {
override suspend fun fetch(tokens: List<UserTokensResponse.Token>) {
updateInternal { hotToken ->
tokens.none {
it.id == hotToken.id && it.contractAddress == hotToken.contractAddress &&
it.networkId == hotToken.networkId
}
}
}
override suspend fun update(currencies: List<CryptoCurrency>) {
updateInternal { hotToken ->
currencies.none {
it.id.rawCurrencyId == hotToken.id &&
(it as? CryptoCurrency.Token)?.contractAddress == hotToken.contractAddress &&
it.network.id.value == hotToken.networkId
}
}
}
private suspend fun updateInternal(filterPredicate: (HotCryptoResponse.Token) -> Boolean) {
if (!onrampFeatureToggles.isHotTokensEnabled) return
runCatching(dispatchers.io) {
tangemTechApi.getHotCrypto(currencyId = getAppCurrencyId()).getOrThrow()
}
.onSuccess {
Timber.d("HotCrypto is successfully updated")
hotCryptoResponseStore.store(
value = it.copy(tokens = it.tokens.filter(filterPredicate)),
)
}
.onFailure {
Timber.e(it, "Unable to fetch hot crypto")
}
}
private suspend fun getAppCurrencyId(): String {
return appPreferencesStore.getObjectSyncOrNull<CurrenciesResponse.Currency>(
key = PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
)
?.id
?: "usd"
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.datasource.exchangeservice.hotcrypto
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.tokens.model.CryptoCurrency
/**
* Hot crypto loader
*
[REDACTED_AUTHOR]
*/
interface HotCryptoLoader {
/**
* Fetch hot crypto
*
* @param tokens already added tokens
*/
suspend fun fetch(tokens: List<UserTokensResponse.Token>)
/**
* Update hot crypto
*
* @param currencies already added crypto currencies
*/
suspend fun update(currencies: List<CryptoCurrency>)
}

View file

@ -0,0 +1,16 @@
package com.tangem.datasource.exchangeservice.hotcrypto
import com.tangem.datasource.api.tangemTech.models.HotCryptoResponse
import com.tangem.datasource.local.datastore.RuntimeStateStore
import javax.inject.Inject
import javax.inject.Singleton
/**
* Store of [HotCryptoResponse]
*
[REDACTED_AUTHOR]
*/
@Singleton
class HotCryptoResponseStore @Inject constructor() : RuntimeStateStore<HotCryptoResponse> by RuntimeStateStore(
defaultValue = HotCryptoResponse(imageHost = null, tokens = emptyList()),
)

View file

@ -1,8 +1,16 @@
package com.tangem.datasource.local.card
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class UsedCardInfo(
@Json(name = "cardId")
val cardId: String,
@Json(name = "isScanned")
val isScanned: Boolean = false,
@Json(name = "isActivationStarted")
val isActivationStarted: Boolean = false,
@Json(name = "isActivationFinished")
val isActivationFinished: Boolean = false,
)

View file

@ -1,6 +1,7 @@
package com.tangem.datasource.local.config.providers.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/** Config provider model */
sealed class ProviderModel {
@ -12,6 +13,7 @@ sealed class ProviderModel {
* "url": "https://example.com"
* }
*/
@JsonClass(generateAdapter = true)
data class Public(
@Json(name = "url") val url: String,
) : ProviderModel()
@ -23,6 +25,7 @@ sealed class ProviderModel {
* "name": "nownodes"
* }
*/
@JsonClass(generateAdapter = true)
data class Private(
@Json(name = "name") val name: String,
) : ProviderModel()

View file

@ -11,6 +11,9 @@ import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStoreFile
import com.tangem.datasource.local.preferences.PreferencesDataStore.INSTANCE
import com.tangem.datasource.local.preferences.PreferencesKeys.APP_LOGS_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_RING_PROMO_KEY
import com.tangem.datasource.local.preferences.utils.CleanupKeyMigration
import com.tangem.datasource.local.preferences.utils.SharedPreferencesKeyMigration
import kotlinx.coroutines.CoroutineScope
@ -80,6 +83,9 @@ internal object PreferencesDataStore {
keyName = PreferencesKeys.BALANCE_HIDING_SETTINGS_KEY.name,
),
CleanupKeyMigration(key = APP_LOGS_KEY),
CleanupKeyMigration(key = IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY),
CleanupKeyMigration(key = IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY),
CleanupKeyMigration(key = SHOULD_SHOW_RING_PROMO_KEY),
)
}
}

View file

@ -17,6 +17,9 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.WAS_TWINS_ONBOARD
/**
* All preferences keys that DataStore<Preferences> is stored.
*
* !!!IMPORTANT!!!
* Before DELETING active key do [CleanupKeyMigration] in [PreferencesDataStore]
*
[REDACTED_AUTHOR]
*/
object PreferencesKeys {
@ -87,10 +90,12 @@ object PreferencesKeys {
val UNSUBMITTED_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "unsubmittedTransactions") }
@Deprecated("Remove after CleanupKeyMigration")
val IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY by lazy {
booleanPreferencesKey(name = "isWalletSwapPromoOkxShown")
}
@Deprecated("Remove after CleanupKeyMigration")
val IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY by lazy {
booleanPreferencesKey(name = "isTokenSwapPromoOkxShown")
}
@ -99,6 +104,7 @@ object PreferencesKeys {
val ADDED_WALLETS_WITH_RING_KEY by lazy { stringSetPreferencesKey(name = "addedWalletsWithRing") }
@Deprecated("Remove after CleanupKeyMigration")
val SHOULD_SHOW_RING_PROMO_KEY by lazy { booleanPreferencesKey(name = "shouldShowRingPromo") }
val ONRAMP_DEFAULT_COUNTRY by lazy { stringPreferencesKey(name = "onrampDefaultCountry") }

View file

@ -4,7 +4,9 @@ import com.tangem.domain.visa.model.VisaAuthTokens
interface VisaAuthTokenStorage {
suspend fun store(tokens: VisaAuthTokens)
suspend fun store(cardId: String, tokens: VisaAuthTokens)
suspend fun get(): VisaAuthTokens?
suspend fun get(cardId: String): VisaAuthTokens?
fun remove(cardId: String)
}

View file

@ -44,17 +44,13 @@ internal fun OkHttpClient.Builder.applyTimeoutAnnotations(): OkHttpClient.Builde
val readTimeout = tag?.method()?.getAnnotation(ReadTimeout::class.java)
val writeTimeout = tag?.method()?.getAnnotation(WriteTimeout::class.java)
chain
.apply {
connectionTimeout?.let { withConnectTimeout(timeout = it.duration, unit = it.unit) }
}
.apply {
readTimeout?.let { withReadTimeout(timeout = it.duration, unit = it.unit) }
}
.apply {
writeTimeout?.let { withWriteTimeout(timeout = it.duration, unit = it.unit) }
}
.proceed(request)
chain.run {
connectionTimeout?.let { withConnectTimeout(timeout = it.duration, unit = it.unit) } ?: this
}.run {
readTimeout?.let { withReadTimeout(timeout = it.duration, unit = it.unit) } ?: this
}.run {
writeTimeout?.let { withWriteTimeout(timeout = it.duration, unit = it.unit) } ?: this
}.proceed(request)
},
)
}

View file

@ -6,16 +6,15 @@ import com.tangem.datasource.BuildConfig
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.api.common.config.*
import com.tangem.datasource.api.common.config.ApiConfig.Companion.DEBUG_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfig.Companion.DEBUG_PG_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfig.Companion.EXTERNAL_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfig.Companion.INTERNAL_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.version.AppVersionProvider
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
@ -31,7 +30,6 @@ private val appVersionProvider = mockk<AppVersionProvider>()
private val expressAuthProvider = mockk<ExpressAuthProvider>()
private val stakeKitAuthProvider = mockk<StakeKitAuthProvider>()
private val appAuthProvider = mockk<AuthProvider>()
private val visaAuthProvider = mockk<TangemVisaAuthProvider>()
// Don't forget to add new config !!!
private val API_CONFIGS = setOf(
@ -39,7 +37,7 @@ private val API_CONFIGS = setOf(
TangemTech(appVersionProvider, appAuthProvider),
StakeKit(stakeKitAuthProvider),
TangemVisaAuth(),
TangemVisa(visaAuthProvider),
TangemVisa(),
)
/**
@ -59,7 +57,6 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY
every { appAuthProvider.getCardId() } returns APP_CARD_ID
every { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY
coEvery { visaAuthProvider.getAuthHeader() } returns VISA_AUTH_HEADER
}
@Test
@ -84,7 +81,6 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
const val STAKE_KIT_API_KEY = "stake_kit_api_key"
const val APP_CARD_ID = "app_card_id"
const val APP_CARD_PUBLIC_KEY = "app_public_key"
const val VISA_AUTH_HEADER = "Bearer visa_auth_header"
@JvmStatic
@Parameterized.Parameters
@ -100,7 +96,9 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
private fun createExpressModel(): Model {
val environment = when (BuildConfig.BUILD_TYPE) {
DEBUG_BUILD_TYPE -> ApiEnvironment.DEV
DEBUG_BUILD_TYPE,
DEBUG_PG_BUILD_TYPE,
-> ApiEnvironment.DEV
INTERNAL_BUILD_TYPE,
MOCKED_BUILD_TYPE,
-> ApiEnvironment.STAGE
@ -115,7 +113,9 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
expected = ApiEnvironmentConfig(
environment = environment,
baseUrl = when (BuildConfig.BUILD_TYPE) {
DEBUG_BUILD_TYPE -> "[REDACTED_ENV_URL]"
DEBUG_BUILD_TYPE,
DEBUG_PG_BUILD_TYPE,
-> "[REDACTED_ENV_URL]"
INTERNAL_BUILD_TYPE,
MOCKED_BUILD_TYPE,
-> "[REDACTED_ENV_URL]"
@ -198,9 +198,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
expected = ApiEnvironmentConfig(
environment = ApiEnvironment.PROD,
baseUrl = "https://bff.tangem.com/",
headers = mapOf(
"Authorization" to ProviderSuspend { VISA_AUTH_HEADER },
),
headers = mapOf(),
),
)
}