Updated on 2026-08-14
This commit is contained in:
commit
bf0cb06cb3
297 changed files with 4771 additions and 2376 deletions
44
Dockerfile
44
Dockerfile
|
|
@ -1,44 +0,0 @@
|
||||||
FROM ubuntu:24.04
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
ENV ANDROID_HOME=/opt/android-sdk
|
|
||||||
ENV BUNDLE_PATH=vendor/bundle
|
|
||||||
ENV PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/34.0.0:$BUNDLE_PATH/bin
|
|
||||||
ENV LC_ALL=en_US.UTF-8
|
|
||||||
ENV LANG=en_US.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
openjdk-17-jdk \
|
|
||||||
wget \
|
|
||||||
unzip \
|
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
ruby \
|
|
||||||
ruby-dev \
|
|
||||||
build-essential \
|
|
||||||
&& apt-get clean
|
|
||||||
|
|
||||||
RUN mkdir -p $ANDROID_HOME/cmdline-tools/latest && \
|
|
||||||
wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -O /tmp/cmdline-tools.zip && \
|
|
||||||
unzip /tmp/cmdline-tools.zip -d $ANDROID_HOME/cmdline-tools/latest && \
|
|
||||||
mv $ANDROID_HOME/cmdline-tools/latest/cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest/ && \
|
|
||||||
rm -rf $ANDROID_HOME/cmdline-tools/latest/cmdline-tools && \
|
|
||||||
rm -f /tmp/cmdline-tools.zip && \
|
|
||||||
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses && \
|
|
||||||
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-31" "platforms;android-34" "build-tools;34.0.0"
|
|
||||||
|
|
||||||
RUN wget https://github.com/lzhiyong/android-sdk-tools/releases/download/34.0.3/android-sdk-tools-static-aarch64.zip -O /tmp/android-sdk-tools-static-aarch64.zip && \
|
|
||||||
unzip /tmp/android-sdk-tools-static-aarch64.zip -d /tmp/android-sdk-tools-static-arm && \
|
|
||||||
cp -r /tmp/android-sdk-tools-static-arm/build-tools/* $ANDROID_HOME/build-tools/34.0.0/ && \
|
|
||||||
rm -rf /tmp/android-sdk-tools-static-arm /tmp/android-sdk-tools-static-aarch64.zip
|
|
||||||
|
|
||||||
RUN gem install bundler:2.5.23
|
|
||||||
RUN gem install fastlane -v 2.211.0 -N -V
|
|
||||||
RUN gem install fastlane-plugin-firebase_app_distribution -v 0.9.1 -N -V
|
|
||||||
|
|
||||||
COPY Gemfile Gemfile.lock ./
|
|
||||||
RUN bundle install --jobs=4 --retry=3 --verbose
|
|
||||||
|
|
||||||
RUN bundle exec fastlane -v
|
|
||||||
|
|
||||||
CMD ["bash"]
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(deps.plugins.android.application)
|
alias(deps.plugins.android.application)
|
||||||
|
|
@ -258,6 +259,10 @@ dependencies {
|
||||||
implementation(deps.decompose.ext.compose)
|
implementation(deps.decompose.ext.compose)
|
||||||
implementation(deps.moshi.adapters)
|
implementation(deps.moshi.adapters)
|
||||||
|
|
||||||
|
implementation(deps.moshi.kotlin)
|
||||||
|
kaptForObfuscatingVariants(deps.moshi.kotlin.codegen)
|
||||||
|
kaptForObfuscatingVariants(deps.retrofit.response.type.keeper)
|
||||||
|
|
||||||
/** Testing libraries */
|
/** Testing libraries */
|
||||||
testImplementation(deps.test.coroutine)
|
testImplementation(deps.test.coroutine)
|
||||||
testImplementation(deps.test.junit)
|
testImplementation(deps.test.junit)
|
||||||
|
|
@ -275,6 +280,7 @@ dependencies {
|
||||||
|
|
||||||
/** Chucker */
|
/** Chucker */
|
||||||
debugImplementation(deps.chucker)
|
debugImplementation(deps.chucker)
|
||||||
|
debugPGImplementation(deps.chucker)
|
||||||
mockedImplementation(deps.chuckerStub)
|
mockedImplementation(deps.chuckerStub)
|
||||||
externalImplementation(deps.chuckerStub)
|
externalImplementation(deps.chuckerStub)
|
||||||
internalImplementation(deps.chuckerStub)
|
internalImplementation(deps.chuckerStub)
|
||||||
|
|
@ -296,5 +302,4 @@ dependencies {
|
||||||
// excludes version 9999.0-empty-to-avoid-conflict-with-guava
|
// excludes version 9999.0-empty-to-avoid-conflict-with-guava
|
||||||
exclude(group = "com.google.guava", module = "listenablefuture")
|
exclude(group = "com.google.guava", module = "listenablefuture")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
223
app/proguard-rules.pro
vendored
223
app/proguard-rules.pro
vendored
|
|
@ -1,21 +1,208 @@
|
||||||
# Add project specific ProGuard rules here.
|
-dontusemixedcaseclassnames
|
||||||
# You can control the set of applied configuration files using the
|
-flattenpackagehierarchy
|
||||||
# proguardFiles setting in build.gradle.kts.
|
-adaptclassstrings
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
# firebase
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
-keep public class com.google.firebase.** { *; }
|
||||||
# class:
|
-keep class com.google.android.gms.internal.** { *; }
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
-keepclasseswithmembers class com.google.firebase.FirebaseException
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
# hedera sdk
|
||||||
# debugging stack traces.
|
-keep class com.hedera.hashgraph.sdk.** { *; }
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
-keep interface com.hedera.hashgraph.sdk.** { *; }
|
||||||
|
-dontwarn com.esaulpaugh.headlong.**
|
||||||
|
-dontwarn edu.umd.cs.findbugs.annotations.SuppressFBWarnings
|
||||||
|
-dontwarn io.grpc.stub.**
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# trustwallet sdk
|
||||||
# hide the original source file name.
|
-keep class wallet.core.jni.** { *; }
|
||||||
#-renamesourcefileattribute SourceFile
|
|
||||||
|
# solana
|
||||||
|
-keep class org.p2p.solanaj.** { *; }
|
||||||
|
-keep class com.tangem.blockchain.blockchains.solana.solanaj.model.** { *; }
|
||||||
|
|
||||||
|
# binance
|
||||||
|
-keep class com.tangem.blockchain.blockchains.binance.client.** { *; }
|
||||||
|
|
||||||
|
# shadow gson
|
||||||
|
-keep,allowobfuscation,allowshrinking class shadow.com.google.gson.reflect.TypeToken
|
||||||
|
-keep,allowobfuscation,allowshrinking class * extends shadow.com.google.gson.reflect.TypeToken
|
||||||
|
|
||||||
|
# polkadot
|
||||||
|
-keep class io.emeraldpay.polkaj.api.RpcRequest { *; }
|
||||||
|
-keep class io.emeraldpay.polkaj.api.RpcResponse { *; }
|
||||||
|
-keep class io.emeraldpay.polkaj.api.RpcResponseError { *; }
|
||||||
|
|
||||||
|
# kethereum
|
||||||
|
-keep class org.kethereum.bip32.model.ExtendedKey**
|
||||||
|
-keepclassmembers class org.kethereum.bip32.model.ExtendedKey** { *; }
|
||||||
|
|
||||||
|
# some crypto
|
||||||
|
-dontwarn net.i2p.crypto.**
|
||||||
|
-dontwarn java.net.http.**
|
||||||
|
-dontwarn lombok.**
|
||||||
|
|
||||||
|
-keep class org.spongycastle.** { *; }
|
||||||
|
|
||||||
|
-dontwarn org.apache.hc.core5.**
|
||||||
|
-dontwarn org.apache.hc.client5.**
|
||||||
|
-dontwarn org.apache.log4j.config.**
|
||||||
|
|
||||||
|
-dontwarn javax.naming.**
|
||||||
|
-dontwarn javax.xml.stream.**
|
||||||
|
-dontwarn javax.script.**
|
||||||
|
|
||||||
|
-dontwarn org.java_websocket.client.WebSocketClient
|
||||||
|
-dontwarn org.java_websocket.handshake.ServerHandshake
|
||||||
|
|
||||||
|
-dontwarn aQute.bnd.annotation.spi.ServiceProvider
|
||||||
|
|
||||||
|
-dontwarn org.threeten.bp.**
|
||||||
|
-keep class org.threeten.bp.*
|
||||||
|
-keepclassmembers class org.threeten.bp.** { *; }
|
||||||
|
|
||||||
|
# joda time
|
||||||
|
# These aren't necessary if including joda-convert
|
||||||
|
-dontwarn org.joda.convert.FromString
|
||||||
|
-dontwarn org.joda.convert.ToString
|
||||||
|
|
||||||
|
-keepnames class org.joda.** implements java.io.Serializable
|
||||||
|
-keepclassmembers class org.joda.** implements java.io.Serializable {
|
||||||
|
static final long serialVersionUID;
|
||||||
|
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||||
|
!static !transient <fields>;
|
||||||
|
private void writeObject(java.io.ObjectOutputStream);
|
||||||
|
private void readObject(java.io.ObjectInputStream);
|
||||||
|
java.lang.Object writeReplace();
|
||||||
|
java.lang.Object readResolve();
|
||||||
|
}
|
||||||
|
# joda time
|
||||||
|
|
||||||
|
# The name of @JsonClass types is used to look up the generated adapter.
|
||||||
|
-keepnames @com.squareup.moshi.JsonClass class *
|
||||||
|
|
||||||
|
-keepclassmembers class * {
|
||||||
|
@com.squareup.moshi.FromJson <methods>;
|
||||||
|
@com.squareup.moshi.ToJson <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclassmembers enum * {
|
||||||
|
public static **[] values();
|
||||||
|
public static ** valueOf(java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
-keep class kotlin.Metadata { *; }
|
||||||
|
-keepclassmembers class kotlin.Metadata {
|
||||||
|
public <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proguard configuration for Jackson 2.x
|
||||||
|
-keep class com.fasterxml.** { *; }
|
||||||
|
-keep class com.fasterxml.jackson.databind.ObjectMapper {
|
||||||
|
public <methods>;
|
||||||
|
protected <methods>;
|
||||||
|
}
|
||||||
|
-keep class com.fasterxml.jackson.databind.ObjectWriter {
|
||||||
|
public ** writeValueAsString(**);
|
||||||
|
}
|
||||||
|
-keepnames class com.fasterxml.jackson.** { *; }
|
||||||
|
-dontwarn com.fasterxml.jackson.databind.**
|
||||||
|
-keep class * implements com.fasterxml.jackson.core.type.TypeReference
|
||||||
|
|
||||||
|
-keep class kotlin.reflect.**
|
||||||
|
-keep public class kotlin.reflect.jvm.internal.impl.** { public *; }
|
||||||
|
|
||||||
|
# Kotlin serialization looks up the generated serializer classes through a function on companion
|
||||||
|
# objects. The companions are looked up reflectively so we need to explicitly keep these functions.
|
||||||
|
-keepclasseswithmembers class **.*$Companion {
|
||||||
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
|
}
|
||||||
|
# If a companion has the serializer function, keep the companion field on the original type so that
|
||||||
|
# the reflective lookup succeeds.
|
||||||
|
-if class **.*$Companion {
|
||||||
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
|
}
|
||||||
|
-keepclassmembers class <1>.<2> {
|
||||||
|
<1>.<2>$Companion Companion;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepnames class * implements android.os.Parcelable {
|
||||||
|
public static final ** CREATOR;
|
||||||
|
}
|
||||||
|
-keep class * implements android.os.Parcelable {
|
||||||
|
public static final android.os.Parcelable$Creator *;
|
||||||
|
}
|
||||||
|
|
||||||
|
-dontwarn okhttp3.**
|
||||||
|
-dontwarn okio.**
|
||||||
|
|
||||||
|
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
|
||||||
|
# EnclosingMethod is required to use InnerClasses.
|
||||||
|
-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*, Exceptions
|
||||||
|
|
||||||
|
# Retrofit does reflection on method and parameter annotations.
|
||||||
|
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
|
||||||
|
|
||||||
|
# Keep annotation default values (e.g., retrofit2.http.Field.encoded).
|
||||||
|
-keepattributes AnnotationDefault
|
||||||
|
|
||||||
|
# Retain service method parameters when optimizing.
|
||||||
|
-keepclassmembers,allowshrinking,allowobfuscation interface * {
|
||||||
|
@retrofit2.http.* <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclasseswithmembers class * {
|
||||||
|
@retrofit2.http.* <methods>;
|
||||||
|
}
|
||||||
|
-keepclassmembernames interface * {
|
||||||
|
@retrofit.http.* <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ignore annotation used for build tooling.
|
||||||
|
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||||
|
|
||||||
|
# Ignore JSR 305 annotations for embedding nullability information.
|
||||||
|
-dontwarn javax.annotation.**
|
||||||
|
|
||||||
|
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
|
||||||
|
-dontwarn kotlin.Unit
|
||||||
|
|
||||||
|
# Top-level functions that can only be used by Kotlin.
|
||||||
|
-dontwarn retrofit2.KotlinExtensions
|
||||||
|
-dontwarn retrofit2.KotlinExtensions.*
|
||||||
|
|
||||||
|
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
|
||||||
|
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
|
||||||
|
-if interface * { @retrofit2.http.* <methods>; }
|
||||||
|
-keep,allowobfuscation interface <1>
|
||||||
|
|
||||||
|
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
|
||||||
|
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
|
||||||
|
-keep,allowobfuscation,allowshrinking class retrofit2.Response
|
||||||
|
|
||||||
|
# With R8 full mode generic signatures are stripped for classes that are not
|
||||||
|
# kept. Suspend functions are wrapped in continuations where the type argument
|
||||||
|
# is used.
|
||||||
|
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
|
||||||
|
|
||||||
|
# public tangem android sdk
|
||||||
|
-keep class com.tangem.operations.** { *; }
|
||||||
|
-keep class com.tangem.crypto.** { *; }
|
||||||
|
-keep class com.tangem.common.card.** { *; }
|
||||||
|
|
||||||
|
# TODO remove (easy to fix in sdk)
|
||||||
|
-keep class com.tangem.common.json.** { *; }
|
||||||
|
-keep class com.tangem.common.SuccessResponse { *; }
|
||||||
|
-keep class com.tangem.common.UserCode { *; }
|
||||||
|
-keep class com.tangem.common.UserCodeType { *; }
|
||||||
|
|
||||||
|
# non-sensitive enums
|
||||||
|
-keep enum com.tangem.domain.apptheme.model.AppThemeMode { *; }
|
||||||
|
|
||||||
|
-keep class com.reown.walletkit.client.Wallet$Model { *; }
|
||||||
|
-keep class com.reown.walletkit.client.Wallet { *; }
|
||||||
|
|
||||||
|
-keep class **.R
|
||||||
|
-keep class **.R$* {
|
||||||
|
<fields>;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 72d9d4b3c3f8e6604078798c4b5567327ed9b48a
|
Subproject commit 6f955d192f7edd876b9f096a30431adeb1594aeb
|
||||||
|
|
@ -33,22 +33,24 @@ internal class DefaultVisaAuthTokenStorage @Inject constructor(
|
||||||
|
|
||||||
private val tokensAdapter = moshi.adapter(VisaAuthTokens::class.java)
|
private val tokensAdapter = moshi.adapter(VisaAuthTokens::class.java)
|
||||||
|
|
||||||
override suspend fun store(tokens: VisaAuthTokens) = withContext(dispatcherProvider.io) {
|
override suspend fun store(cardId: String, tokens: VisaAuthTokens) = withContext(dispatcherProvider.io) {
|
||||||
val json = tokensAdapter.toJson(tokens)
|
val json = tokensAdapter.toJson(tokens)
|
||||||
|
|
||||||
secureStorage.store(
|
secureStorage.store(
|
||||||
json.encodeToByteArray(throwOnInvalidSequence = true),
|
json.encodeToByteArray(throwOnInvalidSequence = true),
|
||||||
VISA_AUTH_TOKENS_KEY,
|
createKey(cardId),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(): VisaAuthTokens? = withContext(dispatcherProvider.io) {
|
override suspend fun get(cardId: String): VisaAuthTokens? = withContext(dispatcherProvider.io) {
|
||||||
secureStorage.get(VISA_AUTH_TOKENS_KEY)
|
secureStorage.get(createKey(cardId))
|
||||||
?.decodeToString(throwOnInvalidSequence = true)
|
?.decodeToString(throwOnInvalidSequence = true)
|
||||||
?.let(tokensAdapter::fromJson)
|
?.let(tokensAdapter::fromJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
override fun remove(cardId: String) {
|
||||||
const val VISA_AUTH_TOKENS_KEY = "visa_auth_tokens"
|
secureStorage.delete(createKey(cardId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createKey(cardId: String): String = "visa_auth_tokens_$cardId"
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
import com.tangem.domain.onramp.*
|
import com.tangem.domain.onramp.*
|
||||||
|
import com.tangem.domain.onramp.repositories.HotCryptoRepository
|
||||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||||
|
|
@ -229,4 +230,10 @@ internal object OnrampDomainModule {
|
||||||
): FetchOnrampCountriesUseCase {
|
): FetchOnrampCountriesUseCase {
|
||||||
return FetchOnrampCountriesUseCase(onrampRepository, onrampErrorResolver)
|
return FetchOnrampCountriesUseCase(onrampRepository, onrampErrorResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetHotCryptoTokensUseCase(hotCryptoRepository: HotCryptoRepository): GetHotCryptoUseCase {
|
||||||
|
return GetHotCryptoUseCase(hotCryptoRepository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -19,12 +19,6 @@ internal object PromoDomainModule {
|
||||||
return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository)
|
return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideShouldShowRingPromoUseCase(promoRepository: PromoRepository): ShouldShowRingPromoUseCase {
|
|
||||||
return ShouldShowRingPromoUseCase(promoRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideShouldShowSwapPromoTokenUseCase(promoRepository: PromoRepository): ShouldShowSwapPromoTokenUseCase {
|
fun provideShouldShowSwapPromoTokenUseCase(promoRepository: PromoRepository): ShouldShowSwapPromoTokenUseCase {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.visa.model.VisaActivationInput
|
import com.tangem.domain.visa.model.VisaActivationInput
|
||||||
import com.tangem.domain.visa.model.VisaAuthChallenge
|
import com.tangem.domain.visa.model.VisaAuthChallenge
|
||||||
import com.tangem.domain.visa.model.VisaCardActivationResponse
|
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||||
import com.tangem.operations.ScanTask
|
import com.tangem.operations.ScanTask
|
||||||
|
|
@ -35,16 +35,19 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||||
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||||
import com.tangem.operations.pins.SetUserCodeCommand
|
import com.tangem.operations.pins.SetUserCodeCommand
|
||||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||||
|
import com.tangem.operations.sign.SignHashResponse
|
||||||
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
|
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
|
||||||
import com.tangem.operations.wallet.CreateWalletResponse
|
import com.tangem.operations.wallet.CreateWalletResponse
|
||||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||||
import com.tangem.sdk.api.TangemSdkManager
|
import com.tangem.sdk.api.TangemSdkManager
|
||||||
|
import com.tangem.sdk.api.visa.VisaCardActivationResponse
|
||||||
import com.tangem.tap.derivationsFinder
|
import com.tangem.tap.derivationsFinder
|
||||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
||||||
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
||||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||||
|
import com.tangem.tap.domain.tasks.visa.VisaCustomerWalletApproveTask
|
||||||
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
|
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
|
||||||
import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask
|
import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask
|
||||||
import com.tangem.tap.domain.twins.FinalizeTwinTask
|
import com.tangem.tap.domain.twins.FinalizeTwinTask
|
||||||
|
|
@ -494,6 +497,17 @@ internal class DefaultTangemSdkManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun visaCustomerWalletApprove(
|
||||||
|
visaDataForApprove: VisaDataForApprove,
|
||||||
|
): CompletionResult<SignHashResponse> {
|
||||||
|
return runTaskAsyncReturnOnMain(
|
||||||
|
runnable = VisaCustomerWalletApproveTask(
|
||||||
|
visaDataForApprove = visaDataForApprove,
|
||||||
|
),
|
||||||
|
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,15 @@ import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.visa.model.VisaActivationInput
|
import com.tangem.domain.visa.model.VisaActivationInput
|
||||||
import com.tangem.domain.visa.model.VisaAuthChallenge
|
import com.tangem.domain.visa.model.VisaAuthChallenge
|
||||||
import com.tangem.domain.visa.model.VisaCardActivationResponse
|
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||||
|
import com.tangem.operations.sign.SignHashResponse
|
||||||
import com.tangem.operations.wallet.CreateWalletResponse
|
import com.tangem.operations.wallet.CreateWalletResponse
|
||||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||||
import com.tangem.sdk.api.TangemSdkManager
|
import com.tangem.sdk.api.TangemSdkManager
|
||||||
|
import com.tangem.sdk.api.visa.VisaCardActivationResponse
|
||||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||||
|
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
|
|
@ -207,4 +209,12 @@ class MockTangemSdkManager(
|
||||||
): CompletionResult<VisaCardActivationResponse> {
|
): CompletionResult<VisaCardActivationResponse> {
|
||||||
error("Not implemented")
|
error("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun visaCustomerWalletApprove(
|
||||||
|
visaDataForApprove: VisaDataForApprove,
|
||||||
|
): CompletionResult<SignHashResponse> {
|
||||||
|
error("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
@ -311,15 +311,12 @@ private class ScanWalletProcessor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getWalletProductType(card: CardDTO): ProductType {
|
private fun getWalletProductType(card: CardDTO): ProductType {
|
||||||
if (RING_BATCH_IDS.contains(card.batchId) || card.batchId.startsWith(RING_BATCH_PREFIX)) {
|
return when {
|
||||||
return ProductType.Ring
|
card.isVisa -> ProductType.Visa
|
||||||
}
|
RING_BATCH_IDS.contains(card.batchId) || card.batchId.startsWith(RING_BATCH_PREFIX) -> ProductType.Ring
|
||||||
return if (card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available &&
|
card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available &&
|
||||||
card.settings.isKeysImportAllowed
|
card.settings.isKeysImportAllowed -> ProductType.Wallet2
|
||||||
) {
|
else -> ProductType.Wallet
|
||||||
ProductType.Wallet2
|
|
||||||
} else {
|
|
||||||
ProductType.Wallet
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
package com.tangem.tap.domain.tasks.visa
|
package com.tangem.tap.domain.tasks.visa
|
||||||
|
|
||||||
import com.reown.util.hexToBytes
|
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
import com.tangem.common.core.CardSession
|
import com.tangem.common.core.CardSession
|
||||||
import com.tangem.common.core.CardSessionRunnable
|
import com.tangem.common.core.CardSessionRunnable
|
||||||
import com.tangem.common.core.CompletionCallback
|
import com.tangem.common.core.CompletionCallback
|
||||||
import com.tangem.common.core.TangemSdkError
|
import com.tangem.common.core.TangemSdkError
|
||||||
|
import com.tangem.common.extensions.hexToBytes
|
||||||
import com.tangem.common.extensions.toHexString
|
import com.tangem.common.extensions.toHexString
|
||||||
import com.tangem.common.map
|
import com.tangem.common.map
|
||||||
|
import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource
|
||||||
|
import com.tangem.crypto.CryptoUtils
|
||||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||||
import com.tangem.datasource.local.visa.VisaOTPStorage
|
import com.tangem.datasource.local.visa.VisaOTPStorage
|
||||||
|
import com.tangem.datasource.local.visa.hasSavedOTP
|
||||||
import com.tangem.domain.common.visa.VisaUtilities
|
import com.tangem.domain.common.visa.VisaUtilities
|
||||||
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.visa.model.*
|
import com.tangem.domain.visa.model.*
|
||||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||||
import com.tangem.domain.visa.repository.VisaAuthRepository
|
import com.tangem.domain.visa.repository.VisaAuthRepository
|
||||||
|
|
@ -20,12 +24,15 @@ import com.tangem.operations.pins.SetUserCodeCommand
|
||||||
import com.tangem.operations.sign.SignHashCommand
|
import com.tangem.operations.sign.SignHashCommand
|
||||||
import com.tangem.operations.sign.SignHashResponse
|
import com.tangem.operations.sign.SignHashResponse
|
||||||
import com.tangem.operations.wallet.CreateWalletTask
|
import com.tangem.operations.wallet.CreateWalletTask
|
||||||
|
import com.tangem.sdk.api.visa.VisaCardActivationResponse
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import timber.log.Timber
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.jvm.Throws
|
import kotlin.jvm.Throws
|
||||||
|
import kotlin.time.measureTimedValue
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
class VisaCardActivationTask @AssistedInject constructor(
|
class VisaCardActivationTask @AssistedInject constructor(
|
||||||
|
|
@ -36,9 +43,15 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
private val otpStorage: VisaOTPStorage,
|
private val otpStorage: VisaOTPStorage,
|
||||||
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
||||||
private val visaAuthRepository: VisaAuthRepository,
|
private val visaAuthRepository: VisaAuthRepository,
|
||||||
private val visaActivationRepository: VisaActivationRepository,
|
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
||||||
) : CardSessionRunnable<VisaCardActivationResponse> {
|
) : CardSessionRunnable<VisaCardActivationResponse> {
|
||||||
|
|
||||||
|
private class SessionContext(
|
||||||
|
val visaActivationRepository: VisaActivationRepository,
|
||||||
|
val cardId: String,
|
||||||
|
val session: CardSession,
|
||||||
|
)
|
||||||
|
|
||||||
override fun run(session: CardSession, callback: CompletionCallback<VisaCardActivationResponse>) {
|
override fun run(session: CardSession, callback: CompletionCallback<VisaCardActivationResponse>) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
callback(runSuspend(session))
|
callback(runSuspend(session))
|
||||||
|
|
@ -52,33 +65,47 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
return CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.WrongCard.message))
|
return CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.WrongCard.message))
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (challengeToSign != null) {
|
val visaActivationRepository = visaActivationRepositoryFactory.create(card.cardId)
|
||||||
signAuthorizationChallenge(session, challengeToSign)
|
|
||||||
} else {
|
|
||||||
val activationOrder = runCatching { visaActivationRepository.getActivationOrderToSign() }
|
|
||||||
.getOrElse {
|
|
||||||
return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
signOrder(session, activationOrder)
|
val context = SessionContext(
|
||||||
}
|
visaActivationRepository = visaActivationRepository,
|
||||||
}
|
cardId = card.cardId,
|
||||||
|
session = session,
|
||||||
|
)
|
||||||
|
|
||||||
private suspend fun signAuthorizationChallenge(
|
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||||
session: CardSession,
|
if (challengeToSign != null) {
|
||||||
challengeToSign: VisaAuthChallenge.Card,
|
context.signAuthorizationChallenge(challengeToSign)
|
||||||
): CompletionResult<VisaCardActivationResponse> {
|
} else {
|
||||||
val attestationCommand = AttestCardKeyCommand(challenge = challengeToSign.challenge.hexToBytes())
|
val activationOrder = runCatching { visaActivationRepository.getActivationOrderToSign() }
|
||||||
val result = suspendCancellableCoroutine { continuation ->
|
.getOrElse {
|
||||||
attestationCommand.run(session = session) { attestationResponse ->
|
return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: ""))
|
||||||
continuation.resume(attestationResponse)
|
}
|
||||||
|
|
||||||
|
context.signOrder(activationOrder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Timber.i("VisaCardActivationTask all time: ${timedResult.duration}")
|
||||||
|
return timedResult.value
|
||||||
|
}
|
||||||
|
|
||||||
return when (result) {
|
private suspend fun SessionContext.signAuthorizationChallenge(
|
||||||
|
challengeToSign: VisaAuthChallenge.Card,
|
||||||
|
): CompletionResult<VisaCardActivationResponse> {
|
||||||
|
val attestationCommand = AttestCardKeyCommand(challenge = CryptoUtils.generateRandomBytes(length = 16))
|
||||||
|
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||||
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
attestationCommand.run(session = session) { attestationResponse ->
|
||||||
|
continuation.resume(attestationResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timber.i("AttestCardKeyCommand time: ${timedResult.duration}")
|
||||||
|
|
||||||
|
return when (val result = timedResult.value) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
|
Timber.i("AttestCardKeyCommand success")
|
||||||
processSignedAuthorizationChallenge(
|
processSignedAuthorizationChallenge(
|
||||||
session = session,
|
|
||||||
signedChallenge = challengeToSign.toSignedChallenge(
|
signedChallenge = challengeToSign.toSignedChallenge(
|
||||||
signedChallenge = result.data.cardSignature.toHexString(),
|
signedChallenge = result.data.cardSignature.toHexString(),
|
||||||
salt = result.data.salt.toHexString(),
|
salt = result.data.salt.toHexString(),
|
||||||
|
|
@ -86,13 +113,13 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
|
Timber.e("AttestCardKeyCommand failure ${result.error}")
|
||||||
CompletionResult.Failure(result.error)
|
CompletionResult.Failure(result.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun processSignedAuthorizationChallenge(
|
private suspend fun SessionContext.processSignedAuthorizationChallenge(
|
||||||
session: CardSession,
|
|
||||||
signedChallenge: VisaAuthSignedChallenge,
|
signedChallenge: VisaAuthSignedChallenge,
|
||||||
): CompletionResult<VisaCardActivationResponse> {
|
): CompletionResult<VisaCardActivationResponse> {
|
||||||
return coroutineScope {
|
return coroutineScope {
|
||||||
|
|
@ -107,12 +134,14 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
|
|
||||||
otpTaskDeferred.await()
|
otpTaskDeferred.await()
|
||||||
|
|
||||||
signOrder(session, order)
|
signOrder(order)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(TangemSdkError::class)
|
@Throws(TangemSdkError::class)
|
||||||
private suspend fun getActivationOrderToSign(signedChallenge: VisaAuthSignedChallenge): ActivationOrder {
|
private suspend fun SessionContext.getActivationOrderToSign(
|
||||||
|
signedChallenge: VisaAuthSignedChallenge,
|
||||||
|
): ActivationOrder {
|
||||||
val tokens = runCatching {
|
val tokens = runCatching {
|
||||||
visaAuthRepository.getAccessTokens(signedChallenge)
|
visaAuthRepository.getAccessTokens(signedChallenge)
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
|
|
@ -121,12 +150,12 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
visaAuthTokenStorage.store(tokens)
|
visaAuthTokenStorage.store(cardId, tokens)
|
||||||
|
|
||||||
return visaActivationRepository.getActivationOrderToSign()
|
return visaActivationRepository.getActivationOrderToSign()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createWallet(session: CardSession): CompletionResult<Unit> {
|
private suspend fun SessionContext.createWallet(session: CardSession): CompletionResult<Unit> {
|
||||||
coroutineScope { ensureActive() }
|
coroutineScope { ensureActive() }
|
||||||
|
|
||||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
|
|
@ -135,122 +164,152 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
createOTP(session)
|
createOTP(session)
|
||||||
} else {
|
} else {
|
||||||
val createWalletTask = CreateWalletTask(VisaUtilities.mandatoryCurve)
|
val createWalletTask = CreateWalletTask(VisaUtilities.mandatoryCurve)
|
||||||
val result = suspendCancellableCoroutine { continuation ->
|
|
||||||
createWalletTask.run(session) { createWalletResult ->
|
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||||
continuation.resume(createWalletResult)
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
createWalletTask.run(session) { createWalletResult ->
|
||||||
|
continuation.resume(createWalletResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (result) {
|
Timber.i("CreateWalletTask time: ${timedResult.duration}")
|
||||||
|
|
||||||
|
when (val result = timedResult.value) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
|
Timber.i("CreateWalletTask success")
|
||||||
createOTP(session)
|
createOTP(session)
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
|
Timber.e("CreateWalletTask failure ${result.error}")
|
||||||
CompletionResult.Failure(result.error)
|
CompletionResult.Failure(result.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createOTP(session: CardSession): CompletionResult<Unit> {
|
private suspend fun SessionContext.createOTP(session: CardSession): CompletionResult<Unit> {
|
||||||
coroutineScope { ensureActive() }
|
coroutineScope { ensureActive() }
|
||||||
|
|
||||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
return if (otpStorage.hasSavedOTP(cardId)) {
|
||||||
|
|
||||||
val otp = otpStorage.getOTP(card.cardId)
|
|
||||||
return if (otp != null) {
|
|
||||||
CompletionResult.Success(Unit)
|
CompletionResult.Success(Unit)
|
||||||
} else {
|
} else {
|
||||||
val otpCommand = GenerateOTPCommand()
|
val otpCommand = GenerateOTPCommand()
|
||||||
val result = suspendCancellableCoroutine { continuation ->
|
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||||
otpCommand.run(session) { otpResult ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
continuation.resume(otpResult)
|
otpCommand.run(session) { otpResult ->
|
||||||
|
continuation.resume(otpResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (result) {
|
Timber.i("GenerateOTPCommand time: ${timedResult.duration}")
|
||||||
|
|
||||||
|
when (val result = timedResult.value) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
otpStorage.saveOTP(card.cardId, result.data.rootOTP)
|
Timber.i("GenerateOTPCommand success")
|
||||||
|
otpStorage.saveOTP(cardId, result.data.rootOTP)
|
||||||
CompletionResult.Success(Unit)
|
CompletionResult.Success(Unit)
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
|
Timber.e("GenerateOTPCommand failure ${result.error}")
|
||||||
CompletionResult.Failure(result.error)
|
CompletionResult.Failure(result.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun signOrder(
|
private suspend fun SessionContext.signOrder(order: ActivationOrder): CompletionResult<VisaCardActivationResponse> {
|
||||||
session: CardSession,
|
val card =
|
||||||
order: ActivationOrder,
|
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
): CompletionResult<VisaCardActivationResponse> {
|
|
||||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
|
||||||
val wallet =
|
val wallet =
|
||||||
card.wallets.firstOrNull() ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve }
|
||||||
val task = SignHashCommand(order.hash.hexToBytes(), wallet.publicKey)
|
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
val result = suspendCancellableCoroutine { continuation ->
|
|
||||||
task.run(session) { signResult ->
|
val task = SignHashCommand(
|
||||||
continuation.resume(signResult)
|
hash = order.hash.hexToBytes(),
|
||||||
|
walletPublicKey = wallet.publicKey,
|
||||||
|
derivationPath = VisaUtilities.visaDefaultDerivationPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||||
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
task.run(session) { signResult ->
|
||||||
|
continuation.resume(signResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (result) {
|
Timber.i("SignHashCommand time: ${timedResult.duration}")
|
||||||
|
|
||||||
|
return when (val result = timedResult.value) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
|
Timber.i("SignHashCommand success")
|
||||||
handleSignedOrder(
|
handleSignedOrder(
|
||||||
session = session,
|
|
||||||
activationOrder = order,
|
activationOrder = order,
|
||||||
response = result.data,
|
response = result.data,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
|
Timber.e("SignHashCommand failure ${result.error}")
|
||||||
CompletionResult.Failure(result.error)
|
CompletionResult.Failure(result.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleSignedOrder(
|
private suspend fun SessionContext.handleSignedOrder(
|
||||||
session: CardSession,
|
|
||||||
activationOrder: ActivationOrder,
|
activationOrder: ActivationOrder,
|
||||||
response: SignHashResponse,
|
response: SignHashResponse,
|
||||||
): CompletionResult<VisaCardActivationResponse> {
|
): CompletionResult<VisaCardActivationResponse> {
|
||||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
|
||||||
|
|
||||||
val signedOrder = SignedActivationOrder(
|
val signedOrder = SignedActivationOrder(
|
||||||
activationOrder = activationOrder,
|
activationOrder = activationOrder,
|
||||||
signature = response.signature.toHexString(),
|
signature = response.signature.toHexString(),
|
||||||
)
|
)
|
||||||
|
|
||||||
val otp = otpStorage.getOTP(card.cardId) ?: return CompletionResult.Failure(
|
val otp = otpStorage.getOTP(cardId) ?: return CompletionResult.Failure(
|
||||||
TangemSdkError.Underlying(VisaActivationError.MissingRootOTP.message),
|
TangemSdkError.Underlying(VisaActivationError.MissingRootOTP.message),
|
||||||
)
|
)
|
||||||
|
|
||||||
val activationResponse = VisaCardActivationResponse(
|
return setupAccessCode().map {
|
||||||
signedActivationOrder = signedOrder,
|
val card =
|
||||||
rootOTP = VisaRootOTP(otp.toHexString()),
|
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
)
|
|
||||||
|
|
||||||
return setupAccessCode(session).map { activationResponse }
|
VisaCardActivationResponse(
|
||||||
|
signedActivationOrder = signedOrder,
|
||||||
|
rootOTP = VisaRootOTP(otp.toHexString()),
|
||||||
|
newCardDTO = CardDTO(card),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun setupAccessCode(session: CardSession): CompletionResult<Unit> {
|
private suspend fun SessionContext.setupAccessCode(): CompletionResult<Unit> {
|
||||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
|
|
||||||
if (card.isAccessCodeSet) {
|
if (card.isAccessCodeSet) {
|
||||||
return CompletionResult.Success(Unit)
|
return CompletionResult.Success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timber.i("Setting access code")
|
||||||
|
|
||||||
val task = SetUserCodeCommand.changeAccessCode(accessCode)
|
val task = SetUserCodeCommand.changeAccessCode(accessCode)
|
||||||
val result = suspendCancellableCoroutine { continuation ->
|
|
||||||
task.run(session) { setAccessCodeResult ->
|
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||||
continuation.resume(setAccessCodeResult)
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
task.run(session) { setAccessCodeResult ->
|
||||||
|
continuation.resume(setAccessCodeResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (result) {
|
Timber.i("SetUserCodeCommand time: ${timedResult.duration}")
|
||||||
|
|
||||||
|
return when (val result = timedResult.value) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
|
Timber.i("SetUserCodeCommand success")
|
||||||
CompletionResult.Success(Unit)
|
CompletionResult.Success(Unit)
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
|
Timber.i("SetUserCodeCommand failure ${result.error}")
|
||||||
CompletionResult.Failure(result.error)
|
CompletionResult.Failure(result.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
package com.tangem.tap.domain.tasks.visa
|
||||||
|
|
||||||
|
import arrow.core.getOrElse
|
||||||
|
import com.tangem.common.CompletionResult
|
||||||
|
import com.tangem.common.card.Card
|
||||||
|
import com.tangem.common.card.CardWallet
|
||||||
|
import com.tangem.common.core.CardSession
|
||||||
|
import com.tangem.common.core.CardSessionRunnable
|
||||||
|
import com.tangem.common.core.CompletionCallback
|
||||||
|
import com.tangem.common.core.TangemSdkError
|
||||||
|
import com.tangem.common.extensions.hexToBytes
|
||||||
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
|
import com.tangem.domain.common.util.derivationStyleProvider
|
||||||
|
import com.tangem.domain.common.visa.VisaUtilities
|
||||||
|
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
|
||||||
|
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
|
||||||
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
|
import com.tangem.domain.visa.model.VisaActivationError
|
||||||
|
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||||
|
import com.tangem.operations.ScanTask
|
||||||
|
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||||
|
import com.tangem.operations.sign.SignHashCommand
|
||||||
|
import com.tangem.operations.sign.SignHashResponse
|
||||||
|
|
||||||
|
class VisaCustomerWalletApproveTask(
|
||||||
|
private val visaDataForApprove: VisaDataForApprove,
|
||||||
|
) : CardSessionRunnable<SignHashResponse> {
|
||||||
|
|
||||||
|
override fun run(session: CardSession, callback: CompletionCallback<SignHashResponse>) {
|
||||||
|
val card = session.environment.card ?: run {
|
||||||
|
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VisaUtilities.isVisaCard(card.firmwareVersion.doubleValue, card.batchId)) {
|
||||||
|
callback(CompletionResult.Failure(TangemSdkError.Underlying("Can't use Visa card for approve")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visaDataForApprove.customerWalletCardId != null && card.cardId != visaDataForApprove.customerWalletCardId) {
|
||||||
|
callback(
|
||||||
|
CompletionResult.Failure(
|
||||||
|
TangemSdkError.Underlying("Use tangem wallet specified during visa registration"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card.settings.isHDWalletAllowed) {
|
||||||
|
proceedApprove(card, session, callback)
|
||||||
|
} else {
|
||||||
|
proceedApproveWithLegacyCard(card, session, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun proceedApprove(card: Card, session: CardSession, callback: CompletionCallback<SignHashResponse>) {
|
||||||
|
val cardDTO = CardDTO(card)
|
||||||
|
|
||||||
|
val derivationStyle = cardDTO.derivationStyleProvider.getDerivationStyle() ?: run {
|
||||||
|
proceedApproveWithLegacyCard(
|
||||||
|
card = card,
|
||||||
|
session = session,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val derivationPath = VisaUtilities.visaDefaultDerivationPath(derivationStyle) ?: run {
|
||||||
|
callback(
|
||||||
|
CompletionResult.Failure(
|
||||||
|
TangemSdkError.Underlying("Failed to generate derivation path with provided derivation style"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run {
|
||||||
|
callback(CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val derivationTask = DeriveWalletPublicKeyTask(
|
||||||
|
walletPublicKey = wallet.publicKey,
|
||||||
|
derivationPath = derivationPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
derivationTask.run(session) { result ->
|
||||||
|
when (result) {
|
||||||
|
is CompletionResult.Success -> {
|
||||||
|
processDerivedKey(
|
||||||
|
wallet = wallet,
|
||||||
|
extendedPublicKey = result.data,
|
||||||
|
derivationPath = derivationPath,
|
||||||
|
session = session,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is CompletionResult.Failure -> {
|
||||||
|
callback(CompletionResult.Failure(result.error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processDerivedKey(
|
||||||
|
wallet: CardWallet,
|
||||||
|
extendedPublicKey: ExtendedPublicKey,
|
||||||
|
derivationPath: DerivationPath,
|
||||||
|
session: CardSession,
|
||||||
|
callback: CompletionCallback<SignHashResponse>,
|
||||||
|
) {
|
||||||
|
val validationResult = VisaWalletPublicKeyUtility.validateExtendedPublicKey(
|
||||||
|
targetAddress = visaDataForApprove.targetAddress,
|
||||||
|
extendedPublicKey = extendedPublicKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
validationResult.onLeft {
|
||||||
|
callback(CompletionResult.Failure(TangemSdkError.Underlying(it.message)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
signApproveData(
|
||||||
|
targetWalletPublicKey = wallet.publicKey,
|
||||||
|
derivationPath = derivationPath,
|
||||||
|
session = session,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun proceedApproveWithLegacyCard(
|
||||||
|
card: Card,
|
||||||
|
session: CardSession,
|
||||||
|
callback: CompletionCallback<SignHashResponse>,
|
||||||
|
) {
|
||||||
|
val publicKey = findKeyWithoutDerivation(
|
||||||
|
targetAddress = visaDataForApprove.targetAddress,
|
||||||
|
card = CardDTO(card),
|
||||||
|
).getOrElse {
|
||||||
|
callback(CompletionResult.Failure(TangemSdkError.Underlying(it.message)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
signApproveData(
|
||||||
|
targetWalletPublicKey = publicKey,
|
||||||
|
derivationPath = null,
|
||||||
|
session = session,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun signApproveData(
|
||||||
|
targetWalletPublicKey: ByteArray,
|
||||||
|
derivationPath: DerivationPath?,
|
||||||
|
session: CardSession,
|
||||||
|
callback: CompletionCallback<SignHashResponse>,
|
||||||
|
) {
|
||||||
|
val signTask = SignHashCommand(
|
||||||
|
hash = visaDataForApprove.approveHash.hexToBytes(),
|
||||||
|
walletPublicKey = targetWalletPublicKey,
|
||||||
|
derivationPath = derivationPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
signTask.run(session) { result ->
|
||||||
|
when (result) {
|
||||||
|
is CompletionResult.Success -> {
|
||||||
|
scanCard(
|
||||||
|
signHashResponse = result.data,
|
||||||
|
session = session,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is CompletionResult.Failure -> {
|
||||||
|
callback(CompletionResult.Failure(result.error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scanCard(
|
||||||
|
signHashResponse: SignHashResponse,
|
||||||
|
session: CardSession,
|
||||||
|
callback: CompletionCallback<SignHashResponse>,
|
||||||
|
) {
|
||||||
|
val scanTask = ScanTask()
|
||||||
|
scanTask.run(session) { result ->
|
||||||
|
when (result) {
|
||||||
|
is CompletionResult.Success -> {
|
||||||
|
callback(CompletionResult.Success(signHashResponse))
|
||||||
|
}
|
||||||
|
is CompletionResult.Failure -> {
|
||||||
|
callback(CompletionResult.Failure(result.error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.tap.domain.userWalletList.model
|
package com.tangem.tap.domain.userWalletList.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
|
@ -7,16 +8,24 @@ import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class UserWalletSensitiveInformation(
|
internal data class UserWalletSensitiveInformation(
|
||||||
|
@Json(name = "wallets")
|
||||||
val wallets: List<CardDTO.Wallet>,
|
val wallets: List<CardDTO.Wallet>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class UserWalletPublicInformation(
|
internal data class UserWalletPublicInformation(
|
||||||
|
@Json(name = "name")
|
||||||
val name: String,
|
val name: String,
|
||||||
|
@Json(name = "walletId")
|
||||||
val walletId: UserWalletId,
|
val walletId: UserWalletId,
|
||||||
|
@Json(name = "artworkUrl")
|
||||||
val artworkUrl: String,
|
val artworkUrl: String,
|
||||||
|
@Json(name = "cardsInWallet")
|
||||||
val cardsInWallet: Set<String>,
|
val cardsInWallet: Set<String>,
|
||||||
|
@Json(name = "scanResponse")
|
||||||
val scanResponse: ScanResponse,
|
val scanResponse: ScanResponse,
|
||||||
|
@Json(name = "isMultiCurrency")
|
||||||
val isMultiCurrency: Boolean,
|
val isMultiCurrency: Boolean,
|
||||||
|
@Json(name = "hasBackupError")
|
||||||
val hasBackupError: Boolean = false,
|
val hasBackupError: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult
|
||||||
import com.tangem.common.card.CardWallet
|
import com.tangem.common.card.CardWallet
|
||||||
import com.tangem.common.core.CardSession
|
import com.tangem.common.core.CardSession
|
||||||
import com.tangem.common.core.TangemSdkError
|
import com.tangem.common.core.TangemSdkError
|
||||||
|
import com.tangem.common.extensions.hexToBytes
|
||||||
import com.tangem.common.extensions.toHexString
|
import com.tangem.common.extensions.toHexString
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
|
|
@ -24,10 +25,16 @@ import kotlin.coroutines.resume
|
||||||
|
|
||||||
internal class VisaCardScanHandler @Inject constructor(
|
internal class VisaCardScanHandler @Inject constructor(
|
||||||
private val visaAuthRepository: VisaAuthRepository,
|
private val visaAuthRepository: VisaAuthRepository,
|
||||||
private val visaActivationRepository: VisaActivationRepository,
|
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
||||||
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private class SessionContext(
|
||||||
|
val visaActivationRepository: VisaActivationRepository,
|
||||||
|
val cardId: String,
|
||||||
|
val session: CardSession,
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun handleVisaCardScan(session: CardSession): CompletionResult<VisaCardActivationStatus> {
|
suspend fun handleVisaCardScan(session: CardSession): CompletionResult<VisaCardActivationStatus> {
|
||||||
Timber.i("Attempting to handle Visa card scan")
|
Timber.i("Attempting to handle Visa card scan")
|
||||||
|
|
||||||
|
|
@ -36,6 +43,14 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val visaActivationRepository = visaActivationRepositoryFactory.create(card.cardId)
|
||||||
|
|
||||||
|
val context = SessionContext(
|
||||||
|
visaActivationRepository = visaActivationRepository,
|
||||||
|
cardId = card.cardId,
|
||||||
|
session = session,
|
||||||
|
)
|
||||||
|
|
||||||
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run {
|
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run {
|
||||||
val activationInput =
|
val activationInput =
|
||||||
VisaActivationInput(card.cardId, card.cardPublicKey, card.isAccessCodeSet)
|
VisaActivationInput(card.cardId, card.cardPublicKey, card.isAccessCodeSet)
|
||||||
|
|
@ -43,13 +58,10 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
return CompletionResult.Success(activationStatus)
|
return CompletionResult.Success(activationStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
return deriveKey(wallet, session)
|
return context.deriveKey(wallet)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun deriveKey(
|
private suspend fun SessionContext.deriveKey(wallet: CardWallet): CompletionResult<VisaCardActivationStatus> {
|
||||||
wallet: CardWallet,
|
|
||||||
session: CardSession,
|
|
||||||
): CompletionResult<VisaCardActivationStatus> {
|
|
||||||
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
|
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
|
||||||
Timber.e("Failed to create derivation path while first scan")
|
Timber.e("Failed to create derivation path while first scan")
|
||||||
|
|
||||||
|
|
@ -64,17 +76,16 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
continuation.resume(result)
|
continuation.resume(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return handleDerivationResponse(derivationTaskResult, session)
|
return handleDerivationResponse(derivationTaskResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleDerivationResponse(
|
private suspend fun SessionContext.handleDerivationResponse(
|
||||||
result: CompletionResult<ExtendedPublicKey>,
|
result: CompletionResult<ExtendedPublicKey>,
|
||||||
session: CardSession,
|
|
||||||
): CompletionResult<VisaCardActivationStatus> {
|
): CompletionResult<VisaCardActivationStatus> {
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
Timber.i("Start task for loading challenge for Visa wallet")
|
Timber.i("Start task for loading challenge for Visa wallet")
|
||||||
handleWalletAuthorization(session)
|
handleWalletAuthorization()
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
CompletionResult.Failure(result.error)
|
CompletionResult.Failure(result.error)
|
||||||
|
|
@ -82,9 +93,8 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleWalletAuthorization(session: CardSession): CompletionResult<VisaCardActivationStatus> {
|
private suspend fun SessionContext.handleWalletAuthorization(): CompletionResult<VisaCardActivationStatus> {
|
||||||
Timber.i("Started handling authorization using Visa wallet")
|
Timber.i("Started handling authorization using Visa wallet")
|
||||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
|
||||||
|
|
||||||
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
|
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
|
||||||
Timber.e("Failed to create derivation path while handling wallet authorization")
|
Timber.e("Failed to create derivation path while handling wallet authorization")
|
||||||
|
|
@ -93,6 +103,8 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
|
|
||||||
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run {
|
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run {
|
||||||
Timber.e("Failed to find extended public key while handling wallet authorization")
|
Timber.e("Failed to find extended public key while handling wallet authorization")
|
||||||
return CompletionResult.Failure(
|
return CompletionResult.Failure(
|
||||||
|
|
@ -108,7 +120,6 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.i("Requesting challenge for wallet authorization")
|
Timber.i("Requesting challenge for wallet authorization")
|
||||||
// Will be changed later after backend implementation
|
|
||||||
val challengeResponse = runCatching {
|
val challengeResponse = runCatching {
|
||||||
visaAuthRepository.getCustomerWalletAuthChallenge(
|
visaAuthRepository.getCustomerWalletAuthChallenge(
|
||||||
cardId = card.cardId,
|
cardId = card.cardId,
|
||||||
|
|
@ -122,14 +133,12 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
publicKey = wallet.publicKey,
|
publicKey = wallet.publicKey,
|
||||||
derivationPath = derivationPath,
|
derivationPath = derivationPath,
|
||||||
nonce = challengeResponse.challenge,
|
nonce = challengeResponse.challenge,
|
||||||
session = session,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return when (signChallengeResult) {
|
return when (signChallengeResult) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
Timber.i("Challenge signed with Wallet public key")
|
Timber.i("Challenge signed with Wallet public key")
|
||||||
handleWalletAuthorizationTokens(
|
handleWalletAuthorizationTokens(
|
||||||
session = session,
|
|
||||||
signedChallenge = challengeResponse
|
signedChallenge = challengeResponse
|
||||||
.toSignedChallenge(signChallengeResult.data.signature.toHexString()),
|
.toSignedChallenge(signChallengeResult.data.signature.toHexString()),
|
||||||
)
|
)
|
||||||
|
|
@ -141,8 +150,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleWalletAuthorizationTokens(
|
private suspend fun SessionContext.handleWalletAuthorizationTokens(
|
||||||
session: CardSession,
|
|
||||||
signedChallenge: VisaAuthSignedChallenge,
|
signedChallenge: VisaAuthSignedChallenge,
|
||||||
): CompletionResult<VisaCardActivationStatus> {
|
): CompletionResult<VisaCardActivationStatus> {
|
||||||
val authorizationTokensResponse = runCatching {
|
val authorizationTokensResponse = runCatching {
|
||||||
|
|
@ -151,17 +159,20 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
Timber.i(
|
Timber.i(
|
||||||
"Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key",
|
"Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key",
|
||||||
)
|
)
|
||||||
return handleCardAuthorization(session)
|
return handleCardAuthorization()
|
||||||
}
|
}
|
||||||
|
|
||||||
visaAuthTokenStorage.store(authorizationTokensResponse)
|
visaAuthTokenStorage.store(
|
||||||
|
cardId = cardId,
|
||||||
|
tokens = authorizationTokensResponse,
|
||||||
|
)
|
||||||
|
|
||||||
Timber.i("Authorized using Wallet public key successfully")
|
Timber.i("Authorized using Wallet public key successfully")
|
||||||
|
|
||||||
return CompletionResult.Success(VisaCardActivationStatus.Activated(authorizationTokensResponse))
|
return CompletionResult.Success(VisaCardActivationStatus.Activated(authorizationTokensResponse))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleCardAuthorization(session: CardSession): CompletionResult<VisaCardActivationStatus> {
|
private suspend fun SessionContext.handleCardAuthorization(): CompletionResult<VisaCardActivationStatus> {
|
||||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
|
|
||||||
Timber.i("Requesting authorization challenge to sign")
|
Timber.i("Requesting authorization challenge to sign")
|
||||||
|
|
@ -178,7 +189,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
|
|
||||||
Timber.i("Received challenge to sign: ${challengeResponse.challenge}")
|
Timber.i("Received challenge to sign: ${challengeResponse.challenge}")
|
||||||
|
|
||||||
val signChallengeResult = signChallengeWithCard(session = session, challenge = challengeResponse.challenge)
|
val signChallengeResult = signChallengeWithCard(challenge = challengeResponse.challenge)
|
||||||
|
|
||||||
val attestCardKeyResponse = when (signChallengeResult) {
|
val attestCardKeyResponse = when (signChallengeResult) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
|
|
@ -210,7 +221,10 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
visaAuthTokenStorage.store(authorizationTokensResponse)
|
visaAuthTokenStorage.store(
|
||||||
|
cardId = card.cardId,
|
||||||
|
tokens = authorizationTokensResponse,
|
||||||
|
)
|
||||||
|
|
||||||
val activationRemoteState = visaActivationRepository.getActivationRemoteState()
|
val activationRemoteState = visaActivationRepository.getActivationRemoteState()
|
||||||
|
|
||||||
|
|
@ -239,47 +253,31 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun signChallengeWithWallet(
|
private suspend fun SessionContext.signChallengeWithWallet(
|
||||||
publicKey: ByteArray,
|
publicKey: ByteArray,
|
||||||
derivationPath: DerivationPath,
|
derivationPath: DerivationPath,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
session: CardSession,
|
|
||||||
): CompletionResult<SignHashResponse> {
|
): CompletionResult<SignHashResponse> {
|
||||||
val signHashCommand = SignHashCommand(publicKey, nonce.toByteArray(), derivationPath)
|
val signHashCommand = SignHashCommand(
|
||||||
val result = suspendCancellableCoroutine {
|
hash = nonce.hexToBytes(),
|
||||||
|
walletPublicKey = publicKey,
|
||||||
|
derivationPath = derivationPath,
|
||||||
|
)
|
||||||
|
return suspendCancellableCoroutine {
|
||||||
signHashCommand.run(session) { result ->
|
signHashCommand.run(session) { result ->
|
||||||
it.resume(result)
|
it.resume(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (result) {
|
|
||||||
is CompletionResult.Success -> {
|
|
||||||
CompletionResult.Success(result.data)
|
|
||||||
}
|
|
||||||
is CompletionResult.Failure -> {
|
|
||||||
CompletionResult.Failure(result.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun signChallengeWithCard(
|
private suspend fun SessionContext.signChallengeWithCard(
|
||||||
session: CardSession,
|
|
||||||
challenge: String,
|
challenge: String,
|
||||||
): CompletionResult<AttestCardKeyResponse> {
|
): CompletionResult<AttestCardKeyResponse> {
|
||||||
val signHashCommand = AttestCardKeyCommand(challenge = challenge.toByteArray())
|
val signHashCommand = AttestCardKeyCommand(challenge = challenge.hexToBytes())
|
||||||
val result = suspendCancellableCoroutine { continuation ->
|
return suspendCancellableCoroutine { continuation ->
|
||||||
signHashCommand.run(session) { result ->
|
signHashCommand.run(session) { result ->
|
||||||
continuation.resume(result)
|
continuation.resume(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (result) {
|
|
||||||
is CompletionResult.Success -> {
|
|
||||||
CompletionResult.Success(result.data)
|
|
||||||
}
|
|
||||||
is CompletionResult.Failure -> {
|
|
||||||
CompletionResult.Failure(result.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +52,8 @@ data class WcSignMessage(
|
||||||
@Json(name = "type")
|
@Json(name = "type")
|
||||||
val type: WCSignType,
|
val type: WCSignType,
|
||||||
) : WcRequestData {
|
) : WcRequestData {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class WCSignType {
|
enum class WCSignType {
|
||||||
MESSAGE, PERSONAL_MESSAGE, TYPED_MESSAGE, SOLANA_MESSAGE,
|
MESSAGE, PERSONAL_MESSAGE, TYPED_MESSAGE, SOLANA_MESSAGE,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,16 @@
|
||||||
package com.tangem.tap.domain.walletconnect2.domain.models
|
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||||
|
|
||||||
data class Account(val chainId: String, val walletAddress: String, val derivationPath: String?)
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Account(
|
||||||
|
@Json(name = "chainId")
|
||||||
|
val chainId: String,
|
||||||
|
|
||||||
|
@Json(name = "walletAddress")
|
||||||
|
val walletAddress: String,
|
||||||
|
|
||||||
|
@Json(name = "derivationPath")
|
||||||
|
val derivationPath: String?,
|
||||||
|
)
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
package com.tangem.tap.domain.walletconnect2.domain.models
|
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Session(
|
data class Session(
|
||||||
|
@Json(name = "topic")
|
||||||
val topic: String,
|
val topic: String,
|
||||||
|
|
||||||
|
@Json(name = "accounts")
|
||||||
val accounts: List<Account>,
|
val accounts: List<Account>,
|
||||||
)
|
)
|
||||||
|
|
@ -2,29 +2,34 @@ package com.tangem.tap.domain.walletconnect2.domain.models.binance
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
class WcBinanceCancelOrder(
|
data class WcBinanceCancelOrder(
|
||||||
@Json(name = "account_number")
|
@Json(name = "account_number")
|
||||||
accountNumber: String,
|
val accountNumber: String,
|
||||||
@Json(name = "chain_id")
|
@Json(name = "chain_id")
|
||||||
chainId: String,
|
val chainId: String,
|
||||||
@Json(name = "data")
|
@Json(name = "data")
|
||||||
data: String?,
|
val data: String?,
|
||||||
@Json(name = "memo")
|
@Json(name = "memo")
|
||||||
memo: String?,
|
val memo: String?,
|
||||||
@Json(name = "sequence")
|
@Json(name = "sequence")
|
||||||
sequence: String,
|
val sequence: String,
|
||||||
@Json(name = "source")
|
@Json(name = "source")
|
||||||
source: String,
|
val source: String,
|
||||||
@Json(name = "msgs")
|
@Json(name = "msgs")
|
||||||
msgs: List<Message>,
|
val msgs: List<Message>,
|
||||||
) : WcBinanceOrder<WcBinanceCancelOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
|
) : WcRequestData {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Message(
|
data class Message(
|
||||||
|
@Json(name = "refid")
|
||||||
val refid: String,
|
val refid: String,
|
||||||
|
@Json(name = "sender")
|
||||||
val sender: String,
|
val sender: String,
|
||||||
|
@Json(name = "symbol")
|
||||||
val symbol: String,
|
val symbol: String,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package com.tangem.tap.domain.walletconnect2.domain.models.binance
|
|
||||||
|
|
||||||
import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
|
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
|
||||||
open class WcBinanceOrder<T>(
|
|
||||||
val accountNumber: String,
|
|
||||||
val chainId: String,
|
|
||||||
val data: String?,
|
|
||||||
val memo: String?,
|
|
||||||
val sequence: String,
|
|
||||||
val source: String,
|
|
||||||
val msgs: List<T>,
|
|
||||||
) : WcRequestData
|
|
||||||
|
|
||||||
data class WcBinanceTxConfirmParam(
|
|
||||||
val ok: Boolean,
|
|
||||||
val errorMsg: String?,
|
|
||||||
) : WcRequestData
|
|
||||||
|
|
@ -4,21 +4,28 @@ import com.github.salomonbrys.kotson.jsonSerializer
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
class WcBinanceTradeOrder(
|
data class WcBinanceTradeOrder(
|
||||||
@Json(name = "account_number")
|
@Json(name = "account_number")
|
||||||
accountNumber: String,
|
val accountNumber: String,
|
||||||
@Json(name = "chain_id")
|
@Json(name = "chain_id")
|
||||||
chainId: String,
|
val chainId: String,
|
||||||
data: String?,
|
@Json(name = "data")
|
||||||
memo: String?,
|
val data: String?,
|
||||||
sequence: String,
|
@Json(name = "memo")
|
||||||
source: String,
|
val memo: String?,
|
||||||
msgs: List<Message>,
|
@Json(name = "sequence")
|
||||||
) : WcBinanceOrder<WcBinanceTradeOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
|
val sequence: String,
|
||||||
|
@Json(name = "source")
|
||||||
|
val source: String,
|
||||||
|
@Json(name = "msgs")
|
||||||
|
val msgs: List<Message>,
|
||||||
|
) : WcRequestData {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class MessageKey(val key: String) {
|
enum class MessageKey(val key: String) {
|
||||||
ID("id"),
|
ID("id"),
|
||||||
ORDER_TYPE("ordertype"),
|
ORDER_TYPE("ordertype"),
|
||||||
|
|
@ -30,14 +37,23 @@ class WcBinanceTradeOrder(
|
||||||
TIME_INFORCE("timeinforce"),
|
TIME_INFORCE("timeinforce"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Message(
|
data class Message(
|
||||||
|
@Json(name = "id")
|
||||||
val id: String,
|
val id: String,
|
||||||
|
@Json(name = "orderType")
|
||||||
val orderType: Int,
|
val orderType: Int,
|
||||||
|
@Json(name = "price")
|
||||||
val price: Long,
|
val price: Long,
|
||||||
|
@Json(name = "quantity")
|
||||||
val quantity: Long,
|
val quantity: Long,
|
||||||
|
@Json(name = "sender")
|
||||||
val sender: String,
|
val sender: String,
|
||||||
|
@Json(name = "side")
|
||||||
val side: Int,
|
val side: Int,
|
||||||
|
@Json(name = "symbol")
|
||||||
val symbol: String,
|
val symbol: String,
|
||||||
|
@Json(name = "timeInforce")
|
||||||
val timeInforce: Int,
|
val timeInforce: Int,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,33 +2,48 @@ package com.tangem.tap.domain.walletconnect2.domain.models.binance
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
class WcBinanceTransferOrder(
|
data class WcBinanceTransferOrder(
|
||||||
@Json(name = "account_number")
|
@Json(name = "account_number")
|
||||||
accountNumber: String,
|
val accountNumber: String,
|
||||||
@Json(name = "chain_id")
|
@Json(name = "chain_id")
|
||||||
chainId: String,
|
val chainId: String,
|
||||||
data: String?,
|
@Json(name = "data")
|
||||||
memo: String?,
|
val data: String?,
|
||||||
sequence: String,
|
@Json(name = "memo")
|
||||||
source: String,
|
val memo: String?,
|
||||||
msgs: List<Message>,
|
@Json(name = "sequence")
|
||||||
) : WcBinanceOrder<WcBinanceTransferOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
|
val sequence: String,
|
||||||
|
@Json(name = "source")
|
||||||
|
val source: String,
|
||||||
|
@Json(name = "msgs")
|
||||||
|
val msgs: List<Message>,
|
||||||
|
) : WcRequestData {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Message(
|
data class Message(
|
||||||
|
@Json(name = "inputs")
|
||||||
val inputs: List<Item>,
|
val inputs: List<Item>,
|
||||||
|
@Json(name = "outputs")
|
||||||
val outputs: List<Item>,
|
val outputs: List<Item>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Item(
|
data class Item(
|
||||||
|
@Json(name = "address")
|
||||||
val address: String,
|
val address: String,
|
||||||
|
@Json(name = "coins")
|
||||||
val coins: List<Coin>,
|
val coins: List<Coin>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Coin(
|
data class Coin(
|
||||||
|
@Json(name = "amount")
|
||||||
val amount: Long,
|
val amount: Long,
|
||||||
|
@Json(name = "denom")
|
||||||
val denom: String,
|
val denom: String,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.tangem.tap.domain.walletconnect2.domain.models.binance
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class WcBinanceTxConfirmParam(
|
||||||
|
@Json(name = "ok")
|
||||||
|
val ok: Boolean,
|
||||||
|
@Json(name = "errorMsg")
|
||||||
|
val errorMsg: String?,
|
||||||
|
) : WcRequestData
|
||||||
|
|
@ -4,7 +4,7 @@ import androidx.activity.compose.BackHandler
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.selection.selectable
|
import androidx.compose.foundation.selection.selectable
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -27,13 +27,12 @@ internal fun SettingsScreensScaffold(
|
||||||
addBottomInsets: Boolean = true,
|
addBottomInsets: Boolean = true,
|
||||||
fab: @Composable () -> Unit = {},
|
fab: @Composable () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val state = rememberScaffoldState(snackbarHostState = snackbarHostState)
|
|
||||||
val backgroundColor = TangemTheme.colors.background.secondary
|
val backgroundColor = TangemTheme.colors.background.secondary
|
||||||
|
|
||||||
BackHandler(onBack = onBackClick)
|
BackHandler(onBack = onBackClick)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
scaffoldState = state,
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
EmptyTopBarWithNavigation(
|
EmptyTopBarWithNavigation(
|
||||||
modifier = Modifier.statusBarsPadding(),
|
modifier = Modifier.statusBarsPadding(),
|
||||||
|
|
@ -43,7 +42,7 @@ internal fun SettingsScreensScaffold(
|
||||||
},
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
contentWindowInsets = WindowInsetsZero,
|
contentWindowInsets = WindowInsetsZero,
|
||||||
backgroundColor = backgroundColor,
|
containerColor = backgroundColor,
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
Box(modifier = Modifier.navigationBarsPadding()) {
|
Box(modifier = Modifier.navigationBarsPadding()) {
|
||||||
fab()
|
fab()
|
||||||
|
|
@ -89,6 +88,7 @@ internal fun ScreenTitle(titleRes: Int, modifier: Modifier = Modifier) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun EmptyTopBarWithNavigation(
|
internal fun EmptyTopBarWithNavigation(
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
|
|
@ -108,8 +108,9 @@ internal fun EmptyTopBarWithNavigation(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
backgroundColor = backgroundColor,
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
elevation = 0.dp,
|
containerColor = backgroundColor,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -15,6 +18,7 @@ import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
|
import com.tangem.core.ui.components.progressbar.TangemLinearProgressIndicator
|
||||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.tap.common.analytics.events.Settings
|
import com.tangem.tap.common.analytics.events.Settings
|
||||||
|
|
@ -71,7 +75,7 @@ private fun AddSessionFab(onAddSession: () -> Unit, modifier: Modifier = Modifie
|
||||||
@Composable
|
@Composable
|
||||||
private fun EmptyScreen(state: WalletConnectScreenState) {
|
private fun EmptyScreen(state: WalletConnectScreenState) {
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
LinearProgressIndicator(
|
TangemLinearProgressIndicator(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
color = TangemTheme.colors.icon.accent,
|
color = TangemTheme.colors.icon.accent,
|
||||||
)
|
)
|
||||||
|
|
@ -102,7 +106,7 @@ private fun EmptyScreen(state: WalletConnectScreenState) {
|
||||||
@Composable
|
@Composable
|
||||||
private fun WalletConnectSessions(state: WalletConnectScreenState) {
|
private fun WalletConnectSessions(state: WalletConnectScreenState) {
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
LinearProgressIndicator(
|
TangemLinearProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(2.dp),
|
.height(2.dp),
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.common.util.twinsIsTwinned
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ProductType
|
import com.tangem.domain.models.scan.ProductType
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||||
import com.tangem.domain.wallets.models.UserWallet
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
|
|
@ -42,6 +43,12 @@ object OnboardingHelper {
|
||||||
store.state.globalState.onboardingState.onboardingManager ?: OnboardingManager(response)
|
store.state.globalState.onboardingState.onboardingManager ?: OnboardingManager(response)
|
||||||
val cardId = response.card.cardId
|
val cardId = response.card.cardId
|
||||||
return when {
|
return when {
|
||||||
|
response.cardTypesResolver.isVisaWallet() -> {
|
||||||
|
if (response.visaCardActivationStatus == null) error("Visa card activation status is null")
|
||||||
|
|
||||||
|
response.visaCardActivationStatus !is VisaCardActivationStatus.Activated
|
||||||
|
}
|
||||||
|
|
||||||
response.cardTypesResolver.isTangemTwins() -> {
|
response.cardTypesResolver.isTangemTwins() -> {
|
||||||
if (!response.twinsIsTwinned()) {
|
if (!response.twinsIsTwinned()) {
|
||||||
true
|
true
|
||||||
|
|
@ -66,7 +73,8 @@ object OnboardingHelper {
|
||||||
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
||||||
val newOnboardingSupportTypes = scanResponse.productType == ProductType.Wallet2 ||
|
val newOnboardingSupportTypes = scanResponse.productType == ProductType.Wallet2 ||
|
||||||
scanResponse.productType == ProductType.Ring ||
|
scanResponse.productType == ProductType.Ring ||
|
||||||
scanResponse.productType == ProductType.Wallet // AppRoute.OnboardingOther is also supported
|
scanResponse.productType == ProductType.Wallet ||
|
||||||
|
scanResponse.productType == ProductType.Visa // AppRoute.OnboardingOther is also supported
|
||||||
if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled &&
|
if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled &&
|
||||||
newOnboardingSupportTypes
|
newOnboardingSupportTypes
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import android.content.DialogInterface
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.material.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.material.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,20 @@
|
||||||
package com.tangem.tap.network.auth
|
package com.tangem.tap.network.auth
|
||||||
|
|
||||||
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
|
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
|
||||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||||
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultVisaAuthProvider @Inject constructor(
|
internal class DefaultVisaAuthProvider @Inject constructor(
|
||||||
private val authStorage: VisaAuthTokenStorage,
|
private val userWalletsListManager: UserWalletsListManager,
|
||||||
) : TangemVisaAuthProvider {
|
) : TangemVisaAuthProvider {
|
||||||
|
|
||||||
override suspend fun getAuthHeader(): String {
|
override suspend fun getAuthHeader(cardId: String): String {
|
||||||
return authStorage.get()?.accessToken?.let { "Bearer $it" } ?: "Error in the app!"
|
val card = userWalletsListManager.userWalletsSync.firstOrNull { it.cardId == cardId }
|
||||||
|
val status = card?.scanResponse?.visaCardActivationStatus as? VisaCardActivationStatus.Activated
|
||||||
|
?: return "Error in the app!"
|
||||||
|
val accessToken = status.visaAuthTokens.accessToken
|
||||||
|
|
||||||
|
return "Bearer $accessToken"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.tap.network.exchangeServices.mercuryo
|
package com.tangem.tap.network.exchangeServices.mercuryo
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
|
@ -11,21 +12,25 @@ interface MercuryoApi {
|
||||||
suspend fun currencies(@Path("apiVersion") apiVersion: String): MercuryoCurrenciesResponse
|
suspend fun currencies(@Path("apiVersion") apiVersion: String): MercuryoCurrenciesResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class MercuryoCurrenciesResponse(
|
data class MercuryoCurrenciesResponse(
|
||||||
val status: Int,
|
@Json(name = "status") val status: Int,
|
||||||
val data: Data,
|
@Json(name = "data") val data: Data,
|
||||||
) {
|
) {
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Data(
|
data class Data(
|
||||||
val fiat: List<String>,
|
@Json(name = "fiat") val fiat: List<String>,
|
||||||
val crypto: List<String>,
|
@Json(name = "crypto") val crypto: List<String>,
|
||||||
val config: Config,
|
@Json(name = "config") val config: Config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Config(
|
data class Config(
|
||||||
@Json(name = "crypto_currencies")
|
@Json(name = "crypto_currencies")
|
||||||
val cryptoCurrencies: List<MercuryoCryptoCurrency>,
|
val cryptoCurrencies: List<MercuryoCryptoCurrency>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class MercuryoCryptoCurrency(
|
data class MercuryoCryptoCurrency(
|
||||||
@Json(name = "currency")
|
@Json(name = "currency")
|
||||||
val currencySymbol: String,
|
val currencySymbol: String,
|
||||||
|
|
|
||||||
|
|
@ -149,5 +149,7 @@ internal val Blockchain.mercuryoNetwork: String?
|
||||||
Blockchain.Clore -> null
|
Blockchain.Clore -> null
|
||||||
Blockchain.OdysseyChain, Blockchain.OdysseyChainTestnet -> null
|
Blockchain.OdysseyChain, Blockchain.OdysseyChainTestnet -> null
|
||||||
Blockchain.Bitrock, Blockchain.BitrockTestnet -> null
|
Blockchain.Bitrock, Blockchain.BitrockTestnet -> null
|
||||||
|
Blockchain.Sonic, Blockchain.SonicTestnet -> null
|
||||||
|
Blockchain.ApeChain, Blockchain.ApeChainTestnet -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,9 @@ interface MoonPayApi {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MoonPayUserStatus(
|
data class MoonPayUserStatus(
|
||||||
|
@Json(name = "isBuyAllowed")
|
||||||
val isBuyAllowed: Boolean,
|
val isBuyAllowed: Boolean,
|
||||||
|
@Json(name = "isSellAllowed")
|
||||||
val isSellAllowed: Boolean,
|
val isSellAllowed: Boolean,
|
||||||
@Json(name = "isAllowed")
|
@Json(name = "isAllowed")
|
||||||
val isMoonpayAllowed: Boolean,
|
val isMoonpayAllowed: Boolean,
|
||||||
|
|
@ -34,18 +36,18 @@ data class MoonPayUserStatus(
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MoonPayCurrencies(
|
data class MoonPayCurrencies(
|
||||||
val type: String,
|
@Json(name = "type") val type: String,
|
||||||
val code: String,
|
@Json(name = "code") val code: String,
|
||||||
val supportsLiveMode: Boolean = false,
|
@Json(name = "supportsLiveMode") val supportsLiveMode: Boolean = false,
|
||||||
val isSuspended: Boolean = true,
|
@Json(name = "isSuspended") val isSuspended: Boolean = true,
|
||||||
val isSupportedInUS: Boolean = false,
|
@Json(name = "isSupportedInUS") val isSupportedInUS: Boolean = false,
|
||||||
val isSellSupported: Boolean = false,
|
@Json(name = "isSellSupported") val isSellSupported: Boolean = false,
|
||||||
val notAllowedUSStates: List<String> = emptyList(),
|
@Json(name = "notAllowedUSStates") val notAllowedUSStates: List<String> = emptyList(),
|
||||||
val metadata: MoonPayCurrenciesMetadata? = null,
|
@Json(name = "metadata") val metadata: MoonPayCurrenciesMetadata? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MoonPayCurrenciesMetadata(
|
data class MoonPayCurrenciesMetadata(
|
||||||
val contractAddress: String?,
|
@Json(name = "contractAddress") val contractAddress: String?,
|
||||||
val networkCode: String?,
|
@Json(name = "networkCode") val networkCode: String?,
|
||||||
)
|
)
|
||||||
|
|
@ -150,4 +150,6 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
|
||||||
VanarChainTestnet -> null
|
VanarChainTestnet -> null
|
||||||
OdysseyChain, OdysseyChainTestnet -> null
|
OdysseyChain, OdysseyChainTestnet -> null
|
||||||
Bitrock, BitrockTestnet -> null
|
Bitrock, BitrockTestnet -> null
|
||||||
|
Sonic, SonicTestnet -> null
|
||||||
|
ApeChain, ApeChainTestnet -> null
|
||||||
}
|
}
|
||||||
17
app/src/main/res/drawable/progress_rounded_shape.xml
Normal file
17
app/src/main/res/drawable/progress_rounded_shape.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@android:id/background">
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="4dp"/>
|
||||||
|
<solid android:color="@color/icon_informative" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item android:id="@android:id/progress">
|
||||||
|
<scale android:scaleWidth="100%">
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<solid android:color="@color/icon_primary_1" />
|
||||||
|
</shape>
|
||||||
|
</scale>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
|
|
@ -4,9 +4,10 @@
|
||||||
android:id="@+id/pb_state"
|
android:id="@+id/pb_state"
|
||||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="20dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:progressBackgroundTint="@color/icon_informative"
|
android:paddingVertical="8dp"
|
||||||
android:progressTint="@color/icon_primary_1" />
|
android:indeterminate="false"
|
||||||
|
android:progressDrawable="@drawable/progress_rounded_shape" />
|
||||||
</merge>
|
</merge>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.tangem.common.timemeasure
|
||||||
|
|
||||||
|
import android.os.SystemClock
|
||||||
|
import kotlin.time.AbstractLongTimeSource
|
||||||
|
import kotlin.time.DurationUnit
|
||||||
|
|
||||||
|
object RealtimeMonotonicTimeSource : AbstractLongTimeSource(DurationUnit.NANOSECONDS) {
|
||||||
|
override fun read(): Long = SystemClock.elapsedRealtimeNanos()
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,6 @@ import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarkerValueForma
|
||||||
import com.patrykandpatrick.vico.core.cartesian.marker.DefaultCartesianMarker
|
import com.patrykandpatrick.vico.core.cartesian.marker.DefaultCartesianMarker
|
||||||
import com.patrykandpatrick.vico.core.common.Dimensions
|
import com.patrykandpatrick.vico.core.common.Dimensions
|
||||||
import com.patrykandpatrick.vico.core.common.LayeredComponent
|
import com.patrykandpatrick.vico.core.common.LayeredComponent
|
||||||
import com.patrykandpatrick.vico.core.common.component.Component
|
|
||||||
import com.patrykandpatrick.vico.core.common.component.TextComponent
|
import com.patrykandpatrick.vico.core.common.component.TextComponent
|
||||||
import com.patrykandpatrick.vico.core.common.shape.Shape
|
import com.patrykandpatrick.vico.core.common.shape.Shape
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
@ -39,7 +38,28 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
|
||||||
|
|
||||||
object : DefaultCartesianMarker(
|
object : DefaultCartesianMarker(
|
||||||
label = TextComponent(textSizeSp = 0f),
|
label = TextComponent(textSizeSp = 0f),
|
||||||
indicator = ::indicator,
|
indicator = { color ->
|
||||||
|
val composeColor = Color(color)
|
||||||
|
|
||||||
|
LayeredComponent(
|
||||||
|
rear = shapeComponent(
|
||||||
|
color = composeColor.copy(alpha = INDICATOR_REAR_COLOR_ALPHA),
|
||||||
|
shape = Shape.Pill,
|
||||||
|
),
|
||||||
|
front = LayeredComponent(
|
||||||
|
rear = shapeComponent(
|
||||||
|
color = composeColor,
|
||||||
|
shape = Shape.Pill,
|
||||||
|
),
|
||||||
|
front = shapeComponent(
|
||||||
|
color = Color.White,
|
||||||
|
shape = Shape.Pill,
|
||||||
|
),
|
||||||
|
padding = indicatorPadding,
|
||||||
|
),
|
||||||
|
padding = indicatorPadding,
|
||||||
|
)
|
||||||
|
},
|
||||||
indicatorSizeDp = INDICATOR_SIZE_DP,
|
indicatorSizeDp = INDICATOR_SIZE_DP,
|
||||||
guideline = guideline,
|
guideline = guideline,
|
||||||
valueFormatter = object : CartesianMarkerValueFormatter {
|
valueFormatter = object : CartesianMarkerValueFormatter {
|
||||||
|
|
@ -81,29 +101,6 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun indicator(color: Int): Component {
|
|
||||||
val composeColor = Color(color)
|
|
||||||
|
|
||||||
return LayeredComponent(
|
|
||||||
rear = shapeComponent(
|
|
||||||
color = composeColor.copy(alpha = INDICATOR_REAR_COLOR_ALPHA),
|
|
||||||
shape = Shape.Pill,
|
|
||||||
),
|
|
||||||
front = LayeredComponent(
|
|
||||||
rear = shapeComponent(
|
|
||||||
color = composeColor,
|
|
||||||
shape = Shape.Pill,
|
|
||||||
),
|
|
||||||
front = shapeComponent(
|
|
||||||
color = Color.White,
|
|
||||||
shape = Shape.Pill,
|
|
||||||
),
|
|
||||||
padding = indicatorPadding,
|
|
||||||
),
|
|
||||||
padding = indicatorPadding,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val indicatorPadding = Dimensions.of(3.dp)
|
private val indicatorPadding = Dimensions.of(3.dp)
|
||||||
private const val LABEL_BACKGROUND_SHADOW_RADIUS_DP = 4f
|
private const val LABEL_BACKGROUND_SHADOW_RADIUS_DP = 4f
|
||||||
private const val LABEL_BACKGROUND_SHADOW_DY_DP = 2f
|
private const val LABEL_BACKGROUND_SHADOW_DY_DP = 2f
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(deps.plugins.android.library)
|
alias(deps.plugins.android.library)
|
||||||
alias(deps.plugins.kotlin.android)
|
alias(deps.plugins.kotlin.android)
|
||||||
|
|
@ -11,7 +13,6 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
/** DI */
|
/** DI */
|
||||||
implementation(deps.hilt.android)
|
implementation(deps.hilt.android)
|
||||||
kapt(deps.hilt.kapt)
|
kapt(deps.hilt.kapt)
|
||||||
|
|
@ -23,6 +24,7 @@ dependencies {
|
||||||
implementation(deps.moshi)
|
implementation(deps.moshi)
|
||||||
implementation(deps.moshi.kotlin)
|
implementation(deps.moshi.kotlin)
|
||||||
implementation(deps.timber)
|
implementation(deps.timber)
|
||||||
|
kaptForObfuscatingVariants(deps.moshi.kotlin.codegen)
|
||||||
|
|
||||||
/** Core modules */
|
/** Core modules */
|
||||||
implementation(projects.core.datasource)
|
implementation(projects.core.datasource)
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,13 @@
|
||||||
{
|
{
|
||||||
"name": "vanar-chain",
|
"name": "vanar-chain",
|
||||||
"version": "undefined"
|
"version": "undefined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sonic",
|
||||||
|
"version": "undefined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apechain",
|
||||||
|
"version": "undefined"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -21,11 +21,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ONBOARDING_CODE_REFACTORING_ENABLED",
|
"name": "ONBOARDING_CODE_REFACTORING_ENABLED",
|
||||||
"version": "undefined"
|
"version": "5.21.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "NAVIGATION_REFACTORING",
|
"name": "NAVIGATION_REFACTORING",
|
||||||
"version": "undefined"
|
"version": "5.21.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "SWAP_STORIES_ENABLED",
|
"name": "SWAP_STORIES_ENABLED",
|
||||||
|
|
@ -34,5 +34,9 @@
|
||||||
{
|
{
|
||||||
"name": "VISA_ONBOARDING_ENABLED",
|
"name": "VISA_ONBOARDING_ENABLED",
|
||||||
"version": "undefined"
|
"version": "undefined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ONRAMP_HOT_TOKENS_ENABLED",
|
||||||
|
"version": "undefined"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(deps.plugins.android.library)
|
alias(deps.plugins.android.library)
|
||||||
alias(deps.plugins.kotlin.android)
|
alias(deps.plugins.kotlin.android)
|
||||||
|
|
@ -29,6 +31,7 @@ dependencies {
|
||||||
implementation(projects.domain.staking.models)
|
implementation(projects.domain.staking.models)
|
||||||
implementation(projects.domain.onramp.models)
|
implementation(projects.domain.onramp.models)
|
||||||
implementation(projects.domain.models)
|
implementation(projects.domain.models)
|
||||||
|
implementation(projects.features.onramp.api)
|
||||||
|
|
||||||
/** Tangem libraries */
|
/** Tangem libraries */
|
||||||
implementation(deps.tangem.blockchain)
|
implementation(deps.tangem.blockchain)
|
||||||
|
|
@ -53,6 +56,8 @@ dependencies {
|
||||||
implementation(deps.okHttp.prettyLogging)
|
implementation(deps.okHttp.prettyLogging)
|
||||||
implementation(deps.retrofit)
|
implementation(deps.retrofit)
|
||||||
implementation(deps.retrofit.moshi)
|
implementation(deps.retrofit.moshi)
|
||||||
|
kaptForObfuscatingVariants(deps.moshi.kotlin.codegen)
|
||||||
|
kaptForObfuscatingVariants(deps.retrofit.response.type.keeper)
|
||||||
|
|
||||||
/** Time */
|
/** Time */
|
||||||
implementation(deps.jodatime)
|
implementation(deps.jodatime)
|
||||||
|
|
@ -62,6 +67,7 @@ dependencies {
|
||||||
|
|
||||||
/** Chucker */
|
/** Chucker */
|
||||||
debugImplementation(deps.chucker)
|
debugImplementation(deps.chucker)
|
||||||
|
debugPGImplementation(deps.chucker)
|
||||||
mockedImplementation(deps.chuckerStub)
|
mockedImplementation(deps.chuckerStub)
|
||||||
externalImplementation(deps.chuckerStub)
|
externalImplementation(deps.chuckerStub)
|
||||||
internalImplementation(deps.chuckerStub)
|
internalImplementation(deps.chuckerStub)
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ sealed class ApiConfig {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal const val DEBUG_BUILD_TYPE = "debug"
|
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 INTERNAL_BUILD_TYPE = "internal"
|
||||||
internal const val MOCKED_BUILD_TYPE = "mocked"
|
internal const val MOCKED_BUILD_TYPE = "mocked"
|
||||||
internal const val EXTERNAL_BUILD_TYPE = "external"
|
internal const val EXTERNAL_BUILD_TYPE = "external"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
package com.tangem.datasource.api.common.config
|
package com.tangem.datasource.api.common.config
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Api environment
|
* Api environment
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class ApiEnvironment {
|
enum class ApiEnvironment {
|
||||||
DEV, STAGE, PROD
|
@Json(name = "DEV")
|
||||||
|
DEV,
|
||||||
|
|
||||||
|
@Json(name = "STAGE")
|
||||||
|
STAGE,
|
||||||
|
|
||||||
|
@Json(name = "PROD")
|
||||||
|
PROD,
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +68,9 @@ internal class Express(
|
||||||
|
|
||||||
fun getInitialEnvironment(): ApiEnvironment {
|
fun getInitialEnvironment(): ApiEnvironment {
|
||||||
return when (BuildConfig.BUILD_TYPE) {
|
return when (BuildConfig.BUILD_TYPE) {
|
||||||
DEBUG_BUILD_TYPE -> ApiEnvironment.DEV
|
DEBUG_BUILD_TYPE,
|
||||||
|
DEBUG_PG_BUILD_TYPE,
|
||||||
|
-> ApiEnvironment.DEV
|
||||||
INTERNAL_BUILD_TYPE,
|
INTERNAL_BUILD_TYPE,
|
||||||
MOCKED_BUILD_TYPE,
|
MOCKED_BUILD_TYPE,
|
||||||
-> ApiEnvironment.STAGE
|
-> ApiEnvironment.STAGE
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
package com.tangem.datasource.api.common.config
|
package com.tangem.datasource.api.common.config
|
||||||
|
|
||||||
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
|
|
||||||
import com.tangem.utils.ProviderSuspend
|
import com.tangem.utils.ProviderSuspend
|
||||||
|
|
||||||
internal class TangemVisa(
|
internal class TangemVisa : ApiConfig() {
|
||||||
private val authProvider: TangemVisaAuthProvider,
|
|
||||||
) : ApiConfig() {
|
|
||||||
|
|
||||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
||||||
|
|
||||||
|
|
@ -19,7 +16,5 @@ internal class TangemVisa(
|
||||||
headers = createHeaders(),
|
headers = createHeaders(),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun createHeaders() = mapOf(
|
private fun createHeaders() = mapOf<String, ProviderSuspend<String>>()
|
||||||
"Authorization" to ProviderSuspend { authProvider.getAuthHeader() },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,5 +2,5 @@ package com.tangem.datasource.api.common.visa
|
||||||
|
|
||||||
interface TangemVisaAuthProvider {
|
interface TangemVisaAuthProvider {
|
||||||
|
|
||||||
suspend fun getAuthHeader(): String
|
suspend fun getAuthHeader(cardId: String): String
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.express.models.request
|
package com.tangem.datasource.api.express.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class AssetsRequestBody(
|
data class AssetsRequestBody(
|
||||||
@Json(name = "tokensList") val tokensList: List<LeastTokenInfo>?,
|
@Json(name = "tokensList") val tokensList: List<LeastTokenInfo>?,
|
||||||
)
|
)
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.express.models.request
|
package com.tangem.datasource.api.express.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExchangeSentRequestBody(
|
data class ExchangeSentRequestBody(
|
||||||
@Json(name = "txId")
|
@Json(name = "txId")
|
||||||
val txId: String,
|
val txId: String,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.express.models.request
|
package com.tangem.datasource.api.express.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class LeastTokenInfo(
|
data class LeastTokenInfo(
|
||||||
@Json(name = "contractAddress")
|
@Json(name = "contractAddress")
|
||||||
val contractAddress: String,
|
val contractAddress: String,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.express.models.request
|
package com.tangem.datasource.api.express.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class PairsRequestBody(
|
data class PairsRequestBody(
|
||||||
@Json(name = "from")
|
@Json(name = "from")
|
||||||
val from: List<LeastTokenInfo>,
|
val from: List<LeastTokenInfo>,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.express.models.response
|
package com.tangem.datasource.api.express.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Asset(
|
data class Asset(
|
||||||
@Json(name = "contractAddress")
|
@Json(name = "contractAddress")
|
||||||
val contractAddress: String,
|
val contractAddress: String,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
package com.tangem.datasource.api.express.models.response
|
package com.tangem.datasource.api.express.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExchangeDataResponseWithTxDetails(
|
data class ExchangeDataResponseWithTxDetails(
|
||||||
|
@Json(name = "dataResponse")
|
||||||
val dataResponse: ExchangeDataResponse,
|
val dataResponse: ExchangeDataResponse,
|
||||||
|
@Json(name = "txDetails")
|
||||||
val txDetails: TxDetails,
|
val txDetails: TxDetails,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExchangeDataResponse(
|
data class ExchangeDataResponse(
|
||||||
@Json(name = "fromAmount")
|
@Json(name = "fromAmount")
|
||||||
val fromAmount: String,
|
val fromAmount: String,
|
||||||
|
|
@ -29,6 +35,7 @@ data class ExchangeDataResponse(
|
||||||
val signature: String,
|
val signature: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class TxDetails(
|
data class TxDetails(
|
||||||
@Json(name = "payoutAddress")
|
@Json(name = "payoutAddress")
|
||||||
val payoutAddress: String,
|
val payoutAddress: String,
|
||||||
|
|
@ -70,6 +77,7 @@ data class TxDetails(
|
||||||
val gas: String?,
|
val gas: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class TxType {
|
enum class TxType {
|
||||||
@Json(name = "send")
|
@Json(name = "send")
|
||||||
SEND,
|
SEND,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.datasource.api.express.models.response
|
package com.tangem.datasource.api.express.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExchangeProvider(
|
data class ExchangeProvider(
|
||||||
@Json(name = "id")
|
@Json(name = "id")
|
||||||
val id: String,
|
val id: String,
|
||||||
|
|
@ -32,6 +34,7 @@ data class ExchangeProvider(
|
||||||
val slippage: BigDecimal?,
|
val slippage: BigDecimal?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class ExchangeProviderType {
|
enum class ExchangeProviderType {
|
||||||
@Json(name = "dex")
|
@Json(name = "dex")
|
||||||
DEX,
|
DEX,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.datasource.api.express.models.response
|
package com.tangem.datasource.api.express.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExchangeQuoteResponse(
|
data class ExchangeQuoteResponse(
|
||||||
|
|
||||||
@Json(name = "fromAmount")
|
@Json(name = "fromAmount")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.express.models.response
|
package com.tangem.datasource.api.express.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExchangeSentResponseBody(
|
data class ExchangeSentResponseBody(
|
||||||
@Json(name = "txId")
|
@Json(name = "txId")
|
||||||
val txId: String,
|
val txId: String,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.express.models.response
|
package com.tangem.datasource.api.express.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExchangeStatusResponse(
|
data class ExchangeStatusResponse(
|
||||||
|
|
||||||
@Json(name = "providerId")
|
@Json(name = "providerId")
|
||||||
|
|
@ -26,6 +28,7 @@ data class ExchangeStatusResponse(
|
||||||
val refundContractAddress: String? = null,
|
val refundContractAddress: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class ExchangeStatus {
|
enum class ExchangeStatus {
|
||||||
|
|
||||||
@Json(name = "new")
|
@Json(name = "new")
|
||||||
|
|
@ -71,6 +74,7 @@ enum class ExchangeStatus {
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExchangeStatusError(
|
data class ExchangeStatusError(
|
||||||
@Json(name = "code")
|
@Json(name = "code")
|
||||||
val code: Int,
|
val code: Int,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
package com.tangem.datasource.api.express.models.response
|
package com.tangem.datasource.api.express.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExpressErrorResponse(
|
data class ExpressErrorResponse(
|
||||||
@Json(name = "error")
|
@Json(name = "error")
|
||||||
val error: ExpressError,
|
val error: ExpressError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExpressError(
|
data class ExpressError(
|
||||||
@Json(name = "code")
|
@Json(name = "code")
|
||||||
val code: Int,
|
val code: Int,
|
||||||
|
|
@ -19,6 +22,7 @@ data class ExpressError(
|
||||||
val value: ExpressErrorValue?,
|
val value: ExpressErrorValue?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExpressErrorValue(
|
data class ExpressErrorValue(
|
||||||
@Json(name = "minAmount")
|
@Json(name = "minAmount")
|
||||||
val minAmount: String?,
|
val minAmount: String?,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.datasource.api.express.models.response
|
package com.tangem.datasource.api.express.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class SwapPair(
|
data class SwapPair(
|
||||||
@Json(name = "from")
|
@Json(name = "from")
|
||||||
val from: LeastTokenInfo,
|
val from: LeastTokenInfo,
|
||||||
|
|
@ -15,6 +17,7 @@ data class SwapPair(
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class SwapPairProvider(
|
data class SwapPairProvider(
|
||||||
@Json(name = "providerId")
|
@Json(name = "providerId")
|
||||||
val providerId: String,
|
val providerId: String,
|
||||||
|
|
@ -23,6 +26,7 @@ data class SwapPairProvider(
|
||||||
val rateTypes: List<RateType>,
|
val rateTypes: List<RateType>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class RateType {
|
enum class RateType {
|
||||||
@Json(name = "float")
|
@Json(name = "float")
|
||||||
FLOAT,
|
FLOAT,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@ package com.tangem.datasource.api.onramp.models.response
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class OnrampDataResponseWithTxDetails(
|
data class OnrampDataResponseWithTxDetails(
|
||||||
|
@Json(name = "dataResponse")
|
||||||
val dataResponse: OnrampDataResponse,
|
val dataResponse: OnrampDataResponse,
|
||||||
|
@Json(name = "txDetails")
|
||||||
val txDetails: OnrampTxDetails,
|
val txDetails: OnrampTxDetails,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ data class OnrampStatusResponse(
|
||||||
val countryCode: String,
|
val countryCode: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class Status {
|
enum class Status {
|
||||||
@Json(name = "created")
|
@Json(name = "created")
|
||||||
Created,
|
Created,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.request
|
package com.tangem.datasource.api.stakekit.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
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.request.ConstructTransactionRequestBody.GasArgs
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class PendingActionRequestBody(
|
data class PendingActionRequestBody(
|
||||||
@Json(name = "type")
|
@Json(name = "type")
|
||||||
val type: StakingActionType,
|
val type: StakingActionType,
|
||||||
|
|
@ -19,6 +21,7 @@ data class PendingActionRequestBody(
|
||||||
val gasArgs: GasArgs? = null, // used only in estimate_gas request
|
val gasArgs: GasArgs? = null, // used only in estimate_gas request
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ActionRequestBody(
|
data class ActionRequestBody(
|
||||||
@Json(name = "integrationId")
|
@Json(name = "integrationId")
|
||||||
val integrationId: String,
|
val integrationId: String,
|
||||||
|
|
@ -32,6 +35,7 @@ data class ActionRequestBody(
|
||||||
val gasArgs: GasArgs? = null, // used only in estimate_gas request
|
val gasArgs: GasArgs? = null, // used only in estimate_gas request
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ActionRequestBodyArgs(
|
data class ActionRequestBodyArgs(
|
||||||
@Json(name = "amount")
|
@Json(name = "amount")
|
||||||
val amount: String,
|
val amount: String,
|
||||||
|
|
@ -55,6 +59,7 @@ data class ActionRequestBodyArgs(
|
||||||
val inputToken: TokenDTO? = null,
|
val inputToken: TokenDTO? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class SignatureVerification(
|
data class SignatureVerification(
|
||||||
@Json(name = "message")
|
@Json(name = "message")
|
||||||
val message: String,
|
val message: String,
|
||||||
|
|
@ -62,6 +67,7 @@ data class SignatureVerification(
|
||||||
val signed: String,
|
val signed: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class TronResource {
|
enum class TronResource {
|
||||||
@Json(name = "ENERGY")
|
@Json(name = "ENERGY")
|
||||||
ENERGY,
|
ENERGY,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.request
|
package com.tangem.datasource.api.stakekit.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Address(
|
data class Address(
|
||||||
@Json(name = "address")
|
@Json(name = "address")
|
||||||
val address: String,
|
val address: String,
|
||||||
|
|
@ -11,6 +13,7 @@ data class Address(
|
||||||
val explorerUrl: String? = null,
|
val explorerUrl: String? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class AdditionalAddresses(
|
data class AdditionalAddresses(
|
||||||
// cosmos-specific
|
// cosmos-specific
|
||||||
@Json(name = "cosmosPubKey")
|
@Json(name = "cosmosPubKey")
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.request
|
package com.tangem.datasource.api.stakekit.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ConstructTransactionRequestBody(
|
data class ConstructTransactionRequestBody(
|
||||||
@Json(name = "gasArgs")
|
@Json(name = "gasArgs")
|
||||||
val gasArgs: GasArgs? = null,
|
val gasArgs: GasArgs? = null,
|
||||||
@Json(name = "ledgerWalletAPICompatible")
|
@Json(name = "ledgerWalletAPICompatible")
|
||||||
val ledgerWalletAPICompatible: Boolean? = null,
|
val ledgerWalletAPICompatible: Boolean? = null,
|
||||||
) {
|
) {
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class GasArgs(
|
data class GasArgs(
|
||||||
// cosmos-specific
|
// cosmos-specific
|
||||||
@Json(name = "gasPrice")
|
@Json(name = "gasPrice")
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,23 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.request
|
package com.tangem.datasource.api.stakekit.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class MultipleYieldBalancesRequestBody(
|
data class MultipleYieldBalancesRequestBody(
|
||||||
@Json(name = "addresses") val addresses: Address,
|
@Json(name = "addresses") val addresses: Address,
|
||||||
@Json(name = "args") val args: MultipleYieldBalancesRequestArgs,
|
@Json(name = "args") val args: MultipleYieldBalancesRequestArgs,
|
||||||
@Json(name = "integrationId") val integrationId: String,
|
@Json(name = "integrationId") val integrationId: String,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Address(
|
data class Address(
|
||||||
@Json(name = "address") val address: String,
|
@Json(name = "address") val address: String,
|
||||||
@Json(name = "additionalAddresses") val additionalAddresses: AdditionalAddresses? = null,
|
@Json(name = "additionalAddresses") val additionalAddresses: AdditionalAddresses? = null,
|
||||||
@Json(name = "explorerUrl") val explorerUrl: String,
|
@Json(name = "explorerUrl") val explorerUrl: String,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class AdditionalAddresses(
|
data class AdditionalAddresses(
|
||||||
@Json(name = "cosmosPubKey") val cosmosPubKey: String? = null,
|
@Json(name = "cosmosPubKey") val cosmosPubKey: String? = null,
|
||||||
@Json(name = "binanceBeaconAddress") val binanceBeaconAddress: String? = null,
|
@Json(name = "binanceBeaconAddress") val binanceBeaconAddress: String? = null,
|
||||||
|
|
@ -25,6 +29,7 @@ data class MultipleYieldBalancesRequestBody(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class MultipleYieldBalancesRequestArgs(
|
data class MultipleYieldBalancesRequestArgs(
|
||||||
@Json(name = "validatorAddresses") val validatorAddresses: List<String>,
|
@Json(name = "validatorAddresses") val validatorAddresses: List<String>,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.request
|
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) {
|
enum class RevenueOption(val value: String) {
|
||||||
SUPPORTS_FEE("supportsFee"),
|
@Json(name = "SUPPORTS_FEE") SUPPORTS_FEE("supportsFee"),
|
||||||
SUPPORTS_REV_SHARE("supportsRevShare"),
|
|
||||||
|
@Json(name = "SUPPORTS_REV_SHARE") SUPPORTS_REV_SHARE("supportsRevShare"),
|
||||||
;
|
;
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.request
|
package com.tangem.datasource.api.stakekit.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class SubmitTransactionHashRequestBody(
|
data class SubmitTransactionHashRequestBody(
|
||||||
@Json(name = "hash")
|
@Json(name = "hash")
|
||||||
val hash: String,
|
val hash: String,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.request
|
package com.tangem.datasource.api.stakekit.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class YieldBalanceRequestBody(
|
data class YieldBalanceRequestBody(
|
||||||
@Json(name = "addresses") val addresses: Address,
|
@Json(name = "addresses") val addresses: Address,
|
||||||
@Json(name = "args") val args: YieldBalanceRequestArgs,
|
@Json(name = "args") val args: YieldBalanceRequestArgs,
|
||||||
@Json(name = "integrationId") val integrationId: String,
|
@Json(name = "integrationId") val integrationId: String,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class YieldBalanceRequestArgs(
|
data class YieldBalanceRequestArgs(
|
||||||
@Json(name = "validatorAddresses") val validatorAddresses: List<String>,
|
@Json(name = "validatorAddresses") val validatorAddresses: List<String>,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.response.model
|
package com.tangem.datasource.api.stakekit.models.response.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class NetworkTypeDTO {
|
enum class NetworkTypeDTO {
|
||||||
@Json(name = "avalanche-c")
|
@Json(name = "avalanche-c")
|
||||||
AVALANCHE_C,
|
AVALANCHE_C,
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ data class BalanceDTO(
|
||||||
val providerId: String?,
|
val providerId: String?,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class BalanceTypeDTO {
|
enum class BalanceTypeDTO {
|
||||||
@Json(name = "available")
|
@Json(name = "available")
|
||||||
AVAILABLE,
|
AVAILABLE,
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ data class YieldDTO(
|
||||||
@Json(name = "preferred")
|
@Json(name = "preferred")
|
||||||
val preferred: Boolean?,
|
val preferred: Boolean?,
|
||||||
) {
|
) {
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = false)
|
||||||
enum class ValidatorStatusDTO {
|
enum class ValidatorStatusDTO {
|
||||||
@Json(name = "active")
|
@Json(name = "active")
|
||||||
ACTIVE,
|
ACTIVE,
|
||||||
|
|
@ -153,6 +153,7 @@ data class YieldDTO(
|
||||||
val enabled: Boolean?,
|
val enabled: Boolean?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class RewardScheduleDTO {
|
enum class RewardScheduleDTO {
|
||||||
@Json(name = "block")
|
@Json(name = "block")
|
||||||
BLOCK,
|
BLOCK,
|
||||||
|
|
@ -178,6 +179,7 @@ data class YieldDTO(
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class RewardClaimingDTO {
|
enum class RewardClaimingDTO {
|
||||||
@Json(name = "auto")
|
@Json(name = "auto")
|
||||||
AUTO,
|
AUTO,
|
||||||
|
|
@ -189,6 +191,7 @@ data class YieldDTO(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class RewardTypeDTO {
|
enum class RewardTypeDTO {
|
||||||
@Json(name = "apy")
|
@Json(name = "apy")
|
||||||
APY, // compound rate
|
APY, // compound rate
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.response.model.action
|
package com.tangem.datasource.api.stakekit.models.response.model.action
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class StakingActionStatusDTO {
|
enum class StakingActionStatusDTO {
|
||||||
@Json(name = "CANCELED")
|
@Json(name = "CANCELED")
|
||||||
CANCELED,
|
CANCELED,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.response.model.action
|
package com.tangem.datasource.api.stakekit.models.response.model.action
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class StakingActionTypeDTO {
|
enum class StakingActionTypeDTO {
|
||||||
@Json(name = "STAKE")
|
@Json(name = "STAKE")
|
||||||
STAKE,
|
STAKE,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.response.model.error
|
package com.tangem.datasource.api.stakekit.models.response.model.error
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
class StakeKitErrorResponse(
|
class StakeKitErrorResponse(
|
||||||
@Json(name = "message")
|
@Json(name = "message")
|
||||||
val message: String? = null,
|
val message: String? = null,
|
||||||
|
|
@ -26,6 +28,7 @@ class StakeKitErrorResponse(
|
||||||
val tags: List<String>? = null,
|
val tags: List<String>? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class StakeKitErrorDetailsDTO(
|
data class StakeKitErrorDetailsDTO(
|
||||||
@Json(name = "arguments")
|
@Json(name = "arguments")
|
||||||
val arguments: String? = null,
|
val arguments: String? = null,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.response.model.transaction
|
package com.tangem.datasource.api.stakekit.models.response.model.transaction
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class StakingTransactionStatusDTO {
|
enum class StakingTransactionStatusDTO {
|
||||||
@Json(name = "NOT_FOUND")
|
@Json(name = "NOT_FOUND")
|
||||||
NOT_FOUND,
|
NOT_FOUND,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.stakekit.models.response.model.transaction
|
package com.tangem.datasource.api.stakekit.models.response.model.transaction
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class StakingTransactionTypeDTO {
|
enum class StakingTransactionTypeDTO {
|
||||||
@Json(name = "SWAP")
|
@Json(name = "SWAP")
|
||||||
SWAP,
|
SWAP,
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,9 @@ interface TangemTechApi {
|
||||||
@Body body: SeedPhraseNotificationDTO,
|
@Body body: SeedPhraseNotificationDTO,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
|
@GET("hot_crypto")
|
||||||
|
suspend fun getHotCrypto(@Query("currency") currencyId: String): ApiResponse<HotCryptoResponse>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val marketsQuoteFields = listOf(
|
val marketsQuoteFields = listOf(
|
||||||
"price",
|
"price",
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class CoinsResponse(
|
data class CoinsResponse(
|
||||||
@Json(name = "imageHost") val imageHost: String?,
|
@Json(name = "imageHost") val imageHost: String?,
|
||||||
@Json(name = "coins") val coins: List<Coin>,
|
@Json(name = "coins") val coins: List<Coin>,
|
||||||
@Json(name = "total") val total: Int,
|
@Json(name = "total") val total: Int,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Coin(
|
data class Coin(
|
||||||
@Json(name = "id") val id: String,
|
@Json(name = "id") val id: String,
|
||||||
@Json(name = "name") val name: String,
|
@Json(name = "name") val name: String,
|
||||||
|
|
@ -17,6 +20,7 @@ data class CoinsResponse(
|
||||||
@Json(name = "networks") val networks: List<Network> = listOf(),
|
@Json(name = "networks") val networks: List<Network> = listOf(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Network(
|
data class Network(
|
||||||
@Json(name = "networkId") val networkId: String,
|
@Json(name = "networkId") val networkId: String,
|
||||||
@Json(name = "contractAddress") val contractAddress: String? = null,
|
@Json(name = "contractAddress") val contractAddress: String? = null,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class CreateUserTokensAccountBody(
|
data class CreateUserTokensAccountBody(
|
||||||
@Json(name = "accountId")
|
@Json(name = "accountId")
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ data class CurrenciesResponse(
|
||||||
@Json(name = "rateBTC") val rateBTC: String,
|
@Json(name = "rateBTC") val rateBTC: String,
|
||||||
@Json(name = "unit") val unit: String, // $, €, ₽
|
@Json(name = "unit") val unit: String, // $, €, ₽
|
||||||
@Json(name = "type") val type: String,
|
@Json(name = "type") val type: String,
|
||||||
val iconSmallUrl: String? = null,
|
@Json(name = "iconSmallUrl") val iconSmallUrl: String? = null,
|
||||||
val iconMediumUrl: String? = null,
|
@Json(name = "iconMediumUrl") val iconMediumUrl: String? = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class FeaturesResponse(
|
data class FeaturesResponse(
|
||||||
@Json(name = "send") val isNewSendEnabled: Boolean,
|
@Json(name = "send") val isNewSendEnabled: Boolean,
|
||||||
)
|
)
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class QuotesResponse(
|
data class QuotesResponse(
|
||||||
@Json(name = "quotes")
|
@Json(name = "quotes")
|
||||||
val quotes: Map<String, Quote>,
|
val quotes: Map<String, Quote>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Quote(
|
data class Quote(
|
||||||
@Json(name = "price")
|
@Json(name = "price")
|
||||||
val price: BigDecimal?,
|
val price: BigDecimal?,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
// rates.keys = networkId's
|
// rates.keys = networkId's
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class RatesResponse(
|
data class RatesResponse(
|
||||||
@Json(name = "rates") val rates: Map<String, Double>,
|
@Json(name = "rates") val rates: Map<String, Double>,
|
||||||
)
|
)
|
||||||
|
|
@ -1,30 +1,36 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import org.joda.time.LocalDate
|
import org.joda.time.LocalDate
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ReferralResponse(
|
data class ReferralResponse(
|
||||||
@Json(name = "conditions") val conditions: Conditions,
|
@Json(name = "conditions") val conditions: Conditions,
|
||||||
@Json(name = "referral") val referral: Referral?,
|
@Json(name = "referral") val referral: Referral?,
|
||||||
@Json(name = "expectedAwards") val expectedAwards: ExpectedAwards?,
|
@Json(name = "expectedAwards") val expectedAwards: ExpectedAwards?,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Conditions(
|
data class Conditions(
|
||||||
@Json(name = "discount") val discount: Discount,
|
@Json(name = "discount") val discount: Discount,
|
||||||
@Json(name = "awards") val awards: List<Award>,
|
@Json(name = "awards") val awards: List<Award>,
|
||||||
@Json(name = "tosLink") val tosLink: String,
|
@Json(name = "tosLink") val tosLink: String,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Discount(
|
data class Discount(
|
||||||
@Json(name = "amount") val amount: Int,
|
@Json(name = "amount") val amount: Int,
|
||||||
@Json(name = "type") val discountType: String,
|
@Json(name = "type") val discountType: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Award(
|
data class Award(
|
||||||
@Json(name = "amount") val amount: Int,
|
@Json(name = "amount") val amount: Int,
|
||||||
@Json(name = "token") val token: Token,
|
@Json(name = "token") val token: Token,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Token(
|
data class Token(
|
||||||
@Json(name = "id") val id: String,
|
@Json(name = "id") val id: String,
|
||||||
@Json(name = "name") val name: String,
|
@Json(name = "name") val name: String,
|
||||||
|
|
@ -36,6 +42,7 @@ data class ReferralResponse(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Referral(
|
data class Referral(
|
||||||
@Json(name = "shareLink") val shareLink: String,
|
@Json(name = "shareLink") val shareLink: String,
|
||||||
@Json(name = "address") val address: String,
|
@Json(name = "address") val address: String,
|
||||||
|
|
@ -44,11 +51,13 @@ data class ReferralResponse(
|
||||||
@Json(name = "termsAcceptedAt") val termsAcceptedAt: String?,
|
@Json(name = "termsAcceptedAt") val termsAcceptedAt: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ExpectedAwards(
|
data class ExpectedAwards(
|
||||||
@Json(name = "numberOfWallets") val numberOfWallets: Int,
|
@Json(name = "numberOfWallets") val numberOfWallets: Int,
|
||||||
@Json(name = "list") val list: List<AwardItem>,
|
@Json(name = "list") val list: List<AwardItem>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class AwardItem(
|
data class AwardItem(
|
||||||
@Json(name = "currency") val currency: String,
|
@Json(name = "currency") val currency: String,
|
||||||
@Json(name = "paymentDate") val paymentDate: LocalDate,
|
@Json(name = "paymentDate") val paymentDate: LocalDate,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sales response
|
* Sales response
|
||||||
*/
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class SalesResponse(
|
data class SalesResponse(
|
||||||
@Json(name = "sales") val sales: List<Sales>,
|
@Json(name = "sales") val sales: List<Sales>,
|
||||||
)
|
)
|
||||||
|
|
@ -17,6 +19,7 @@ data class SalesResponse(
|
||||||
* @property product product that is sales
|
* @property product product that is sales
|
||||||
* @property notification optional notification for product
|
* @property notification optional notification for product
|
||||||
*/
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Sales(
|
data class Sales(
|
||||||
@Json(name = "id") val id: String,
|
@Json(name = "id") val id: String,
|
||||||
@Json(name = "state") val state: String,
|
@Json(name = "state") val state: String,
|
||||||
|
|
@ -31,12 +34,14 @@ data class Sales(
|
||||||
* @property code code that shows what product it is
|
* @property code code that shows what product it is
|
||||||
* @property name product name
|
* @property name product name
|
||||||
*/
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Product(
|
data class Product(
|
||||||
@Json(name = "id") val id: String,
|
@Json(name = "id") val id: String,
|
||||||
@Json(name = "code") val code: String,
|
@Json(name = "code") val code: String,
|
||||||
@Json(name = "name") val name: String,
|
@Json(name = "name") val name: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Notification(
|
data class Notification(
|
||||||
@Json(name = "type") val type: String,
|
@Json(name = "type") val type: String,
|
||||||
@Json(name = "title") val title: String,
|
@Json(name = "title") val title: String,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shop response
|
* Shop response
|
||||||
|
|
@ -9,6 +10,7 @@ import com.squareup.moshi.Json
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class ShopResponse(
|
data class ShopResponse(
|
||||||
@Json(name = "canOrder") val isOrderingAvailable: Boolean,
|
@Json(name = "canOrder") val isOrderingAvailable: Boolean,
|
||||||
)
|
)
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/** Body for make user referral */
|
/** Body for make user referral */
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class StartReferralBody(
|
data class StartReferralBody(
|
||||||
@Json(name = "walletId") val walletId: String,
|
@Json(name = "walletId") val walletId: String,
|
||||||
@Json(name = "networkId") val networkId: String,
|
@Json(name = "networkId") val networkId: String,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class UpdateUserTokensAccountBody(
|
data class UpdateUserTokensAccountBody(
|
||||||
@Json(name = "accountTitle")
|
@Json(name = "accountTitle")
|
||||||
val title: String,
|
val title: String,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class UserTokensAccountResponse(
|
data class UserTokensAccountResponse(
|
||||||
@Json(name = "accountId")
|
@Json(name = "accountId")
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import com.tangem.common.extensions.calculateHashCode
|
import com.tangem.common.extensions.calculateHashCode
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class UserTokensResponse(
|
data class UserTokensResponse(
|
||||||
@Json(name = "version") val version: Int = 0,
|
@Json(name = "version") val version: Int = 0,
|
||||||
@Json(name = "group") val group: GroupType,
|
@Json(name = "group") val group: GroupType,
|
||||||
|
|
@ -10,6 +12,7 @@ data class UserTokensResponse(
|
||||||
@Json(name = "tokens") val tokens: List<Token> = emptyList(),
|
@Json(name = "tokens") val tokens: List<Token> = emptyList(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Token(
|
data class Token(
|
||||||
@Json(name = "id") val id: String? = null,
|
@Json(name = "id") val id: String? = null,
|
||||||
@Json(name = "networkId") val networkId: String,
|
@Json(name = "networkId") val networkId: String,
|
||||||
|
|
@ -36,6 +39,7 @@ data class UserTokensResponse(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class GroupType {
|
enum class GroupType {
|
||||||
@Json(name = "none")
|
@Json(name = "none")
|
||||||
NONE,
|
NONE,
|
||||||
|
|
@ -47,6 +51,7 @@ data class UserTokensResponse(
|
||||||
NETWORK,
|
NETWORK,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class SortType {
|
enum class SortType {
|
||||||
@Json(name = "balance")
|
@Json(name = "balance")
|
||||||
BALANCE,
|
BALANCE,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class UserTokensSettingsResponse(
|
data class UserTokensSettingsResponse(
|
||||||
@Json(name = "group")
|
@Json(name = "group")
|
||||||
val group: UserTokensResponse.GroupType,
|
val group: UserTokensResponse.GroupType,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
package com.tangem.datasource.api.tangemTech.models.v2
|
package com.tangem.datasource.api.tangemTech.models.v2
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class UserTokensResponseV2(
|
data class UserTokensResponseV2(
|
||||||
@Json(name = "accounts")
|
@Json(name = "accounts")
|
||||||
val accounts: List<TokensAccount>,
|
val accounts: List<TokensAccount>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class TokensAccount(
|
data class TokensAccount(
|
||||||
@Json(name = "id")
|
@Json(name = "id")
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
package com.tangem.datasource.api.visa
|
package com.tangem.datasource.api.visa
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.utils.ReadTimeout
|
||||||
import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse
|
import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Header
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
interface TangemVisaApi {
|
interface TangemVisaApi {
|
||||||
|
|
||||||
@GET("activation-status")
|
@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
|
||||||
}
|
}
|
||||||
|
|
@ -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.Express
|
||||||
import com.tangem.datasource.api.common.config.StakeKit
|
import com.tangem.datasource.api.common.config.StakeKit
|
||||||
import com.tangem.datasource.api.common.config.TangemTech
|
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.datasource.local.config.environment.EnvironmentConfigStorage
|
||||||
import com.tangem.lib.auth.ExpressAuthProvider
|
import com.tangem.lib.auth.ExpressAuthProvider
|
||||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||||
|
|
@ -47,6 +46,5 @@ internal object ApiConfigsModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@IntoSet
|
@IntoSet
|
||||||
fun provideTangemVisaConfig(tangemVisaAuthProvider: TangemVisaAuthProvider): ApiConfig =
|
fun provideTangemVisaConfig(): ApiConfig = TangemVisa()
|
||||||
TangemVisa(tangemVisaAuthProvider)
|
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +194,7 @@ internal object NetworkModule {
|
||||||
clientBuilder = {
|
clientBuilder = {
|
||||||
addInterceptor(
|
addInterceptor(
|
||||||
NetworkLogsSaveInterceptor(appLogsStore),
|
NetworkLogsSaveInterceptor(appLogsStore),
|
||||||
)
|
).applyTimeoutAnnotations()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +215,7 @@ internal object NetworkModule {
|
||||||
clientBuilder = {
|
clientBuilder = {
|
||||||
addInterceptor(
|
addInterceptor(
|
||||||
NetworkLogsSaveInterceptor(appLogsStore),
|
NetworkLogsSaveInterceptor(appLogsStore),
|
||||||
)
|
).applyTimeoutAnnotations()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>)
|
||||||
|
}
|
||||||
|
|
@ -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()),
|
||||||
|
)
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
package com.tangem.datasource.local.card
|
package com.tangem.datasource.local.card
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class UsedCardInfo(
|
data class UsedCardInfo(
|
||||||
|
@Json(name = "cardId")
|
||||||
val cardId: String,
|
val cardId: String,
|
||||||
|
@Json(name = "isScanned")
|
||||||
val isScanned: Boolean = false,
|
val isScanned: Boolean = false,
|
||||||
|
@Json(name = "isActivationStarted")
|
||||||
val isActivationStarted: Boolean = false,
|
val isActivationStarted: Boolean = false,
|
||||||
|
@Json(name = "isActivationFinished")
|
||||||
val isActivationFinished: Boolean = false,
|
val isActivationFinished: Boolean = false,
|
||||||
)
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue