Updated on 2026-08-14
This commit is contained in:
commit
2066204261
463 changed files with 6404 additions and 3051 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.225.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 com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.android.application)
|
||||
|
|
@ -258,6 +259,10 @@ dependencies {
|
|||
implementation(deps.decompose.ext.compose)
|
||||
implementation(deps.moshi.adapters)
|
||||
|
||||
implementation(deps.moshi.kotlin)
|
||||
kaptForObfuscatingVariants(deps.moshi.kotlin.codegen)
|
||||
kaptForObfuscatingVariants(deps.retrofit.response.type.keeper)
|
||||
|
||||
/** Testing libraries */
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
|
|
@ -275,6 +280,7 @@ dependencies {
|
|||
|
||||
/** Chucker */
|
||||
debugImplementation(deps.chucker)
|
||||
debugPGImplementation(deps.chucker)
|
||||
mockedImplementation(deps.chuckerStub)
|
||||
externalImplementation(deps.chuckerStub)
|
||||
internalImplementation(deps.chuckerStub)
|
||||
|
|
@ -296,5 +302,4 @@ dependencies {
|
|||
// excludes version 9999.0-empty-to-avoid-conflict-with-guava
|
||||
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.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.kts.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
-dontusemixedcaseclassnames
|
||||
-flattenpackagehierarchy
|
||||
-adaptclassstrings
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
# firebase
|
||||
-keep public class com.google.firebase.** { *; }
|
||||
-keep class com.google.android.gms.internal.** { *; }
|
||||
-keepclasseswithmembers class com.google.firebase.FirebaseException
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
# hedera sdk
|
||||
-keep class com.hedera.hashgraph.sdk.** { *; }
|
||||
-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
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
# trustwallet sdk
|
||||
-keep class wallet.core.jni.** { *; }
|
||||
|
||||
# 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 104b52520e8721eb775e2733deb361eed5067a63
|
||||
|
|
@ -222,7 +222,10 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
private fun updateLogFiles() {
|
||||
appLogsStore.deleteOldLogsFile()
|
||||
appLogsStore.deleteLastLogFile()
|
||||
|
||||
if (!BuildConfig.TESTER_MENU_ENABLED) {
|
||||
appLogsStore.deleteLastLogFile()
|
||||
}
|
||||
|
||||
// Temporally logs are not saved
|
||||
// scope.launch {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,10 @@ class TangemAppLoggerInitializer(
|
|||
}
|
||||
|
||||
if (PERMITTED_PRIORITY.contains(priority)) {
|
||||
appLogsStore.saveLogMessage(message)
|
||||
appLogsStore.saveLogMessage(
|
||||
tag = tag ?: "TangemAppLogger",
|
||||
message = message,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,22 +33,24 @@ internal class DefaultVisaAuthTokenStorage @Inject constructor(
|
|||
|
||||
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)
|
||||
|
||||
secureStorage.store(
|
||||
json.encodeToByteArray(throwOnInvalidSequence = true),
|
||||
VISA_AUTH_TOKENS_KEY,
|
||||
createKey(cardId),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun get(): VisaAuthTokens? = withContext(dispatcherProvider.io) {
|
||||
secureStorage.get(VISA_AUTH_TOKENS_KEY)
|
||||
override suspend fun get(cardId: String): VisaAuthTokens? = withContext(dispatcherProvider.io) {
|
||||
secureStorage.get(createKey(cardId))
|
||||
?.decodeToString(throwOnInvalidSequence = true)
|
||||
?.let(tokensAdapter::fromJson)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val VISA_AUTH_TOKENS_KEY = "visa_auth_tokens"
|
||||
override fun remove(cardId: String) {
|
||||
secureStorage.delete(createKey(cardId))
|
||||
}
|
||||
|
||||
private fun createKey(cardId: String): String = "visa_auth_tokens_$cardId"
|
||||
}
|
||||
|
|
@ -15,6 +15,6 @@ internal class TangemBlockchainSDKLogger(
|
|||
) : BlockchainSDKLogger {
|
||||
|
||||
override fun log(level: BlockchainSDKLogger.Level, message: String) {
|
||||
appLogsStore.saveLogMessage(message)
|
||||
appLogsStore.saveLogMessage(tag = "BlockchainSDK_${level.name}", message)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.card.*
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
|
|
@ -22,14 +21,6 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object CardDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSetAccessCodeRequestPolicyUseCase(
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
): SetAccessCodeRequestPolicyUseCase {
|
||||
return SetAccessCodeRequestPolicyUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSetCardWasScannedUseCase(cardRepository: CardRepository): SetCardWasScannedUseCase {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
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.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
|
|
@ -229,4 +230,10 @@ internal object OnrampDomainModule {
|
|||
): FetchOnrampCountriesUseCase {
|
||||
return FetchOnrampCountriesUseCase(onrampRepository, onrampErrorResolver)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetHotCryptoTokensUseCase(hotCryptoRepository: HotCryptoRepository): GetHotCryptoUseCase {
|
||||
return GetHotCryptoUseCase(hotCryptoRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,12 +19,6 @@ internal object PromoDomainModule {
|
|||
return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideShouldShowRingPromoUseCase(promoRepository: PromoRepository): ShouldShowRingPromoUseCase {
|
||||
return ShouldShowRingPromoUseCase(promoRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideShouldShowSwapPromoTokenUseCase(promoRepository: PromoRepository): ShouldShowSwapPromoTokenUseCase {
|
||||
|
|
|
|||
|
|
@ -182,12 +182,6 @@ internal object SettingsDomainModule {
|
|||
): NeverRequestPermissionUseCase {
|
||||
return NeverRequestPermissionUseCase(repository = permissionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideShouldSaveAccessCodesUseCase(settingsRepository: SettingsRepository): ShouldSaveAccessCodesUseCase {
|
||||
return ShouldSaveAccessCodesUseCase(settingsRepository = settingsRepository)
|
||||
}
|
||||
// endregion
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.model.VisaActivationInput
|
||||
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.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
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.pins.SetUserCodeCommand
|
||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||
import com.tangem.operations.sign.SignHashResponse
|
||||
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.sdk.api.visa.VisaCardActivationResponse
|
||||
import com.tangem.tap.derivationsFinder
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
||||
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||
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.CreateSecondTwinWalletTask
|
||||
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
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -19,13 +19,15 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.model.VisaActivationInput
|
||||
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.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||
import com.tangem.operations.sign.SignHashResponse
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.sdk.api.visa.VisaCardActivationResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
|
|
@ -207,4 +209,12 @@ class MockTangemSdkManager(
|
|||
): CompletionResult<VisaCardActivationResponse> {
|
||||
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 {
|
||||
if (RING_BATCH_IDS.contains(card.batchId) || card.batchId.startsWith(RING_BATCH_PREFIX)) {
|
||||
return ProductType.Ring
|
||||
}
|
||||
return if (card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available &&
|
||||
card.settings.isKeysImportAllowed
|
||||
) {
|
||||
ProductType.Wallet2
|
||||
} else {
|
||||
ProductType.Wallet
|
||||
return when {
|
||||
card.isVisa -> ProductType.Visa
|
||||
RING_BATCH_IDS.contains(card.batchId) || card.batchId.startsWith(RING_BATCH_PREFIX) -> ProductType.Ring
|
||||
card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available &&
|
||||
card.settings.isKeysImportAllowed -> ProductType.Wallet2
|
||||
else -> ProductType.Wallet
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import com.reown.util.hexToBytes
|
||||
import com.tangem.common.CompletionResult
|
||||
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.common.extensions.toHexString
|
||||
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.VisaOTPStorage
|
||||
import com.tangem.datasource.local.visa.hasSavedOTP
|
||||
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.repository.VisaActivationRepository
|
||||
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.SignHashResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.sdk.api.visa.VisaCardActivationResponse
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.*
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.jvm.Throws
|
||||
import kotlin.time.measureTimedValue
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class VisaCardActivationTask @AssistedInject constructor(
|
||||
|
|
@ -36,9 +43,15 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
private val otpStorage: VisaOTPStorage,
|
||||
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
||||
private val visaAuthRepository: VisaAuthRepository,
|
||||
private val visaActivationRepository: VisaActivationRepository,
|
||||
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
||||
) : CardSessionRunnable<VisaCardActivationResponse> {
|
||||
|
||||
private class SessionContext(
|
||||
val visaActivationRepository: VisaActivationRepository,
|
||||
val cardId: String,
|
||||
val session: CardSession,
|
||||
)
|
||||
|
||||
override fun run(session: CardSession, callback: CompletionCallback<VisaCardActivationResponse>) {
|
||||
coroutineScope.launch {
|
||||
callback(runSuspend(session))
|
||||
|
|
@ -52,33 +65,47 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
return CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.WrongCard.message))
|
||||
}
|
||||
|
||||
return if (challengeToSign != null) {
|
||||
signAuthorizationChallenge(session, challengeToSign)
|
||||
} else {
|
||||
val activationOrder = runCatching { visaActivationRepository.getActivationOrderToSign() }
|
||||
.getOrElse {
|
||||
return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: ""))
|
||||
}
|
||||
val visaActivationRepository = visaActivationRepositoryFactory.create(card.cardId)
|
||||
|
||||
signOrder(session, activationOrder)
|
||||
}
|
||||
}
|
||||
val context = SessionContext(
|
||||
visaActivationRepository = visaActivationRepository,
|
||||
cardId = card.cardId,
|
||||
session = session,
|
||||
)
|
||||
|
||||
private suspend fun signAuthorizationChallenge(
|
||||
session: CardSession,
|
||||
challengeToSign: VisaAuthChallenge.Card,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val attestationCommand = AttestCardKeyCommand(challenge = challengeToSign.challenge.hexToBytes())
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
attestationCommand.run(session = session) { attestationResponse ->
|
||||
continuation.resume(attestationResponse)
|
||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||
if (challengeToSign != null) {
|
||||
context.signAuthorizationChallenge(challengeToSign)
|
||||
} else {
|
||||
val activationOrder = runCatching { visaActivationRepository.getActivationOrderToSign() }
|
||||
.getOrElse {
|
||||
return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: ""))
|
||||
}
|
||||
|
||||
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 -> {
|
||||
Timber.i("AttestCardKeyCommand success")
|
||||
processSignedAuthorizationChallenge(
|
||||
session = session,
|
||||
signedChallenge = challengeToSign.toSignedChallenge(
|
||||
signedChallenge = result.data.cardSignature.toHexString(),
|
||||
salt = result.data.salt.toHexString(),
|
||||
|
|
@ -86,13 +113,13 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("AttestCardKeyCommand failure ${result.error}")
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processSignedAuthorizationChallenge(
|
||||
session: CardSession,
|
||||
private suspend fun SessionContext.processSignedAuthorizationChallenge(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
return coroutineScope {
|
||||
|
|
@ -107,12 +134,14 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
otpTaskDeferred.await()
|
||||
|
||||
signOrder(session, order)
|
||||
signOrder(order)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(TangemSdkError::class)
|
||||
private suspend fun getActivationOrderToSign(signedChallenge: VisaAuthSignedChallenge): ActivationOrder {
|
||||
private suspend fun SessionContext.getActivationOrderToSign(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
): ActivationOrder {
|
||||
val tokens = runCatching {
|
||||
visaAuthRepository.getAccessTokens(signedChallenge)
|
||||
}.getOrElse {
|
||||
|
|
@ -121,12 +150,12 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
visaAuthTokenStorage.store(tokens)
|
||||
visaAuthTokenStorage.store(cardId, tokens)
|
||||
|
||||
return visaActivationRepository.getActivationOrderToSign()
|
||||
}
|
||||
|
||||
private suspend fun createWallet(session: CardSession): CompletionResult<Unit> {
|
||||
private suspend fun SessionContext.createWallet(session: CardSession): CompletionResult<Unit> {
|
||||
coroutineScope { ensureActive() }
|
||||
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
|
@ -135,122 +164,152 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
createOTP(session)
|
||||
} else {
|
||||
val createWalletTask = CreateWalletTask(VisaUtilities.mandatoryCurve)
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
createWalletTask.run(session) { createWalletResult ->
|
||||
continuation.resume(createWalletResult)
|
||||
|
||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||
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 -> {
|
||||
Timber.i("CreateWalletTask success")
|
||||
createOTP(session)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("CreateWalletTask 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() }
|
||||
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val otp = otpStorage.getOTP(card.cardId)
|
||||
return if (otp != null) {
|
||||
return if (otpStorage.hasSavedOTP(cardId)) {
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
val otpCommand = GenerateOTPCommand()
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
otpCommand.run(session) { otpResult ->
|
||||
continuation.resume(otpResult)
|
||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
otpCommand.run(session) { otpResult ->
|
||||
continuation.resume(otpResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (result) {
|
||||
Timber.i("GenerateOTPCommand time: ${timedResult.duration}")
|
||||
|
||||
when (val result = timedResult.value) {
|
||||
is CompletionResult.Success -> {
|
||||
otpStorage.saveOTP(card.cardId, result.data.rootOTP)
|
||||
Timber.i("GenerateOTPCommand success")
|
||||
otpStorage.saveOTP(cardId, result.data.rootOTP)
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("GenerateOTPCommand failure ${result.error}")
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun signOrder(
|
||||
session: CardSession,
|
||||
order: ActivationOrder,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
private suspend fun SessionContext.signOrder(order: ActivationOrder): CompletionResult<VisaCardActivationResponse> {
|
||||
val card =
|
||||
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
val wallet =
|
||||
card.wallets.firstOrNull() ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
val task = SignHashCommand(order.hash.hexToBytes(), wallet.publicKey)
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
task.run(session) { signResult ->
|
||||
continuation.resume(signResult)
|
||||
card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve }
|
||||
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val task = SignHashCommand(
|
||||
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 -> {
|
||||
Timber.i("SignHashCommand success")
|
||||
handleSignedOrder(
|
||||
session = session,
|
||||
activationOrder = order,
|
||||
response = result.data,
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("SignHashCommand failure ${result.error}")
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleSignedOrder(
|
||||
session: CardSession,
|
||||
private suspend fun SessionContext.handleSignedOrder(
|
||||
activationOrder: ActivationOrder,
|
||||
response: SignHashResponse,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val signedOrder = SignedActivationOrder(
|
||||
activationOrder = activationOrder,
|
||||
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),
|
||||
)
|
||||
|
||||
val activationResponse = VisaCardActivationResponse(
|
||||
signedActivationOrder = signedOrder,
|
||||
rootOTP = VisaRootOTP(otp.toHexString()),
|
||||
)
|
||||
return setupAccessCode().map {
|
||||
val card =
|
||||
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())
|
||||
|
||||
if (card.isAccessCodeSet) {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
Timber.i("Setting access code")
|
||||
|
||||
val task = SetUserCodeCommand.changeAccessCode(accessCode)
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
task.run(session) { setAccessCodeResult ->
|
||||
continuation.resume(setAccessCodeResult)
|
||||
|
||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||
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 -> {
|
||||
Timber.i("SetUserCodeCommand success")
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.i("SetUserCodeCommand 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
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
|
@ -7,16 +8,24 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserWalletSensitiveInformation(
|
||||
@Json(name = "wallets")
|
||||
val wallets: List<CardDTO.Wallet>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserWalletPublicInformation(
|
||||
@Json(name = "name")
|
||||
val name: String,
|
||||
@Json(name = "walletId")
|
||||
val walletId: UserWalletId,
|
||||
@Json(name = "artworkUrl")
|
||||
val artworkUrl: String,
|
||||
@Json(name = "cardsInWallet")
|
||||
val cardsInWallet: Set<String>,
|
||||
@Json(name = "scanResponse")
|
||||
val scanResponse: ScanResponse,
|
||||
@Json(name = "isMultiCurrency")
|
||||
val isMultiCurrency: Boolean,
|
||||
@Json(name = "hasBackupError")
|
||||
val hasBackupError: Boolean = false,
|
||||
)
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
|
|
@ -24,10 +25,16 @@ import kotlin.coroutines.resume
|
|||
|
||||
internal class VisaCardScanHandler @Inject constructor(
|
||||
private val visaAuthRepository: VisaAuthRepository,
|
||||
private val visaActivationRepository: VisaActivationRepository,
|
||||
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
||||
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
||||
) {
|
||||
|
||||
private class SessionContext(
|
||||
val visaActivationRepository: VisaActivationRepository,
|
||||
val cardId: String,
|
||||
val session: CardSession,
|
||||
)
|
||||
|
||||
suspend fun handleVisaCardScan(session: CardSession): CompletionResult<VisaCardActivationStatus> {
|
||||
Timber.i("Attempting to handle Visa card scan")
|
||||
|
||||
|
|
@ -36,6 +43,14 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
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 activationInput =
|
||||
VisaActivationInput(card.cardId, card.cardPublicKey, card.isAccessCodeSet)
|
||||
|
|
@ -43,13 +58,10 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
return CompletionResult.Success(activationStatus)
|
||||
}
|
||||
|
||||
return deriveKey(wallet, session)
|
||||
return context.deriveKey(wallet)
|
||||
}
|
||||
|
||||
private suspend fun deriveKey(
|
||||
wallet: CardWallet,
|
||||
session: CardSession,
|
||||
): CompletionResult<VisaCardActivationStatus> {
|
||||
private suspend fun SessionContext.deriveKey(wallet: CardWallet): CompletionResult<VisaCardActivationStatus> {
|
||||
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
|
||||
Timber.e("Failed to create derivation path while first scan")
|
||||
|
||||
|
|
@ -64,17 +76,16 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
return handleDerivationResponse(derivationTaskResult, session)
|
||||
return handleDerivationResponse(derivationTaskResult)
|
||||
}
|
||||
|
||||
private suspend fun handleDerivationResponse(
|
||||
private suspend fun SessionContext.handleDerivationResponse(
|
||||
result: CompletionResult<ExtendedPublicKey>,
|
||||
session: CardSession,
|
||||
): CompletionResult<VisaCardActivationStatus> {
|
||||
return when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
Timber.i("Start task for loading challenge for Visa wallet")
|
||||
handleWalletAuthorization(session)
|
||||
handleWalletAuthorization()
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
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")
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
|
||||
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 {
|
||||
Timber.e("Failed to find extended public key while handling wallet authorization")
|
||||
return CompletionResult.Failure(
|
||||
|
|
@ -108,7 +120,6 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
}
|
||||
|
||||
Timber.i("Requesting challenge for wallet authorization")
|
||||
// Will be changed later after backend implementation
|
||||
val challengeResponse = runCatching {
|
||||
visaAuthRepository.getCustomerWalletAuthChallenge(
|
||||
cardId = card.cardId,
|
||||
|
|
@ -122,14 +133,12 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
publicKey = wallet.publicKey,
|
||||
derivationPath = derivationPath,
|
||||
nonce = challengeResponse.challenge,
|
||||
session = session,
|
||||
)
|
||||
|
||||
return when (signChallengeResult) {
|
||||
is CompletionResult.Success -> {
|
||||
Timber.i("Challenge signed with Wallet public key")
|
||||
handleWalletAuthorizationTokens(
|
||||
session = session,
|
||||
signedChallenge = challengeResponse
|
||||
.toSignedChallenge(signChallengeResult.data.signature.toHexString()),
|
||||
)
|
||||
|
|
@ -141,8 +150,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun handleWalletAuthorizationTokens(
|
||||
session: CardSession,
|
||||
private suspend fun SessionContext.handleWalletAuthorizationTokens(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
): CompletionResult<VisaCardActivationStatus> {
|
||||
val authorizationTokensResponse = runCatching {
|
||||
|
|
@ -151,17 +159,20 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
Timber.i(
|
||||
"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")
|
||||
|
||||
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())
|
||||
|
||||
Timber.i("Requesting authorization challenge to sign")
|
||||
|
|
@ -178,7 +189,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
|
||||
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) {
|
||||
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()
|
||||
|
||||
|
|
@ -239,47 +253,31 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun signChallengeWithWallet(
|
||||
private suspend fun SessionContext.signChallengeWithWallet(
|
||||
publicKey: ByteArray,
|
||||
derivationPath: DerivationPath,
|
||||
nonce: String,
|
||||
session: CardSession,
|
||||
): CompletionResult<SignHashResponse> {
|
||||
val signHashCommand = SignHashCommand(publicKey, nonce.toByteArray(), derivationPath)
|
||||
val result = suspendCancellableCoroutine {
|
||||
val signHashCommand = SignHashCommand(
|
||||
hash = nonce.hexToBytes(),
|
||||
walletPublicKey = publicKey,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
return suspendCancellableCoroutine {
|
||||
signHashCommand.run(session) { 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(
|
||||
session: CardSession,
|
||||
private suspend fun SessionContext.signChallengeWithCard(
|
||||
challenge: String,
|
||||
): CompletionResult<AttestCardKeyResponse> {
|
||||
val signHashCommand = AttestCardKeyCommand(challenge = challenge.toByteArray())
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
val signHashCommand = AttestCardKeyCommand(challenge = challenge.hexToBytes())
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
signHashCommand.run(session) { 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")
|
||||
val type: WCSignType,
|
||||
) : WcRequestData {
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class WCSignType {
|
||||
MESSAGE, PERSONAL_MESSAGE, TYPED_MESSAGE, SOLANA_MESSAGE,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,16 @@
|
|||
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
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Session(
|
||||
@Json(name = "topic")
|
||||
val topic: String,
|
||||
|
||||
@Json(name = "accounts")
|
||||
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.JsonClass
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@JsonClass(generateAdapter = true)
|
||||
class WcBinanceCancelOrder(
|
||||
data class WcBinanceCancelOrder(
|
||||
@Json(name = "account_number")
|
||||
accountNumber: String,
|
||||
val accountNumber: String,
|
||||
@Json(name = "chain_id")
|
||||
chainId: String,
|
||||
val chainId: String,
|
||||
@Json(name = "data")
|
||||
data: String?,
|
||||
val data: String?,
|
||||
@Json(name = "memo")
|
||||
memo: String?,
|
||||
val memo: String?,
|
||||
@Json(name = "sequence")
|
||||
sequence: String,
|
||||
val sequence: String,
|
||||
@Json(name = "source")
|
||||
source: String,
|
||||
val source: String,
|
||||
@Json(name = "msgs")
|
||||
msgs: List<Message>,
|
||||
) : WcBinanceOrder<WcBinanceCancelOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
|
||||
val msgs: List<Message>,
|
||||
) : WcRequestData {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Message(
|
||||
@Json(name = "refid")
|
||||
val refid: String,
|
||||
@Json(name = "sender")
|
||||
val sender: String,
|
||||
@Json(name = "symbol")
|
||||
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.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@JsonClass(generateAdapter = true)
|
||||
class WcBinanceTradeOrder(
|
||||
data class WcBinanceTradeOrder(
|
||||
@Json(name = "account_number")
|
||||
accountNumber: String,
|
||||
val accountNumber: String,
|
||||
@Json(name = "chain_id")
|
||||
chainId: String,
|
||||
data: String?,
|
||||
memo: String?,
|
||||
sequence: String,
|
||||
source: String,
|
||||
msgs: List<Message>,
|
||||
) : WcBinanceOrder<WcBinanceTradeOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
|
||||
val chainId: String,
|
||||
@Json(name = "data")
|
||||
val data: String?,
|
||||
@Json(name = "memo")
|
||||
val memo: String?,
|
||||
@Json(name = "sequence")
|
||||
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) {
|
||||
ID("id"),
|
||||
ORDER_TYPE("ordertype"),
|
||||
|
|
@ -30,14 +37,23 @@ class WcBinanceTradeOrder(
|
|||
TIME_INFORCE("timeinforce"),
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Message(
|
||||
@Json(name = "id")
|
||||
val id: String,
|
||||
@Json(name = "orderType")
|
||||
val orderType: Int,
|
||||
@Json(name = "price")
|
||||
val price: Long,
|
||||
@Json(name = "quantity")
|
||||
val quantity: Long,
|
||||
@Json(name = "sender")
|
||||
val sender: String,
|
||||
@Json(name = "side")
|
||||
val side: Int,
|
||||
@Json(name = "symbol")
|
||||
val symbol: String,
|
||||
@Json(name = "timeInforce")
|
||||
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.JsonClass
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@JsonClass(generateAdapter = true)
|
||||
class WcBinanceTransferOrder(
|
||||
data class WcBinanceTransferOrder(
|
||||
@Json(name = "account_number")
|
||||
accountNumber: String,
|
||||
val accountNumber: String,
|
||||
@Json(name = "chain_id")
|
||||
chainId: String,
|
||||
data: String?,
|
||||
memo: String?,
|
||||
sequence: String,
|
||||
source: String,
|
||||
msgs: List<Message>,
|
||||
) : WcBinanceOrder<WcBinanceTransferOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
|
||||
val chainId: String,
|
||||
@Json(name = "data")
|
||||
val data: String?,
|
||||
@Json(name = "memo")
|
||||
val memo: String?,
|
||||
@Json(name = "sequence")
|
||||
val sequence: String,
|
||||
@Json(name = "source")
|
||||
val source: String,
|
||||
@Json(name = "msgs")
|
||||
val msgs: List<Message>,
|
||||
) : WcRequestData {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Message(
|
||||
@Json(name = "inputs")
|
||||
val inputs: List<Item>,
|
||||
@Json(name = "outputs")
|
||||
val outputs: List<Item>,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Item(
|
||||
@Json(name = "address")
|
||||
val address: String,
|
||||
@Json(name = "coins")
|
||||
val coins: List<Coin>,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Coin(
|
||||
@Json(name = "amount")
|
||||
val amount: Long,
|
||||
@Json(name = "denom")
|
||||
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,6 +4,7 @@ import android.os.Bundle
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.routing.AppRoute
|
||||
|
|
@ -11,13 +12,16 @@ import com.tangem.common.routing.AppRouter
|
|||
import com.tangem.common.routing.bundle.unbundle
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.getBackupCardsCount
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
|
|
@ -34,17 +38,24 @@ import com.tangem.wallet.R
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@HiltViewModel
|
||||
internal class CardSettingsViewModel @Inject constructor(
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val cardSettingsInteractor: CardSettingsInteractor,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
private var previousBiometricsRequestPolicy: Boolean = false
|
||||
|
||||
private val userWalletId = savedStateHandle.get<Bundle>(AppRoute.CardSettings.USER_WALLET_ID_KEY)
|
||||
?.unbundle(UserWalletId.serializer())
|
||||
?: error("User wallet ID is required for CardSettingsViewModel")
|
||||
|
|
@ -52,12 +63,32 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
val screenState: MutableStateFlow<CardSettingsScreenState> = MutableStateFlow(getInitialState())
|
||||
|
||||
init {
|
||||
updateAccessCodeRequestPolicy()
|
||||
|
||||
cardSettingsInteractor.scannedScanResponse
|
||||
.filterNotNull()
|
||||
.onEach(::updateCardDetails)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
// Restore the previous value of access code request policy
|
||||
cardSdkConfigRepository.isBiometricsRequestPolicy = previousBiometricsRequestPolicy
|
||||
}
|
||||
|
||||
private fun updateAccessCodeRequestPolicy() {
|
||||
runBlocking {
|
||||
// !!!IMPORTANT!!!: Do not forget to restore the previous value in onCleared() method
|
||||
previousBiometricsRequestPolicy = cardSdkConfigRepository.isBiometricsRequestPolicy
|
||||
|
||||
val userWallet = getUserWalletUseCase(userWalletId)
|
||||
.getOrElse { error("User wallet $userWalletId not found") }
|
||||
|
||||
cardSdkConfigRepository.isBiometricsRequestPolicy = userWallet.scanResponse.card.isAccessCodeSet &&
|
||||
settingsRepository.shouldSaveAccessCodes()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialState() = CardSettingsScreenState(
|
||||
cardDetails = null,
|
||||
onElementClick = ::handleClickingItem,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import androidx.activity.compose.BackHandler
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -27,13 +27,12 @@ internal fun SettingsScreensScaffold(
|
|||
addBottomInsets: Boolean = true,
|
||||
fab: @Composable () -> Unit = {},
|
||||
) {
|
||||
val state = rememberScaffoldState(snackbarHostState = snackbarHostState)
|
||||
val backgroundColor = TangemTheme.colors.background.secondary
|
||||
|
||||
BackHandler(onBack = onBackClick)
|
||||
|
||||
Scaffold(
|
||||
scaffoldState = state,
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
topBar = {
|
||||
EmptyTopBarWithNavigation(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
|
|
@ -43,7 +42,7 @@ internal fun SettingsScreensScaffold(
|
|||
},
|
||||
modifier = modifier,
|
||||
contentWindowInsets = WindowInsetsZero,
|
||||
backgroundColor = backgroundColor,
|
||||
containerColor = backgroundColor,
|
||||
floatingActionButton = {
|
||||
Box(modifier = Modifier.navigationBarsPadding()) {
|
||||
fab()
|
||||
|
|
@ -89,6 +88,7 @@ internal fun ScreenTitle(titleRes: Int, modifier: Modifier = Modifier) {
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun EmptyTopBarWithNavigation(
|
||||
onBackClick: () -> Unit,
|
||||
|
|
@ -108,8 +108,9 @@ internal fun EmptyTopBarWithNavigation(
|
|||
)
|
||||
}
|
||||
},
|
||||
backgroundColor = backgroundColor,
|
||||
elevation = 0.dp,
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = backgroundColor,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
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.ui.Alignment
|
||||
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.unit.dp
|
||||
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.res.TangemTheme
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
|
|
@ -71,7 +75,7 @@ private fun AddSessionFab(onAddSession: () -> Unit, modifier: Modifier = Modifie
|
|||
@Composable
|
||||
private fun EmptyScreen(state: WalletConnectScreenState) {
|
||||
if (state.isLoading) {
|
||||
LinearProgressIndicator(
|
||||
TangemLinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
|
|
@ -102,7 +106,7 @@ private fun EmptyScreen(state: WalletConnectScreenState) {
|
|||
@Composable
|
||||
private fun WalletConnectSessions(state: WalletConnectScreenState) {
|
||||
if (state.isLoading) {
|
||||
LinearProgressIndicator(
|
||||
TangemLinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(2.dp),
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import com.tangem.core.analytics.models.AnalyticsParam
|
|||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.SetAccessCodeRequestPolicyUseCase
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.ShouldSaveAccessCodesUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
|
|
@ -39,11 +39,11 @@ import javax.inject.Inject
|
|||
@Suppress("LongParameterList")
|
||||
@HiltViewModel
|
||||
internal class HomeViewModel @Inject constructor(
|
||||
private val shouldSaveAccessCodesUseCase: ShouldSaveAccessCodesUseCase,
|
||||
private val setAccessCodeRequestPolicyUseCase: SetAccessCodeRequestPolicyUseCase,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : ViewModel() {
|
||||
|
|
@ -73,7 +73,7 @@ internal class HomeViewModel @Inject constructor(
|
|||
|
||||
private fun scanCard() {
|
||||
viewModelScope.launch {
|
||||
setAccessCodeRequestPolicyUseCase(isBiometricsRequestPolicy = shouldSaveAccessCodesUseCase())
|
||||
cardSdkConfigRepository.isBiometricsRequestPolicy = settingsRepository.shouldSaveAccessCodes()
|
||||
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = AnalyticsParam.ScreensSources.Intro,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.common.util.twinsIsTwinned
|
|||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
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.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -42,6 +43,12 @@ object OnboardingHelper {
|
|||
store.state.globalState.onboardingState.onboardingManager ?: OnboardingManager(response)
|
||||
val cardId = response.card.cardId
|
||||
return when {
|
||||
response.cardTypesResolver.isVisaWallet() -> {
|
||||
if (response.visaCardActivationStatus == null) error("Visa card activation status is null")
|
||||
|
||||
response.visaCardActivationStatus !is VisaCardActivationStatus.Activated
|
||||
}
|
||||
|
||||
response.cardTypesResolver.isTangemTwins() -> {
|
||||
if (!response.twinsIsTwinned()) {
|
||||
true
|
||||
|
|
@ -66,7 +73,8 @@ object OnboardingHelper {
|
|||
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
||||
val newOnboardingSupportTypes = scanResponse.productType == ProductType.Wallet2 ||
|
||||
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 &&
|
||||
newOnboardingSupportTypes
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import android.content.DialogInterface
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
package com.tangem.tap.network.auth
|
||||
|
||||
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
|
||||
|
||||
internal class DefaultVisaAuthProvider @Inject constructor(
|
||||
private val authStorage: VisaAuthTokenStorage,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
) : TangemVisaAuthProvider {
|
||||
|
||||
override suspend fun getAuthHeader(): String {
|
||||
return authStorage.get()?.accessToken?.let { "Bearer $it" } ?: "Error in the app!"
|
||||
override suspend fun getAuthHeader(cardId: String): String {
|
||||
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
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
|
|
@ -11,21 +12,25 @@ interface MercuryoApi {
|
|||
suspend fun currencies(@Path("apiVersion") apiVersion: String): MercuryoCurrenciesResponse
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MercuryoCurrenciesResponse(
|
||||
val status: Int,
|
||||
val data: Data,
|
||||
@Json(name = "status") val status: Int,
|
||||
@Json(name = "data") val data: Data,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
val fiat: List<String>,
|
||||
val crypto: List<String>,
|
||||
val config: Config,
|
||||
@Json(name = "fiat") val fiat: List<String>,
|
||||
@Json(name = "crypto") val crypto: List<String>,
|
||||
@Json(name = "config") val config: Config,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Config(
|
||||
@Json(name = "crypto_currencies")
|
||||
val cryptoCurrencies: List<MercuryoCryptoCurrency>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MercuryoCryptoCurrency(
|
||||
@Json(name = "currency")
|
||||
val currencySymbol: String,
|
||||
|
|
|
|||
|
|
@ -149,5 +149,7 @@ internal val Blockchain.mercuryoNetwork: String?
|
|||
Blockchain.Clore -> null
|
||||
Blockchain.OdysseyChain, Blockchain.OdysseyChainTestnet -> 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)
|
||||
data class MoonPayUserStatus(
|
||||
@Json(name = "isBuyAllowed")
|
||||
val isBuyAllowed: Boolean,
|
||||
@Json(name = "isSellAllowed")
|
||||
val isSellAllowed: Boolean,
|
||||
@Json(name = "isAllowed")
|
||||
val isMoonpayAllowed: Boolean,
|
||||
|
|
@ -34,18 +36,18 @@ data class MoonPayUserStatus(
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MoonPayCurrencies(
|
||||
val type: String,
|
||||
val code: String,
|
||||
val supportsLiveMode: Boolean = false,
|
||||
val isSuspended: Boolean = true,
|
||||
val isSupportedInUS: Boolean = false,
|
||||
val isSellSupported: Boolean = false,
|
||||
val notAllowedUSStates: List<String> = emptyList(),
|
||||
val metadata: MoonPayCurrenciesMetadata? = null,
|
||||
@Json(name = "type") val type: String,
|
||||
@Json(name = "code") val code: String,
|
||||
@Json(name = "supportsLiveMode") val supportsLiveMode: Boolean = false,
|
||||
@Json(name = "isSuspended") val isSuspended: Boolean = true,
|
||||
@Json(name = "isSupportedInUS") val isSupportedInUS: Boolean = false,
|
||||
@Json(name = "isSellSupported") val isSellSupported: Boolean = false,
|
||||
@Json(name = "notAllowedUSStates") val notAllowedUSStates: List<String> = emptyList(),
|
||||
@Json(name = "metadata") val metadata: MoonPayCurrenciesMetadata? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MoonPayCurrenciesMetadata(
|
||||
val contractAddress: String?,
|
||||
val networkCode: String?,
|
||||
@Json(name = "contractAddress") val contractAddress: String?,
|
||||
@Json(name = "networkCode") val networkCode: String?,
|
||||
)
|
||||
|
|
@ -150,4 +150,6 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
|
|||
VanarChainTestnet -> null
|
||||
OdysseyChain, OdysseyChainTestnet -> 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"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:progressBackgroundTint="@color/icon_informative"
|
||||
android:progressTint="@color/icon_primary_1" />
|
||||
android:paddingVertical="8dp"
|
||||
android:indeterminate="false"
|
||||
android:progressDrawable="@drawable/progress_rounded_shape" />
|
||||
</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.common.Dimensions
|
||||
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.shape.Shape
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -39,7 +38,28 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
|
|||
|
||||
object : DefaultCartesianMarker(
|
||||
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,
|
||||
guideline = guideline,
|
||||
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 const val LABEL_BACKGROUND_SHADOW_RADIUS_DP = 4f
|
||||
private const val LABEL_BACKGROUND_SHADOW_DY_DP = 2f
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.common.ui.expressStatus
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.ExpressHideButton(isTerminal: Boolean, isAutoDisposable: Boolean, onClick: () -> Unit) {
|
||||
AnimatedVisibility(
|
||||
visible = isTerminal && !isAutoDisposable,
|
||||
label = "Hide button visibility animation",
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.express_status_hide_button_text),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.button,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp, end = 12.dp, top = 14.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(bounded = false),
|
||||
onClick = onClick,
|
||||
)
|
||||
.padding(vertical = 10.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,11 @@ fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) {
|
|||
SpacerH12()
|
||||
ExpressStatusBlock(state = state.info.status)
|
||||
ExpressStatusNotificationBlock(state = state.info.notification)
|
||||
ExpressHideButton(
|
||||
isTerminal = state.activeStatus.isTerminal,
|
||||
isAutoDisposable = state.activeStatus.isAutoDisposable,
|
||||
onClick = state.info.onDisposeExpressStatus,
|
||||
)
|
||||
SpacerH24()
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ data class ExpressTransactionStateInfoUM(
|
|||
val timestampFormatted: TextReference,
|
||||
val onGoToProviderClick: (String) -> Unit,
|
||||
val onClick: () -> Unit,
|
||||
val onDisposeExpressStatus: () -> Unit,
|
||||
val iconState: ExpressTransactionStateIconUM,
|
||||
val toAmount: TextReference,
|
||||
val toFiatAmount: TextReference?,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
|
|
@ -11,7 +13,6 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
|
@ -23,6 +24,7 @@ dependencies {
|
|||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.timber)
|
||||
kaptForObfuscatingVariants(deps.moshi.kotlin.codegen)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.datasource)
|
||||
|
|
|
|||
|
|
@ -14,5 +14,13 @@
|
|||
{
|
||||
"name": "vanar-chain",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "sonic",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "apechain",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
},
|
||||
{
|
||||
"name": "ONBOARDING_CODE_REFACTORING_ENABLED",
|
||||
"version": "undefined"
|
||||
"version": "5.21.0"
|
||||
},
|
||||
{
|
||||
"name": "NAVIGATION_REFACTORING",
|
||||
|
|
@ -34,5 +34,13 @@
|
|||
{
|
||||
"name": "VISA_ONBOARDING_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "ONRAMP_HOT_TOKENS_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "STAKING_TON_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
|
|
@ -29,6 +31,7 @@ dependencies {
|
|||
implementation(projects.domain.staking.models)
|
||||
implementation(projects.domain.onramp.models)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.features.onramp.api)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(deps.tangem.blockchain)
|
||||
|
|
@ -53,6 +56,8 @@ dependencies {
|
|||
implementation(deps.okHttp.prettyLogging)
|
||||
implementation(deps.retrofit)
|
||||
implementation(deps.retrofit.moshi)
|
||||
kaptForObfuscatingVariants(deps.moshi.kotlin.codegen)
|
||||
kaptForObfuscatingVariants(deps.retrofit.response.type.keeper)
|
||||
|
||||
/** Time */
|
||||
implementation(deps.jodatime)
|
||||
|
|
@ -62,6 +67,7 @@ dependencies {
|
|||
|
||||
/** Chucker */
|
||||
debugImplementation(deps.chucker)
|
||||
debugPGImplementation(deps.chucker)
|
||||
mockedImplementation(deps.chuckerStub)
|
||||
externalImplementation(deps.chuckerStub)
|
||||
internalImplementation(deps.chuckerStub)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ sealed class ApiConfig {
|
|||
|
||||
companion object {
|
||||
internal const val DEBUG_BUILD_TYPE = "debug"
|
||||
internal const val DEBUG_PG_BUILD_TYPE = "debugPG"
|
||||
internal const val INTERNAL_BUILD_TYPE = "internal"
|
||||
internal const val MOCKED_BUILD_TYPE = "mocked"
|
||||
internal const val EXTERNAL_BUILD_TYPE = "external"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,21 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Api environment
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class ApiEnvironment {
|
||||
DEV, STAGE, PROD
|
||||
@Json(name = "DEV")
|
||||
DEV,
|
||||
|
||||
@Json(name = "STAGE")
|
||||
STAGE,
|
||||
|
||||
@Json(name = "PROD")
|
||||
PROD,
|
||||
}
|
||||
|
|
@ -68,7 +68,9 @@ internal class Express(
|
|||
|
||||
fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
DEBUG_BUILD_TYPE -> ApiEnvironment.DEV
|
||||
DEBUG_BUILD_TYPE,
|
||||
DEBUG_PG_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV
|
||||
INTERNAL_BUILD_TYPE,
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.STAGE
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
|
||||
internal class TangemVisa(
|
||||
private val authProvider: TangemVisaAuthProvider,
|
||||
) : ApiConfig() {
|
||||
internal class TangemVisa : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
||||
|
||||
|
|
@ -19,7 +16,5 @@ internal class TangemVisa(
|
|||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createHeaders() = mapOf(
|
||||
"Authorization" to ProviderSuspend { authProvider.getAuthHeader() },
|
||||
)
|
||||
private fun createHeaders() = mapOf<String, ProviderSuspend<String>>()
|
||||
}
|
||||
|
|
@ -2,5 +2,5 @@ package com.tangem.datasource.api.common.visa
|
|||
|
||||
interface TangemVisaAuthProvider {
|
||||
|
||||
suspend fun getAuthHeader(): String
|
||||
suspend fun getAuthHeader(cardId: String): String
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.express.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AssetsRequestBody(
|
||||
@Json(name = "tokensList") val tokensList: List<LeastTokenInfo>?,
|
||||
)
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.express.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeSentRequestBody(
|
||||
@Json(name = "txId")
|
||||
val txId: String,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.express.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LeastTokenInfo(
|
||||
@Json(name = "contractAddress")
|
||||
val contractAddress: String,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.express.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PairsRequestBody(
|
||||
@Json(name = "from")
|
||||
val from: List<LeastTokenInfo>,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Asset(
|
||||
@Json(name = "contractAddress")
|
||||
val contractAddress: String,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeDataResponseWithTxDetails(
|
||||
@Json(name = "dataResponse")
|
||||
val dataResponse: ExchangeDataResponse,
|
||||
@Json(name = "txDetails")
|
||||
val txDetails: TxDetails,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeDataResponse(
|
||||
@Json(name = "fromAmount")
|
||||
val fromAmount: String,
|
||||
|
|
@ -29,6 +35,7 @@ data class ExchangeDataResponse(
|
|||
val signature: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TxDetails(
|
||||
@Json(name = "payoutAddress")
|
||||
val payoutAddress: String,
|
||||
|
|
@ -70,6 +77,7 @@ data class TxDetails(
|
|||
val gas: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class TxType {
|
||||
@Json(name = "send")
|
||||
SEND,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeProvider(
|
||||
@Json(name = "id")
|
||||
val id: String,
|
||||
|
|
@ -32,6 +34,7 @@ data class ExchangeProvider(
|
|||
val slippage: BigDecimal?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class ExchangeProviderType {
|
||||
@Json(name = "dex")
|
||||
DEX,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeQuoteResponse(
|
||||
|
||||
@Json(name = "fromAmount")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeSentResponseBody(
|
||||
@Json(name = "txId")
|
||||
val txId: String,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeStatusResponse(
|
||||
|
||||
@Json(name = "providerId")
|
||||
|
|
@ -26,6 +28,7 @@ data class ExchangeStatusResponse(
|
|||
val refundContractAddress: String? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class ExchangeStatus {
|
||||
|
||||
@Json(name = "new")
|
||||
|
|
@ -71,6 +74,7 @@ enum class ExchangeStatus {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExchangeStatusError(
|
||||
@Json(name = "code")
|
||||
val code: Int,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExpressErrorResponse(
|
||||
@Json(name = "error")
|
||||
val error: ExpressError,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExpressError(
|
||||
@Json(name = "code")
|
||||
val code: Int,
|
||||
|
|
@ -19,6 +22,7 @@ data class ExpressError(
|
|||
val value: ExpressErrorValue?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExpressErrorValue(
|
||||
@Json(name = "minAmount")
|
||||
val minAmount: String?,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.datasource.api.express.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SwapPair(
|
||||
@Json(name = "from")
|
||||
val from: LeastTokenInfo,
|
||||
|
|
@ -15,6 +17,7 @@ data class SwapPair(
|
|||
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SwapPairProvider(
|
||||
@Json(name = "providerId")
|
||||
val providerId: String,
|
||||
|
|
@ -23,6 +26,7 @@ data class SwapPairProvider(
|
|||
val rateTypes: List<RateType>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class RateType {
|
||||
@Json(name = "float")
|
||||
FLOAT,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ package com.tangem.datasource.api.onramp.models.response
|
|||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnrampDataResponseWithTxDetails(
|
||||
@Json(name = "dataResponse")
|
||||
val dataResponse: OnrampDataResponse,
|
||||
@Json(name = "txDetails")
|
||||
val txDetails: OnrampTxDetails,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ data class OnrampStatusResponse(
|
|||
val countryCode: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Status {
|
||||
@Json(name = "created")
|
||||
Created,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.stakekit.models.request.ConstructTransactionRequestBody.GasArgs
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PendingActionRequestBody(
|
||||
@Json(name = "type")
|
||||
val type: StakingActionType,
|
||||
|
|
@ -19,6 +21,7 @@ data class PendingActionRequestBody(
|
|||
val gasArgs: GasArgs? = null, // used only in estimate_gas request
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ActionRequestBody(
|
||||
@Json(name = "integrationId")
|
||||
val integrationId: String,
|
||||
|
|
@ -32,6 +35,7 @@ data class ActionRequestBody(
|
|||
val gasArgs: GasArgs? = null, // used only in estimate_gas request
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ActionRequestBodyArgs(
|
||||
@Json(name = "amount")
|
||||
val amount: String,
|
||||
|
|
@ -55,6 +59,7 @@ data class ActionRequestBodyArgs(
|
|||
val inputToken: TokenDTO? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SignatureVerification(
|
||||
@Json(name = "message")
|
||||
val message: String,
|
||||
|
|
@ -62,6 +67,7 @@ data class SignatureVerification(
|
|||
val signed: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class TronResource {
|
||||
@Json(name = "ENERGY")
|
||||
ENERGY,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Address(
|
||||
@Json(name = "address")
|
||||
val address: String,
|
||||
|
|
@ -11,6 +13,7 @@ data class Address(
|
|||
val explorerUrl: String? = null,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AdditionalAddresses(
|
||||
// cosmos-specific
|
||||
@Json(name = "cosmosPubKey")
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ConstructTransactionRequestBody(
|
||||
@Json(name = "gasArgs")
|
||||
val gasArgs: GasArgs? = null,
|
||||
@Json(name = "ledgerWalletAPICompatible")
|
||||
val ledgerWalletAPICompatible: Boolean? = null,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GasArgs(
|
||||
// cosmos-specific
|
||||
@Json(name = "gasPrice")
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MultipleYieldBalancesRequestBody(
|
||||
@Json(name = "addresses") val addresses: Address,
|
||||
@Json(name = "args") val args: MultipleYieldBalancesRequestArgs,
|
||||
@Json(name = "integrationId") val integrationId: String,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Address(
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "additionalAddresses") val additionalAddresses: AdditionalAddresses? = null,
|
||||
@Json(name = "explorerUrl") val explorerUrl: String,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AdditionalAddresses(
|
||||
@Json(name = "cosmosPubKey") val cosmosPubKey: String? = null,
|
||||
@Json(name = "binanceBeaconAddress") val binanceBeaconAddress: String? = null,
|
||||
|
|
@ -25,6 +29,7 @@ data class MultipleYieldBalancesRequestBody(
|
|||
)
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MultipleYieldBalancesRequestArgs(
|
||||
@Json(name = "validatorAddresses") val validatorAddresses: List<String>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class RevenueOption(val value: String) {
|
||||
SUPPORTS_FEE("supportsFee"),
|
||||
SUPPORTS_REV_SHARE("supportsRevShare"),
|
||||
@Json(name = "SUPPORTS_FEE") SUPPORTS_FEE("supportsFee"),
|
||||
|
||||
@Json(name = "SUPPORTS_REV_SHARE") SUPPORTS_REV_SHARE("supportsRevShare"),
|
||||
;
|
||||
|
||||
override fun toString(): String {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SubmitTransactionHashRequestBody(
|
||||
@Json(name = "hash")
|
||||
val hash: String,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class YieldBalanceRequestBody(
|
||||
@Json(name = "addresses") val addresses: Address,
|
||||
@Json(name = "args") val args: YieldBalanceRequestArgs,
|
||||
@Json(name = "integrationId") val integrationId: String,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class YieldBalanceRequestArgs(
|
||||
@Json(name = "validatorAddresses") val validatorAddresses: List<String>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class NetworkTypeDTO {
|
||||
@Json(name = "avalanche-c")
|
||||
AVALANCHE_C,
|
||||
|
|
@ -201,5 +203,8 @@ enum class NetworkTypeDTO {
|
|||
@Json(name = "tron")
|
||||
TRON,
|
||||
|
||||
@Json(name = "ton")
|
||||
TON,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ data class BalanceDTO(
|
|||
val providerId: String?,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class BalanceTypeDTO {
|
||||
@Json(name = "available")
|
||||
AVAILABLE,
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ data class YieldDTO(
|
|||
@Json(name = "preferred")
|
||||
val preferred: Boolean?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class ValidatorStatusDTO {
|
||||
@Json(name = "active")
|
||||
ACTIVE,
|
||||
|
|
@ -99,6 +99,9 @@ data class YieldDTO(
|
|||
@Json(name = "jailed")
|
||||
JAILED,
|
||||
|
||||
@Json(name = "full")
|
||||
FULL,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
|
@ -153,6 +156,7 @@ data class YieldDTO(
|
|||
val enabled: Boolean?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class RewardScheduleDTO {
|
||||
@Json(name = "block")
|
||||
BLOCK,
|
||||
|
|
@ -178,6 +182,7 @@ data class YieldDTO(
|
|||
UNKNOWN,
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class RewardClaimingDTO {
|
||||
@Json(name = "auto")
|
||||
AUTO,
|
||||
|
|
@ -189,6 +194,7 @@ data class YieldDTO(
|
|||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class RewardTypeDTO {
|
||||
@Json(name = "apy")
|
||||
APY, // compound rate
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.action
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class StakingActionStatusDTO {
|
||||
@Json(name = "CANCELED")
|
||||
CANCELED,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.action
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class StakingActionTypeDTO {
|
||||
@Json(name = "STAKE")
|
||||
STAKE,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.error
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class StakeKitErrorResponse(
|
||||
@Json(name = "message")
|
||||
val message: String? = null,
|
||||
|
|
@ -26,6 +28,7 @@ class StakeKitErrorResponse(
|
|||
val tags: List<String>? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class StakeKitErrorDetailsDTO(
|
||||
@Json(name = "arguments")
|
||||
val arguments: String? = null,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.transaction
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class StakingTransactionStatusDTO {
|
||||
@Json(name = "NOT_FOUND")
|
||||
NOT_FOUND,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.transaction
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class StakingTransactionTypeDTO {
|
||||
@Json(name = "SWAP")
|
||||
SWAP,
|
||||
|
|
|
|||
|
|
@ -122,6 +122,9 @@ interface TangemTechApi {
|
|||
@Body body: SeedPhraseNotificationDTO,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@GET("hot_crypto")
|
||||
suspend fun getHotCrypto(@Query("currency") currencyId: String): ApiResponse<HotCryptoResponse>
|
||||
|
||||
companion object {
|
||||
val marketsQuoteFields = listOf(
|
||||
"price",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CoinsResponse(
|
||||
@Json(name = "imageHost") val imageHost: String?,
|
||||
@Json(name = "coins") val coins: List<Coin>,
|
||||
@Json(name = "total") val total: Int,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Coin(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String,
|
||||
|
|
@ -17,6 +20,7 @@ data class CoinsResponse(
|
|||
@Json(name = "networks") val networks: List<Network> = listOf(),
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Network(
|
||||
@Json(name = "networkId") val networkId: String,
|
||||
@Json(name = "contractAddress") val contractAddress: String? = null,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CreateUserTokensAccountBody(
|
||||
@Json(name = "accountId")
|
||||
val id: Int,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ data class CurrenciesResponse(
|
|||
@Json(name = "rateBTC") val rateBTC: String,
|
||||
@Json(name = "unit") val unit: String, // $, €, ₽
|
||||
@Json(name = "type") val type: String,
|
||||
val iconSmallUrl: String? = null,
|
||||
val iconMediumUrl: String? = null,
|
||||
@Json(name = "iconSmallUrl") val iconSmallUrl: String? = null,
|
||||
@Json(name = "iconMediumUrl") val iconMediumUrl: String? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class FeaturesResponse(
|
||||
@Json(name = "send") val isNewSendEnabled: Boolean,
|
||||
)
|
||||
|
|
@ -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
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class QuotesResponse(
|
||||
@Json(name = "quotes")
|
||||
val quotes: Map<String, Quote>,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Quote(
|
||||
@Json(name = "price")
|
||||
val price: BigDecimal?,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
// rates.keys = networkId's
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RatesResponse(
|
||||
@Json(name = "rates") val rates: Map<String, Double>,
|
||||
)
|
||||
|
|
@ -1,30 +1,36 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.joda.time.LocalDate
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReferralResponse(
|
||||
@Json(name = "conditions") val conditions: Conditions,
|
||||
@Json(name = "referral") val referral: Referral?,
|
||||
@Json(name = "expectedAwards") val expectedAwards: ExpectedAwards?,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Conditions(
|
||||
@Json(name = "discount") val discount: Discount,
|
||||
@Json(name = "awards") val awards: List<Award>,
|
||||
@Json(name = "tosLink") val tosLink: String,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Discount(
|
||||
@Json(name = "amount") val amount: Int,
|
||||
@Json(name = "type") val discountType: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Award(
|
||||
@Json(name = "amount") val amount: Int,
|
||||
@Json(name = "token") val token: Token,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Token(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String,
|
||||
|
|
@ -36,6 +42,7 @@ data class ReferralResponse(
|
|||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Referral(
|
||||
@Json(name = "shareLink") val shareLink: String,
|
||||
@Json(name = "address") val address: String,
|
||||
|
|
@ -44,11 +51,13 @@ data class ReferralResponse(
|
|||
@Json(name = "termsAcceptedAt") val termsAcceptedAt: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExpectedAwards(
|
||||
@Json(name = "numberOfWallets") val numberOfWallets: Int,
|
||||
@Json(name = "list") val list: List<AwardItem>,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AwardItem(
|
||||
@Json(name = "currency") val currency: String,
|
||||
@Json(name = "paymentDate") val paymentDate: LocalDate,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Sales response
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SalesResponse(
|
||||
@Json(name = "sales") val sales: List<Sales>,
|
||||
)
|
||||
|
|
@ -17,6 +19,7 @@ data class SalesResponse(
|
|||
* @property product product that is sales
|
||||
* @property notification optional notification for product
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Sales(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "state") val state: String,
|
||||
|
|
@ -31,12 +34,14 @@ data class Sales(
|
|||
* @property code code that shows what product it is
|
||||
* @property name product name
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Product(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "code") val code: String,
|
||||
@Json(name = "name") val name: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Notification(
|
||||
@Json(name = "type") val type: String,
|
||||
@Json(name = "title") val title: String,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Shop response
|
||||
|
|
@ -9,6 +10,7 @@ import com.squareup.moshi.Json
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ShopResponse(
|
||||
@Json(name = "canOrder") val isOrderingAvailable: Boolean,
|
||||
)
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/** Body for make user referral */
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class StartReferralBody(
|
||||
@Json(name = "walletId") val walletId: String,
|
||||
@Json(name = "networkId") val networkId: String,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UpdateUserTokensAccountBody(
|
||||
@Json(name = "accountTitle")
|
||||
val title: String,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UserTokensAccountResponse(
|
||||
@Json(name = "accountId")
|
||||
val id: Int,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.common.extensions.calculateHashCode
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UserTokensResponse(
|
||||
@Json(name = "version") val version: Int = 0,
|
||||
@Json(name = "group") val group: GroupType,
|
||||
|
|
@ -10,6 +12,7 @@ data class UserTokensResponse(
|
|||
@Json(name = "tokens") val tokens: List<Token> = emptyList(),
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Token(
|
||||
@Json(name = "id") val id: String? = null,
|
||||
@Json(name = "networkId") val networkId: String,
|
||||
|
|
@ -36,6 +39,7 @@ data class UserTokensResponse(
|
|||
)
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class GroupType {
|
||||
@Json(name = "none")
|
||||
NONE,
|
||||
|
|
@ -47,6 +51,7 @@ data class UserTokensResponse(
|
|||
NETWORK,
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class SortType {
|
||||
@Json(name = "balance")
|
||||
BALANCE,
|
||||
|
|
|
|||
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