Updated on 2026-08-14
This commit is contained in:
commit
c8024b166d
656 changed files with 18270 additions and 4656 deletions
|
|
@ -109,6 +109,8 @@ dependencies {
|
|||
implementation(projects.domain.promo)
|
||||
implementation(projects.domain.promo.models)
|
||||
implementation(projects.domain.networks)
|
||||
implementation(projects.domain.quotes)
|
||||
implementation(projects.domain.notifications)
|
||||
|
||||
implementation(projects.common)
|
||||
implementation(projects.common.routing)
|
||||
|
|
@ -152,6 +154,7 @@ dependencies {
|
|||
implementation(projects.data.nft)
|
||||
implementation(projects.data.onramp)
|
||||
implementation(projects.data.networks)
|
||||
implementation(projects.data.quotes)
|
||||
|
||||
/** Features */
|
||||
implementation(projects.features.onboarding)
|
||||
|
|
@ -273,6 +276,7 @@ dependencies {
|
|||
implementation(deps.reKotlin)
|
||||
implementation(deps.zxing.qrCore)
|
||||
implementation(deps.coil)
|
||||
implementation(deps.coil.gif)
|
||||
implementation(deps.amplitude)
|
||||
implementation(deps.kotsonGson)
|
||||
implementation(deps.spongecastle.core)
|
||||
|
|
@ -318,6 +322,7 @@ dependencies {
|
|||
/** Chucker */
|
||||
debugImplementation(deps.chucker)
|
||||
mockedImplementation(deps.chuckerStub)
|
||||
externalImplementation(deps.chuckerStub)
|
||||
internalImplementation(deps.chuckerStub)
|
||||
releaseImplementation(deps.chuckerStub)
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 29f73d909e14cb3760118b197b1929a6088bc40e
|
||||
Subproject commit 7a607593e34f0977474613f077532215eaf0ffe6
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.app.Application
|
||||
import android.os.StrictMode
|
||||
import android.os.StrictMode.ThreadPolicy
|
||||
import android.os.StrictMode.VmPolicy
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.Configuration
|
||||
import coil.ImageLoader
|
||||
|
|
@ -71,9 +74,7 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.EntryPoints
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.*
|
||||
import org.rekotlin.Store
|
||||
import kotlin.collections.set
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository
|
||||
|
|
@ -228,12 +229,32 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
|
||||
// endregion
|
||||
|
||||
private val appScope = MainScope()
|
||||
|
||||
override fun onCreate() {
|
||||
enableStrictModeInDebug()
|
||||
super.onCreate()
|
||||
|
||||
init()
|
||||
}
|
||||
|
||||
updateLogFiles()
|
||||
private fun enableStrictModeInDebug() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setThreadPolicy(
|
||||
ThreadPolicy.Builder()
|
||||
.detectDiskReads()
|
||||
.detectDiskWrites()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build(),
|
||||
)
|
||||
StrictMode.setVmPolicy(
|
||||
VmPolicy.Builder()
|
||||
.detectLeakedSqlLiteObjects()
|
||||
.detectLeakedClosableObjects()
|
||||
.penaltyLog()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLogFiles() {
|
||||
|
|
@ -260,18 +281,31 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
foregroundActivityObserver = ForegroundActivityObserver()
|
||||
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
|
||||
|
||||
// TODO: Try to performance and user experience.
|
||||
// [REDACTED_JIRA]
|
||||
// We need to initialize the toggles and excludedBlockchainsManager before the MainActivity starts using them.
|
||||
runBlocking {
|
||||
awaitAll(
|
||||
async { featureTogglesManager.init() },
|
||||
async { excludedBlockchainsManager.init() },
|
||||
async { initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize()) },
|
||||
async {
|
||||
featureTogglesManager.init()
|
||||
},
|
||||
async {
|
||||
excludedBlockchainsManager.init()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
loadNativeLibraries()
|
||||
appScope.launch {
|
||||
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
|
||||
launch(Dispatchers.IO) {
|
||||
loadNativeLibraries()
|
||||
walletConnect2Repository.init(
|
||||
projectId = environmentConfigStorage.getConfigSync().walletConnectProjectId,
|
||||
)
|
||||
updateLogFiles()
|
||||
}
|
||||
}
|
||||
|
||||
ExceptionHandler.append(blockchainExceptionHandler)
|
||||
|
||||
if (LogConfig.network.blockchainSdkNetwork) {
|
||||
BlockchainSdkRetrofitBuilder.interceptors = listOf(
|
||||
createNetworkLoggingInterceptor(),
|
||||
|
|
@ -287,9 +321,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
appStateHolder.mainStore = store
|
||||
|
||||
walletConnect2Repository.init(projectId = environmentConfigStorage.getConfigSync().walletConnectProjectId)
|
||||
appStateHolder.mainStore = store
|
||||
}
|
||||
|
||||
private fun createReduxStore(): Store<AppState> {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.tap.common.images
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import coil.ImageLoader
|
||||
import coil.decode.GifDecoder
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.memory.MemoryCache
|
||||
import coil.request.CachePolicy
|
||||
import coil.util.Logger
|
||||
|
|
@ -25,6 +28,15 @@ fun createCoilImageLoader(context: Context, logEnabled: Boolean = false): ImageL
|
|||
.build()
|
||||
}
|
||||
}
|
||||
.components {
|
||||
// According to Coil ImageDecoder API is faster and supports animated WebP and HEIF
|
||||
// https://coil-kt.github.io/coil/gifs/
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
add(ImageDecoderDecoder.Factory())
|
||||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
}
|
||||
}
|
||||
.memoryCachePolicy(CachePolicy.ENABLED)
|
||||
.memoryCache {
|
||||
MemoryCache.Builder(context)
|
||||
|
|
|
|||
|
|
@ -20,18 +20,22 @@ internal class DefaultVisaAuthTokenStorage @Inject constructor(
|
|||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : VisaAuthTokenStorage {
|
||||
|
||||
private val secureStorage = AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "visa_auth_storage",
|
||||
),
|
||||
)
|
||||
private val secureStorage by lazy {
|
||||
AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "visa_auth_storage",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private val moshi = Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
private val moshi by lazy {
|
||||
Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
}
|
||||
|
||||
private val tokensAdapter = moshi.adapter(VisaAuthTokens::class.java)
|
||||
private val tokensAdapter by lazy { moshi.adapter(VisaAuthTokens::class.java) }
|
||||
|
||||
override suspend fun store(cardId: String, tokens: VisaAuthTokens) = withContext(dispatcherProvider.io) {
|
||||
val json = tokensAdapter.toJson(tokens)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@ class DefaultVisaOTPStorage @Inject constructor(
|
|||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : VisaOTPStorage {
|
||||
|
||||
private val secureStorage = AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "visa_otp_storage",
|
||||
),
|
||||
)
|
||||
private val secureStorage by lazy {
|
||||
AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "visa_otp_storage",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun saveOTP(cardId: String, data: VisaOtpData) = withContext(dispatcherProvider.io) {
|
||||
secureStorage.store(data.rootOTP, VISA_ROOT_OTP_KEY_PREFIX + cardId)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ internal class RuntimeUserWalletsStore(
|
|||
return userWalletsListManager.userWalletsSync.firstOrNull { it.walletId == key }
|
||||
}
|
||||
|
||||
override suspend fun getSyncStrict(key: UserWalletId): UserWallet {
|
||||
override fun getSyncStrict(key: UserWalletId): UserWallet {
|
||||
return requireNotNull(getSyncOrNull(key)) { "Unable to find user wallet with provided ID: $key" }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.domain.managetokens.*
|
|||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -73,6 +74,7 @@ internal object ManageTokensDomainModule {
|
|||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): SaveManagedTokensUseCase {
|
||||
return SaveManagedTokensUseCase(
|
||||
|
|
@ -84,6 +86,7 @@ internal object ManageTokensDomainModule {
|
|||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import com.tangem.domain.card.repository.DerivationsRepository
|
|||
import com.tangem.domain.markets.*
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -49,8 +51,16 @@ object MarketsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetTokenQuotesUseCase(quotesRepository: QuotesRepository): GetCurrencyQuotesUseCase {
|
||||
return GetCurrencyQuotesUseCase(quotesRepository = quotesRepository)
|
||||
fun provideGetTokenQuotesUseCase(
|
||||
quotesRepository: QuotesRepository,
|
||||
singleQuoteSupplier: SingleQuoteSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): GetCurrencyQuotesUseCase {
|
||||
return GetCurrencyQuotesUseCase(
|
||||
quotesRepository = quotesRepository,
|
||||
singleQuoteSupplier = singleQuoteSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -63,6 +73,7 @@ object MarketsDomainModule {
|
|||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): SaveMarketTokensUseCase {
|
||||
return SaveMarketTokensUseCase(
|
||||
|
|
@ -73,6 +84,7 @@ object MarketsDomainModule {
|
|||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,16 @@ internal object NFTDomainModule {
|
|||
nftRepository = nftRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesRefreshAllUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
): RefreshAllNFTUseCase = RefreshAllNFTUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesFetchNFTCollectionAssetsUseCase(nftRepository: NFTRepository): FetchNFTCollectionAssetsUseCase =
|
||||
|
|
@ -47,7 +57,7 @@ internal object NFTDomainModule {
|
|||
fun providesGetNFTAvailableNetworksUseCase(
|
||||
nftRepository: NFTRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): GetNFTAvailableNetworksUseCase = GetNFTAvailableNetworksUseCase(
|
||||
): GetNFTNetworksUseCase = GetNFTNetworksUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
|
|
@ -66,4 +76,11 @@ internal object NFTDomainModule {
|
|||
GetNFTNetworkStatusUseCase(
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetNFTExploreUrlUseCase(nftRepository: NFTRepository): GetNFTExploreUrlUseCase =
|
||||
GetNFTExploreUrlUseCase(
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.notifications.GetApplicationIdUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object NotificationsDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetApplicationIdUseCase(notificationsRepository: NotificationsRepository): GetApplicationIdUseCase =
|
||||
GetApplicationIdUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
)
|
||||
}
|
||||
|
|
@ -7,7 +7,11 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
|||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.quotes.QuotesRepositoryV2
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.operations.BaseCurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
|
|
@ -35,6 +39,7 @@ internal object TokensDomainModule {
|
|||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): AddCryptoCurrenciesUseCase {
|
||||
return AddCryptoCurrenciesUseCase(
|
||||
|
|
@ -43,6 +48,7 @@ internal object TokensDomainModule {
|
|||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -55,6 +61,7 @@ internal object TokensDomainModule {
|
|||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): FetchTokenListUseCase {
|
||||
return FetchTokenListUseCase(
|
||||
|
|
@ -63,6 +70,7 @@ internal object TokensDomainModule {
|
|||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -168,6 +176,7 @@ internal object TokensDomainModule {
|
|||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): FetchCurrencyStatusUseCase {
|
||||
return FetchCurrencyStatusUseCase(
|
||||
|
|
@ -176,6 +185,7 @@ internal object TokensDomainModule {
|
|||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -188,6 +198,7 @@ internal object TokensDomainModule {
|
|||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): FetchCardTokenListUseCase {
|
||||
return FetchCardTokenListUseCase(
|
||||
|
|
@ -196,6 +207,7 @@ internal object TokensDomainModule {
|
|||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -364,10 +376,14 @@ internal object TokensDomainModule {
|
|||
fun provideRefreshMultiCurrencyWalletQuotesUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): RefreshMultiCurrencyWalletQuotesUseCase {
|
||||
return RefreshMultiCurrencyWalletQuotesUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -386,20 +402,28 @@ internal object TokensDomainModule {
|
|||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
quotesRepositoryV2: QuotesRepositoryV2,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
singleQuoteSupplier: SingleQuoteSupplier,
|
||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
): BaseCurrenciesStatusesOperations {
|
||||
return CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
quotesRepositoryV2 = quotesRepositoryV2,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
singleQuoteSupplier = singleQuoteSupplier,
|
||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -410,21 +434,35 @@ internal object TokensDomainModule {
|
|||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
quotesRepositoryV2: QuotesRepositoryV2,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
singleQuoteSupplier: SingleQuoteSupplier,
|
||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
): BaseCurrencyStatusOperations {
|
||||
return CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
quotesRepositoryV2 = quotesRepositoryV2,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
singleQuoteSupplier = singleQuoteSupplier,
|
||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
|
||||
return GetCryptoCurrenciesUseCase(currenciesRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,21 @@ internal object TransactionDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetEthSpecificFeeUseCase(walletManagersFacade: WalletManagersFacade): GetEthSpecificFeeUseCase {
|
||||
return GetEthSpecificFeeUseCase(walletManagersFacade = walletManagersFacade)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTransferGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetTransferFeeUseCase {
|
||||
return GetTransferFeeUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
demoConfig = DemoConfig(),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSendTransactionUseCase(
|
||||
|
|
@ -147,4 +162,30 @@ internal object TransactionDomainModule {
|
|||
fun provideGetAllowanceUseCase(transactionRepository: TransactionRepository): GetAllowanceUseCase {
|
||||
return GetAllowanceUseCase(transactionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCreateTransferTransactionUseCase(
|
||||
transactionRepository: TransactionRepository,
|
||||
): CreateTransferTransactionUseCase {
|
||||
return CreateTransferTransactionUseCase(transactionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePrepareForSendUseCase(
|
||||
transactionRepository: TransactionRepository,
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
): PrepareForSendUseCase {
|
||||
return PrepareForSendUseCase(transactionRepository, cardSdkConfigRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSignUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
): SignUseCase {
|
||||
return SignUseCase(cardSdkConfigRepository, walletManagersFacade)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
|
||||
import com.tangem.domain.walletconnect.repository.WalletConnectRepository
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.WcSessionsUseCase
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -19,4 +21,10 @@ internal object WalletConnectDomainModule {
|
|||
): CheckIsWalletConnectAvailableUseCase {
|
||||
return CheckIsWalletConnectAvailableUseCase(walletConnectRepository = walletConnectRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWcSessionsUseCase(sessionsManager: WcSessionsManager): WcSessionsUseCase {
|
||||
return WcSessionsUseCase(sessionsManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSy
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -172,8 +174,16 @@ internal object WalletsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCardImageUseCase(onlineCardVerifier: OnlineCardVerifier): GetCardImageUseCase {
|
||||
return GetCardImageUseCase(verifier = onlineCardVerifier)
|
||||
fun provideGetCardImageUseCase(
|
||||
onlineCardVerifier: OnlineCardVerifier,
|
||||
cardArtworksProvider: CardArtworksProvider,
|
||||
cardSdkFeatureToggles: CardSdkFeatureToggles,
|
||||
): GetCardImageUseCase {
|
||||
return GetCardImageUseCase(
|
||||
verifier = onlineCardVerifier,
|
||||
cardArtworksProvider = cardArtworksProvider,
|
||||
cardSdkFeatureToggles = cardSdkFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.common.TwinCardNumber
|
|||
import com.tangem.domain.common.getTwinCardNumber
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ suspend fun CardDTO.getOrLoadCardArtworkUrl(
|
|||
if (artworkId.isNullOrEmpty()) {
|
||||
ifAnyError()
|
||||
} else {
|
||||
OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
|
||||
CardArtworksProvider.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,12 @@ import arrow.core.Either
|
|||
import arrow.core.getOrElse
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.core.*
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.map
|
||||
import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource
|
||||
|
|
@ -46,7 +44,7 @@ import timber.log.Timber
|
|||
import kotlin.coroutines.resume
|
||||
import kotlin.time.measureTimedValue
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
class VisaCardActivationTask @AssistedInject constructor(
|
||||
@Assisted private val mode: VisaCardActivationTaskMode,
|
||||
@Assisted private val activationInput: VisaActivationInput,
|
||||
|
|
@ -97,7 +95,23 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
context.signAuthorizationChallenge(mode.authorizationChallenge)
|
||||
}
|
||||
is VisaCardActivationTaskMode.SignOnly -> {
|
||||
context.signData(mode.dataToSignByCardWallet)
|
||||
val wallet =
|
||||
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val derivedPublicKey = when (val deriveKeyResult = context.deriveKey(wallet.publicKey)) {
|
||||
is CompletionResult.Failure -> {
|
||||
return CompletionResult.Failure(deriveKeyResult.error)
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
deriveKeyResult.data
|
||||
}
|
||||
}
|
||||
|
||||
context.signData(
|
||||
mode.dataToSignByCardWallet,
|
||||
derivedPublicKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -138,9 +152,35 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
private suspend fun SessionContext.processSignedAuthorizationChallenge(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
when (val createWalletResult = createWallet()) {
|
||||
is CompletionResult.Failure -> return CompletionResult.Failure(createWalletResult.error)
|
||||
is CompletionResult.Success -> {}
|
||||
}
|
||||
|
||||
val card =
|
||||
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val wallet =
|
||||
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val derivedPublicKey = when (val deriveKeyResult = deriveKey(wallet.publicKey)) {
|
||||
is CompletionResult.Failure -> {
|
||||
return CompletionResult.Failure(deriveKeyResult.error)
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
deriveKeyResult.data
|
||||
}
|
||||
}
|
||||
|
||||
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey)
|
||||
.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
||||
.value
|
||||
|
||||
return coroutineScope {
|
||||
val dataToSignDeferred = async { getDataToSign(signedChallenge) }
|
||||
val otpTaskDeferred = async { createWallet() }
|
||||
val dataToSignDeferred =
|
||||
async { getDataToSign(signedChallenge = signedChallenge, cardWalletAddress = walletAddress) }
|
||||
val otpTaskDeferred = async { createOTP() }
|
||||
|
||||
val dataToSign = dataToSignDeferred.await()
|
||||
.getOrElse {
|
||||
|
|
@ -150,12 +190,16 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
otpTaskDeferred.await()
|
||||
|
||||
signData(dataToSign)
|
||||
signData(
|
||||
dataToSign = dataToSign,
|
||||
derivedPublicKey = derivedPublicKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun SessionContext.getDataToSign(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
cardWalletAddress: String,
|
||||
): Either<TangemError, VisaDataToSignByCardWallet> = either {
|
||||
catch(
|
||||
block = {
|
||||
|
|
@ -168,7 +212,12 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
raise(VisaActivationError.WrongRemoteState.tangemError)
|
||||
}
|
||||
|
||||
visaActivationRepository.getCardWalletAcceptanceData(remoteState.request)
|
||||
visaActivationRepository.getCardWalletAcceptanceData(
|
||||
VisaCardWalletDataToSignRequest(
|
||||
activationOrderInfo = remoteState.activationOrderInfo,
|
||||
cardWalletAddress = cardWalletAddress,
|
||||
),
|
||||
)
|
||||
},
|
||||
catch = {
|
||||
raise(VisaAuthorizationAPIError.tangemError)
|
||||
|
|
@ -182,7 +231,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
return if (card.wallets.any { it.curve == EllipticCurve.Secp256k1 }) {
|
||||
createOTP()
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
val createWalletTask = CreateWalletTask(EllipticCurve.Secp256k1)
|
||||
|
||||
|
|
@ -199,7 +248,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
when (val result = timedResult.value) {
|
||||
is CompletionResult.Success -> {
|
||||
Timber.i("CreateWalletTask success")
|
||||
createOTP()
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("CreateWalletTask failure ${result.error}")
|
||||
|
|
@ -245,26 +294,15 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
private suspend fun SessionContext.signData(
|
||||
dataToSign: VisaDataToSignByCardWallet,
|
||||
derivedPublicKey: ExtendedPublicKey,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val card =
|
||||
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val wallet =
|
||||
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val derivedPublicKey = when (val deriveKeyResult = deriveKey(wallet.publicKey)) {
|
||||
is CompletionResult.Failure -> {
|
||||
return CompletionResult.Failure(deriveKeyResult.error)
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
deriveKeyResult.data
|
||||
}
|
||||
}
|
||||
|
||||
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey)
|
||||
.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
||||
.value
|
||||
|
||||
val task = SignHashCommand(
|
||||
hash = dataToSign.hashToSign.hexToBytes(),
|
||||
walletPublicKey = wallet.publicKey,
|
||||
|
|
@ -286,8 +324,8 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
Timber.i("SignHashCommand success")
|
||||
handleSignedData(
|
||||
dataToSign = dataToSign,
|
||||
walletAddress = walletAddress,
|
||||
response = result.data,
|
||||
derivedPublicKey = derivedPublicKey,
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -313,7 +351,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
private suspend fun SessionContext.handleSignedData(
|
||||
dataToSign: VisaDataToSignByCardWallet,
|
||||
walletAddress: String,
|
||||
derivedPublicKey: ExtendedPublicKey,
|
||||
response: SignHashResponse,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val otp = otpStorage.getOTP(cardId) ?: run {
|
||||
|
|
@ -321,11 +359,16 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
otpStorage.getOTP(cardId) ?: return CompletionResult.Failure(VisaActivationError.MissingRootOTP.tangemError)
|
||||
}
|
||||
|
||||
val rsvSignature = EthereumUtils.prepareSignedMessageData(
|
||||
signedHash = response.signature,
|
||||
hashToSign = dataToSign.hashToSign.hexToBytes(),
|
||||
publicKey = derivedPublicKey.publicKey.toDecompressedPublicKey(),
|
||||
)
|
||||
|
||||
val signedActivationData = dataToSign.sign(
|
||||
cardWalletAddress = walletAddress,
|
||||
rootOTP = otp.rootOTP.toHexString(),
|
||||
otpCounter = otp.counter,
|
||||
signature = response.signature.toHexString(),
|
||||
signature = rsvSignature,
|
||||
)
|
||||
|
||||
return setupAccessCode().map {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
|
|
@ -10,7 +11,7 @@ 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.extensions.toDecompressedPublicKey
|
||||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
|
|
@ -129,6 +130,7 @@ class VisaCustomerWalletApproveTask(
|
|||
targetWalletPublicKey = wallet.publicKey,
|
||||
derivationPath = derivationPath,
|
||||
session = session,
|
||||
extendedPublicKey = extendedPublicKey,
|
||||
callback = callback,
|
||||
)
|
||||
}
|
||||
|
|
@ -149,6 +151,7 @@ class VisaCustomerWalletApproveTask(
|
|||
signApproveData(
|
||||
targetWalletPublicKey = publicKey,
|
||||
derivationPath = null,
|
||||
extendedPublicKey = null,
|
||||
session = session,
|
||||
callback = callback,
|
||||
)
|
||||
|
|
@ -157,11 +160,14 @@ class VisaCustomerWalletApproveTask(
|
|||
private fun signApproveData(
|
||||
targetWalletPublicKey: ByteArray,
|
||||
derivationPath: DerivationPath?,
|
||||
extendedPublicKey: ExtendedPublicKey?,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
val hashToSign = visaDataForApprove.dataToSign.hashToSign.hexToBytes()
|
||||
|
||||
val signTask = SignHashCommand(
|
||||
hash = visaDataForApprove.dataToSign.hashToSign.hexToBytes(),
|
||||
hash = hashToSign,
|
||||
walletPublicKey = targetWalletPublicKey,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
|
@ -169,11 +175,18 @@ class VisaCustomerWalletApproveTask(
|
|||
signTask.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val rsvSignature = EthereumUtils.prepareSignedMessageData(
|
||||
signedHash = result.data.signature,
|
||||
hashToSign = hashToSign,
|
||||
publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey()
|
||||
?: targetWalletPublicKey.toDecompressedPublicKey(),
|
||||
)
|
||||
|
||||
scanCard(
|
||||
session = session,
|
||||
callback = callback,
|
||||
signedData = visaDataForApprove.dataToSign.sign(
|
||||
signature = result.data.signature.toHexString(),
|
||||
signature = rsvSignature,
|
||||
customerWalletAddress = visaDataForApprove.targetAddress,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,4 +9,10 @@ internal class DefaultTokensFeatureToggles(
|
|||
|
||||
override val isNetworksLoadingRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "NETWORKS_LOADING_REFACTORING_ENABLED")
|
||||
|
||||
override val isQuotesLoadingRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "QUOTES_LOADING_REFACTORING_ENABLED")
|
||||
|
||||
override val isStakingLoadingRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_LOADING_REFACTORING_ENABLED")
|
||||
}
|
||||
|
|
@ -21,8 +21,6 @@ internal data class UserWalletPublicInformation(
|
|||
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")
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@ internal class DefaultUserWalletsPublicInformationRepository(
|
|||
moshi: Moshi,
|
||||
private val secureStorage: SecureStorage,
|
||||
) : UserWalletsPublicInformationRepository {
|
||||
private val publicInformationAdapter: JsonAdapter<List<UserWalletPublicInformation>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, UserWalletPublicInformation::class.java),
|
||||
)
|
||||
|
||||
private val publicInformationAdapter: JsonAdapter<List<UserWalletPublicInformation>> by lazy {
|
||||
moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, UserWalletPublicInformation::class.java),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
|
|
|
|||
|
|
@ -24,12 +24,15 @@ internal class DefaultUserWalletsSensitiveInformationRepository(
|
|||
private val secureStorage: SecureStorage,
|
||||
) : UserWalletsSensitiveInformationRepository {
|
||||
|
||||
private val sensitiveInformationAdapter: JsonAdapter<UserWalletSensitiveInformation> = moshi.adapter(
|
||||
UserWalletSensitiveInformation::class.java,
|
||||
)
|
||||
private val encryptedSensitiveInformationMapAdapter: JsonAdapter<Map<String, ByteArray>> = moshi.adapter(
|
||||
Types.newParameterizedType(Map::class.java, String::class.java, ByteArray::class.java),
|
||||
)
|
||||
private val sensitiveInformationAdapter: JsonAdapter<UserWalletSensitiveInformation> by lazy {
|
||||
moshi.adapter(UserWalletSensitiveInformation::class.java)
|
||||
}
|
||||
|
||||
private val encryptedSensitiveInformationMapAdapter: JsonAdapter<Map<String, ByteArray>> by lazy {
|
||||
moshi.adapter(
|
||||
Types.newParameterizedType(Map::class.java, String::class.java, ByteArray::class.java),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult<Unit> {
|
||||
if (encryptionKey == null) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation
|
|||
get() = UserWalletPublicInformation(
|
||||
name = name,
|
||||
walletId = walletId,
|
||||
artworkUrl = artworkUrl,
|
||||
cardsInWallet = cardsInWallet,
|
||||
isMultiCurrency = isMultiCurrency,
|
||||
scanResponse = scanResponse.copy(
|
||||
|
|
@ -31,7 +30,6 @@ internal fun UserWalletPublicInformation.toUserWallet(): UserWallet {
|
|||
return UserWallet(
|
||||
name = name,
|
||||
walletId = walletId,
|
||||
artworkUrl = artworkUrl,
|
||||
cardsInWallet = cardsInWallet,
|
||||
scanResponse = scanResponse,
|
||||
isMultiCurrency = isMultiCurrency,
|
||||
|
|
|
|||
|
|
@ -128,6 +128,8 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
|
||||
Timber.i("Requesting challenge for wallet authorization")
|
||||
val challengeResponse = runCatching {
|
||||
// TODO [REDACTED_TASK_KEY]
|
||||
error("sign and get specific error to switch to card_id flow")
|
||||
visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value)
|
||||
}.getOrElse {
|
||||
Timber.i(
|
||||
|
|
@ -180,6 +182,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
return CompletionResult.Success(VisaCardActivationStatus.Activated(authorizationTokensResponse))
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private suspend fun SessionContext.handleCardAuthorization(
|
||||
cardWalletAddress: String,
|
||||
): CompletionResult<VisaCardActivationStatus> {
|
||||
|
|
@ -232,11 +235,17 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
tokens = authorizationTokensResponse,
|
||||
)
|
||||
|
||||
val activationRemoteState = visaActivationRepository.getActivationRemoteState()
|
||||
val activationRemoteState = runCatching {
|
||||
visaActivationRepository.getActivationRemoteState()
|
||||
}.getOrElse {
|
||||
Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.message}")
|
||||
return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError)
|
||||
}
|
||||
|
||||
val error = when (activationRemoteState) {
|
||||
VisaActivationRemoteState.BlockedForActivation -> VisaActivationError.BlockedForActivation
|
||||
VisaActivationRemoteState.Activated -> VisaActivationError.InvalidActivationState
|
||||
VisaActivationRemoteState.Failed -> VisaActivationError.FailedRemoteState
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import com.tangem.Message
|
|||
import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.smartcontract.CompiledSmartContractCallData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.*
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
|
|
@ -16,6 +16,7 @@ import com.tangem.common.extensions.toHexString
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType
|
||||
import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
|
|
@ -101,7 +102,7 @@ class WalletConnectSdkHelper {
|
|||
sourceAddress = transaction.from,
|
||||
destinationAddress = destinationAddress,
|
||||
extras = EthereumTransactionExtras(
|
||||
data = transaction.data.removePrefix(HEX_PREFIX).hexToBytes(),
|
||||
callData = CompiledSmartContractCallData(transaction.data.removePrefix(HEX_PREFIX).hexToBytes()),
|
||||
gasLimit = gasLimit.toBigInteger(),
|
||||
nonce = transaction.nonce?.hexToBigDecimal()?.toBigInteger(),
|
||||
),
|
||||
|
|
@ -191,7 +192,7 @@ class WalletConnectSdkHelper {
|
|||
val gasLimitResult = (walletManager as? EthereumGasLoader)?.getGasLimit(
|
||||
amount = Amount(value, walletManager.wallet.blockchain),
|
||||
destination = transaction.to ?: "",
|
||||
data = transaction.data,
|
||||
callData = CompiledSmartContractCallData(transaction.data.hexToBytes()),
|
||||
)
|
||||
return when (gasLimitResult) {
|
||||
is Result.Success -> gasLimitResult.data.toBigDecimal().multiply(BigDecimal("1.2"))
|
||||
|
|
@ -357,36 +358,9 @@ class WalletConnectSdkHelper {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createMessageData(message: WcSignMessage): ByteArray {
|
||||
val messageData = try {
|
||||
message.data.removePrefix(HEX_PREFIX).hexToBytes()
|
||||
} catch (exception: Exception) {
|
||||
message.data.asciiToHex()?.hexToBytes() ?: byteArrayOf()
|
||||
}
|
||||
private fun createMessageData(message: WcSignMessage): ByteArray = LegacySdkHelper.createMessageData(message.data)
|
||||
|
||||
val prefixData = (ETH_MESSAGE_PREFIX + messageData.size.toString()).toByteArray()
|
||||
return (prefixData + messageData).toKeccak()
|
||||
}
|
||||
|
||||
private fun String.hexToAscii(): String? {
|
||||
return try {
|
||||
removePrefix(HEX_PREFIX).hexToBytes()
|
||||
.map {
|
||||
val char = it.toInt().toChar()
|
||||
if (char.isAscii()) char else return null
|
||||
}
|
||||
.joinToString("")
|
||||
} catch (exception: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.asciiToHex(): String? {
|
||||
return map {
|
||||
if (!it.isAscii()) return null
|
||||
Integer.toHexString(it.code)
|
||||
}.joinToString("")
|
||||
}
|
||||
private fun String.hexToAscii(): String? = LegacySdkHelper.hexToAscii(hex = this)
|
||||
|
||||
suspend fun signPersonalMessage(
|
||||
hashToSign: ByteArray,
|
||||
|
|
@ -535,7 +509,6 @@ class WalletConnectSdkHelper {
|
|||
"{\"signature\":\"$signature\",\"publicKey\":\"$publicKey\"}"
|
||||
|
||||
private companion object {
|
||||
const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n"
|
||||
const val HEX_PREFIX = "0x"
|
||||
const val DEFAULT_MAX_GASLIMIT = 350000
|
||||
// TODO remove after [REDACTED_JIRA]
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.sdk.extensions.localizedDescriptionRes
|
||||
|
|
@ -250,7 +250,7 @@ private suspend fun loadArtworkForCard(cardId: String, cardPublicKey: ByteArray,
|
|||
if (artworkId.isNullOrEmpty()) {
|
||||
defaultArtwork ?: Uri.EMPTY
|
||||
} else {
|
||||
Uri.parse(OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId))
|
||||
Uri.parse(CardArtworksProvider.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId))
|
||||
}
|
||||
}
|
||||
is Result.Failure -> defaultArtwork ?: Uri.EMPTY
|
||||
|
|
|
|||
|
|
@ -1,68 +1,14 @@
|
|||
package com.tangem.tap.network.auth
|
||||
|
||||
import com.tangem.common.CardIdRangeDec
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
internal class DefaultExpressAuthProvider(
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : ExpressAuthProvider {
|
||||
internal class DefaultExpressAuthProvider : ExpressAuthProvider {
|
||||
|
||||
private var uuid = AtomicReference(UUID.randomUUID())
|
||||
|
||||
override fun getUserId(): String {
|
||||
return userWalletsStore.selectedUserWalletOrNull?.walletId?.stringValue ?: error("No user id provided")
|
||||
}
|
||||
|
||||
override fun getSessionId(): String {
|
||||
return uuid.get().toString()
|
||||
}
|
||||
|
||||
override fun getRefCode(): String {
|
||||
val selectedUserWallet = userWalletsStore.selectedUserWalletOrNull ?: error("Can not get selected user wallet")
|
||||
|
||||
return when {
|
||||
isRing(selectedUserWallet) -> "ring"
|
||||
isChangeNow(selectedUserWallet) -> "ChangeNow"
|
||||
isPartner(selectedUserWallet) -> "partner"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun isRing(selectedUserWallet: UserWallet): Boolean {
|
||||
val addedWalletsWithRings = runBlocking {
|
||||
appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY,
|
||||
default = emptySet(),
|
||||
)
|
||||
}
|
||||
|
||||
return addedWalletsWithRings.contains(selectedUserWallet.walletId.stringValue)
|
||||
}
|
||||
|
||||
private fun isChangeNow(selectedUserWallet: UserWallet): Boolean {
|
||||
val changeNowRange = CardIdRangeDec(
|
||||
start = "AF99001800554008",
|
||||
end = "AF99001800559994",
|
||||
)
|
||||
val card = selectedUserWallet.scanResponse.card
|
||||
return card.batchId == BATCH_ID_CHANGENOW || changeNowRange?.contains(card.cardId) == true
|
||||
}
|
||||
|
||||
private fun isPartner(selectedUserWallet: UserWallet): Boolean {
|
||||
return selectedUserWallet.scanResponse.card.batchId == BATCH_ID_PARTNER
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val BATCH_ID_CHANGENOW = "BB000013"
|
||||
const val BATCH_ID_PARTNER = "AF990015"
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,6 @@ package com.tangem.tap.network.auth.di
|
|||
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||
|
|
@ -30,14 +28,8 @@ internal class AuthModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideExpressAuthProvider(
|
||||
userWalletsStore: UserWalletsStore,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
): ExpressAuthProvider {
|
||||
return DefaultExpressAuthProvider(
|
||||
userWalletsStore = userWalletsStore,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
)
|
||||
fun provideExpressAuthProvider(): ExpressAuthProvider {
|
||||
return DefaultExpressAuthProvider()
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -1,366 +0,0 @@
|
|||
package com.tangem.tap.proxy
|
||||
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.blockchains.optimism.EthereumOptimisticRollupWalletManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.externallinkprovider.TxExploreState
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.models.*
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.math.MathContext
|
||||
import java.math.RoundingMode
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class TransactionManagerImpl(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
) : TransactionManager {
|
||||
|
||||
override fun getExplorerTransactionLink(networkId: String, txAddress: String): String {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: error("blockchain not found")
|
||||
return when (val txUrlState = blockchain.getExploreTxUrl(txAddress)) {
|
||||
TxExploreState.Unsupported -> ""
|
||||
is TxExploreState.Url -> txUrlState.url
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateWalletManager(networkId: String, derivationPath: String?) {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
getActualWalletManager(blockchain, derivationPath).update()
|
||||
}
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
override suspend fun getFee(
|
||||
networkId: String,
|
||||
amountToSend: Amount,
|
||||
currencyToSend: Currency,
|
||||
destinationAddress: String,
|
||||
increaseBy: Int?,
|
||||
data: String?,
|
||||
derivationPath: String?,
|
||||
): ProxyFees {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
||||
if (walletManager is EthereumWalletManager) {
|
||||
if (walletManager is EthereumOptimisticRollupWalletManager) {
|
||||
return getFeeForOptimismBlockchain(
|
||||
walletManager = walletManager,
|
||||
amount = amountToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
data = data,
|
||||
)
|
||||
}
|
||||
return getFeeForEthereumBlockchain(
|
||||
walletManager = walletManager,
|
||||
blockchain = blockchain,
|
||||
amountToSend = amountToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
data = data,
|
||||
increaseBy = increaseBy,
|
||||
)
|
||||
} else {
|
||||
return getFeeForBlockchain(
|
||||
walletManager = walletManager,
|
||||
amountToSend = amountToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBlockchainInfo(networkId: String): ProxyNetworkInfo {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
return ProxyNetworkInfo(
|
||||
name = blockchain.fullName,
|
||||
blockchainId = blockchain.id,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getFeeForBlockchain(
|
||||
walletManager: WalletManager,
|
||||
amountToSend: Amount,
|
||||
destinationAddress: String,
|
||||
): ProxyFees {
|
||||
val fee = (walletManager as? TransactionSender)?.getFee(
|
||||
amount = amountToSend,
|
||||
destination = destinationAddress,
|
||||
) ?: error("Cannot cast to TransactionSender")
|
||||
return when (fee) {
|
||||
is Result.Success -> {
|
||||
// for not EVM blockchains set gasLimit ZERO for now
|
||||
when (fee.data) {
|
||||
is TransactionFee.Single -> {
|
||||
val singleFee = when (val normalFee = (fee.data as TransactionFee.Single).normal) {
|
||||
is Fee.CardanoToken -> {
|
||||
ProxyFee.CardanoToken(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
minAdaValue = normalFee.minAdaValue,
|
||||
)
|
||||
}
|
||||
is Fee.Filecoin -> {
|
||||
ProxyFee.Filecoin(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
gasPremium = normalFee.gasPremium,
|
||||
)
|
||||
}
|
||||
is Fee.Sui -> {
|
||||
ProxyFee.Sui(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
gasPrice = normalFee.gasPrice,
|
||||
gasBudget = normalFee.gasBudget,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = normalFee.amount),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ProxyFees.SingleFee(singleFee = singleFee)
|
||||
}
|
||||
is TransactionFee.Choosable -> {
|
||||
val choosableFee = fee.data as TransactionFee.Choosable
|
||||
ProxyFees.MultipleFees(
|
||||
minFee = ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
|
||||
),
|
||||
normalFee = ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = choosableFee.normal.amount),
|
||||
),
|
||||
priorityFee = ProxyFee.Common(
|
||||
gasLimit = BigInteger.ZERO,
|
||||
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
error(fee.error.message ?: fee.error.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private suspend fun getFeeForEthereumBlockchain(
|
||||
walletManager: EthereumWalletManager,
|
||||
blockchain: Blockchain,
|
||||
amountToSend: Amount,
|
||||
destinationAddress: String,
|
||||
data: String?,
|
||||
increaseBy: Int?,
|
||||
): ProxyFees {
|
||||
val gasLimit = getGasLimit(
|
||||
evmWalletManager = walletManager,
|
||||
amount = amountToSend,
|
||||
destinationAddress = destinationAddress,
|
||||
data = data,
|
||||
).increaseBigIntegerByPercents(increaseBy)
|
||||
return when (val gasPrice = walletManager.getGasPrice()) {
|
||||
is Result.Success -> {
|
||||
createMultipleProxyFees(gasPrice = gasPrice.data, gasLimit = gasLimit, blockchain = blockchain)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
error(gasPrice.error.message ?: gasPrice.error.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getFeeForOptimismBlockchain(
|
||||
walletManager: EthereumOptimisticRollupWalletManager,
|
||||
amount: Amount,
|
||||
destinationAddress: String,
|
||||
data: String?,
|
||||
): ProxyFees {
|
||||
val fee = if (data.isNullOrEmpty()) {
|
||||
walletManager.getFee(amount, destinationAddress)
|
||||
} else {
|
||||
walletManager.getFee(amount, destinationAddress, data)
|
||||
}
|
||||
return when (fee) {
|
||||
is Result.Success -> {
|
||||
val choosableFee = fee.data
|
||||
|
||||
val minProxyFee = ProxyFee.Common(
|
||||
gasLimit = (choosableFee.minimum as Fee.Ethereum).gasLimit,
|
||||
fee = convertToProxyAmount(amount = choosableFee.minimum.amount),
|
||||
)
|
||||
val normalProxyFee = ProxyFee.Common(
|
||||
gasLimit = (choosableFee.normal as Fee.Ethereum).gasLimit,
|
||||
fee = convertToProxyAmount(amount = choosableFee.normal.amount),
|
||||
)
|
||||
val priorityProxyFee = ProxyFee.Common(
|
||||
gasLimit = (choosableFee.priority as Fee.Ethereum).gasLimit,
|
||||
fee = convertToProxyAmount(amount = choosableFee.priority.amount),
|
||||
)
|
||||
|
||||
ProxyFees.MultipleFees(
|
||||
minFee = minProxyFee,
|
||||
normalFee = normalProxyFee,
|
||||
priorityFee = priorityProxyFee,
|
||||
)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
error(fee.error.message ?: fee.error.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getGasLimit(
|
||||
evmWalletManager: EthereumWalletManager,
|
||||
amount: Amount,
|
||||
destinationAddress: String,
|
||||
data: String?,
|
||||
): BigInteger {
|
||||
val result = if (data.isNullOrEmpty()) {
|
||||
evmWalletManager.getGasLimit(
|
||||
amount = amount,
|
||||
destination = destinationAddress,
|
||||
)
|
||||
} else {
|
||||
evmWalletManager.getGasLimit(
|
||||
amount = amount,
|
||||
destination = destinationAddress,
|
||||
data = data,
|
||||
)
|
||||
}
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
return result.data
|
||||
}
|
||||
is Result.Failure -> {
|
||||
error(result.error.message ?: result.error.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getFeeForGas(networkId: String, gas: BigInteger, derivationPath: String?): ProxyFees {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
||||
val gasPriceResult = (walletManager as? EthereumWalletManager)?.getGasPrice()
|
||||
?: error("not supported for $blockchain")
|
||||
val gasPrice = when (gasPriceResult) {
|
||||
is Result.Failure -> error("fail to receive gasPrice")
|
||||
is Result.Success -> gasPriceResult.data
|
||||
}
|
||||
return createMultipleProxyFees(gasPrice, gas, blockchain)
|
||||
}
|
||||
|
||||
private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
|
||||
val selectedUserWallet = requireNotNull(
|
||||
userWalletsListManager.selectedUserWalletSync,
|
||||
) { "userWallet or userWalletsListManager is null" }
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
selectedUserWallet.walletId,
|
||||
blockchain,
|
||||
derivationPath,
|
||||
)
|
||||
|
||||
return requireNotNull(walletManager) { "no wallet manager found" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create proxy fees
|
||||
*
|
||||
* @param gasPrice min fee gasPrice
|
||||
* @param gasLimit
|
||||
* @param blockchain
|
||||
*/
|
||||
private fun createMultipleProxyFees(gasPrice: BigInteger, gasLimit: BigInteger, blockchain: Blockchain): ProxyFees {
|
||||
val patchedGasLimit = gasLimit.toBigDecimal().increaseForMantleIfNeeded(blockchain).toBigInteger()
|
||||
val gasPriceNormal = gasPrice
|
||||
.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE)
|
||||
val gasPricePriority = gasPrice.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE)
|
||||
val feeMin = patchedGasLimit.multiply(gasPrice).toBigDecimal(
|
||||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
val feeNormal = patchedGasLimit.multiply(gasPriceNormal).toBigDecimal(
|
||||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
val feePriority = patchedGasLimit.multiply(gasPricePriority).toBigDecimal(
|
||||
scale = blockchain.decimals(),
|
||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
val minFee = ProxyFee.Common(
|
||||
gasLimit = patchedGasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
value = feeMin,
|
||||
decimals = blockchain.decimals(),
|
||||
),
|
||||
)
|
||||
val normalFee = ProxyFee.Common(
|
||||
gasLimit = patchedGasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
value = feeNormal,
|
||||
decimals = blockchain.decimals(),
|
||||
),
|
||||
)
|
||||
val priorityFee = ProxyFee.Common(
|
||||
gasLimit = patchedGasLimit,
|
||||
fee = ProxyAmount(
|
||||
currencySymbol = blockchain.currency,
|
||||
value = feePriority,
|
||||
decimals = blockchain.decimals(),
|
||||
),
|
||||
)
|
||||
return ProxyFees.MultipleFees(
|
||||
minFee = minFee,
|
||||
normalFee = normalFee,
|
||||
priorityFee = priorityFee,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertToProxyAmount(amount: Amount): ProxyAmount {
|
||||
return ProxyAmount(
|
||||
currencySymbol = amount.currencySymbol,
|
||||
value = amount.value ?: BigDecimal.ZERO,
|
||||
decimals = amount.decimals,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase big integer by percents
|
||||
*
|
||||
* @param percents in format 150 -> 50%
|
||||
* @return increased value
|
||||
*/
|
||||
private fun BigInteger.increaseBigIntegerByPercents(percents: Int?): BigInteger {
|
||||
return if (percents != null && percents != 0) {
|
||||
this.multiply(percents.toBigInteger()).divide(BigInteger("100"))
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Workaround for Mantle. Remove after [REDACTED_JIRA]
|
||||
private fun BigDecimal.increaseForMantleIfNeeded(blockchain: Blockchain): BigDecimal {
|
||||
return if (blockchain == Blockchain.Mantle) {
|
||||
this.multiply(MANTLE_FEE_ESTIMATE_MULTIPLIER)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE = 150 // 50%
|
||||
private const val MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE = 200 // 50%
|
||||
private val MANTLE_FEE_ESTIMATE_MULTIPLIER = BigDecimal("1.8")
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,8 @@ package com.tangem.tap.proxy.di
|
|||
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.TransactionManagerImpl
|
||||
import com.tangem.tap.proxy.UserWalletManagerImpl
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -37,16 +35,4 @@ internal object ProxyModule {
|
|||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTransactionManager(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
): TransactionManager {
|
||||
return TransactionManagerImpl(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
|||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
|
|
@ -76,5 +77,6 @@ data class DaggerGraphState(
|
|||
val settingsManager: SettingsManager? = null,
|
||||
val uiMessageSender: UiMessageSender? = null,
|
||||
val onlineCardVerifier: OnlineCardVerifier? = null,
|
||||
val cardArworksProvider: CardArtworksProvider? = null,
|
||||
val userWalletBuilderFactory: UserWalletBuilder.Factory? = null,
|
||||
) : StateType
|
||||
|
|
@ -12,13 +12,12 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
|||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||
import com.tangem.features.nft.component.NFTCollectionsComponent
|
||||
import com.tangem.features.nft.component.NFTDetailsComponent
|
||||
import com.tangem.features.nft.component.NFTReceiveComponent
|
||||
import com.tangem.features.nft.component.*
|
||||
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
|
||||
import com.tangem.features.onramp.component.*
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.features.send.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.NFTSendComponent
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.staking.api.StakingComponent
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
|
|
@ -83,9 +82,8 @@ internal class ChildFactory @Inject constructor(
|
|||
private val sendComponentFactoryV2: com.tangem.features.send.v2.api.SendComponent.Factory,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
private val redesignedWalletConnectComponentFactory: RedisegnedWalletConnectComponent.Factory,
|
||||
private val nftCollectionsComponentFactory: NFTCollectionsComponent.Factory,
|
||||
private val nftReceiveComponentFactory: NFTReceiveComponent.Factory,
|
||||
private val nftDetailsComponentFactory: NFTDetailsComponent.Factory,
|
||||
private val nftComponentFactory: NFTComponent.Factory,
|
||||
private val nftSendComponentFactory: NFTSendComponent.Factory,
|
||||
private val testerRouter: TesterRouter,
|
||||
private val routingFeatureToggles: RoutingFeatureToggles,
|
||||
private val walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
|
|
@ -182,7 +180,7 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.OnrampSuccess -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = OnrampSuccessComponent.Params(route.externalTxId),
|
||||
params = OnrampSuccessComponent.Params(route.txId),
|
||||
componentFactory = onrampSuccessComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -405,24 +403,26 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = walletComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.NFTCollections ->
|
||||
is AppRoute.NFT ->
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = NFTCollectionsComponent.Params(userWalletId = route.userWalletId),
|
||||
componentFactory = nftCollectionsComponentFactory,
|
||||
params = NFTComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
walletName = route.walletName,
|
||||
),
|
||||
componentFactory = nftComponentFactory,
|
||||
)
|
||||
is AppRoute.NFTReceive ->
|
||||
is AppRoute.NFTSend -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = NFTReceiveComponent.Params(userWalletId = route.userWalletId),
|
||||
componentFactory = nftReceiveComponentFactory,
|
||||
)
|
||||
is AppRoute.NFTDetails ->
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = NFTDetailsComponent.Params(userWalletId = route.userWalletId, nftAsset = route.nftAsset),
|
||||
componentFactory = nftDetailsComponentFactory,
|
||||
params = NFTSendComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
nftAsset = route.nftAsset,
|
||||
nftCollectionName = route.nftCollectionName,
|
||||
),
|
||||
componentFactory = nftSendComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.OnboardingNote,
|
||||
is AppRoute.SaveWallet,
|
||||
is AppRoute.OnboardingOther,
|
||||
|
|
@ -703,7 +703,7 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.OnrampSuccess -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = OnrampSuccessComponent.Params(route.externalTxId),
|
||||
params = OnrampSuccessComponent.Params(route.txId),
|
||||
componentFactory = onrampSuccessComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -757,24 +757,26 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = storiesComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.NFTCollections ->
|
||||
is AppRoute.NFT ->
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = NFTCollectionsComponent.Params(userWalletId = route.userWalletId),
|
||||
componentFactory = nftCollectionsComponentFactory,
|
||||
params = NFTComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
walletName = route.walletName,
|
||||
),
|
||||
componentFactory = nftComponentFactory,
|
||||
)
|
||||
is AppRoute.NFTReceive ->
|
||||
is AppRoute.NFTSend -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = NFTReceiveComponent.Params(userWalletId = route.userWalletId),
|
||||
componentFactory = nftReceiveComponentFactory,
|
||||
)
|
||||
is AppRoute.NFTDetails ->
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = NFTDetailsComponent.Params(userWalletId = route.userWalletId, nftAsset = route.nftAsset),
|
||||
componentFactory = nftDetailsComponentFactory,
|
||||
params = NFTSendComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
nftAsset = route.nftAsset,
|
||||
nftCollectionName = route.nftCollectionName,
|
||||
),
|
||||
componentFactory = nftSendComponentFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ internal class DefaultDerivationsRepositoryTest {
|
|||
private val defaultUserWallet = UserWallet(
|
||||
name = "",
|
||||
walletId = defaultUserWalletId,
|
||||
artworkUrl = "",
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(cardConfig = GenericCardConfig(2), derivedKeys = emptyMap()),
|
||||
|
|
|
|||
|
|
@ -194,7 +194,6 @@ internal class BiometricUserWalletsListManagerTest(private val model: Model) {
|
|||
return UserWallet(
|
||||
name = "Wallet $id",
|
||||
walletId = UserWalletId(stringValue = id),
|
||||
artworkUrl = "",
|
||||
cardsInWallet = emptySet(),
|
||||
isMultiCurrency = true,
|
||||
hasBackupError = false,
|
||||
|
|
|
|||
|
|
@ -59,9 +59,41 @@ val assembleInternalQA by tasks.registering {
|
|||
}
|
||||
}
|
||||
|
||||
val assembleExternalQA by tasks.registering {
|
||||
group = "build"
|
||||
description = "Builds external APK to 'build/outputs' directory"
|
||||
|
||||
val appOutputApkDir = "$projectDir/app/build/outputs/apk/external"
|
||||
val rootOutputApkDir = "$buildDir/outputs"
|
||||
val injected = objects.newInstance<Injected>()
|
||||
|
||||
dependsOn(":app:assembleExternal")
|
||||
|
||||
doFirst {
|
||||
injected.fs.delete {
|
||||
delete(appOutputApkDir)
|
||||
delete("$rootOutputApkDir/app-external.apk")
|
||||
}
|
||||
}
|
||||
doLast {
|
||||
injected.fs.copy {
|
||||
from("$appOutputApkDir/app-external.apk")
|
||||
into(rootOutputApkDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val assembleQA by tasks.registering {
|
||||
group = "build"
|
||||
description = "Builds internal and external APKs to 'build/outputs' directory"
|
||||
|
||||
dependsOn(assembleInternalQA)
|
||||
dependsOn(assembleExternalQA)
|
||||
}
|
||||
|
||||
val generateComposeMetrics by tasks.registering {
|
||||
group = "other"
|
||||
description = "Build internal APK and generates compose metrics to 'build/compose-metrics' directory"
|
||||
description = "Build external APK and generates compose metrics to 'build/compose-metrics' directory"
|
||||
|
||||
subprojects {
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||
|
|
@ -86,5 +118,5 @@ val generateComposeMetrics by tasks.registering {
|
|||
}
|
||||
}
|
||||
|
||||
finalizedBy(assembleInternalQA)
|
||||
finalizedBy(assembleExternalQA)
|
||||
}
|
||||
|
|
@ -233,8 +233,8 @@ sealed class AppRoute(val path: String) : Route {
|
|||
|
||||
@Serializable
|
||||
data class OnrampSuccess(
|
||||
val externalTxId: String,
|
||||
) : AppRoute(path = "/onramp/success/$externalTxId"), RouteBundleParams {
|
||||
val txId: String,
|
||||
) : AppRoute(path = "/onramp/success/$txId"), RouteBundleParams {
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
}
|
||||
|
||||
|
|
@ -281,18 +281,15 @@ sealed class AppRoute(val path: String) : Route {
|
|||
) : AppRoute(path = "/stories$storyId")
|
||||
|
||||
@Serializable
|
||||
data class NFTCollections(
|
||||
data class NFT(
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/nft_collections/${userWalletId.stringValue}")
|
||||
val walletName: String,
|
||||
) : AppRoute(path = "/nft/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
data class NFTReceive(
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/nft_receive/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
data class NFTDetails(
|
||||
data class NFTSend(
|
||||
val userWalletId: UserWalletId,
|
||||
val nftAsset: NFTAsset,
|
||||
) : AppRoute(path = "/nft_details/${userWalletId.stringValue}/${nftAsset.collectionId}/${nftAsset.id.stringValue}")
|
||||
val nftCollectionName: String,
|
||||
) : AppRoute(path = "/send/nft/${userWalletId.stringValue}/$nftCollectionName/${nftAsset.id}")
|
||||
}
|
||||
|
|
@ -9,10 +9,15 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
implementation(projects.data.common)
|
||||
implementation(projects.data.staking)
|
||||
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.staking.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
|
@ -20,6 +25,7 @@ dependencies {
|
|||
implementation(projects.libs.blockchainSdk)
|
||||
|
||||
implementation(deps.androidx.datastore)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.test.coroutine)
|
||||
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.common.test.data.quote
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object MockQuoteResponseFactory {
|
||||
|
||||
fun createSinglePrice(value: BigDecimal): QuotesResponse.Quote {
|
||||
return QuotesResponse.Quote(
|
||||
price = value,
|
||||
priceChange24h = value,
|
||||
priceChange1w = value,
|
||||
priceChange30d = value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.common.test.data.quote
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.local.quote.converter.QuoteConverter
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
|
||||
fun QuotesResponse.Quote.toDomain(rawCurrencyId: String, source: StatusSource = StatusSource.ACTUAL): Quote {
|
||||
return QuoteConverter(source = source).convert(value = mapOf(rawCurrencyId to this).entries.first())
|
||||
}
|
||||
|
||||
fun Pair<String, QuotesResponse.Quote>.toDomain(source: StatusSource = StatusSource.ACTUAL): Quote {
|
||||
return QuoteConverter(source = source).convert(value = mapOf(this).entries.first())
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.common.test.data.staking
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.request.Address
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.domain.staking.model.StakingID
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object MockYieldBalanceWrapperDTOFactory {
|
||||
|
||||
val defaultStakingId = StakingID(
|
||||
integrationId = "ton-ton-chorus-one-pools-staking",
|
||||
address = "0x1",
|
||||
)
|
||||
|
||||
fun createWithBalance(stakingId: StakingID = defaultStakingId): YieldBalanceWrapperDTO {
|
||||
return YieldBalanceWrapperDTO(
|
||||
addresses = Address(address = stakingId.address),
|
||||
balances = listOf(
|
||||
BalanceDTO(
|
||||
groupId = "groupId",
|
||||
type = BalanceDTO.BalanceTypeDTO.UNKNOWN,
|
||||
amount = BigDecimal.ONE,
|
||||
date = null,
|
||||
pricePerShare = BigDecimal.ZERO,
|
||||
pendingActions = listOf(),
|
||||
pendingActionConstraints = null,
|
||||
tokenDTO = TokenDTO(
|
||||
name = "The-Open-Network",
|
||||
network = NetworkTypeDTO.TON,
|
||||
symbol = "TON",
|
||||
decimals = 8,
|
||||
address = null,
|
||||
coinGeckoId = null,
|
||||
logoURI = null,
|
||||
isPoints = null,
|
||||
),
|
||||
validatorAddress = null,
|
||||
validatorAddresses = null,
|
||||
providerId = null,
|
||||
),
|
||||
),
|
||||
integrationId = stakingId.integrationId,
|
||||
)
|
||||
}
|
||||
|
||||
fun createWithEmptyBalance(stakingId: StakingID = defaultStakingId): YieldBalanceWrapperDTO {
|
||||
return YieldBalanceWrapperDTO(
|
||||
addresses = Address(address = stakingId.address),
|
||||
balances = emptyList(),
|
||||
integrationId = stakingId.integrationId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.common.test.data.staking
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.AddressArgumentDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import com.tangem.domain.staking.model.StakingID
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object MockYieldDTOFactory {
|
||||
|
||||
val defaultStakingID = StakingID(
|
||||
integrationId = "ton-ton-chorus-one-pools-staking",
|
||||
address = "0x1",
|
||||
)
|
||||
|
||||
fun create(stakingID: StakingID = defaultStakingID): YieldDTO {
|
||||
return YieldDTO(
|
||||
id = stakingID.integrationId,
|
||||
token = TokenDTO(
|
||||
name = "Miguel Estes",
|
||||
network = NetworkTypeDTO.POLYGON,
|
||||
symbol = "splendide",
|
||||
decimals = 1323,
|
||||
address = null,
|
||||
coinGeckoId = null,
|
||||
logoURI = null,
|
||||
isPoints = null,
|
||||
),
|
||||
tokens = listOf(),
|
||||
args = YieldDTO.ArgsDTO(
|
||||
enter = YieldDTO.ArgsDTO.Enter(
|
||||
addresses = YieldDTO.ArgsDTO.Enter.Addresses(
|
||||
address = AddressArgumentDTO(required = false),
|
||||
),
|
||||
args = mapOf(),
|
||||
),
|
||||
exit = null,
|
||||
),
|
||||
status = YieldDTO.StatusDTO(enter = true, exit = true),
|
||||
apy = BigDecimal.ONE,
|
||||
rewardRate = 1.0,
|
||||
rewardType = YieldDTO.RewardTypeDTO.UNKNOWN,
|
||||
metadata = YieldDTO.MetadataDTO(
|
||||
name = "name",
|
||||
logoUri = "logoUri",
|
||||
description = "description",
|
||||
documentation = null,
|
||||
gasFeeTokenDTO = TokenDTO(
|
||||
name = "Johnnie Mullen",
|
||||
network = NetworkTypeDTO.POLYGON,
|
||||
symbol = "fuisset",
|
||||
decimals = 2957,
|
||||
address = null,
|
||||
coinGeckoId = null,
|
||||
logoURI = null,
|
||||
isPoints = null,
|
||||
),
|
||||
tokenDTO = TokenDTO(
|
||||
name = "Lazaro Wood",
|
||||
network = NetworkTypeDTO.POLYGON,
|
||||
symbol = "vocent",
|
||||
decimals = 1602,
|
||||
address = null,
|
||||
coinGeckoId = null,
|
||||
logoURI = null,
|
||||
isPoints = null,
|
||||
),
|
||||
tokensDTO = listOf(),
|
||||
type = "type",
|
||||
rewardSchedule = YieldDTO.MetadataDTO.RewardScheduleDTO.DAY,
|
||||
cooldownPeriod = null,
|
||||
warmupPeriod = YieldDTO.MetadataDTO.PeriodDTO(1),
|
||||
rewardClaiming = YieldDTO.MetadataDTO.RewardClaimingDTO.AUTO,
|
||||
defaultValidator = null,
|
||||
minimumStake = null,
|
||||
supportsMultipleValidators = null,
|
||||
revshare = YieldDTO.MetadataDTO.EnabledDTO(enabled = true),
|
||||
fee = YieldDTO.MetadataDTO.EnabledDTO(enabled = true),
|
||||
),
|
||||
validators = listOf(),
|
||||
isAvailable = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ object MockUserWalletFactory {
|
|||
return UserWallet(
|
||||
walletId = userWalletId,
|
||||
name = "Wallet 1",
|
||||
artworkUrl = "",
|
||||
cardsInWallet = emptySet(),
|
||||
scanResponse = scanResponse,
|
||||
isMultiCurrency = scanResponse.cardTypesResolver.isMultiwalletAllowed(),
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
package com.tangem.common.test.utils
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.TestCoroutineScheduler
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun <T> CoroutineScope.getEmittedValues(testScheduler: TestCoroutineScheduler, actual: Flow<T>): List<T> {
|
||||
fun <T> TestScope.getEmittedValues(flow: Flow<T>): List<T> {
|
||||
val values = mutableListOf<T>()
|
||||
|
||||
launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
actual.toList(values)
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
flow.toList(values)
|
||||
}
|
||||
|
||||
return values
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.material3.CardColors
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -61,7 +61,7 @@ fun UserWalletItem(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
CardImage(imageUrl = state.imageUrl)
|
||||
CardImage(state.imageState)
|
||||
NameAndInfo(
|
||||
modifier = Modifier.weight(1f),
|
||||
name = state.name,
|
||||
|
|
@ -147,39 +147,49 @@ private fun NameAndInfo(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun CardImage(imageUrl: String, modifier: Modifier = Modifier) {
|
||||
private fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modifier) {
|
||||
val imageModifier = modifier
|
||||
.width(TangemTheme.dimens.size24)
|
||||
.height(TangemTheme.dimens.size36)
|
||||
.clip(TangemTheme.shapes.roundedCornersSmall)
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = imageModifier,
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.transformations(RotationTransformation(angle = 90f))
|
||||
.size(
|
||||
width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() },
|
||||
height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() },
|
||||
)
|
||||
.data(imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(enable = false)
|
||||
.build(),
|
||||
loading = {
|
||||
when (imageState) {
|
||||
is UserWalletItemUM.ImageState.Loading -> {
|
||||
RectangleShimmer(
|
||||
modifier = imageModifier,
|
||||
radius = TangemTheme.dimens.size2,
|
||||
)
|
||||
},
|
||||
error = {
|
||||
Image(
|
||||
}
|
||||
is UserWalletItemUM.ImageState.Image -> {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = imageModifier,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.img_card_wallet_2_gray_22_36),
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.transformations(RotationTransformation(angle = 90f))
|
||||
.size(
|
||||
width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() },
|
||||
height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() },
|
||||
)
|
||||
.data(imageState.artwork.verifiedArtwork?.toByteArray() ?: imageState.artwork.defaultUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(enable = false)
|
||||
.build(),
|
||||
loading = {
|
||||
RectangleShimmer(
|
||||
modifier = imageModifier,
|
||||
radius = TangemTheme.dimens.size2,
|
||||
)
|
||||
},
|
||||
error = {
|
||||
Image(
|
||||
modifier = imageModifier,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.img_card_wallet_2_gray_22_36),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -215,7 +225,6 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider<UserWal
|
|||
name = stringReference("My Wallet"),
|
||||
information = getInformation(cardCount = 1),
|
||||
balance = UserWalletItemUM.Balance.Locked,
|
||||
imageUrl = "",
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
|
|
@ -224,7 +233,6 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider<UserWal
|
|||
name = stringReference("Old wallet"),
|
||||
information = getInformation(cardCount = 2),
|
||||
balance = UserWalletItemUM.Balance.Hidden,
|
||||
imageUrl = "",
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
endIcon = UserWalletItemUM.EndIcon.Arrow,
|
||||
|
|
@ -234,7 +242,6 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider<UserWal
|
|||
name = stringReference("Multi Card"),
|
||||
information = getInformation(cardCount = 3),
|
||||
balance = UserWalletItemUM.Balance.Failed,
|
||||
imageUrl = "",
|
||||
isEnabled = false,
|
||||
endIcon = UserWalletItemUM.EndIcon.Checkmark,
|
||||
onClick = {},
|
||||
|
|
@ -244,7 +251,6 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider<UserWal
|
|||
name = stringReference("Multi Card"),
|
||||
information = getInformation(cardCount = 3),
|
||||
balance = UserWalletItemUM.Balance.Loading,
|
||||
imageUrl = "",
|
||||
isEnabled = false,
|
||||
endIcon = UserWalletItemUM.EndIcon.Checkmark,
|
||||
onClick = {},
|
||||
|
|
@ -257,7 +263,6 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider<UserWal
|
|||
value = "1.2345 BTC",
|
||||
isFlickering = false,
|
||||
),
|
||||
imageUrl = "",
|
||||
isEnabled = false,
|
||||
endIcon = UserWalletItemUM.EndIcon.Checkmark,
|
||||
onClick = {},
|
||||
|
|
@ -270,7 +275,6 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider<UserWal
|
|||
value = "1.2345 BTC",
|
||||
isFlickering = true,
|
||||
),
|
||||
imageUrl = "",
|
||||
isEnabled = false,
|
||||
endIcon = UserWalletItemUM.EndIcon.Checkmark,
|
||||
onClick = {},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.common.ui.userwallet.converter
|
|||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
|
|
@ -9,6 +10,7 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.models.ArtworkModel
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -31,6 +33,7 @@ class UserWalletItemUMConverter(
|
|||
private val balance: TotalFiatBalance? = null,
|
||||
private val isBalanceHidden: Boolean = false,
|
||||
private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None,
|
||||
private val artwork: ArtworkModel? = null,
|
||||
) : Converter<UserWallet, UserWalletItemUM> {
|
||||
|
||||
override fun convert(value: UserWallet): UserWalletItemUM {
|
||||
|
|
@ -40,10 +43,12 @@ class UserWalletItemUMConverter(
|
|||
name = stringReference(name),
|
||||
information = getInfo(userWallet = this),
|
||||
balance = getBalanceInfo(userWallet = this),
|
||||
imageUrl = artworkUrl,
|
||||
isEnabled = !isLocked,
|
||||
endIcon = endIcon,
|
||||
onClick = { onClick(value.walletId) },
|
||||
imageState = artwork?.let {
|
||||
UserWalletItemUM.ImageState.Image(ArtworkUM(it.verifiedArtwork, it.defaultUrl))
|
||||
} ?: UserWalletItemUM.ImageState.Loading,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.common.ui.userwallet.state
|
||||
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import javax.annotation.concurrent.Immutable
|
||||
|
|
@ -10,7 +11,7 @@ data class UserWalletItemUM(
|
|||
val name: TextReference,
|
||||
val information: TextReference,
|
||||
val balance: Balance,
|
||||
val imageUrl: String,
|
||||
val imageState: ImageState = ImageState.Loading,
|
||||
val isEnabled: Boolean,
|
||||
val endIcon: EndIcon = EndIcon.None,
|
||||
val onClick: () -> Unit,
|
||||
|
|
@ -36,4 +37,14 @@ data class UserWalletItemUM(
|
|||
val isFlickering: Boolean,
|
||||
) : Balance()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class ImageState {
|
||||
|
||||
data object Loading : ImageState()
|
||||
|
||||
data class Image(
|
||||
val artwork: ArtworkUM,
|
||||
) : ImageState()
|
||||
}
|
||||
}
|
||||
|
|
@ -22,9 +22,35 @@ sealed class MainScreenAnalyticsEvent(
|
|||
)
|
||||
|
||||
// region Action Buttons feature
|
||||
data class ButtonBuy(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
|
||||
data class ButtonBuy(
|
||||
val status: AnalyticsParam.Status,
|
||||
val screenType: String? = null,
|
||||
) : MainScreenAnalyticsEvent(
|
||||
event = "Button - Buy",
|
||||
params = mapOf(AnalyticsParam.STATUS to status.value),
|
||||
params = buildMap {
|
||||
put(AnalyticsParam.STATUS, status.value)
|
||||
screenType?.let { put(AnalyticsParam.TYPE, it) }
|
||||
},
|
||||
)
|
||||
|
||||
data object ButtonReceive : MainScreenAnalyticsEvent(
|
||||
event = "Button - Receive",
|
||||
)
|
||||
|
||||
data object LimitsClicked : MainScreenAnalyticsEvent(
|
||||
event = "Limits Clicked",
|
||||
)
|
||||
|
||||
data object NoticeBalancesInfo : MainScreenAnalyticsEvent(
|
||||
event = "Notice - Balances Info",
|
||||
)
|
||||
|
||||
data object NoticeLimitsInfo : MainScreenAnalyticsEvent(
|
||||
event = "Notice - Limits Info",
|
||||
)
|
||||
|
||||
data object ButtonExplore : MainScreenAnalyticsEvent(
|
||||
event = "Button - Explore",
|
||||
)
|
||||
|
||||
data class ButtonSwap(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
|
||||
|
|
@ -83,4 +109,9 @@ sealed class MainScreenAnalyticsEvent(
|
|||
params = mapOf(ERROR_CODE to errorCode),
|
||||
)
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
const val VISA_TYPE = "Visa"
|
||||
const val WALLET_TYPE = "Wallet"
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,10 @@
|
|||
"name": "NOTE_REFACTORING_ENABLED",
|
||||
"version": "5.23.0"
|
||||
},
|
||||
{
|
||||
"name": "NEW_ARTWORK_LOADING",
|
||||
"version": "5.24.0"
|
||||
},
|
||||
{
|
||||
"name": "NEW_ATTESTATION_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -62,5 +66,13 @@
|
|||
{
|
||||
"name": "NETWORKS_LOADING_REFACTORING_ENABLED",
|
||||
"version": "5.23.0"
|
||||
},
|
||||
{
|
||||
"name": "QUOTES_LOADING_REFACTORING_ENABLED",
|
||||
"version": "5.24.0"
|
||||
},
|
||||
{
|
||||
"name": "STAKING_LOADING_REFACTORING_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ internal class DevFeatureTogglesManager(
|
|||
|
||||
val savedFeatureToggles = appPreferencesStore.getObjectSyncOrNull<Map<String, Boolean>>(
|
||||
key = PreferencesKeys.FEATURE_TOGGLES_KEY,
|
||||
) ?: emptyMap()
|
||||
) ?: emptyMap<String, Boolean>()
|
||||
|
||||
val localFeatureToggles = localTogglesStorage.toggles
|
||||
.associateToggles(currentVersion = versionProvider.get().orEmpty())
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.core.configtoggle.manager
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import com.google.common.truth.Truth
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.configtoggle.feature.impl.DevFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.impl.FeatureTogglesConstants
|
||||
import com.tangem.core.configtoggle.storage.ConfigToggle
|
||||
|
|
@ -12,6 +13,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -24,7 +26,11 @@ import kotlin.collections.set
|
|||
internal class DevTogglesManagerTest {
|
||||
|
||||
private val localTogglesStorage = mockk<TogglesStorage>()
|
||||
private val appPreferenceStore = mockk<AppPreferencesStore>(relaxed = true)
|
||||
private val appPreferenceStore = AppPreferencesStore(
|
||||
moshi = Moshi.Builder().build(),
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
preferencesDataStore = mockk(relaxed = true),
|
||||
)
|
||||
private val versionProvider = mockk<VersionProvider>()
|
||||
private val manager = DevFeatureTogglesManager(
|
||||
localTogglesStorage = localTogglesStorage,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.squareup.moshi.Types
|
|||
import com.tangem.core.configtoggle.feature.impl.FeatureTogglesConstants
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.asset.reader.AssetReader
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -24,7 +25,11 @@ internal class LocalTogglesStorageTest {
|
|||
private val jsonAdapter = mockk<JsonAdapter<List<ConfigToggle>>>()
|
||||
|
||||
// Impossible to mockk AssetLoader because it implement inline functions
|
||||
private val assetLoader = AssetLoader(assetReader = assetReader, moshi = moshi)
|
||||
private val assetLoader = AssetLoader(
|
||||
assetReader = assetReader,
|
||||
moshi = moshi,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
private val storage = LocalTogglesStorage(assetLoader)
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ dependencies {
|
|||
/** Chucker */
|
||||
debugImplementation(deps.chucker)
|
||||
mockedImplementation(deps.chuckerStub)
|
||||
externalImplementation(deps.chuckerStub)
|
||||
internalImplementation(deps.chuckerStub)
|
||||
releaseImplementation(deps.chuckerStub)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.api.common.blockaid
|
||||
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.DomainScanResponse
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.TransactionScanResponse
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface BlockAidApi {
|
||||
|
||||
@POST("site/scan")
|
||||
suspend fun scanDomain(@Body request: DomainScanRequest): DomainScanResponse
|
||||
|
||||
@POST("evm/json-rpc/scan")
|
||||
suspend fun scanJsonRpc(@Body request: EvmTransactionScanRequest): TransactionScanResponse
|
||||
|
||||
@POST("solana/message/scan")
|
||||
suspend fun scanSolanaMessage(@Body request: SolanaTransactionScanRequest): TransactionScanResponse
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DomainScanRequest(
|
||||
@Json(name = "url") val url: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.TransactionMetadata
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class EvmTransactionScanRequest(
|
||||
@Json(name = "chain") val chain: String,
|
||||
@Json(name = "account_address") val accountAddress: String,
|
||||
@Json(name = "method") val method: String,
|
||||
@Json(name = "data") val data: RpcData,
|
||||
@Json(name = "options") val options: List<String> = listOf("simulation", "validation"),
|
||||
@Json(name = "metadata") val metadata: TransactionMetadata,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RpcData(
|
||||
@Json(name = "jsonrpc") val jsonrpc: String = "2.0",
|
||||
@Json(name = "method") val method: String,
|
||||
@Json(name = "params") val params: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.TransactionMetadata
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SolanaTransactionScanRequest(
|
||||
@Json(name = "encoding") val encoding: String = "base64",
|
||||
@Json(name = "chain") val chain: String,
|
||||
@Json(name = "method") val method: String,
|
||||
@Json(name = "options") val options: List<String> = listOf("simulation, validation"),
|
||||
@Json(name = "metadata") val metadata: TransactionMetadata,
|
||||
@Json(name = "account_address") val accountAddress: String,
|
||||
@Json(name = "transactions") val transactions: List<String>,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AccountSummaryResponse(
|
||||
@Json(name = "assets_diffs") val assetsDiffs: List<AssetDiff>,
|
||||
@Json(name = "exposures") val exposures: List<Exposure>,
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AssetDiff(
|
||||
@Json(name = "asset_type") val assetType: String,
|
||||
@Json(name = "asset") val asset: Asset,
|
||||
@Json(name = "in") val inTransfer: List<Transfer>? = null,
|
||||
@Json(name = "out") val outTransfer: List<Transfer>? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Asset(
|
||||
@Json(name = "chain_id") val chainId: Int? = null,
|
||||
@Json(name = "logo_url") val logoUrl: String? = null,
|
||||
@Json(name = "symbol") val symbol: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Transfer(
|
||||
@Json(name = "value") val value: String,
|
||||
@Json(name = "raw_value") val rawValue: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DomainScanResponse(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "is_malicious") val isMalicious: Boolean?,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Exposure(
|
||||
@Json(name = "asset") val asset: Asset,
|
||||
@Json(name = "spenders") val spenders: Map<String, SpenderDetails>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SpenderDetails(
|
||||
@Json(name = "exposure") val exposure: List<ExposureDetail>,
|
||||
@Json(name = "is_approved_for_all") val isApprovedForAll: Boolean? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExposureDetail(
|
||||
@Json(name = "value") val value: String,
|
||||
@Json(name = "raw_value") val rawValue: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SimulationResponse(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "account_summary") val accountSummary: AccountSummaryResponse,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TransactionMetadata(
|
||||
@Json(name = "domain") val domain: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TransactionScanResponse(
|
||||
@Json(name = "validation") val validation: ValidationResponse,
|
||||
@Json(name = "simulation") val simulation: SimulationResponse,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ValidationResponse(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "result_type") val resultType: String,
|
||||
)
|
||||
|
|
@ -27,6 +27,7 @@ sealed class ApiConfig {
|
|||
TangemVisaAuth,
|
||||
TangemVisa,
|
||||
TangemCardSdk,
|
||||
BlockAid,
|
||||
}
|
||||
|
||||
private fun initializeId(): ID {
|
||||
|
|
@ -37,6 +38,7 @@ sealed class ApiConfig {
|
|||
is TangemVisaAuth -> ID.TangemVisaAuth
|
||||
is TangemVisa -> ID.TangemVisa
|
||||
is TangemCardSdk -> ID.TangemCardSdk
|
||||
is BlockAid -> ID.BlockAid
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +46,7 @@ sealed class ApiConfig {
|
|||
internal const val DEBUG_BUILD_TYPE = "debug"
|
||||
internal const val INTERNAL_BUILD_TYPE = "internal"
|
||||
internal const val MOCKED_BUILD_TYPE = "mocked"
|
||||
internal const val EXTERNAL_BUILD_TYPE = "external"
|
||||
internal const val RELEASE_BUILD_TYPE = "release"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
|
||||
internal class BlockAid(
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
||||
|
||||
override val environmentConfigs = listOf(
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.blockaid.io/v0/",
|
||||
headers = buildMap {
|
||||
environmentConfigStorage.getConfigSync().blockAidApiKey?.let { apiKey ->
|
||||
put("X-API-KEY", ProviderSuspend { apiKey })
|
||||
}
|
||||
put("accept", ProviderSuspend { "application/json" })
|
||||
put("content-type", ProviderSuspend { "application/json" })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -48,10 +48,8 @@ internal class Express(
|
|||
|
||||
private fun createHeaders(isProd: Boolean) = buildMap {
|
||||
put(key = "api-key", value = ProviderSuspend { getApiKey(isProd) })
|
||||
put(key = "user-id", value = ProviderSuspend(expressAuthProvider::getUserId))
|
||||
put(key = "session-id", value = ProviderSuspend(expressAuthProvider::getSessionId))
|
||||
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider).values)
|
||||
put(key = "refcode", value = ProviderSuspend(expressAuthProvider::getRefCode))
|
||||
}
|
||||
|
||||
private fun getApiKey(isProd: Boolean): String {
|
||||
|
|
@ -73,6 +71,7 @@ internal class Express(
|
|||
INTERNAL_BUILD_TYPE,
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.STAGE
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ internal class TangemVisa(
|
|||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://bff.tangem.com/",
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ internal class TangemVisaAuth(
|
|||
|
||||
private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.STAGE,
|
||||
baseUrl = "https://api-s.tangem.org/",
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,7 @@ import com.tangem.datasource.api.express.models.request.AssetsRequestBody
|
|||
import com.tangem.datasource.api.express.models.request.ExchangeSentRequestBody
|
||||
import com.tangem.datasource.api.express.models.request.PairsRequestBody
|
||||
import com.tangem.datasource.api.express.models.response.*
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.*
|
||||
|
||||
/**
|
||||
* Interface of Tangem Express API (new swap mechanism)
|
||||
|
|
@ -17,16 +14,29 @@ import retrofit2.http.Query
|
|||
interface TangemExpressApi {
|
||||
|
||||
@POST("assets")
|
||||
suspend fun getAssets(@Body body: AssetsRequestBody): ApiResponse<List<Asset>>
|
||||
suspend fun getAssets(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Body body: AssetsRequestBody,
|
||||
): ApiResponse<List<Asset>>
|
||||
|
||||
@POST("pairs")
|
||||
suspend fun getPairs(@Body body: PairsRequestBody): ApiResponse<List<SwapPair>>
|
||||
suspend fun getPairs(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Body body: PairsRequestBody,
|
||||
): ApiResponse<List<SwapPair>>
|
||||
|
||||
@GET("providers")
|
||||
suspend fun getProviders(): ApiResponse<List<ExchangeProvider>>
|
||||
suspend fun getProviders(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
): ApiResponse<List<ExchangeProvider>>
|
||||
|
||||
@GET("exchange-quote")
|
||||
suspend fun getExchangeQuote(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("fromContractAddress") fromContractAddress: String,
|
||||
@Query("fromNetwork") fromNetwork: String,
|
||||
@Query("toContractAddress") toContractAddress: String,
|
||||
|
|
@ -40,6 +50,8 @@ interface TangemExpressApi {
|
|||
|
||||
@GET("exchange-data")
|
||||
suspend fun getExchangeData(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("fromContractAddress") fromContractAddress: String,
|
||||
@Query("fromNetwork") fromNetwork: String,
|
||||
@Query("toContractAddress") toContractAddress: String,
|
||||
|
|
@ -57,8 +69,16 @@ interface TangemExpressApi {
|
|||
): ApiResponse<ExchangeDataResponse>
|
||||
|
||||
@GET("exchange-status")
|
||||
suspend fun getExchangeStatus(@Query("txId") txId: String): ApiResponse<ExchangeStatusResponse>
|
||||
suspend fun getExchangeStatus(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("txId") txId: String,
|
||||
): ApiResponse<ExchangeStatusResponse>
|
||||
|
||||
@POST("exchange-sent")
|
||||
suspend fun exchangeSent(@Body body: ExchangeSentRequestBody): ApiResponse<ExchangeSentResponseBody>
|
||||
suspend fun exchangeSent(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Body body: ExchangeSentRequestBody,
|
||||
): ApiResponse<ExchangeSentResponseBody>
|
||||
}
|
||||
|
|
@ -3,37 +3,52 @@ package com.tangem.datasource.api.onramp
|
|||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.*
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
|
||||
interface OnrampApi {
|
||||
|
||||
@GET("currencies")
|
||||
suspend fun getCurrencies(): ApiResponse<List<OnrampCurrencyDTO>>
|
||||
suspend fun getCurrencies(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
): ApiResponse<List<OnrampCurrencyDTO>>
|
||||
|
||||
@GET("countries")
|
||||
suspend fun getCountries(): ApiResponse<List<OnrampCountryDTO>>
|
||||
suspend fun getCountries(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
): ApiResponse<List<OnrampCountryDTO>>
|
||||
|
||||
@GET("country-by-ip")
|
||||
suspend fun getCountryByIp(): ApiResponse<OnrampCountryDTO>
|
||||
suspend fun getCountryByIp(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
): ApiResponse<OnrampCountryDTO>
|
||||
|
||||
@GET("payment-methods")
|
||||
suspend fun getPaymentMethods(): ApiResponse<List<PaymentMethodDTO>>
|
||||
suspend fun getPaymentMethods(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
): ApiResponse<List<PaymentMethodDTO>>
|
||||
|
||||
@POST("onramp-pairs")
|
||||
suspend fun getPairs(@Body body: OnrampPairsRequest): ApiResponse<List<OnrampPairDTO>>
|
||||
suspend fun getPairs(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Body body: OnrampPairsRequest,
|
||||
): ApiResponse<List<OnrampPairDTO>>
|
||||
|
||||
@GET("onramp-quote")
|
||||
suspend fun getQuote(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("fromCurrencyCode") fromCurrencyCode: String,
|
||||
@Query("fromPrecision") fromPrecision: Int,
|
||||
@Query("toContractAddress") toContractAddress: String,
|
||||
|
|
@ -47,6 +62,8 @@ interface OnrampApi {
|
|||
|
||||
@GET("onramp-data")
|
||||
suspend fun getData(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("fromCurrencyCode") fromCurrencyCode: String,
|
||||
@Query("fromPrecision") fromPrecision: Int,
|
||||
@Query("toContractAddress") toContractAddress: String,
|
||||
|
|
@ -64,5 +81,9 @@ interface OnrampApi {
|
|||
): ApiResponse<OnrampDataResponse>
|
||||
|
||||
@GET("onramp-status")
|
||||
suspend fun getStatus(@Query("txId") txId: String): ApiResponse<OnrampStatusResponse>
|
||||
suspend fun getStatus(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("txId") txId: String,
|
||||
): ApiResponse<OnrampStatusResponse>
|
||||
}
|
||||
|
|
@ -91,4 +91,10 @@ enum class Status {
|
|||
|
||||
@Json(name = "paused")
|
||||
Paused,
|
||||
|
||||
@Json(name = "refund-in-progress")
|
||||
RefundInProgress,
|
||||
|
||||
@Json(name = "refunded")
|
||||
Refunded,
|
||||
}
|
||||
|
|
@ -140,6 +140,40 @@ interface TangemTechApi {
|
|||
@GET("stories/{story_id}")
|
||||
suspend fun getStoryById(@Path("story_id") storyId: String): ApiResponse<StoryContentResponse>
|
||||
|
||||
// region push notifications
|
||||
@GET("notification/push_notifications_eligible_networks")
|
||||
suspend fun getEligibleNetworksForPushNotifications(): ApiResponse<List<CryptoNetworkResponse>>
|
||||
|
||||
@POST("user-wallets/applications/")
|
||||
suspend fun createApplicationId(
|
||||
@Body
|
||||
body: NotificationApplicationCreateBody,
|
||||
): ApiResponse<NotificationApplicationIdResponse>
|
||||
|
||||
@PATCH("user-wallets/applications/{application_id}")
|
||||
suspend fun updatePushTokenForApplicationId(
|
||||
@Path("application_id") applicationId: String,
|
||||
@Body body: NotificationApplicationCreateBody,
|
||||
): ApiResponse<String>
|
||||
|
||||
@PATCH("user-wallets/wallets/{wallet_id}/notify")
|
||||
suspend fun setNotificationsEnabled(@Path("wallet_id") walletId: String, @Body body: WalletBody): ApiResponse<Unit>
|
||||
// endregion
|
||||
|
||||
// region wallets
|
||||
@PATCH("user-wallets/wallets/{wallet_id}")
|
||||
suspend fun updateWallet(@Path("wallet_id") walletId: String, @Body body: WalletBody): ApiResponse<Unit>
|
||||
|
||||
@POST("user-wallets/wallets/create-and-connect-by-appuid/{application_id}")
|
||||
suspend fun associateApplicationIdWithWallets(
|
||||
@Path("application_id") applicationId: String,
|
||||
@Body body: List<WalletIdBody>,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@GET("user-wallets/wallets/{wallet_id}")
|
||||
suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse<WalletResponse>
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
val marketsQuoteFields = listOf(
|
||||
"price",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CryptoNetworkResponse(
|
||||
@Json(name = "id") val id: Int,
|
||||
@Json(name = "networkId") val networkId: String,
|
||||
@Json(name = "name") val name: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NotificationApplicationCreateBody(
|
||||
@Json(name = "pushToken") val pushToken: String? = null,
|
||||
@Json(name = "platform") val platform: String? = null,
|
||||
@Json(name = "device") val device: String? = null,
|
||||
@Json(name = "systemVersion") val systemVersion: String? = null,
|
||||
@Json(name = "language") val language: String? = null,
|
||||
@Json(name = "timezone") val timezone: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NotificationApplicationIdResponse(
|
||||
@Json(name = "uid") val appId: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NotificationApplicationUpdateBody(
|
||||
@Json(name = "pushToken") val pushToken: String,
|
||||
@Json(name = "systemVersion") val systemVersion: String? = null,
|
||||
@Json(name = "language") val language: String? = null,
|
||||
@Json(name = "timezone") val timezone: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WalletBody(
|
||||
@Json(name = "notifyStatus") val notifyStatus: Boolean? = null,
|
||||
@Json(name = "name") val name: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WalletIdBody(
|
||||
@Json(name = "id") val walletId: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WalletResponse(
|
||||
@Json(name = "notifyStatus") val notifyStatus: Boolean,
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String? = null,
|
||||
)
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.datasource.api.visa
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.utils.ReadTimeout
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationStatusRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetCardWalletAcceptanceRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetCustomerWalletAcceptanceRequest
|
||||
import com.tangem.datasource.api.visa.models.request.SetPinCodeRequest
|
||||
import com.tangem.datasource.api.visa.models.response.*
|
||||
import retrofit2.http.Body
|
||||
|
|
@ -11,60 +13,40 @@ import retrofit2.http.GET
|
|||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
interface TangemVisaApi {
|
||||
|
||||
@GET("product_instance/activation_status")
|
||||
@POST("v1/activation/status")
|
||||
suspend fun getRemoteActivationStatus(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("customer_id") customerId: String,
|
||||
@Query("product_instance_id") productInstanceId: String,
|
||||
@Query("card_id") cardId: String,
|
||||
@Query("card_public_key") cardPublicKey: String,
|
||||
@Body request: ActivationStatusRequest,
|
||||
): ApiResponse<CardActivationRemoteStateResponse>
|
||||
|
||||
@ReadTimeout(duration = 20, TimeUnit.MINUTES)
|
||||
@GET("product_instance/activation_status")
|
||||
suspend fun getRemoteActivationStatusLongPoll(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("customer_id") customerId: String,
|
||||
@Query("product_instance_id") productInstanceId: String,
|
||||
@Query("card_id") cardId: String,
|
||||
@Query("card_public_key") cardPublicKey: String,
|
||||
): ApiResponse<CardActivationRemoteStateResponse>
|
||||
|
||||
@GET("product_instance/card_wallet_acceptance")
|
||||
@POST("v1/activation/acceptance/message")
|
||||
suspend fun getCardWalletAcceptance(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("customer_id") customerId: String,
|
||||
@Query("product_instance_id") productInstanceId: String,
|
||||
@Query("activation_order") activationOrderId: String,
|
||||
@Query("customer_wallet_address") customerWalletAddress: String,
|
||||
): ApiResponse<CardWalletDataToSignResponse>
|
||||
@Body request: GetCardWalletAcceptanceRequest,
|
||||
): ApiResponse<VisaDataToSignResponse>
|
||||
|
||||
@GET("product_instance/customer_wallet_acceptance")
|
||||
@POST("v1/activation/acceptance/message")
|
||||
suspend fun getCustomerWalletAcceptance(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("customer_id") customerId: String,
|
||||
@Query("product_instance_id") productInstanceId: String,
|
||||
@Query("activation_order") activationOrderId: String,
|
||||
@Query("card_wallet_address") cardWalletAddress: String,
|
||||
): ApiResponse<CustomerWalletDataToSignResponse>
|
||||
@Body request: GetCustomerWalletAcceptanceRequest,
|
||||
): ApiResponse<VisaDataToSignResponse>
|
||||
|
||||
@POST("product_instance/activation_by_card_wallet")
|
||||
@POST("v1/activation/data")
|
||||
suspend fun activateByCardWallet(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: ActivationByCardWalletRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("product_instance/activation_by_customer_wallet")
|
||||
@POST("v1/activation/data")
|
||||
suspend fun activateByCustomerWallet(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: ActivationByCustomerWalletRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("product_instance/issuer_activation")
|
||||
@POST("v1/activation/pin")
|
||||
suspend fun setPinCode(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: SetPinCodeRequest,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,29 @@
|
|||
package com.tangem.datasource.api.visa
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.visa.models.request.*
|
||||
import com.tangem.datasource.api.visa.models.response.GenerateNonceResponse
|
||||
import com.tangem.datasource.api.visa.models.response.JWTResponse
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface TangemVisaAuthApi {
|
||||
|
||||
@POST("auth/card_wallet")
|
||||
suspend fun generateNonceByWalletAddress(
|
||||
@Query("card_wallet_address") cardWalletAddress: String,
|
||||
): GenerateNonceResponse
|
||||
@POST("v1/auth/challenge")
|
||||
suspend fun generateNonceByCardId(@Body request: GenerateNoneByCardIdRequest): GenerateNonceResponse
|
||||
|
||||
@POST("auth/card_id")
|
||||
suspend fun generateNonceByCard(
|
||||
@Query("card_id") cardId: String,
|
||||
@Query("card_public_key") cardPublicKey: String,
|
||||
): GenerateNonceResponse
|
||||
@POST("v1/auth/challenge")
|
||||
suspend fun generateNonceByCardWallet(@Body request: GenerateNoneByCardWalletRequest): GenerateNonceResponse
|
||||
|
||||
@POST("auth/get_token")
|
||||
suspend fun getAccessToken(
|
||||
@Query("session_id") sessionId: String,
|
||||
@Query("signature") signature: String,
|
||||
@Query("salt") salt: String?,
|
||||
): JWTResponse
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): JWTResponse
|
||||
|
||||
@POST("auth/refresh_token")
|
||||
suspend fun refreshAccessToken(@Field("refresh_token") refreshToken: String): ApiResponse<JWTResponse>
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): JWTResponse
|
||||
|
||||
@POST("v1/auth/token/refresh")
|
||||
suspend fun refreshCardIdAccessToken(@Body request: RefreshTokenByCardIdRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token/refresh")
|
||||
suspend fun refreshCardWalletAccessToken(@Body request: RefreshTokenByCardWalletRequest): ApiResponse<JWTResponse>
|
||||
}
|
||||
|
|
@ -5,22 +5,15 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ActivationByCardWalletRequest(
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "product_instance_id") val productInstanceId: String,
|
||||
@Json(name = "activation_order_id") val activationOrderId: String,
|
||||
@Json(name = "data") val data: Data,
|
||||
@Json(name = "order_id") val orderId: String,
|
||||
@Json(name = "card_wallet") val cardWallet: CardWallet,
|
||||
@Json(name = "deploy_acceptance_signature") val deployAcceptanceSignature: String,
|
||||
@Json(name = "otp") val otp: Otp,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
@Json(name = "card_wallet") val cardWallet: CardWallet,
|
||||
@Json(name = "otp") val otp: Otp,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CardWallet(
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "card_wallet_confirmation") val cardWalletConfirmation: CardWalletConfirmation?,
|
||||
@Json(name = "deploy_acceptance_signature") val deployAcceptanceSignature: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -5,19 +5,12 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ActivationByCustomerWalletRequest(
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "product_instance_id") val productInstanceId: String,
|
||||
@Json(name = "activation_order_id") val activationOrderId: String,
|
||||
@Json(name = "data") val data: Data,
|
||||
@Json(name = "order_id") val orderId: String,
|
||||
@Json(name = "customer_wallet") val customerWallet: CustomerWallet,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
@Json(name = "customer_wallet") val customerWallet: CustomerWallet,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CustomerWallet(
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "deploy_acceptance_signature") val deployAcceptanceSignature: String,
|
||||
@Json(name = "address") val customerWalletAddress: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ActivationStatusRequest(
|
||||
@Json(name = "card_id") val cardId: String,
|
||||
@Json(name = "card_public_key") val cardPublicKey: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GenerateNoneByCardIdRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_id",
|
||||
@Json(name = "card_id") val cardId: String,
|
||||
@Json(name = "card_public_key") val cardPublicKey: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GenerateNoneByCardWalletRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_wallet",
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetAccessTokenByCardIdRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_id",
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
@Json(name = "signature") val signature: String,
|
||||
@Json(name = "salt") val salt: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetAccessTokenByCardWalletRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_wallet",
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
@Json(name = "signature") val signature: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetCardWalletAcceptanceRequest(
|
||||
@Json(name = "type") val type: String = "card_wallet",
|
||||
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetCustomerWalletAcceptanceRequest(
|
||||
@Json(name = "type") val type: String = "customer_wallet",
|
||||
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RefreshTokenByCardIdRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_id",
|
||||
@Json(name = "refresh_token") val refreshToken: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RefreshTokenByCardWalletRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_wallet",
|
||||
@Json(name = "refresh_token") val refreshToken: String,
|
||||
)
|
||||
|
|
@ -5,14 +5,8 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SetPinCodeRequest(
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "activation_order_id") val activationOrderId: String,
|
||||
@Json(name = "product_instance_id") val productInstanceId: String,
|
||||
@Json(name = "data") val data: Data,
|
||||
) {
|
||||
data class Data(
|
||||
@Json(name = "session_key") val sessionKey: String,
|
||||
@Json(name = "iv") val iv: String,
|
||||
@Json(name = "encrypted_pin") val encryptedPin: String,
|
||||
)
|
||||
}
|
||||
@Json(name = "order_id") val orderId: String,
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
@Json(name = "iv") val iv: String,
|
||||
@Json(name = "pin") val pin: String,
|
||||
)
|
||||
|
|
@ -5,12 +5,21 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CardActivationRemoteStateResponse(
|
||||
@Json(name = "activation_status") val status: String,
|
||||
@Json(name = "activation_order") val activationOrder: ActivationOrder?,
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "order") val activationOrder: ActivationOrder?,
|
||||
@Json(name = "stepChangeCode") val stepChangeCode: Int?,
|
||||
@Json(name = "updatedAt") val updatedAt: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ActivationOrder(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.datasource.api.visa.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CustomerWalletDataToSignResponse(
|
||||
@Json(name = "data_for_customer_wallet") val dataForCardWallet: Data,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
@Json(name = "hash") val hash: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -5,6 +5,11 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GenerateNonceResponse(
|
||||
@Json(name = "nonce") val nonce: String,
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
)
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "nonce") val nonce: String,
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -5,12 +5,17 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class JWTResponse(
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
@Json(name = "expires_in") val expiresIn: Int,
|
||||
@Json(name = "refresh_expires_in") val refreshExpiresIn: Int,
|
||||
@Json(name = "refresh_token") val refreshToken: String,
|
||||
@Json(name = "token_type") val tokenType: String,
|
||||
@Json(name = "not-before-policy") val notBeforePolicy: Int,
|
||||
@Json(name = "session_state") val sessionState: String,
|
||||
@Json(name = "scope") val scope: String,
|
||||
)
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
@Json(name = "expires_in") val expiresIn: Int,
|
||||
@Json(name = "refresh_expires_in") val refreshExpiresIn: Int,
|
||||
@Json(name = "refresh_token") val refreshToken: String,
|
||||
@Json(name = "token_type") val tokenType: String,
|
||||
@Json(name = "not-before-policy") val notBeforePolicy: Int,
|
||||
@Json(name = "session_state") val sessionState: String,
|
||||
@Json(name = "scope") val scope: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -4,11 +4,11 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CardWalletDataToSignResponse(
|
||||
@Json(name = "dataForCardWallet") val dataForCardWallet: Data,
|
||||
data class VisaDataToSignResponse(
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
data class Result(
|
||||
@Json(name = "hash") val hash: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.appcurrency
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Store of app currency data model [CurrenciesResponse.Currency]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface AppCurrencyResponseStore {
|
||||
|
||||
/** Get flow of [CurrenciesResponse.Currency] */
|
||||
fun get(): Flow<CurrenciesResponse.Currency?>
|
||||
|
||||
/** Get [CurrenciesResponse.Currency] synchronously or null */
|
||||
suspend fun getSyncOrNull(): CurrenciesResponse.Currency?
|
||||
}
|
||||
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