Updated on 2026-08-14
This commit is contained in:
commit
cf69bb5975
67 changed files with 2232 additions and 337 deletions
|
|
@ -9,6 +9,17 @@ plugins {
|
|||
id("configuration")
|
||||
}
|
||||
|
||||
//conflict of dependencies when adding WalletConnectV2.0 library
|
||||
configurations {
|
||||
all {
|
||||
exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18")
|
||||
resolutionStrategy {
|
||||
force("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||
}
|
||||
exclude(group = "com.github.komputing.kethereum")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("libs/walletconnect-1.5.6.aar"))
|
||||
implementation(project(":domain:legacy"))
|
||||
|
|
@ -124,6 +135,8 @@ dependencies {
|
|||
implementation(deps.armadillo)
|
||||
implementation(deps.mviCore.watcher)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.walletConnectCore)
|
||||
implementation(deps.walletConnectWeb3)
|
||||
|
||||
/** Testing libraries */
|
||||
testImplementation(deps.test.junit)
|
||||
|
|
|
|||
|
|
@ -34,10 +34,11 @@
|
|||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:hardwareAccelerated="true"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:allowBackup">
|
||||
tools:replace="android:allowBackup, android:fullBackupContent">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.wallet.api.enabled"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit c714678d9c46dee1b15ef9e8d27aa23afa1dfd8d
|
||||
Subproject commit 9eaedddd9bfe5a1f216887d31eda0fabeba1c11d
|
||||
|
|
@ -32,6 +32,7 @@ import com.tangem.tap.domain.TangemSdkManager
|
|||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
|
||||
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.shop.redux.ShopAction
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
|
|
@ -91,6 +92,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
@Inject
|
||||
lateinit var walletRouter: WalletRouter
|
||||
|
||||
@Inject
|
||||
lateinit var walletConnectInteractor: WalletConnectInteractor
|
||||
|
||||
private var snackbar: Snackbar? = null
|
||||
private val dialogManager = DialogManager()
|
||||
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
|
||||
|
|
@ -119,12 +123,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
GooglePayService(createPaymentsClient(this), this),
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatch(
|
||||
DaggerGraphAction.SetActivityDependencies(
|
||||
testerRouter = testerRouter,
|
||||
scanCardUseCase = scanCardUseCase,
|
||||
walletRouter = walletRouter,
|
||||
walletConnectInteractor = walletConnectInteractor,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +1,60 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import coil.ImageLoader
|
||||
import coil.ImageLoaderFactory
|
||||
import com.tangem.Log
|
||||
import com.tangem.LogFormat
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.FeaturesLocalLoader
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.domain.DomainLayer
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.tap.common.IntentHandler
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.topup.TopUpController
|
||||
import com.tangem.tap.common.chat.ChatManager
|
||||
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
|
||||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
import com.tangem.tap.common.log.TangemLogCollector
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.appReducer
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.shop.TangemShopService
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator
|
||||
import com.tangem.tap.domain.totalBalance.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
|
||||
import com.tangem.tap.domain.walletCurrencies.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.domain.walletStores.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
|
||||
import android.app.*
|
||||
import android.content.*
|
||||
import android.content.pm.*
|
||||
import coil.*
|
||||
import com.tangem.*
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.network.*
|
||||
import com.tangem.core.analytics.*
|
||||
import com.tangem.core.featuretoggle.manager.*
|
||||
import com.tangem.data.source.preferences.*
|
||||
import com.tangem.datasource.api.common.*
|
||||
import com.tangem.datasource.asset.*
|
||||
import com.tangem.datasource.config.*
|
||||
import com.tangem.datasource.config.models.*
|
||||
import com.tangem.datasource.connection.*
|
||||
import com.tangem.domain.*
|
||||
import com.tangem.domain.common.*
|
||||
import com.tangem.features.wallet.featuretoggles.*
|
||||
import com.tangem.tap.common.*
|
||||
import com.tangem.tap.common.analytics.*
|
||||
import com.tangem.tap.common.analytics.api.*
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.*
|
||||
import com.tangem.tap.common.analytics.handlers.appsFlyer.*
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.*
|
||||
import com.tangem.tap.common.analytics.topup.*
|
||||
import com.tangem.tap.common.chat.*
|
||||
import com.tangem.tap.common.feedback.*
|
||||
import com.tangem.tap.common.images.*
|
||||
import com.tangem.tap.common.log.*
|
||||
import com.tangem.tap.common.redux.*
|
||||
import com.tangem.tap.common.redux.global.*
|
||||
import com.tangem.tap.common.shop.*
|
||||
import com.tangem.tap.domain.configurable.warningMessage.*
|
||||
import com.tangem.tap.domain.tokens.*
|
||||
import com.tangem.tap.domain.totalBalance.*
|
||||
import com.tangem.tap.domain.totalBalance.di.*
|
||||
import com.tangem.tap.domain.walletCurrencies.*
|
||||
import com.tangem.tap.domain.walletCurrencies.di.*
|
||||
import com.tangem.tap.domain.walletStores.*
|
||||
import com.tangem.tap.domain.walletStores.di.*
|
||||
import com.tangem.tap.domain.walletStores.repository.*
|
||||
import com.tangem.tap.domain.walletStores.repository.di.*
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.domain.walletconnect2.domain.*
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.*
|
||||
import com.tangem.tap.proxy.*
|
||||
import com.tangem.tap.proxy.redux.*
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import dagger.hilt.android.*
|
||||
import kotlinx.coroutines.*
|
||||
import okhttp3.logging.*
|
||||
import org.rekotlin.*
|
||||
import timber.log.*
|
||||
import javax.inject.*
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository as WalletConnect2Repository
|
||||
|
||||
lateinit var store: Store<AppState>
|
||||
|
||||
|
|
@ -137,6 +131,12 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
@Inject
|
||||
lateinit var walletFeatureToggles: WalletFeatureToggles
|
||||
|
||||
@Inject
|
||||
lateinit var walletConnect2Repository: WalletConnect2Repository
|
||||
|
||||
@Inject
|
||||
lateinit var walletConnectSessionsRepository: WalletConnectSessionsRepository
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
|
@ -151,6 +151,8 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
networkConnectionManager = networkConnectionManager,
|
||||
customTokenFeatureToggles = customTokenFeatureToggles,
|
||||
walletFeatureToggles = walletFeatureToggles,
|
||||
walletConnectRepository = walletConnect2Repository,
|
||||
walletConnectSessionsRepository = walletConnectSessionsRepository,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -193,6 +195,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
}
|
||||
|
||||
initTopUpController()
|
||||
walletConnect2Repository.init(projectId = configManager.config.walletConnectProjectId)
|
||||
}
|
||||
|
||||
private fun initTopUpController() {
|
||||
|
|
|
|||
|
|
@ -93,14 +93,11 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
ChooseNetworkDialog.create(state.dialog.session, state.dialog.networks, context)
|
||||
is WalletConnectDialog.ClipboardOrScanQr ->
|
||||
ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context)
|
||||
is WalletConnectDialog.RequestTransaction -> TransactionDialog.create(state.dialog.dialogData, context)
|
||||
is WalletConnectDialog.RequestTransaction -> TransactionDialog.create(state.dialog.data, context)
|
||||
is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context)
|
||||
is WalletConnectDialog.BnbTransactionDialog ->
|
||||
BnbTransactionDialog.create(
|
||||
data = state.dialog.data,
|
||||
session = state.dialog.session,
|
||||
sessionId = state.dialog.sessionId,
|
||||
dAppName = state.dialog.dAppName,
|
||||
preparedData = state.dialog.data,
|
||||
context = context,
|
||||
)
|
||||
is WalletConnectDialog.UnsupportedNetwork ->
|
||||
|
|
@ -109,6 +106,15 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
messageRes = R.string.wallet_connect_scanner_error_unsupported_network,
|
||||
context = context,
|
||||
)
|
||||
is WalletConnectDialog.SessionProposalDialog -> {
|
||||
SessionProposalDialog.create(
|
||||
sessionProposal = state.dialog.sessionProposal,
|
||||
networks = state.dialog.networks,
|
||||
context = context,
|
||||
onApprove = state.dialog.onApprove,
|
||||
onReject = state.dialog.onReject,
|
||||
)
|
||||
}
|
||||
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
|
||||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.tap.common.extensions.readFile
|
||||
import com.tangem.tap.common.extensions.rewriteFile
|
||||
|
||||
interface FileReader {
|
||||
fun readFile(fileName: String): String
|
||||
fun rewriteFile(content: String, fileName: String)
|
||||
}
|
||||
|
||||
class AndroidFileReader(private val context: Context) : FileReader {
|
||||
override fun readFile(fileName: String): String {
|
||||
return context.readFile(fileName)
|
||||
}
|
||||
|
||||
override fun rewriteFile(content: String, fileName: String) {
|
||||
context.rewriteFile(content, fileName)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,11 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import androidx.annotation.AnyRes
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
fun Context.readFile(fileName: String): String = this.openFileInput(fileName).bufferedReader().readText()
|
||||
|
||||
fun Context.rewriteFile(content: String, fileName: String) {
|
||||
this.openFileOutput(fileName, Context.MODE_PRIVATE).use {
|
||||
it.write(content.toByteArray(), 0, content.length)
|
||||
}
|
||||
}
|
||||
import android.content.*
|
||||
import android.content.pm.*
|
||||
import android.content.res.*
|
||||
import android.net.*
|
||||
import androidx.annotation.*
|
||||
import androidx.core.content.*
|
||||
|
||||
fun Context.isPermissionGranted(permission: String): Boolean {
|
||||
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
|
||||
|
|
|
|||
|
|
@ -20,6 +20,18 @@ sealed class AppDialog : StateDialog {
|
|||
val onOk: VoidCallback? = null,
|
||||
) : AppDialog()
|
||||
|
||||
data class OkCancelDialogRes(
|
||||
val headerId: Int,
|
||||
val messageId: Int,
|
||||
val okButton: DialogButton,
|
||||
val cancelButton: DialogButton,
|
||||
) : AppDialog()
|
||||
|
||||
data class DialogButton(
|
||||
val title: Int,
|
||||
val action: VoidCallback? = null,
|
||||
)
|
||||
|
||||
object ScanFailsDialog : AppDialog()
|
||||
|
||||
data class AddressInfoDialog(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.core.analytics.Analytics
|
||||
|
|
@ -13,6 +9,7 @@ import com.tangem.domain.common.extensions.withMainContext
|
|||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
|
|
@ -25,10 +22,6 @@ import com.tangem.tap.features.disclaimer.createDisclaimer
|
|||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -85,10 +78,24 @@ class TapWalletManager(
|
|||
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
|
||||
store.dispatch(WalletAction.Warnings.CheckIfNeeded)
|
||||
}
|
||||
|
||||
setupWalletConnectV2(userWallet)
|
||||
loadData(userWallet, refresh)
|
||||
}
|
||||
|
||||
private fun setupWalletConnectV2(userWallet: UserWallet) {
|
||||
val cardId = if (userWallet.cardsInWallet.size == 1) {
|
||||
userWallet.cardId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
scope.launch {
|
||||
store.state.daggerGraphState.walletConnectInteractor?.startListening(
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
cardId = cardId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadData(userWallet: UserWallet, refresh: Boolean = false) {
|
||||
walletStoresManager.fetch(userWallet, refresh)
|
||||
.doOnSuccess {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.files.AndroidFileReader
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.tap.common.AndroidFileReader
|
||||
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
|
||||
import com.tangem.tap.domain.tokens.converters.CurrencyConverter
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.squareup.moshi.JsonAdapter
|
|||
import com.tangem.Log
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.tap.common.FileReader
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
|
||||
class UserTokensStorageService(private val fileReader: FileReader) {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import com.google.gson.GsonBuilder
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.tradeOrderSerializer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.TradeData
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
|
||||
import com.trustwallet.walletconnect.models.binance.tradeOrderSerializer
|
||||
import timber.log.Timber
|
||||
|
||||
object BnbHelper {
|
||||
|
|
@ -39,6 +41,50 @@ object BnbHelper {
|
|||
)
|
||||
}
|
||||
|
||||
fun WcBinanceTradeOrder.toWCBinanceTradeOrder(): WCBinanceTradeOrder {
|
||||
return WCBinanceTradeOrder(
|
||||
account_number = accountNumber,
|
||||
chain_id = chainId,
|
||||
data = data,
|
||||
memo = memo,
|
||||
sequence = sequence,
|
||||
source = source,
|
||||
msgs = msgs.map { it.toWCBinanceTradeOrderMessage() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun WcBinanceTradeOrder.Message.toWCBinanceTradeOrderMessage(): WCBinanceTradeOrder.Message {
|
||||
return WCBinanceTradeOrder.Message(id, orderType, price, quantity, sender, side, symbol, timeInforce)
|
||||
}
|
||||
|
||||
fun WcBinanceTransferOrder.toWCBinanceTransferOrder(): WCBinanceTransferOrder {
|
||||
return WCBinanceTransferOrder(
|
||||
account_number = accountNumber,
|
||||
chain_id = chainId,
|
||||
data = data,
|
||||
memo = memo,
|
||||
sequence = sequence,
|
||||
source = source,
|
||||
msgs = msgs.map { it.toWCBinanceTransferOrderMessage() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun WcBinanceTransferOrder.Message.toWCBinanceTransferOrderMessage(): WCBinanceTransferOrder.Message {
|
||||
return WCBinanceTransferOrder.Message(
|
||||
inputs.map { it.toWCBinanceItem() },
|
||||
outputs.map { it.toWCBinanceItem() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun WcBinanceTransferOrder.Message.Item.toWCBinanceItem(): WCBinanceTransferOrder.Message.Item {
|
||||
return WCBinanceTransferOrder.Message.Item(
|
||||
address,
|
||||
coins.map {
|
||||
WCBinanceTransferOrder.Message.Item.Coin(it.amount, it.denom)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade {
|
||||
val address = order.msgs.first().sender
|
||||
|
||||
|
|
|
|||
|
|
@ -4,19 +4,18 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.walletconnect.extensions.isDappSupported
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcTransactionType
|
||||
import com.tangem.tap.domain.walletconnect.extensions.toWcEthereumSignMessage
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.*
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.walletConnectRepository
|
||||
|
|
@ -61,7 +60,7 @@ class WalletConnectManager {
|
|||
HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }
|
||||
}
|
||||
|
||||
private var sessions: MutableMap<WCSession, WalletConnectActiveData> = mutableMapOf()
|
||||
private var sessions: MutableMap<Topic, WalletConnectActiveData> = mutableMapOf()
|
||||
|
||||
fun connect(wcUri: String) {
|
||||
val session = WCSession.from(wcUri).guard {
|
||||
|
|
@ -73,7 +72,7 @@ class WalletConnectManager {
|
|||
)
|
||||
return
|
||||
}
|
||||
if (sessions[session] != null) {
|
||||
if (sessions[session.topic] != null) {
|
||||
store.dispatchOnMain(WalletConnectAction.RefuseOpeningSession)
|
||||
return
|
||||
}
|
||||
|
|
@ -93,7 +92,7 @@ class WalletConnectManager {
|
|||
return
|
||||
}
|
||||
|
||||
sessions[session] = WalletConnectActiveData(
|
||||
sessions[session.topic] = WalletConnectActiveData(
|
||||
peerId = peerId,
|
||||
remotePeerId = null,
|
||||
session = session,
|
||||
|
|
@ -104,26 +103,26 @@ class WalletConnectManager {
|
|||
}
|
||||
|
||||
fun updateSession(session: WalletConnectSession) {
|
||||
val updatedSession = sessions[session.session]?.copy(
|
||||
val updatedSession = sessions[session.session.topic]?.copy(
|
||||
wallet = session.wallet,
|
||||
)
|
||||
if (updatedSession != null) {
|
||||
sessions[session.session] = updatedSession
|
||||
sessions[session.session.topic] = updatedSession
|
||||
}
|
||||
}
|
||||
|
||||
fun updateBlockchain(session: WalletConnectSession) {
|
||||
sessions[session.session]?.client?.updateSession(
|
||||
sessions[session.session.topic]?.client?.updateSession(
|
||||
accounts = listOfNotNull(session.getAddress()),
|
||||
chainId = session.wallet.blockchain?.getChainId(),
|
||||
approved = true,
|
||||
)
|
||||
|
||||
val updatedSession = sessions[session.session]?.copy(
|
||||
val updatedSession = sessions[session.session.topic]?.copy(
|
||||
wallet = session.wallet,
|
||||
)
|
||||
if (updatedSession != null) {
|
||||
sessions[session.session] = updatedSession
|
||||
sessions[session.session.topic] = updatedSession
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +130,7 @@ class WalletConnectManager {
|
|||
private fun setupConnectionTimeoutCheck(session: WCSession) {
|
||||
scope.launch {
|
||||
delay(20_000)
|
||||
val data = sessions[session]
|
||||
val data = sessions[session.topic]
|
||||
if (data != null && data.peerMeta == null) {
|
||||
disconnect(session)
|
||||
store.dispatchOnMain(WalletConnectAction.OpeningSessionTimeout(session))
|
||||
|
|
@ -160,13 +159,13 @@ class WalletConnectManager {
|
|||
setListeners(it.client)
|
||||
it.client.connect(it.session, tangemPeerMeta, it.peerId, it.remotePeerId)
|
||||
}
|
||||
}.associateBy { it.session }.toMutableMap()
|
||||
}.associateBy { it.session.topic }.toMutableMap()
|
||||
|
||||
store.dispatchOnMain(WalletConnectAction.SetSessionsRestored(sessions))
|
||||
}
|
||||
|
||||
fun approve(session: WCSession) {
|
||||
val activeData = sessions[session] ?: return
|
||||
val activeData = sessions[session.topic] ?: return
|
||||
removeSimilarSessions(activeData)
|
||||
|
||||
val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey ?: return
|
||||
|
|
@ -203,18 +202,18 @@ class WalletConnectManager {
|
|||
sessionsToRemove.forEach { disconnect(it.value.session) }
|
||||
}
|
||||
|
||||
fun rejectRequest(session: WCSession, id: Long) {
|
||||
val activeData = sessions[session] ?: return
|
||||
fun rejectRequest(topic: String, id: Long) {
|
||||
val activeData = sessions[topic] ?: return
|
||||
activeData.client.rejectRequest(id)
|
||||
}
|
||||
|
||||
fun acceptRequest(session: WCSession, id: Long, data: String) {
|
||||
val activeData = sessions[session] ?: return
|
||||
fun acceptRequest(topic: String, id: Long, data: String) {
|
||||
val activeData = sessions[topic] ?: return
|
||||
activeData.client.approveRequest(id, data)
|
||||
}
|
||||
|
||||
fun disconnect(session: WCSession) {
|
||||
val activeData = sessions[session] ?: return
|
||||
val activeData = sessions[session.topic] ?: return
|
||||
val disconnected = if (activeData.client.isConnected) {
|
||||
activeData.client.killSession()
|
||||
} else {
|
||||
|
|
@ -228,118 +227,145 @@ class WalletConnectManager {
|
|||
|
||||
private fun onSessionClosed(session: WCSession) {
|
||||
Analytics.send(WalletConnect.SessionDisconnected())
|
||||
sessions.remove(session)
|
||||
sessions.remove(session.topic)
|
||||
walletConnectRepository.removeSession(session)
|
||||
store.dispatchOnMain(WalletConnectAction.RemoveSession(session))
|
||||
}
|
||||
|
||||
fun handleTransactionRequest(
|
||||
transaction: WCEthereumTransaction,
|
||||
transaction: WcEthereumTransaction,
|
||||
session: WalletConnectSession,
|
||||
id: Long,
|
||||
type: WcTransactionType,
|
||||
type: WcEthTransactionType,
|
||||
) {
|
||||
val activeData = sessions[session.session] ?: return
|
||||
val activeData = sessions[session.session.topic] ?: return
|
||||
scope.launch {
|
||||
val data = WalletConnectSdkHelper().prepareTransactionData(
|
||||
transaction = transaction,
|
||||
session = session,
|
||||
id = id,
|
||||
type = type,
|
||||
EthTransactionData(
|
||||
transaction = transaction,
|
||||
networkId = session.wallet.blockchain?.toNetworkId() ?: "",
|
||||
rawDerivationPath = session.wallet.derivationPath?.rawPath,
|
||||
id = id,
|
||||
topic = session.session.topic,
|
||||
type = type,
|
||||
metaName = session.peerMeta.name,
|
||||
metaUrl = session.peerMeta.url,
|
||||
),
|
||||
).guard {
|
||||
sessions[session.session] = activeData.copy(transactionData = null)
|
||||
sessions[session.session.topic] = activeData.copy(transactionData = null)
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.RejectRequest(
|
||||
session.session,
|
||||
session.session.topic,
|
||||
id,
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
sessions[session.session] = activeData.copy(transactionData = data)
|
||||
sessions[session.session.topic] = activeData.copy(transactionData = data)
|
||||
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.RequestTransaction(
|
||||
data.dialogData,
|
||||
WcPreparedRequest.EthTransaction(
|
||||
preparedRequestData = data,
|
||||
topic = session.session.topic,
|
||||
requestId = id,
|
||||
derivationPath = data.walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun completeTransaction(session: WCSession) {
|
||||
val activeData = sessions[session]
|
||||
fun completeTransaction(topic: Topic) {
|
||||
val activeData = sessions[topic]
|
||||
val data = activeData?.transactionData ?: return
|
||||
scope.launch {
|
||||
val hash = WalletConnectSdkHelper().completeTransaction(data, cardId).guard {
|
||||
sessions[data.session.session] = activeData.copy(transactionData = null)
|
||||
sessions[topic] = activeData.copy(transactionData = null)
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.RejectRequest(
|
||||
data.session.session,
|
||||
topic,
|
||||
data.id,
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
acceptRequest(session, data.id, hash)
|
||||
sessions[session] = activeData.copy(transactionData = null)
|
||||
acceptRequest(topic, data.id, hash)
|
||||
sessions[topic] = activeData.copy(transactionData = null)
|
||||
}
|
||||
}
|
||||
|
||||
fun signBnb(id: Long, data: ByteArray, sessionData: WCSession) {
|
||||
val activeData = sessions[sessionData] ?: return
|
||||
fun signBnb(id: Long, data: ByteArray, topic: Topic) {
|
||||
val activeData = sessions[topic] ?: return
|
||||
scope.launch {
|
||||
val hash = WalletConnectSdkHelper().signBnbTransaction(data, activeData, cardId).guard {
|
||||
val hash = WalletConnectSdkHelper().signBnbTransaction(
|
||||
data = data,
|
||||
networkId = activeData.wallet.blockchain?.toNetworkId() ?: "",
|
||||
derivationPath = activeData.wallet.derivationPath?.rawPath,
|
||||
cardId = cardId,
|
||||
).guard {
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.RejectRequest(
|
||||
sessionData,
|
||||
topic,
|
||||
id,
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
acceptRequest(sessionData, id, hash)
|
||||
acceptRequest(topic, id, hash)
|
||||
}
|
||||
}
|
||||
|
||||
fun handlePersonalSignRequest(message: WCEthereumSignMessage, session: WalletConnectSession, id: Long) {
|
||||
val activeData = sessions[session.session] ?: return
|
||||
val activeData = sessions[session.session.topic] ?: return
|
||||
scope.launch {
|
||||
val data = WalletConnectSdkHelper().prepareDataForPersonalSign(
|
||||
message = message,
|
||||
session = session,
|
||||
message = message.toWcEthereumSignMessage(),
|
||||
topic = session.session.topic,
|
||||
id = id,
|
||||
metaName = session.peerMeta.name,
|
||||
)
|
||||
sessions[session.session] = activeData.copy(personalSignData = data)
|
||||
sessions[session.session.topic] = activeData.copy(personalSignData = data)
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.PersonalSign(
|
||||
data.dialogData,
|
||||
WcPreparedRequest.EthSign(
|
||||
preparedRequestData = data,
|
||||
topic = session.session.topic,
|
||||
requestId = id,
|
||||
derivationPath = session.wallet.derivationPath?.rawPath,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendSignedMessage(session: WCSession) {
|
||||
val activeData = sessions[session]
|
||||
fun sendSignedMessage(topic: Topic) {
|
||||
val activeData = sessions[topic]
|
||||
val data = activeData?.personalSignData ?: return
|
||||
scope.launch {
|
||||
val hash = WalletConnectSdkHelper().signPersonalMessage(data.hash, activeData.wallet, cardId)
|
||||
val hash = WalletConnectSdkHelper().signPersonalMessage(
|
||||
hashToSign = data.hash,
|
||||
networkId = activeData.wallet.blockchain?.toNetworkId() ?: "",
|
||||
derivationPath = activeData.wallet.derivationPath?.rawPath,
|
||||
cardId = cardId,
|
||||
)
|
||||
.guard {
|
||||
sessions[data.session.session] = activeData.copy(transactionData = null)
|
||||
sessions[topic] = activeData.copy(transactionData = null)
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.RejectRequest(
|
||||
data.session.session,
|
||||
topic,
|
||||
data.id,
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
sessions[session] = activeData.copy(personalSignData = data)
|
||||
acceptRequest(data.session.session, data.id, hash)
|
||||
sessions[data.session.session] = activeData.copy(transactionData = null)
|
||||
sessions[topic] = activeData.copy(personalSignData = data)
|
||||
acceptRequest(topic, data.id, hash)
|
||||
sessions[topic] = activeData.copy(transactionData = null)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -348,7 +374,7 @@ class WalletConnectManager {
|
|||
client.onSessionRequest = { id: Long, peer: WCPeerMeta ->
|
||||
Timber.d("OnSessionRequest: $peer")
|
||||
val session = client.session
|
||||
val data = sessions[session]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId)
|
||||
val data = sessions[session?.topic]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId)
|
||||
if (data != null && session != null) {
|
||||
if (!peer.isDappSupported()) {
|
||||
store.dispatchOnMain(
|
||||
|
|
@ -358,7 +384,7 @@ class WalletConnectManager {
|
|||
),
|
||||
)
|
||||
} else {
|
||||
sessions[session] = data
|
||||
sessions[session.topic] = data
|
||||
val sessionData = data.toWalletConnectSession()
|
||||
sessionData?.let {
|
||||
store.dispatchOnMain(
|
||||
|
|
@ -384,13 +410,13 @@ class WalletConnectManager {
|
|||
// AnalyticsAnOld.WcAction.SendTransaction
|
||||
// )
|
||||
// )
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.HandleTransactionRequest(
|
||||
transaction = transaction,
|
||||
session = sessionData,
|
||||
id = id,
|
||||
type = WcTransactionType.EthSendTransaction,
|
||||
type = WcEthTransactionType.EthSendTransaction,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -402,13 +428,13 @@ class WalletConnectManager {
|
|||
// AnalyticsAnOld.WcAction.SignTransaction
|
||||
// )
|
||||
// )
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.HandleTransactionRequest(
|
||||
transaction = transaction,
|
||||
session = sessionData,
|
||||
id = id,
|
||||
type = WcTransactionType.EthSignTransaction,
|
||||
type = WcEthTransactionType.EthSignTransaction,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -420,7 +446,7 @@ class WalletConnectManager {
|
|||
// AnalyticsAnOld.WcAction.PersonalSign
|
||||
// )
|
||||
// )
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.HandlePersonalSignRequest(
|
||||
message,
|
||||
|
|
@ -434,7 +460,7 @@ class WalletConnectManager {
|
|||
client.onBnbCancel = { id: Long, order: WCBinanceCancelOrder ->
|
||||
}
|
||||
client.onBnbTrade = { id: Long, order: WCBinanceTradeOrder ->
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.BinanceTransaction.Trade(
|
||||
id = id,
|
||||
|
|
@ -445,7 +471,7 @@ class WalletConnectManager {
|
|||
}
|
||||
}
|
||||
client.onBnbTransfer = { id: Long, order: WCBinanceTransferOrder ->
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.BinanceTransaction.Transfer(
|
||||
id = id,
|
||||
|
|
@ -484,7 +510,7 @@ class WalletConnectManager {
|
|||
private fun switchChain(chainId: Int, client: WCClient) {
|
||||
val blockchain = Blockchain.fromChainId(chainId)
|
||||
Timber.d("WC switch chainID\nNew Blockchain: $blockchain")
|
||||
val session = sessions[client.session]?.toWalletConnectSession()
|
||||
val session = sessions[client.session?.topic]?.toWalletConnectSession()
|
||||
if (session != null) {
|
||||
store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session))
|
||||
}
|
||||
|
|
@ -499,6 +525,8 @@ class WalletConnectManager {
|
|||
}
|
||||
}
|
||||
|
||||
typealias Topic = String
|
||||
|
||||
data class WalletConnectActiveData(
|
||||
val peerId: String,
|
||||
val remotePeerId: String?,
|
||||
|
|
|
|||
|
|
@ -5,14 +5,7 @@ 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.Companion.toKeccak
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.CommonSigner
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionSender
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.blockchain.extensions.hexToBigDecimal
|
||||
|
|
@ -23,41 +16,40 @@ import com.tangem.common.extensions.hexToBytes
|
|||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTradeOrder
|
||||
import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTransferOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumSignMessage
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcTransactionType
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdk
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.MESSAGE
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.TYPED_MESSAGE
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
class WalletConnectSdkHelper {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun prepareTransactionData(
|
||||
transaction: WCEthereumTransaction,
|
||||
session: WalletConnectSession,
|
||||
id: Long,
|
||||
type: WcTransactionType,
|
||||
): WcTransactionData? {
|
||||
val walletManager = getWalletManager(session) ?: return null
|
||||
suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData? {
|
||||
val transaction = data.transaction
|
||||
val blockchain = Blockchain.fromNetworkId(data.networkId) ?: return null
|
||||
val walletManager = getWalletManager(blockchain, data.rawDerivationPath) ?: return null
|
||||
|
||||
walletManager.safeUpdate()
|
||||
val wallet = walletManager.wallet
|
||||
|
|
@ -94,53 +86,49 @@ class WalletConnectSdkHelper {
|
|||
nonce = transaction.nonce?.hexToBigDecimal()?.toBigInteger(),
|
||||
),
|
||||
)
|
||||
|
||||
val dialogData = TransactionRequestDialogData(
|
||||
dAppName = session.peerMeta.name,
|
||||
dAppUrl = session.peerMeta.url,
|
||||
dAppName = data.metaName,
|
||||
dAppUrl = data.metaUrl,
|
||||
amount = value.toFormattedString(decimals),
|
||||
gasAmount = fee.toFormattedString(decimals),
|
||||
totalAmount = total.toFormattedString(decimals),
|
||||
balance = balance.toFormattedString(decimals),
|
||||
isEnoughFundsToSend = balance - total >= BigDecimal.ZERO,
|
||||
session = session.session,
|
||||
id = id,
|
||||
type = type,
|
||||
topic = data.topic,
|
||||
id = data.id,
|
||||
type = data.type,
|
||||
)
|
||||
return WcTransactionData(
|
||||
type = type,
|
||||
type = data.type,
|
||||
transaction = transactionData,
|
||||
session = session,
|
||||
id = id,
|
||||
topic = data.topic,
|
||||
id = data.id,
|
||||
walletManager = walletManager,
|
||||
dialogData = dialogData,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getWalletManager(session: WalletConnectSession): WalletManager? {
|
||||
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
|
||||
val publicKey = Wallet.PublicKey(
|
||||
session.wallet.walletPublicKey ?: return null,
|
||||
session.wallet.derivedPublicKey,
|
||||
session.wallet.derivationPath,
|
||||
)
|
||||
val blockchain = session.wallet.getBlockchainForSession()
|
||||
return factory.makeWalletManager(
|
||||
private fun getWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager? {
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchain,
|
||||
publicKey = publicKey,
|
||||
derivationPath = derivationPath,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
return store.state.walletState.getWalletManager(blockchainNetwork)
|
||||
}
|
||||
|
||||
suspend fun completeTransaction(data: WcTransactionData, cardId: String?): String? {
|
||||
return when (data.type) {
|
||||
WcTransactionType.EthSendTransaction -> sendTransaction(data, cardId)
|
||||
WcTransactionType.EthSignTransaction -> signTransaction(data, cardId)
|
||||
WcEthTransactionType.EthSendTransaction -> sendTransaction(data, cardId)
|
||||
WcEthTransactionType.EthSignTransaction -> signTransaction(data, cardId)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getGasLimitFromTx(
|
||||
value: BigDecimal,
|
||||
walletManager: WalletManager,
|
||||
transaction: WCEthereumTransaction,
|
||||
transaction: WcEthereumTransaction,
|
||||
): BigDecimal {
|
||||
return transaction.gas?.hexToBigDecimal()
|
||||
?: transaction.gasLimit?.hexToBigDecimal()
|
||||
|
|
@ -154,7 +142,7 @@ class WalletConnectSdkHelper {
|
|||
private suspend fun getGaLimitFromBlockchain(
|
||||
value: BigDecimal,
|
||||
walletManager: WalletManager,
|
||||
transaction: WCEthereumTransaction,
|
||||
transaction: WcEthereumTransaction,
|
||||
): BigDecimal {
|
||||
val gasLimitResult = (walletManager as? EthereumGasLoader)?.getGasLimit(
|
||||
amount = Amount(value, walletManager.wallet.blockchain),
|
||||
|
|
@ -216,17 +204,32 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun signBnbTransaction(data: ByteArray, session: WalletConnectActiveData, cardId: String?): String? {
|
||||
fun prepareBnbTradeOrder(data: WcBinanceTradeOrder): BinanceMessageData.Trade {
|
||||
return BnbHelper.createMessageData(data.toWCBinanceTradeOrder())
|
||||
}
|
||||
|
||||
fun prepareBnbTransferOrder(data: WcBinanceTransferOrder): BinanceMessageData.Transfer {
|
||||
return BnbHelper.createMessageData(data.toWCBinanceTransferOrder())
|
||||
}
|
||||
|
||||
suspend fun signBnbTransaction(
|
||||
data: ByteArray,
|
||||
networkId: String,
|
||||
derivationPath: String?,
|
||||
cardId: String?,
|
||||
): String? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
|
||||
val wallet = getWalletManager(blockchain, derivationPath)?.wallet ?: return null
|
||||
|
||||
val command = SignHashCommand(
|
||||
hash = data,
|
||||
walletPublicKey = session.wallet.walletPublicKey ?: return null,
|
||||
derivationPath = session.wallet.derivationPath,
|
||||
walletPublicKey = wallet.publicKey.seedKey,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
)
|
||||
val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message(), cardId = cardId)
|
||||
return when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val key = session.wallet.derivedPublicKey?.toDecompressedPublicKey()
|
||||
?: session.wallet.walletPublicKey.toDecompressedPublicKey()
|
||||
val key = wallet.publicKey.blockchainKey.toDecompressedPublicKey()
|
||||
getBnbResultString(
|
||||
key.toHexString(),
|
||||
result.data.signature.toHexString(),
|
||||
|
|
@ -241,13 +244,16 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
|
||||
fun prepareDataForPersonalSign(
|
||||
message: WCEthereumSignMessage,
|
||||
session: WalletConnectSession,
|
||||
message: WcEthereumSignMessage,
|
||||
topic: String,
|
||||
metaName: String,
|
||||
id: Long,
|
||||
): WcPersonalSignData {
|
||||
val messageData = when (message.type) {
|
||||
MESSAGE, PERSONAL_MESSAGE -> createMessageData(message)
|
||||
TYPED_MESSAGE -> EthereumUtils.makeTypedDataHash(message.data)
|
||||
WcEthereumSignMessage.WCSignType.MESSAGE,
|
||||
WcEthereumSignMessage.WCSignType.PERSONAL_MESSAGE,
|
||||
-> createMessageData(message)
|
||||
WcEthereumSignMessage.WCSignType.TYPED_MESSAGE -> EthereumUtils.makeTypedDataHash(message.data)
|
||||
}
|
||||
|
||||
val messageString = message.data.hexToAscii()
|
||||
|
|
@ -255,20 +261,21 @@ class WalletConnectSdkHelper {
|
|||
?: message.data
|
||||
|
||||
val dialogData = PersonalSignDialogData(
|
||||
dAppName = session.peerMeta.name,
|
||||
dAppName = metaName,
|
||||
message = messageString,
|
||||
session = session.session,
|
||||
topic = topic,
|
||||
id = id,
|
||||
)
|
||||
return WcPersonalSignData(
|
||||
hash = messageData,
|
||||
session = session,
|
||||
topic = topic,
|
||||
id = id,
|
||||
dialogData = dialogData,
|
||||
type = message.type,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMessageData(message: WCEthereumSignMessage): ByteArray {
|
||||
private fun createMessageData(message: WcEthereumSignMessage): ByteArray {
|
||||
val messageData = try {
|
||||
message.data.removePrefix(HEX_PREFIX).hexToBytes()
|
||||
} catch (exception: Exception) {
|
||||
|
|
@ -299,16 +306,27 @@ class WalletConnectSdkHelper {
|
|||
}.joinToString("")
|
||||
}
|
||||
|
||||
suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession, cardId: String?): String? {
|
||||
val key = wallet.derivedPublicKey ?: wallet.walletPublicKey
|
||||
val command = SignHashCommand(hashToSign, wallet.walletPublicKey!!, wallet.derivationPath)
|
||||
suspend fun signPersonalMessage(
|
||||
hashToSign: ByteArray,
|
||||
networkId: String,
|
||||
derivationPath: String?,
|
||||
cardId: String?,
|
||||
): String? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
|
||||
val wallet = getWalletManager(blockchain, derivationPath)?.wallet ?: return null
|
||||
|
||||
val command = SignHashCommand(
|
||||
hash = hashToSign,
|
||||
walletPublicKey = wallet.publicKey.seedKey,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
)
|
||||
return when (val result = tangemSdkManager.runTaskAsync(command, cardId)) {
|
||||
is CompletionResult.Success -> {
|
||||
val hash = result.data.signature
|
||||
return EthereumUtils.prepareSignedMessageData(
|
||||
signedHash = hash,
|
||||
hashToSign = hashToSign,
|
||||
publicKey = CryptoUtils.decompressPublicKey(key!!),
|
||||
publicKey = wallet.publicKey.blockchainKey.toDecompressedPublicKey(),
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,39 @@
|
|||
package com.tangem.tap.domain.walletconnect.extensions
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumSignMessage
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
|
||||
import com.trustwallet.walletconnect.models.WCPeerMeta
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
|
||||
|
||||
fun WCPeerMeta.isDappSupported(): Boolean {
|
||||
return !unsupportedDappsList.any { this.url.contains(it) }
|
||||
}
|
||||
|
||||
private val unsupportedDappsList: List<String> = listOf("dydx.exchange")
|
||||
private val unsupportedDappsList: List<String> = listOf("dydx.exchange")
|
||||
|
||||
fun WCEthereumTransaction.toWcEthTransaction(): WcEthereumTransaction {
|
||||
return WcEthereumTransaction(
|
||||
from = from,
|
||||
to = to,
|
||||
nonce = nonce,
|
||||
gasPrice = gasPrice,
|
||||
maxFeePerGas = maxFeePerGas,
|
||||
maxPriorityFeePerGas = maxPriorityFeePerGas,
|
||||
gas = gas,
|
||||
gasLimit = gasLimit,
|
||||
value = value,
|
||||
data = data,
|
||||
)
|
||||
}
|
||||
|
||||
fun WCEthereumSignMessage.toWcEthereumSignMessage(): WcEthereumSignMessage {
|
||||
return WcEthereumSignMessage(
|
||||
raw = raw,
|
||||
type = when (type) {
|
||||
WCEthereumSignMessage.WCSignType.MESSAGE -> WcEthereumSignMessage.WCSignType.MESSAGE
|
||||
WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE -> WcEthereumSignMessage.WCSignType.PERSONAL_MESSAGE
|
||||
WCEthereumSignMessage.WCSignType.TYPED_MESSAGE -> WcEthereumSignMessage.WCSignType.TYPED_MESSAGE
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.tap.domain.walletconnect2.app
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.domain.common.extensions.*
|
||||
import com.tangem.tap.domain.walletconnect2.domain.*
|
||||
|
||||
class TangemWcBlockchainHelper : WcBlockchainHelper {
|
||||
override fun chainIdToNetworkIdOrNull(chainId: String): String? {
|
||||
val parsed = chainId.split(CHAIN_SEPARATOR)
|
||||
if (parsed.size != 2) return null
|
||||
when {
|
||||
parsed.first() == EVM_NAMESPACE -> {
|
||||
val chainIdInt = parsed[1].toIntOrNull() ?: return null
|
||||
return Blockchain.fromChainId(chainIdInt)?.toNetworkId()
|
||||
}
|
||||
else -> {
|
||||
val blockchain = Blockchain.fromNetworkId(parsed.first())
|
||||
return if (supportedNonEvmBlockchains.contains(blockchain)) blockchain?.toNetworkId() else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun networkIdToChainIdOrNull(networkId: String): String? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId)
|
||||
val namespace = blockchain?.getCaip2Namespace() ?: return null
|
||||
val chainId = blockchain.getCaip2ChainId() ?: return null
|
||||
return "$namespace$CHAIN_SEPARATOR$chainId"
|
||||
}
|
||||
|
||||
override fun getNamespaceFromFullChainIdOrNull(chainId: String): String? {
|
||||
val parsed = chainId.split(CHAIN_SEPARATOR)
|
||||
return parsed.firstOrNull()
|
||||
}
|
||||
|
||||
override fun chainIdToFullNameOrNull(chainId: String): String? {
|
||||
val networkId = chainIdToNetworkIdOrNull(chainId) ?: return null
|
||||
return Blockchain.fromNetworkId(networkId)?.fullName
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.getCaip2ChainId(): String? {
|
||||
if (this.isEvm()) return this.getChainId()?.toString()
|
||||
|
||||
return when (this) {
|
||||
Blockchain.Solana -> "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ"
|
||||
Blockchain.Polkadot -> "91b171bb158e2d3848fa23a9f1c25182"
|
||||
Blockchain.Tron -> "0x2b6653dc"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.getCaip2Namespace(): String? {
|
||||
return when {
|
||||
this.isEvm() -> EVM_NAMESPACE
|
||||
supportedNonEvmBlockchains.contains(this) -> this.toNetworkId()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private val supportedNonEvmBlockchains = emptySet<Blockchain>() // TODO: add supported networks
|
||||
|
||||
private const val EVM_NAMESPACE = "eip155"
|
||||
private const val CHAIN_SEPARATOR = ":"
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.tap.domain.walletconnect2.app
|
||||
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectEventsHandler
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
|
||||
class WalletConnectEventsHandlerImpl : WalletConnectEventsHandler {
|
||||
override fun onProposalReceived(proposal: WalletConnectEvents.SessionProposal, networksFormatted: String) {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.SessionProposalDialog(
|
||||
sessionProposal = proposal,
|
||||
networks = networksFormatted,
|
||||
onApprove = { store.dispatchOnMain(WalletConnectAction.ApproveProposal) },
|
||||
onReject = { store.dispatchOnMain(WalletConnectAction.RejectProposal) },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onSessionEstablished() {
|
||||
store.dispatchOnMain(WalletConnectAction.SessionEstablished)
|
||||
}
|
||||
|
||||
override fun onSessionRejected(error: WalletConnectError) {
|
||||
store.dispatchOnMain(WalletConnectAction.SessionRejected(error))
|
||||
}
|
||||
|
||||
override fun onListOfSessionsUpdated(sessions: List<WcSessionForScreen>) {
|
||||
Timber.d("WC2: List of sessions updated. Sessions: $sessions")
|
||||
store.dispatchOnMain(WalletConnectAction.SessionListUpdated(sessions))
|
||||
}
|
||||
|
||||
override fun onSessionRequest(request: WcPreparedRequest) {
|
||||
store.dispatchOnMain(WalletConnectAction.ShowSessionRequest(request))
|
||||
}
|
||||
|
||||
override fun onUnsupportedRequest() {
|
||||
store.dispatchOnMain(WalletConnectAction.RejectUnsupportedRequest)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
package com.tangem.tap.domain.walletconnect2.data
|
||||
|
||||
import android.app.*
|
||||
import com.tangem.tap.domain.walletconnect2.domain.*
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.*
|
||||
import com.walletconnect.android.*
|
||||
import com.walletconnect.android.relay.*
|
||||
import com.walletconnect.web3.wallet.client.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.*
|
||||
import javax.inject.*
|
||||
|
||||
class WalletConnectRepositoryImpl @Inject constructor(
|
||||
private val application: Application,
|
||||
private val wcRequestDeserializer: WcJrpcRequestsDeserializer,
|
||||
) : WalletConnectRepository {
|
||||
|
||||
private var sessionProposal: Wallet.Model.SessionProposal? = null
|
||||
private var userNamespaces: Map<NetworkNamespace, List<Account>>? = null
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
private val _events: MutableSharedFlow<WalletConnectEvents> = MutableSharedFlow()
|
||||
override val events: Flow<WalletConnectEvents> = _events
|
||||
|
||||
private val _activeSessions: MutableSharedFlow<List<WalletConnectSession>> = MutableSharedFlow()
|
||||
override val activeSessions: Flow<List<WalletConnectSession>> = _activeSessions
|
||||
|
||||
/**
|
||||
* @param projectId Project ID at https://cloud.walletconnect.com/
|
||||
*/
|
||||
override fun init(projectId: String) {
|
||||
val relayUrl = "relay.walletconnect.com"
|
||||
val serverUrl = "wss://$relayUrl?projectId=$projectId"
|
||||
val connectionType = ConnectionType.AUTOMATIC
|
||||
|
||||
val appMetaData = Core.Model.AppMetaData(
|
||||
name = "Tangem",
|
||||
description = "Tangem Wallet",
|
||||
url = "tangem.com",
|
||||
icons = listOf(
|
||||
"https://user-images.githubusercontent.com/24321494/124071202-72a00900-da58-11eb-935a-dcdab21de52b.png",
|
||||
),
|
||||
redirect = "kotlin-wallet-wc:/request", // Custom Redirect URI
|
||||
)
|
||||
|
||||
CoreClient.initialize(
|
||||
relayServerUrl = serverUrl,
|
||||
connectionType = connectionType,
|
||||
application = application,
|
||||
metaData = appMetaData,
|
||||
) { error ->
|
||||
Timber.d("Error while initializing client: $error")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ExternalApprovalError(error.throwable.message),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Web3Wallet.initialize(Wallet.Params.Init(core = CoreClient)) { error ->
|
||||
Timber.d("Error while initializing Web3Wallet: $error")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ExternalApprovalError(error.throwable.message),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val walletDelegate = defineWalletDelegate()
|
||||
Web3Wallet.setWalletDelegate(walletDelegate)
|
||||
}
|
||||
|
||||
private fun defineWalletDelegate(): Web3Wallet.WalletDelegate {
|
||||
return object : Web3Wallet.WalletDelegate {
|
||||
override fun onSessionProposal(sessionProposal: Wallet.Model.SessionProposal) {
|
||||
// Triggered when wallet receives the session proposal sent by a Dapp
|
||||
Timber.d("sessionProposal: $sessionProposal")
|
||||
this@WalletConnectRepositoryImpl.sessionProposal = sessionProposal
|
||||
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionProposal(
|
||||
sessionProposal.name,
|
||||
sessionProposal.description,
|
||||
sessionProposal.url,
|
||||
sessionProposal.icons,
|
||||
sessionProposal.requiredNamespaces.values.flatMap { it.chains ?: emptyList() },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSessionRequest(sessionRequest: Wallet.Model.SessionRequest) {
|
||||
// Triggered when a Dapp sends SessionRequest to sign a transaction or a message
|
||||
Timber.d("sessionRequest: $sessionRequest")
|
||||
val request = wcRequestDeserializer.deserialize(
|
||||
method = sessionRequest.request.method,
|
||||
params = sessionRequest.request.params,
|
||||
)
|
||||
Timber.d("sessionRequestParsed: $request")
|
||||
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionRequest(
|
||||
request = request,
|
||||
chainId = sessionRequest.chainId,
|
||||
topic = sessionRequest.topic,
|
||||
id = sessionRequest.request.id,
|
||||
metaUrl = sessionRequest.peerMetaData?.url ?: "",
|
||||
metaName = sessionRequest.peerMetaData?.name ?: "",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAuthRequest(authRequest: Wallet.Model.AuthRequest) {
|
||||
// Triggered when Dapp / Requester makes an authorization request
|
||||
}
|
||||
|
||||
override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) {
|
||||
// Triggered when the session is deleted by the peer
|
||||
if (sessionDelete is Wallet.Model.SessionDelete.Success) {
|
||||
scope.launch {
|
||||
_events.emit(WalletConnectEvents.SessionDeleted(sessionDelete.topic))
|
||||
}
|
||||
updateSessions()
|
||||
}
|
||||
Timber.d("onSessionDelete: $sessionDelete")
|
||||
}
|
||||
|
||||
override fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) {
|
||||
// Triggered when wallet receives the session settlement response from Dapp
|
||||
Timber.d("onSessionSettleResponse: $settleSessionResponse")
|
||||
if (settleSessionResponse is Wallet.Model.SettledSessionResponse.Result) {
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalSuccess(
|
||||
topic = settleSessionResponse.session.topic,
|
||||
accounts = userNamespaces?.flatMap { it.value } ?: emptyList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
updateSessions()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSessionUpdateResponse(sessionUpdateResponse: Wallet.Model.SessionUpdateResponse) {
|
||||
// Triggered when wallet receives the session update response from Dapp
|
||||
Timber.d("onSessionUpdateResponse: $sessionUpdateResponse")
|
||||
updateSessions()
|
||||
}
|
||||
|
||||
override fun onConnectionStateChange(state: Wallet.Model.ConnectionState) {
|
||||
// Triggered whenever the connection state is changed
|
||||
Timber.d("onConnectionStateChange: $state")
|
||||
if (state.isAvailable) updateSessions()
|
||||
}
|
||||
|
||||
override fun onError(error: Wallet.Model.Error) {
|
||||
// Triggered whenever there is an issue inside the SDK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun pair(uri: String) {
|
||||
Web3Wallet.pair(Wallet.Params.Pair(uri))
|
||||
}
|
||||
|
||||
override fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>) {
|
||||
this.userNamespaces = userNamespaces
|
||||
|
||||
val sessionProposal: Wallet.Model.SessionProposal = requireNotNull(this.sessionProposal)
|
||||
|
||||
val missingNetworks = findMissingNetworks(
|
||||
namespaces = sessionProposal.requiredNamespaces,
|
||||
userNamespaces = userNamespaces,
|
||||
)
|
||||
if (missingNetworks.isNotEmpty()) {
|
||||
Timber.e("Unsupported blockchains: $missingNetworks")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ApprovalErrorMissingNetworks(missingNetworks.toList()),
|
||||
),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val userChains = userNamespaces.flatMap { namespace ->
|
||||
namespace.value.map { it.chainId to "${it.chainId}:${it.walletAddress}" }
|
||||
}.groupBy { pair -> pair.first }
|
||||
.mapValues { entry -> entry.value.map { pair -> pair.second }.toSet() }
|
||||
|
||||
val preparedNamespaces = sessionProposal.requiredNamespaces.map { requiredNamespace ->
|
||||
val accounts = requiredNamespace.value.chains?.mapNotNull { userChains[it] }?.flatten() ?: emptyList()
|
||||
|
||||
requiredNamespace.key to Wallet.Model.Namespace.Session(
|
||||
accounts = accounts,
|
||||
methods = requiredNamespace.value.methods,
|
||||
events = requiredNamespace.value.events,
|
||||
)
|
||||
}.toMap()
|
||||
|
||||
val sessionApproval = Wallet.Params.SessionApprove(sessionProposal.proposerPublicKey, preparedNamespaces)
|
||||
|
||||
Timber.d("Session approval is prepared for sending: $sessionApproval")
|
||||
|
||||
Web3Wallet.approveSession(
|
||||
params = sessionApproval,
|
||||
onSuccess = {
|
||||
Timber.d("Approved successfully: $it")
|
||||
},
|
||||
onError = {
|
||||
Timber.d("Error while approving: $it")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ExternalApprovalError(it.throwable.message),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun sendRequest(topic: String, id: Long, result: String) {
|
||||
Web3Wallet.respondSessionRequest(
|
||||
params = Wallet.Params.SessionRequestResponse(
|
||||
sessionTopic = topic,
|
||||
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcResult(
|
||||
id = id,
|
||||
result = result,
|
||||
),
|
||||
),
|
||||
onSuccess = {},
|
||||
onError = {},
|
||||
)
|
||||
}
|
||||
|
||||
override fun rejectRequest(topic: String, id: Long) {
|
||||
Web3Wallet.respondSessionRequest(
|
||||
params = Wallet.Params.SessionRequestResponse(
|
||||
sessionTopic = topic,
|
||||
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcError(
|
||||
id = id,
|
||||
code = 0,
|
||||
message = "",
|
||||
),
|
||||
),
|
||||
onSuccess = {},
|
||||
onError = {},
|
||||
)
|
||||
}
|
||||
|
||||
override fun reject() {
|
||||
Web3Wallet.rejectSession(
|
||||
params = Wallet.Params.SessionReject(
|
||||
sessionProposal?.proposerPublicKey ?: "",
|
||||
"",
|
||||
),
|
||||
onSuccess = {
|
||||
Timber.d("Rejected successfully: $it")
|
||||
},
|
||||
onError = {
|
||||
Timber.d("Error while rejecting: $it")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun disconnect(topic: String) {
|
||||
Web3Wallet.disconnectSession(
|
||||
params = Wallet.Params.SessionDisconnect(topic),
|
||||
onSuccess = {
|
||||
updateSessions()
|
||||
Timber.d("Disconnected successfully: $it")
|
||||
},
|
||||
onError = {
|
||||
Timber.d("Error while disconnecting: $it")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun send(topic: String, id: Long, data: String) {
|
||||
Web3Wallet.respondSessionRequest(
|
||||
params = Wallet.Params.SessionRequestResponse(
|
||||
sessionTopic = topic,
|
||||
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcResult(
|
||||
id = id,
|
||||
result = data,
|
||||
),
|
||||
),
|
||||
onError = {},
|
||||
onSuccess = {},
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateSessions() {
|
||||
scope.launch {
|
||||
val availableSessions = Web3Wallet.getListOfActiveSessions()
|
||||
.map {
|
||||
WalletConnectSession(
|
||||
topic = it.topic,
|
||||
icon = it.metaData?.icons?.firstOrNull(),
|
||||
name = it.metaData?.name,
|
||||
url = it.metaData?.url,
|
||||
)
|
||||
}
|
||||
Timber.d("Available sessions: $availableSessions")
|
||||
_activeSessions.emit(availableSessions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findMissingNetworks(
|
||||
namespaces: Map<String, Wallet.Model.Namespace.Proposal>,
|
||||
userNamespaces: Map<NetworkNamespace, List<Account>>,
|
||||
): Collection<String> {
|
||||
val requiredChains = namespaces.values.flatMap { it.chains ?: emptyList() }
|
||||
val userChains = userNamespaces.flatMap { it.value.map { account -> account.chainId } }
|
||||
return requiredChains.subtract(userChains.toSet())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.tap.domain.walletconnect2.data
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Session
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class WalletConnectSessionsRepositoryImpl @Inject constructor(
|
||||
@SdkMoshi private val moshi: Moshi,
|
||||
private val fileReader: FileReader,
|
||||
) :
|
||||
WalletConnectSessionsRepository {
|
||||
|
||||
private val sessionsAdapter: JsonAdapter<List<Session>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, Session::class.java),
|
||||
)
|
||||
|
||||
override suspend fun loadSessions(userWallet: String): List<Session> {
|
||||
return try {
|
||||
val fileContent = fileReader.readFile(getFileNameForUserWallet(userWallet))
|
||||
sessionsAdapter.fromJson(fileContent) ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveSession(userWallet: String, session: Session) {
|
||||
val updatedList = loadSessions(userWallet).plus(session)
|
||||
writeSessionToFile(sessions = updatedList, userWallet = userWallet)
|
||||
}
|
||||
|
||||
override suspend fun removeSession(userWallet: String, topic: String) {
|
||||
val updatedList = loadSessions(userWallet).filterNot { it.topic == topic }
|
||||
writeSessionToFile(sessions = updatedList, userWallet = userWallet)
|
||||
}
|
||||
|
||||
private fun writeSessionToFile(sessions: List<Session>, userWallet: String) {
|
||||
val serialized = sessionsAdapter.toJson(sessions)
|
||||
try {
|
||||
fileReader.rewriteFile(serialized, getFileNameForUserWallet(userWallet))
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FILE_NAME_PREFIX = "wc_2"
|
||||
|
||||
private fun getFileNameForUserWallet(userWallet: String): String {
|
||||
return "$FILE_NAME_PREFIX-${userWallet.uppercase()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.tap.domain.walletconnect2.di
|
||||
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
||||
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
||||
import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl
|
||||
import com.tangem.tap.domain.walletconnect2.data.WalletConnectRepositoryImpl
|
||||
import com.tangem.tap.domain.walletconnect2.data.WalletConnectSessionsRepositoryImpl
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
class WalletConnectInteractorModule {
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
fun provideWalletConnectInteractor(
|
||||
wcRepository: WalletConnectRepository,
|
||||
wcSessionsRepository: WalletConnectSessionsRepository,
|
||||
): WalletConnectInteractor {
|
||||
return WalletConnectInteractor(
|
||||
handler = WalletConnectEventsHandlerImpl(),
|
||||
walletConnectRepository = wcRepository,
|
||||
sessionsRepository = wcSessionsRepository,
|
||||
sdkHelper = WalletConnectSdkHelper(),
|
||||
blockchainHelper = TangemWcBlockchainHelper(),
|
||||
dispatcher = AppCoroutineDispatcherProvider(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface WalletConnectModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindWalletConnectRepository(repository: WalletConnectRepositoryImpl): WalletConnectRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindWalletConnectSessionsRepository(
|
||||
repository: WalletConnectSessionsRepositoryImpl,
|
||||
): WalletConnectSessionsRepository
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
|
||||
|
||||
interface WalletConnectEventsHandler {
|
||||
fun onProposalReceived(proposal: WalletConnectEvents.SessionProposal, networksFormatted: String)
|
||||
|
||||
fun onSessionEstablished()
|
||||
|
||||
fun onSessionRejected(error: WalletConnectError)
|
||||
|
||||
fun onListOfSessionsUpdated(sessions: List<WcSessionForScreen>)
|
||||
|
||||
fun onSessionRequest(request: WcPreparedRequest)
|
||||
|
||||
fun onUnsupportedRequest()
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.domain.walletconnect.*
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.*
|
||||
import com.tangem.tap.features.details.ui.walletconnect.*
|
||||
import com.tangem.utils.coroutines.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.*
|
||||
|
||||
class WalletConnectInteractor(
|
||||
private val handler: WalletConnectEventsHandler,
|
||||
private val walletConnectRepository: WalletConnectRepository,
|
||||
private val sessionsRepository: WalletConnectSessionsRepository,
|
||||
private val sdkHelper: WalletConnectSdkHelper,
|
||||
private val dispatcher: CoroutineDispatcherProvider,
|
||||
val blockchainHelper: WcBlockchainHelper,
|
||||
) {
|
||||
|
||||
private val events = walletConnectRepository.events
|
||||
private val sessions = walletConnectRepository.activeSessions
|
||||
|
||||
private var userWalletId: String = ""
|
||||
private var cardId: String? = null
|
||||
|
||||
private var currentRequest: WalletConnectEvents.SessionRequest? = null
|
||||
private val sessionRequestConverter = WcSessionRequestConverter(
|
||||
blockchainHelper = blockchainHelper,
|
||||
sessionsRepository = sessionsRepository,
|
||||
sdkHelper = sdkHelper,
|
||||
)
|
||||
|
||||
suspend fun startListening(userWalletId: String, cardId: String?) {
|
||||
this.userWalletId = userWalletId
|
||||
this.cardId = cardId
|
||||
coroutineScope {
|
||||
launch { subscribeToEvents() }
|
||||
launch { subscribeToSessions() }
|
||||
}
|
||||
walletConnectRepository.updateSessions()
|
||||
}
|
||||
|
||||
private suspend fun subscribeToEvents() {
|
||||
events
|
||||
.onEach { wcEvent ->
|
||||
when (wcEvent) {
|
||||
is WalletConnectEvents.SessionProposal -> {
|
||||
Timber.d("WC session proposal event received")
|
||||
val networksFormatted = wcEvent.chainIds
|
||||
.mapNotNull { blockchainHelper.chainIdToFullNameOrNull(it) }
|
||||
.toString()
|
||||
handler.onProposalReceived(proposal = wcEvent, networksFormatted = networksFormatted)
|
||||
}
|
||||
is WalletConnectEvents.SessionApprovalError -> {
|
||||
val error = when (wcEvent.error) {
|
||||
is WalletConnectError.ApprovalErrorMissingNetworks -> {
|
||||
val missingNetworks = wcEvent.error.missingChains
|
||||
.map { blockchainHelper.chainIdToNetworkIdOrNull(it) }
|
||||
val containsUnsupportedNetworks = missingNetworks.any { it == null }
|
||||
if (containsUnsupportedNetworks) {
|
||||
WalletConnectError.ApprovalErrorUnsupportedNetwork
|
||||
} else {
|
||||
WalletConnectError.ApprovalErrorAddNetwork(missingNetworks.filterNotNull())
|
||||
}
|
||||
}
|
||||
else -> wcEvent.error
|
||||
}
|
||||
handler.onSessionRejected(error)
|
||||
}
|
||||
is WalletConnectEvents.SessionApprovalSuccess -> {
|
||||
sessionsRepository.saveSession(
|
||||
userWallet = userWalletId,
|
||||
session = Session.fromAccounts(
|
||||
accounts = wcEvent.accounts,
|
||||
topic = wcEvent.topic,
|
||||
),
|
||||
)
|
||||
handler.onSessionEstablished()
|
||||
}
|
||||
is WalletConnectEvents.SessionDeleted -> {
|
||||
sessionsRepository.removeSession(userWalletId, wcEvent.topic)
|
||||
}
|
||||
is WalletConnectEvents.SessionRequest -> {
|
||||
handleRequest(wcEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(dispatcher.io)
|
||||
.collect()
|
||||
}
|
||||
|
||||
private suspend fun subscribeToSessions() {
|
||||
sessions
|
||||
.onEach { listOfSessions ->
|
||||
val relevantTopics = getTopicsForUserWallet(userWalletId, sessionsRepository)
|
||||
val filteredSessions = filterSessionsForUserWallet(listOfSessions, relevantTopics)
|
||||
handler.onListOfSessionsUpdated(filteredSessions)
|
||||
}
|
||||
.flowOn(dispatcher.io)
|
||||
.collect()
|
||||
}
|
||||
|
||||
private fun filterSessionsForUserWallet(
|
||||
availableSessions: List<WalletConnectSession>,
|
||||
relevantTopics: List<String>,
|
||||
): List<WcSessionForScreen> {
|
||||
return availableSessions.filter { relevantTopics.contains(it.topic) }
|
||||
.map {
|
||||
WcSessionForScreen(
|
||||
description = it.name ?: "",
|
||||
sessionId = it.topic,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getTopicsForUserWallet(
|
||||
userWalletId: String,
|
||||
repository: WalletConnectSessionsRepository,
|
||||
): List<String> {
|
||||
return repository.loadSessions(userWalletId).map { it.topic }
|
||||
}
|
||||
|
||||
fun approveSessionProposal(accounts: List<Account>) {
|
||||
val userNamespaces: Map<NetworkNamespace, List<Account>> = accounts
|
||||
.groupBy { account ->
|
||||
blockchainHelper.getNamespaceFromFullChainIdOrNull(account.chainId)
|
||||
?.let { NetworkNamespace(it) }
|
||||
}.filterNotNull()
|
||||
walletConnectRepository.approve(
|
||||
userNamespaces = userNamespaces,
|
||||
)
|
||||
}
|
||||
|
||||
fun rejectSessionProposal() {
|
||||
walletConnectRepository.reject()
|
||||
}
|
||||
|
||||
fun disconnectSession(topic: String) {
|
||||
walletConnectRepository.disconnect(topic)
|
||||
}
|
||||
|
||||
fun rejectRequest(topic: String, id: Long) {
|
||||
walletConnectRepository.rejectRequest(topic, id)
|
||||
}
|
||||
|
||||
private suspend fun handleRequest(sessionRequest: WalletConnectEvents.SessionRequest) {
|
||||
val error: WalletConnectError? = when {
|
||||
sessionsRepository.loadSessions(userWalletId).none { it.topic == sessionRequest.topic } -> {
|
||||
WalletConnectError.WrongUserWallet
|
||||
}
|
||||
sessionRequest.request is WcRequest.CustomRequest -> {
|
||||
WalletConnectError.UnsupportedMethod
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
if (error != null) {
|
||||
walletConnectRepository.rejectRequest(sessionRequest.topic, sessionRequest.id)
|
||||
return
|
||||
}
|
||||
|
||||
when (sessionRequest.request) {
|
||||
is WcRequest.BnbCancel -> Unit
|
||||
is WcRequest.BnbTxConfirm -> walletConnectRepository.sendRequest(
|
||||
topic = sessionRequest.topic,
|
||||
id = sessionRequest.id,
|
||||
result = "",
|
||||
)
|
||||
else -> {
|
||||
currentRequest = sessionRequest
|
||||
val data = prepareRequestData(sessionRequest)
|
||||
if (data != null) {
|
||||
handler.onSessionRequest(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun continueWithRequest(request: WcPreparedRequest) {
|
||||
val currentRequest = this.currentRequest
|
||||
if (currentRequest == null || request.topic != currentRequest.topic) return
|
||||
|
||||
val networkId = blockchainHelper.chainIdToNetworkIdOrNull(currentRequest.chainId ?: "") ?: return
|
||||
val signedHash = when (request) {
|
||||
is WcPreparedRequest.BnbTransaction -> sdkHelper.signBnbTransaction(
|
||||
data = request.preparedRequestData.data.data,
|
||||
networkId = networkId,
|
||||
derivationPath = request.derivationPath,
|
||||
cardId = cardId,
|
||||
)
|
||||
is WcPreparedRequest.EthTransaction -> sdkHelper.completeTransaction(
|
||||
data = request.preparedRequestData,
|
||||
cardId = cardId,
|
||||
)
|
||||
is WcPreparedRequest.EthSign -> sdkHelper.signPersonalMessage(
|
||||
hashToSign = request.preparedRequestData.hash,
|
||||
networkId = networkId,
|
||||
derivationPath = request.derivationPath,
|
||||
cardId = cardId,
|
||||
)
|
||||
}
|
||||
|
||||
Timber.d("Signed hash: $signedHash")
|
||||
|
||||
if (signedHash == null) {
|
||||
walletConnectRepository.rejectRequest(
|
||||
topic = request.topic,
|
||||
id = request.requestId,
|
||||
)
|
||||
} else {
|
||||
walletConnectRepository.sendRequest(
|
||||
topic = request.topic,
|
||||
id = request.requestId,
|
||||
result = signedHash,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun prepareRequestData(sessionRequest: WalletConnectEvents.SessionRequest): WcPreparedRequest? {
|
||||
return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Account
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.NetworkNamespace
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectSession
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WalletConnectRepository {
|
||||
|
||||
val events: Flow<WalletConnectEvents>
|
||||
|
||||
val activeSessions: Flow<List<WalletConnectSession>>
|
||||
|
||||
fun init(projectId: String)
|
||||
|
||||
fun updateSessions()
|
||||
|
||||
fun pair(uri: String)
|
||||
|
||||
fun disconnect(topic: String)
|
||||
|
||||
fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>)
|
||||
|
||||
fun reject()
|
||||
|
||||
fun sendRequest(topic: String, id: Long, result: String)
|
||||
|
||||
fun rejectRequest(topic: String, id: Long)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Session
|
||||
|
||||
interface WalletConnectSessionsRepository {
|
||||
suspend fun loadSessions(userWallet: String): List<Session>
|
||||
|
||||
suspend fun saveSession(userWallet: String, session: Session)
|
||||
|
||||
suspend fun removeSession(userWallet: String, topic: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
interface WcBlockchainHelper {
|
||||
fun chainIdToNetworkIdOrNull(chainId: String): String?
|
||||
|
||||
fun networkIdToChainIdOrNull(networkId: String): String?
|
||||
|
||||
fun getNamespaceFromFullChainIdOrNull(chainId: String): String?
|
||||
|
||||
fun chainIdToFullNameOrNull(chainId: String): String?
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WCBinanceTxConfirmParam
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceCancelOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
enum class WcJrpcMethods(val code: String) {
|
||||
|
||||
ETH_SIGN("eth_sign"),
|
||||
ETH_PERSONAL_SIGN("personal_sign"),
|
||||
ETH_SIGN_TYPE_DATA("eth_signTypedData"),
|
||||
ETH_SIGN_TYPE_DATA_V4("eth_signTypedData_v4"),
|
||||
ETH_SIGN_TRANSACTION("eth_signTransaction"),
|
||||
ETH_SEND_TRANSACTION("eth_sendTransaction"),
|
||||
BNB_SIGN("bnb_sign"),
|
||||
BNB_TRANSACTION_CONFIRM("bnb_tx_confirmation"),
|
||||
SIGN_TRANSACTION("trust_signTransaction"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromCode(code: String): WcJrpcMethods? = values().firstOrNull { it.code == code }
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WCSignTransaction(
|
||||
val network: Int,
|
||||
val transaction: String,
|
||||
) : WcRequestData
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WcEthereumSignMessage(
|
||||
val raw: List<String>,
|
||||
val type: WCSignType,
|
||||
) : WcRequestData {
|
||||
enum class WCSignType {
|
||||
MESSAGE, PERSONAL_MESSAGE, TYPED_MESSAGE
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw parameters will always be the message and the address. Depending on the WCSignType,
|
||||
* those parameters can be swapped as description below:
|
||||
*
|
||||
* - MESSAGE: `[address, data ]`
|
||||
* - TYPED_MESSAGE: `[address, data]`
|
||||
* - PERSONAL_MESSAGE: `[data, address]`
|
||||
*
|
||||
* reference: https://docs.walletconnect.org/json-rpc/ethereum#eth_signtypeddata
|
||||
*/
|
||||
val data
|
||||
get() = when (type) {
|
||||
WCSignType.PERSONAL_MESSAGE -> raw[0]
|
||||
else -> raw[1]
|
||||
}
|
||||
|
||||
val address
|
||||
get() = when (type) {
|
||||
WCSignType.PERSONAL_MESSAGE -> raw[1]
|
||||
else -> raw[0]
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WcEthereumTransaction(
|
||||
val from: String,
|
||||
val to: String?,
|
||||
val nonce: String?,
|
||||
val gasPrice: String?,
|
||||
val maxFeePerGas: String?,
|
||||
val maxPriorityFeePerGas: String?,
|
||||
val gas: String?,
|
||||
val gasLimit: String?,
|
||||
val value: String?,
|
||||
val data: String,
|
||||
) : WcRequestData
|
||||
|
||||
interface WcRequestData
|
||||
|
||||
data class WcCustomRequestData(val data: String) : WcRequestData
|
||||
|
||||
sealed class WcRequest(open val data: WcRequestData) {
|
||||
data class EthSign(override val data: WcEthereumSignMessage) : WcRequest(data)
|
||||
data class EthSignTransaction(override val data: WcEthereumTransaction) : WcRequest(data)
|
||||
data class EthSendTransaction(override val data: WcEthereumTransaction) : WcRequest(data)
|
||||
data class BnbTrade(override val data: WcBinanceTradeOrder) : WcRequest(data)
|
||||
data class BnbCancel(override val data: WcBinanceCancelOrder) : WcRequest(data)
|
||||
data class BnbTransfer(override val data: WcBinanceTransferOrder) : WcRequest(data)
|
||||
data class BnbTxConfirm(override val data: WCBinanceTxConfirmParam) : WcRequest(data)
|
||||
data class SignTransaction(override val data: WCSignTransaction) : WcRequest(data)
|
||||
data class CustomRequest(override val data: WcCustomRequestData) : WcRequest(data)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private val moshi: Moshi) {
|
||||
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
fun deserialize(method: String, params: String): WcRequest {
|
||||
val customRequest = WcRequest.CustomRequest(WcCustomRequestData(params))
|
||||
val wcMethod: WcJrpcMethods = WcJrpcMethods.fromCode(method) ?: return customRequest
|
||||
|
||||
return when (wcMethod) {
|
||||
WcJrpcMethods.ETH_SIGN_TRANSACTION -> {
|
||||
val deserializedParams = moshi.adapter<List<WcEthereumTransaction>>(
|
||||
Types.newParameterizedType(List::class.java, WcEthereumTransaction::class.java),
|
||||
).fromJsonFirstOrNull(params) ?: return customRequest
|
||||
WcRequest.EthSignTransaction(data = deserializedParams)
|
||||
}
|
||||
WcJrpcMethods.ETH_SEND_TRANSACTION -> {
|
||||
val deserializedParams = moshi.adapter<List<WcEthereumTransaction>>(
|
||||
Types.newParameterizedType(List::class.java, WcEthereumTransaction::class.java),
|
||||
).fromJsonFirstOrNull(params) ?: return customRequest
|
||||
WcRequest.EthSendTransaction(data = deserializedParams)
|
||||
}
|
||||
WcJrpcMethods.ETH_SIGN -> {
|
||||
val deserializedParams = moshi.adapter<List<String>>(
|
||||
Types.newParameterizedType(List::class.java, String::class.java),
|
||||
).fromJsonOrNull(params) ?: return customRequest
|
||||
val data = WcEthereumSignMessage(
|
||||
raw = deserializedParams,
|
||||
type = WcEthereumSignMessage.WCSignType.MESSAGE,
|
||||
)
|
||||
WcRequest.EthSign(data = data)
|
||||
}
|
||||
WcJrpcMethods.ETH_PERSONAL_SIGN -> {
|
||||
val deserializedParams = moshi.adapter<List<String>>(
|
||||
Types.newParameterizedType(List::class.java, String::class.java),
|
||||
).fromJsonOrNull(params) ?: return customRequest
|
||||
val data = WcEthereumSignMessage(
|
||||
raw = deserializedParams,
|
||||
type = WcEthereumSignMessage.WCSignType.PERSONAL_MESSAGE,
|
||||
)
|
||||
WcRequest.EthSign(data = data)
|
||||
}
|
||||
WcJrpcMethods.ETH_SIGN_TYPE_DATA, WcJrpcMethods.ETH_SIGN_TYPE_DATA_V4 -> {
|
||||
val deserializedParams = listOf(
|
||||
params.substring(params.indexOf("\"") + 1, params.indexOf("\"", startIndex = 2)),
|
||||
params.substring(params.indexOfFirst { it == '{' }, params.indexOfLast { it == '}' } + 1),
|
||||
)
|
||||
val data = WcEthereumSignMessage(
|
||||
deserializedParams,
|
||||
WcEthereumSignMessage.WCSignType.TYPED_MESSAGE,
|
||||
)
|
||||
Timber.d("TypedData params: $deserializedParams")
|
||||
WcRequest.EthSign(data)
|
||||
}
|
||||
WcJrpcMethods.BNB_SIGN -> {
|
||||
return deserializeBnb(moshi, params) ?: customRequest
|
||||
}
|
||||
WcJrpcMethods.BNB_TRANSACTION_CONFIRM -> {
|
||||
val deserializedParams = moshi.adapter<List<WCBinanceTxConfirmParam>>(
|
||||
Types.newParameterizedType(List::class.java, WCBinanceTxConfirmParam::class.java),
|
||||
).fromJsonFirstOrNull(params) ?: return customRequest
|
||||
WcRequest.BnbTxConfirm(data = deserializedParams)
|
||||
}
|
||||
WcJrpcMethods.SIGN_TRANSACTION -> {
|
||||
val deserializedParams = moshi.adapter<List<WCSignTransaction>>(
|
||||
Types.newParameterizedType(List::class.java, WCSignTransaction::class.java),
|
||||
).fromJsonFirstOrNull(params) ?: return customRequest
|
||||
WcRequest.SignTransaction(data = deserializedParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deserializeBnb(moshi: Moshi, params: String): WcRequest? {
|
||||
val cancelOrder = moshi.adapter<List<WcBinanceCancelOrder>>(
|
||||
Types.newParameterizedType(List::class.java, WcBinanceCancelOrder::class.java),
|
||||
).fromJsonFirstOrNull(params)
|
||||
if (cancelOrder != null) return WcRequest.BnbCancel(cancelOrder)
|
||||
|
||||
val tradeOrder = moshi.adapter<List<WcBinanceTradeOrder>>(
|
||||
Types.newParameterizedType(List::class.java, WcBinanceTradeOrder::class.java),
|
||||
).fromJsonFirstOrNull(params)
|
||||
if (tradeOrder != null) return WcRequest.BnbTrade(tradeOrder)
|
||||
|
||||
val transferOrder = moshi.adapter<List<WcBinanceTransferOrder>>(
|
||||
Types.newParameterizedType(List::class.java, WcBinanceTransferOrder::class.java),
|
||||
).fromJsonFirstOrNull(params)
|
||||
if (transferOrder != null) return WcRequest.BnbTransfer(transferOrder)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun <T> JsonAdapter<T>.fromJsonOrNull(data: String): T? {
|
||||
return try {
|
||||
this.fromJson(data)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e.message)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> JsonAdapter<List<T>>.fromJsonFirstOrNull(data: String): T? {
|
||||
return try {
|
||||
this.fromJson(data)?.firstOrNull()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e.message)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData
|
||||
|
||||
sealed class WcPreparedRequest(
|
||||
open val preparedRequestData: Any,
|
||||
val topic: String,
|
||||
val requestId: Long,
|
||||
val derivationPath: String?,
|
||||
) {
|
||||
class EthSign(
|
||||
override val preparedRequestData: WcPersonalSignData,
|
||||
topic: String,
|
||||
requestId: Long,
|
||||
derivationPath: String?,
|
||||
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
|
||||
|
||||
class EthTransaction(
|
||||
override val preparedRequestData: WcTransactionData,
|
||||
topic: String,
|
||||
requestId: Long,
|
||||
derivationPath: String?,
|
||||
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
|
||||
|
||||
class BnbTransaction(
|
||||
override val preparedRequestData: BnbData,
|
||||
topic: String,
|
||||
requestId: Long,
|
||||
derivationPath: String?,
|
||||
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
|
||||
|
||||
class WcSessionRequestConverter(
|
||||
private val blockchainHelper: WcBlockchainHelper,
|
||||
private val sessionsRepository: WalletConnectSessionsRepository,
|
||||
private val sdkHelper: WalletConnectSdkHelper,
|
||||
) {
|
||||
|
||||
@Suppress("LongMethod")
|
||||
suspend fun prepareRequest(
|
||||
sessionRequest: WalletConnectEvents.SessionRequest,
|
||||
userWalletId: String,
|
||||
): WcPreparedRequest? {
|
||||
val networkId = blockchainHelper.chainIdToNetworkIdOrNull(sessionRequest.chainId ?: "") ?: return null
|
||||
val derivationPath = getDerivationPath(
|
||||
sessionsRepository = sessionsRepository,
|
||||
sessionRequest = sessionRequest,
|
||||
userWalletId = userWalletId,
|
||||
walletAddress = getWalletAddress(sessionRequest.request),
|
||||
)
|
||||
return when (val request = sessionRequest.request) {
|
||||
is WcRequest.EthSendTransaction -> {
|
||||
val data = sdkHelper.prepareTransactionData(
|
||||
EthTransactionData(
|
||||
transaction = request.data,
|
||||
networkId = networkId,
|
||||
rawDerivationPath = derivationPath,
|
||||
id = sessionRequest.id,
|
||||
topic = sessionRequest.topic,
|
||||
type = WcEthTransactionType.EthSendTransaction,
|
||||
metaName = sessionRequest.metaName,
|
||||
metaUrl = sessionRequest.metaUrl,
|
||||
),
|
||||
) ?: return null
|
||||
WcPreparedRequest.EthTransaction(
|
||||
preparedRequestData = data,
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
is WcRequest.EthSignTransaction -> {
|
||||
val data = sdkHelper.prepareTransactionData(
|
||||
EthTransactionData(
|
||||
transaction = request.data,
|
||||
networkId = networkId,
|
||||
rawDerivationPath = derivationPath,
|
||||
id = sessionRequest.id,
|
||||
topic = sessionRequest.topic,
|
||||
type = WcEthTransactionType.EthSignTransaction,
|
||||
metaName = sessionRequest.metaName,
|
||||
metaUrl = sessionRequest.metaUrl,
|
||||
),
|
||||
) ?: return null
|
||||
WcPreparedRequest.EthTransaction(
|
||||
preparedRequestData = data,
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
is WcRequest.EthSign -> {
|
||||
val data = sdkHelper.prepareDataForPersonalSign(
|
||||
request.data,
|
||||
sessionRequest.topic,
|
||||
sessionRequest.metaName,
|
||||
sessionRequest.id,
|
||||
)
|
||||
WcPreparedRequest.EthSign(
|
||||
preparedRequestData = data,
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
is WcRequest.BnbTrade -> {
|
||||
val data = sdkHelper.prepareBnbTradeOrder(request.data)
|
||||
WcPreparedRequest.BnbTransaction(
|
||||
BnbData(
|
||||
data = data,
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
dAppName = sessionRequest.metaName,
|
||||
),
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
is WcRequest.BnbTransfer -> {
|
||||
val data = sdkHelper.prepareBnbTransferOrder(request.data)
|
||||
WcPreparedRequest.BnbTransaction(
|
||||
BnbData(
|
||||
data = data,
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
dAppName = sessionRequest.metaName,
|
||||
),
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWalletAddress(request: WcRequest): String? {
|
||||
return when (request) {
|
||||
is WcRequest.BnbTrade -> request.data.accountNumber
|
||||
is WcRequest.BnbTransfer -> request.data.accountNumber
|
||||
is WcRequest.EthSendTransaction -> request.data.from
|
||||
is WcRequest.EthSignTransaction -> request.data.from
|
||||
is WcRequest.EthSign -> request.data.address
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getDerivationPath(
|
||||
sessionsRepository: WalletConnectSessionsRepository,
|
||||
sessionRequest: WalletConnectEvents.SessionRequest,
|
||||
userWalletId: String,
|
||||
walletAddress: String?,
|
||||
): String? {
|
||||
return sessionsRepository.loadSessions(userWalletId)
|
||||
.firstOrNull { it.topic == sessionRequest.topic }
|
||||
?.accounts?.firstOrNull { it.chainId == sessionRequest.chainId && it.walletAddress == walletAddress }
|
||||
?.derivationPath
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
data class Account(val chainId: String, val walletAddress: String, val derivationPath: String?)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
|
||||
data class BnbData(
|
||||
val data: BinanceMessageData,
|
||||
val topic: String,
|
||||
val requestId: Long,
|
||||
val dAppName: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
data class ChainWithDerivation(
|
||||
val chain: String,
|
||||
val derivationPath: String?,
|
||||
)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
|
||||
|
||||
data class EthTransactionData(
|
||||
val transaction: WcEthereumTransaction,
|
||||
val networkId: String,
|
||||
val rawDerivationPath: String?,
|
||||
val id: Long,
|
||||
val topic: String,
|
||||
val type: WcEthTransactionType,
|
||||
val metaName: String,
|
||||
val metaUrl: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
@JvmInline
|
||||
value class NetworkNamespace(val value: String)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
data class Session(
|
||||
val topic: String,
|
||||
val accounts: List<Account>,
|
||||
) {
|
||||
companion object {
|
||||
fun fromAccounts(accounts: List<Account>, topic: String): Session {
|
||||
return Session(
|
||||
topic = topic,
|
||||
accounts = accounts,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
sealed class WalletConnectError : Exception() {
|
||||
data class ApprovalErrorMissingNetworks(val missingChains: List<String>) : WalletConnectError()
|
||||
data class ApprovalErrorAddNetwork(val networks: List<String>) : WalletConnectError()
|
||||
object ApprovalErrorUnsupportedNetwork : WalletConnectError()
|
||||
data class ExternalApprovalError(override val message: String?) : WalletConnectError()
|
||||
object WrongUserWallet : WalletConnectError()
|
||||
object UnsupportedMethod : WalletConnectError()
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcRequest
|
||||
import java.net.URI
|
||||
|
||||
sealed interface WalletConnectEvents {
|
||||
data class SessionProposal(
|
||||
val name: String,
|
||||
val description: String,
|
||||
val url: String,
|
||||
val icons: List<URI>,
|
||||
val chainIds: List<String>,
|
||||
) : WalletConnectEvents
|
||||
|
||||
data class SessionApprovalError(val error: WalletConnectError) : WalletConnectEvents
|
||||
data class SessionApprovalSuccess(val topic: String, val accounts: List<Account>) : WalletConnectEvents
|
||||
data class SessionDeleted(val topic: String) : WalletConnectEvents
|
||||
|
||||
data class SessionRequest(
|
||||
val request: WcRequest,
|
||||
val chainId: String?,
|
||||
val topic: String,
|
||||
val id: Long,
|
||||
val metaName: String,
|
||||
val metaUrl: String,
|
||||
) : WalletConnectEvents
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
data class WalletConnectSession(
|
||||
val topic: String,
|
||||
val icon: String?,
|
||||
val name: String?,
|
||||
val url: String?,
|
||||
)
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models.binance
|
||||
|
||||
import com.squareup.moshi.*
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@JsonClass(generateAdapter = true)
|
||||
class WcBinanceCancelOrder(
|
||||
@Json(name = "account_number")
|
||||
accountNumber: String,
|
||||
@Json(name = "chain_id")
|
||||
chainId: String,
|
||||
@Json(name = "data")
|
||||
data: String?,
|
||||
@Json(name = "memo")
|
||||
memo: String?,
|
||||
@Json(name = "sequence")
|
||||
sequence: String,
|
||||
@Json(name = "source")
|
||||
source: String,
|
||||
@Json(name = "msgs")
|
||||
msgs: List<Message>,
|
||||
) : WcBinanceOrder<WcBinanceCancelOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
|
||||
|
||||
enum class MessageKey(val key: String) {
|
||||
REFID("refid"),
|
||||
SENDER("sender"),
|
||||
SYMBOL("symbol"),
|
||||
}
|
||||
|
||||
data class Message(
|
||||
val refid: String,
|
||||
val sender: String,
|
||||
val symbol: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models.binance
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
open class WcBinanceOrder<T>(
|
||||
val accountNumber: String,
|
||||
val chainId: String,
|
||||
val data: String?,
|
||||
val memo: String?,
|
||||
val sequence: String,
|
||||
val source: String,
|
||||
val msgs: List<T>,
|
||||
) : WcRequestData
|
||||
|
||||
data class WCBinanceTxConfirmParam(
|
||||
val ok: Boolean,
|
||||
val errorMsg: String?,
|
||||
) : WcRequestData
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models.binance
|
||||
|
||||
import com.github.salomonbrys.kotson.*
|
||||
import com.google.gson.JsonObject
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@JsonClass(generateAdapter = true)
|
||||
class WcBinanceTradeOrder(
|
||||
@Json(name = "account_number")
|
||||
accountNumber: String,
|
||||
@Json(name = "chain_id")
|
||||
chainId: String,
|
||||
data: String?,
|
||||
memo: String?,
|
||||
sequence: String,
|
||||
source: String,
|
||||
msgs: List<Message>,
|
||||
) : WcBinanceOrder<WcBinanceTradeOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
|
||||
|
||||
enum class MessageKey(val key: String) {
|
||||
ID("id"),
|
||||
ORDER_TYPE("ordertype"),
|
||||
PRICE("price"),
|
||||
QUANTITY("quantity"),
|
||||
SENDER("sender"),
|
||||
SIDE("side"),
|
||||
SYMBOL("symbol"),
|
||||
TIME_INFORCE("timeinforce"),
|
||||
}
|
||||
|
||||
data class Message(
|
||||
val id: String,
|
||||
val orderType: Int,
|
||||
val price: Long,
|
||||
val quantity: Long,
|
||||
val sender: String,
|
||||
val side: Int,
|
||||
val symbol: String,
|
||||
val timeInforce: Int,
|
||||
)
|
||||
}
|
||||
|
||||
val tradeOrderDeserializer = jsonDeserializer {
|
||||
WcBinanceTradeOrder.Message(
|
||||
id = it.json[WcBinanceTradeOrder.MessageKey.ID.key].string,
|
||||
orderType = it.json[WcBinanceTradeOrder.MessageKey.ORDER_TYPE.key].int,
|
||||
price = it.json[WcBinanceTradeOrder.MessageKey.PRICE.key].long,
|
||||
quantity = it.json[WcBinanceTradeOrder.MessageKey.QUANTITY.key].long,
|
||||
sender = it.json[WcBinanceTradeOrder.MessageKey.SENDER.key].string,
|
||||
side = it.json[WcBinanceTradeOrder.MessageKey.SIDE.key].int,
|
||||
symbol = it.json[WcBinanceTradeOrder.MessageKey.SYMBOL.key].string,
|
||||
timeInforce = it.json[WcBinanceTradeOrder.MessageKey.TIME_INFORCE.key].int,
|
||||
)
|
||||
}
|
||||
|
||||
val tradeOrderSerializer = jsonSerializer<WcBinanceTradeOrder.Message> {
|
||||
val jsonObject = JsonObject()
|
||||
jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.ID.key, it.src.id)
|
||||
jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.ORDER_TYPE.key, it.src.orderType)
|
||||
jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.PRICE.key, it.src.price)
|
||||
jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.QUANTITY.key, it.src.quantity)
|
||||
jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.SENDER.key, it.src.sender)
|
||||
jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.SIDE.key, it.src.side)
|
||||
jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.SYMBOL.key, it.src.symbol)
|
||||
jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.TIME_INFORCE.key, it.src.timeInforce)
|
||||
|
||||
jsonObject
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models.binance
|
||||
|
||||
data class WcBinanceTradePair(val from: String, val to: String) {
|
||||
companion object {
|
||||
fun from(symbol: String): WcBinanceTradePair? {
|
||||
val pair = symbol.split("_")
|
||||
|
||||
return if (pair.size > 1) {
|
||||
val firstParts = pair[0].split("-")
|
||||
val secondParts = pair[1].split("-")
|
||||
WcBinanceTradePair(firstParts[0], secondParts[0])
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models.binance
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@JsonClass(generateAdapter = true)
|
||||
class WcBinanceTransferOrder(
|
||||
@Json(name = "account_number")
|
||||
accountNumber: String,
|
||||
@Json(name = "chain_id")
|
||||
chainId: String,
|
||||
data: String?,
|
||||
memo: String?,
|
||||
sequence: String,
|
||||
source: String,
|
||||
msgs: List<Message>,
|
||||
) : WcBinanceOrder<WcBinanceTransferOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
|
||||
|
||||
enum class MessageKey(val key: String) {
|
||||
INPUTS("inputs"),
|
||||
OUTPUTS("outputs"),
|
||||
}
|
||||
|
||||
data class Message(
|
||||
val inputs: List<Item>,
|
||||
val outputs: List<Item>,
|
||||
) {
|
||||
|
||||
data class Item(
|
||||
val address: String,
|
||||
val coins: List<Coin>,
|
||||
) {
|
||||
|
||||
data class Coin(
|
||||
val amount: Long,
|
||||
val denom: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,10 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.walletconnect.Topic
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
|
||||
import com.tangem.wallet.R
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
|
||||
|
|
@ -58,7 +62,7 @@ sealed class WalletConnectAction : Action {
|
|||
data class SetSessionsRestored(val sessions: List<WalletConnectSession>) :
|
||||
WalletConnectAction()
|
||||
|
||||
data class DisconnectSession(val session: WCSession) : WalletConnectAction()
|
||||
data class DisconnectSession(val topic: String, val session: WCSession?) : WalletConnectAction()
|
||||
|
||||
data class RemoveSession(val session: WCSession) : WalletConnectAction()
|
||||
|
||||
|
|
@ -66,7 +70,7 @@ sealed class WalletConnectAction : Action {
|
|||
val transaction: WCEthereumTransaction,
|
||||
val session: WalletConnectSession,
|
||||
val id: Long,
|
||||
val type: WcTransactionType,
|
||||
val type: WcEthTransactionType,
|
||||
) :
|
||||
WalletConnectAction()
|
||||
|
||||
|
|
@ -78,11 +82,11 @@ sealed class WalletConnectAction : Action {
|
|||
val id: Long,
|
||||
) : WalletConnectAction()
|
||||
|
||||
data class SendTransaction(val session: WCSession) : WalletConnectAction()
|
||||
data class SendTransaction(val topic: Topic) : WalletConnectAction()
|
||||
|
||||
data class SignMessage(val session: WCSession) : WalletConnectAction()
|
||||
data class SignMessage(val topic: Topic) : WalletConnectAction()
|
||||
|
||||
data class RejectRequest(val session: WCSession, val id: Long) : WalletConnectAction()
|
||||
data class RejectRequest(val topic: Topic, val id: Long) : WalletConnectAction()
|
||||
|
||||
object NotEnoughFunds : WalletConnectAction(), NotificationAction {
|
||||
override val messageResource = R.string.wallet_connect_create_tx_not_enough_funds
|
||||
|
|
@ -108,7 +112,22 @@ sealed class WalletConnectAction : Action {
|
|||
data class Sign(
|
||||
val id: Long,
|
||||
val data: ByteArray,
|
||||
val sessionData: WCSession,
|
||||
val topic: Topic,
|
||||
) : WalletConnectAction()
|
||||
}
|
||||
|
||||
//region WalletConnect 2.0
|
||||
object ApproveProposal : WalletConnectAction()
|
||||
object RejectProposal : WalletConnectAction()
|
||||
|
||||
object SessionEstablished : WalletConnectAction()
|
||||
data class SessionRejected(val error: WalletConnectError) : WalletConnectAction()
|
||||
data class SessionListUpdated(val sessions: List<WcSessionForScreen>) : WalletConnectAction()
|
||||
|
||||
data class ShowSessionRequest(val sessionRequest: WcPreparedRequest) : WalletConnectAction()
|
||||
|
||||
object RejectUnsupportedRequest : WalletConnectAction()
|
||||
|
||||
data class PerformRequestedAction(val sessionRequest: WcPreparedRequest) : WalletConnectAction()
|
||||
//endregion WalletConnect 2.0
|
||||
}
|
||||
|
|
@ -5,8 +5,10 @@ import com.tangem.blockchain.common.DerivationStyle
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
|
|
@ -19,8 +21,16 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
|||
import com.tangem.tap.domain.walletconnect.BnbHelper
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
|
||||
import com.tangem.tap.domain.walletconnect.extensions.toWcEthTransaction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Account
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -29,8 +39,14 @@ import org.rekotlin.Action
|
|||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class WalletConnectMiddleware {
|
||||
private var walletConnectManager = WalletConnectManager()
|
||||
private val walletConnectInteractor: WalletConnectInteractor
|
||||
get() = store.state.daggerGraphState.get(DaggerGraphState::walletConnectInteractor)
|
||||
private val walletConnectRepository: WalletConnectRepository
|
||||
get() = store.state.daggerGraphState.get(DaggerGraphState::walletConnectRepository)
|
||||
|
||||
val walletConnectMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
|
|
@ -115,9 +131,13 @@ class WalletConnectMiddleware {
|
|||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard))
|
||||
}
|
||||
is WalletConnectAction.OpenSession -> {
|
||||
walletConnectManager.connect(
|
||||
wcUri = action.wcUri,
|
||||
)
|
||||
val index = action.wcUri.indexOf("@")
|
||||
when (action.wcUri[index + 1]) {
|
||||
'1' -> {
|
||||
walletConnectManager.connect(wcUri = action.wcUri)
|
||||
}
|
||||
'2' -> walletConnectRepository.pair(uri = action.wcUri)
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.RefuseOpeningSession -> {
|
||||
store.dispatch(
|
||||
|
|
@ -134,11 +154,15 @@ class WalletConnectMiddleware {
|
|||
walletConnectManager.approve(action.session)
|
||||
}
|
||||
is WalletConnectAction.DisconnectSession -> {
|
||||
walletConnectManager.disconnect(action.session)
|
||||
if (action.session != null) {
|
||||
walletConnectManager.disconnect(action.session)
|
||||
} else {
|
||||
walletConnectInteractor.disconnectSession(action.topic)
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.HandleTransactionRequest -> {
|
||||
walletConnectManager.handleTransactionRequest(
|
||||
transaction = action.transaction,
|
||||
transaction = action.transaction.toWcEthTransaction(),
|
||||
session = action.session,
|
||||
id = action.id,
|
||||
type = action.type,
|
||||
|
|
@ -152,23 +176,31 @@ class WalletConnectMiddleware {
|
|||
)
|
||||
}
|
||||
is WalletConnectAction.RejectRequest -> {
|
||||
walletConnectManager.rejectRequest(action.session, action.id)
|
||||
walletConnectManager.rejectRequest(action.topic, action.id)
|
||||
walletConnectInteractor.rejectRequest(action.topic, action.id)
|
||||
}
|
||||
is WalletConnectAction.SendTransaction -> {
|
||||
walletConnectManager.completeTransaction(action.session)
|
||||
walletConnectManager.completeTransaction(action.topic)
|
||||
}
|
||||
is WalletConnectAction.SignMessage -> {
|
||||
walletConnectManager.sendSignedMessage(action.session)
|
||||
walletConnectManager.sendSignedMessage(action.topic)
|
||||
}
|
||||
is WalletConnectAction.BinanceTransaction.Trade -> {
|
||||
val messageData = BnbHelper.createMessageData(action.order)
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.BnbTransactionDialog(
|
||||
data = messageData,
|
||||
session = action.sessionData.session,
|
||||
sessionId = action.id,
|
||||
dAppName = action.sessionData.peerMeta.name,
|
||||
WcPreparedRequest.BnbTransaction(
|
||||
preparedRequestData = BnbData(
|
||||
data = messageData,
|
||||
topic = action.sessionData.session.topic,
|
||||
requestId = action.id,
|
||||
dAppName = action.sessionData.peerMeta.name,
|
||||
),
|
||||
topic = action.sessionData.session.topic,
|
||||
requestId = action.id,
|
||||
derivationPath = action.sessionData.wallet.derivationPath?.rawPath,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -178,10 +210,17 @@ class WalletConnectMiddleware {
|
|||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.BnbTransactionDialog(
|
||||
data = messageData,
|
||||
session = action.sessionData.session,
|
||||
sessionId = action.id,
|
||||
dAppName = action.sessionData.peerMeta.name,
|
||||
WcPreparedRequest.BnbTransaction(
|
||||
preparedRequestData = BnbData(
|
||||
data = messageData,
|
||||
topic = action.sessionData.session.topic,
|
||||
requestId = action.id,
|
||||
dAppName = action.sessionData.peerMeta.name,
|
||||
),
|
||||
topic = action.sessionData.session.topic,
|
||||
requestId = action.id,
|
||||
derivationPath = action.sessionData.wallet.derivationPath?.rawPath,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -190,7 +229,7 @@ class WalletConnectMiddleware {
|
|||
walletConnectManager.signBnb(
|
||||
id = action.id,
|
||||
data = action.data,
|
||||
sessionData = action.sessionData,
|
||||
topic = action.topic,
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.SwitchBlockchain -> {
|
||||
|
|
@ -226,6 +265,70 @@ class WalletConnectMiddleware {
|
|||
is WalletConnectAction.UpdateBlockchain -> {
|
||||
walletConnectManager.updateBlockchain(action.updatedSession)
|
||||
}
|
||||
is WalletConnectAction.ApproveProposal -> {
|
||||
val accounts = store.state.walletState.walletManagers
|
||||
.mapNotNull {
|
||||
val wallet = it.wallet
|
||||
val chainId = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull(
|
||||
wallet.blockchain.toNetworkId(),
|
||||
)
|
||||
chainId?.let {
|
||||
Account(
|
||||
chainId,
|
||||
wallet.address,
|
||||
wallet.publicKey.derivationPath?.rawPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
walletConnectInteractor.approveSessionProposal(accounts)
|
||||
}
|
||||
is WalletConnectAction.RejectProposal -> {
|
||||
walletConnectInteractor.rejectSessionProposal()
|
||||
}
|
||||
is WalletConnectAction.SessionEstablished -> {
|
||||
}
|
||||
is WalletConnectAction.SessionRejected -> {
|
||||
when (action.error) {
|
||||
is WalletConnectError.ApprovalErrorAddNetwork -> {
|
||||
val blockchains = action.error.networks.mapNotNull { Blockchain.fromNetworkId(it)?.fullName }
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.AddNetwork(blockchains.toString()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
WalletConnectError.ApprovalErrorUnsupportedNetwork -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
|
||||
}
|
||||
is WalletConnectError.ExternalApprovalError -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
AppDialog.SimpleOkWarningDialog(
|
||||
message = action.error.message ?: "",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
is WalletConnectAction.ShowSessionRequest -> {
|
||||
val dialog: WalletConnectDialog = when (val request = action.sessionRequest) {
|
||||
is WcPreparedRequest.BnbTransaction -> WalletConnectDialog.BnbTransactionDialog(request)
|
||||
is WcPreparedRequest.EthTransaction -> WalletConnectDialog.RequestTransaction(request)
|
||||
is WcPreparedRequest.EthSign -> WalletConnectDialog.PersonalSign(request)
|
||||
}
|
||||
store.dispatch(GlobalAction.ShowDialog(dialog))
|
||||
}
|
||||
is WalletConnectAction.PerformRequestedAction -> {
|
||||
scope.launch { walletConnectInteractor.continueWithRequest(action.sessionRequest) }
|
||||
}
|
||||
is WalletConnectAction.RejectUnsupportedRequest -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,15 +27,23 @@ object WalletConnectReducer {
|
|||
state.sessions.filterNot { it.session.toUri() == action.session.toUri() }
|
||||
state.copy(sessions = sessions)
|
||||
}
|
||||
is WalletConnectAction.UnsupportedCard -> state.copy(loading = false)
|
||||
is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false)
|
||||
is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false)
|
||||
is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false)
|
||||
is WalletConnectAction.UnsupportedCard,
|
||||
is WalletConnectAction.RefuseOpeningSession,
|
||||
is WalletConnectAction.OpeningSessionTimeout,
|
||||
is WalletConnectAction.FailureEstablishingSession,
|
||||
-> state.copy(loading = false)
|
||||
is WalletConnectAction.UpdateBlockchain -> state.copy(
|
||||
sessions = state.sessions
|
||||
.filterNot { it.peerId == action.updatedSession.peerId } + action.updatedSession,
|
||||
)
|
||||
|
||||
is WalletConnectAction.ApproveProposal -> state.copy(loading = true)
|
||||
is WalletConnectAction.RejectProposal,
|
||||
is WalletConnectAction.SessionEstablished,
|
||||
is WalletConnectAction.SessionRejected,
|
||||
-> state.copy(loading = false)
|
||||
is WalletConnectAction.SessionListUpdated -> state.copy(
|
||||
wc2Sessions = action.sessions,
|
||||
)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import com.tangem.blockchain.common.WalletManager
|
|||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumSignMessage
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
|
||||
import com.trustwallet.walletconnect.models.WCPeerMeta
|
||||
|
|
@ -16,6 +20,7 @@ import com.trustwallet.walletconnect.models.session.WCSession
|
|||
data class WalletConnectState(
|
||||
val loading: Boolean = false,
|
||||
val sessions: List<WalletConnectSession> = listOf(),
|
||||
val wc2Sessions: List<WcSessionForScreen> = listOf(),
|
||||
val newSessionData: NewWcSessionData? = null,
|
||||
)
|
||||
|
||||
|
|
@ -95,43 +100,47 @@ sealed class WalletConnectDialog : StateDialog {
|
|||
val networks: List<Blockchain>,
|
||||
) : WalletConnectDialog()
|
||||
|
||||
data class SessionProposalDialog(
|
||||
val sessionProposal: WalletConnectEvents.SessionProposal,
|
||||
val networks: String,
|
||||
val onApprove: () -> Unit,
|
||||
val onReject: () -> Unit,
|
||||
) : WalletConnectDialog()
|
||||
|
||||
data class ChooseNetwork(
|
||||
val session: WalletConnectSession,
|
||||
val networks: List<Blockchain>,
|
||||
) : WalletConnectDialog()
|
||||
|
||||
data class RequestTransaction(val dialogData: TransactionRequestDialogData) :
|
||||
data class RequestTransaction(val data: WcPreparedRequest.EthTransaction) :
|
||||
WalletConnectDialog()
|
||||
|
||||
data class PersonalSign(val data: PersonalSignDialogData) : WalletConnectDialog()
|
||||
data class PersonalSign(val data: WcPreparedRequest.EthSign) : WalletConnectDialog()
|
||||
data class BnbTransactionDialog(
|
||||
val data: BinanceMessageData,
|
||||
val session: WCSession,
|
||||
val sessionId: Long,
|
||||
val dAppName: String,
|
||||
val data: WcPreparedRequest.BnbTransaction,
|
||||
) : WalletConnectDialog()
|
||||
}
|
||||
|
||||
data class WcTransactionData(
|
||||
val type: WcTransactionType,
|
||||
val type: WcEthTransactionType,
|
||||
val transaction: TransactionData,
|
||||
val session: WalletConnectSession,
|
||||
val topic: String,
|
||||
val id: Long,
|
||||
val walletManager: WalletManager,
|
||||
val dialogData: TransactionRequestDialogData,
|
||||
)
|
||||
|
||||
enum class WcTransactionType {
|
||||
enum class WcEthTransactionType {
|
||||
EthSignTransaction,
|
||||
EthSendTransaction,
|
||||
}
|
||||
|
||||
data class WcPersonalSignData(
|
||||
val hash: ByteArray,
|
||||
val session: WalletConnectSession,
|
||||
val topic: String,
|
||||
val id: Long,
|
||||
val dialogData: PersonalSignDialogData,
|
||||
|
||||
val type: WcEthereumSignMessage.WCSignType,
|
||||
)
|
||||
|
||||
sealed class BinanceMessageData(
|
||||
|
|
|
|||
|
|
@ -1,24 +1,11 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.FloatingActionButton
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -36,6 +23,7 @@ import com.tangem.tap.common.analytics.events.Settings
|
|||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
fun WalletConnectScreen(state: WalletConnectScreenState, onBackClick: () -> Unit) {
|
||||
|
|
@ -164,7 +152,7 @@ private fun WalletConnectSessions(state: WalletConnectScreenState) {
|
|||
private fun WalletConnectScreenPreview() {
|
||||
WalletConnectScreen(
|
||||
state = WalletConnectScreenState(
|
||||
sessions = listOf(
|
||||
sessions = persistentListOf(
|
||||
WcSessionForScreen(
|
||||
description = "session from some dApp",
|
||||
sessionId = "",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect
|
||||
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class WalletConnectScreenState(
|
||||
val sessions: List<WcSessionForScreen>,
|
||||
val sessions: ImmutableList<WcSessionForScreen>,
|
||||
val isLoading: Boolean = false,
|
||||
val onRemoveSession: (String) -> Unit = {},
|
||||
val onAddSession: (String?) -> Unit = {},
|
||||
|
|
|
|||
|
|
@ -4,23 +4,35 @@ import com.tangem.tap.common.redux.AppState
|
|||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
class WalletConnectViewModel(private val store: Store<AppState>) {
|
||||
fun updateState(state: WalletConnectState): WalletConnectScreenState {
|
||||
Timber.d("WC2 Sessions: ${state.wc2Sessions}")
|
||||
val sessions = state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) } + state.wc2Sessions
|
||||
return WalletConnectScreenState(
|
||||
state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) },
|
||||
sessions.toImmutableList(),
|
||||
isLoading = state.loading,
|
||||
onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, state.sessions) },
|
||||
onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, state.sessions, state.wc2Sessions) },
|
||||
onAddSession = { copiedUri -> store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun onRemoveSession(sessionUri: String, sessions: List<WalletConnectSession>) {
|
||||
private fun onRemoveSession(
|
||||
sessionUri: String,
|
||||
sessions: List<WalletConnectSession>,
|
||||
wc2sessions: List<WcSessionForScreen>,
|
||||
) {
|
||||
sessions
|
||||
.firstOrNull { it.session.toUri() == sessionUri }
|
||||
?.let { baseSession ->
|
||||
store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session))
|
||||
store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session.topic, baseSession.session))
|
||||
return
|
||||
}
|
||||
wc2sessions.firstOrNull { it.sessionId == sessionUri }?.let {
|
||||
store.dispatch(WalletConnectAction.DisconnectSession(sessionUri, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,20 +3,15 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
|||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.trustwallet.walletconnect.models.session.WCSession
|
||||
|
||||
object BnbTransactionDialog {
|
||||
fun create(
|
||||
data: BinanceMessageData,
|
||||
session: WCSession,
|
||||
sessionId: Long,
|
||||
dAppName: String,
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
fun create(preparedData: WcPreparedRequest.BnbTransaction, context: Context): AlertDialog {
|
||||
val data = preparedData.preparedRequestData.data
|
||||
val message = when (data) {
|
||||
is BinanceMessageData.Trade -> data.tradeData.map {
|
||||
context.getString(
|
||||
|
|
@ -37,7 +32,7 @@ object BnbTransactionDialog {
|
|||
|
||||
val fullMessage = context.getString(
|
||||
R.string.wallet_connect_bnb_sign_message,
|
||||
dAppName,
|
||||
preparedData.preparedRequestData.dAppName,
|
||||
message,
|
||||
)
|
||||
val positiveButtonTitle = context.getText(R.string.common_sign)
|
||||
|
|
@ -48,14 +43,15 @@ object BnbTransactionDialog {
|
|||
setPositiveButton(positiveButtonTitle) { _, _ ->
|
||||
store.dispatch(
|
||||
WalletConnectAction.BinanceTransaction.Sign(
|
||||
id = sessionId,
|
||||
id = preparedData.requestId,
|
||||
data = data.data,
|
||||
sessionData = session,
|
||||
topic = preparedData.topic,
|
||||
),
|
||||
)
|
||||
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.RejectRequest(session, sessionId))
|
||||
store.dispatch(WalletConnectAction.RejectRequest(preparedData.topic, preparedData.requestId))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
|
|
|
|||
|
|
@ -3,22 +3,24 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
|||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.trustwallet.walletconnect.models.session.WCSession
|
||||
|
||||
object PersonalSignDialog {
|
||||
fun create(data: PersonalSignDialogData, context: Context): AlertDialog {
|
||||
fun create(preparedData: WcPreparedRequest.EthSign, context: Context): AlertDialog {
|
||||
val data = preparedData.preparedRequestData.dialogData
|
||||
val message = context.getString(R.string.wallet_connect_alert_sign_message, data.message)
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect_title))
|
||||
setMessage(message)
|
||||
setPositiveButton(context.getText(R.string.common_sign)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.SignMessage(data.session))
|
||||
store.dispatch(WalletConnectAction.SignMessage(data.topic))
|
||||
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id))
|
||||
store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
|
|
@ -30,6 +32,6 @@ object PersonalSignDialog {
|
|||
data class PersonalSignDialogData(
|
||||
val dAppName: String,
|
||||
val message: String,
|
||||
val session: WCSession,
|
||||
val topic: String,
|
||||
val id: Long,
|
||||
)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object SessionProposalDialog {
|
||||
fun create(
|
||||
sessionProposal: WalletConnectEvents.SessionProposal,
|
||||
networks: String,
|
||||
context: Context,
|
||||
onApprove: () -> Unit,
|
||||
onReject: () -> Unit,
|
||||
): AlertDialog {
|
||||
val message = context.getString(
|
||||
R.string.wallet_connect_request_session_start,
|
||||
sessionProposal.name,
|
||||
networks,
|
||||
sessionProposal.url,
|
||||
)
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect_title))
|
||||
setMessage(message)
|
||||
setPositiveButton(context.getText(R.string.common_start)) { _, _ ->
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
onApprove()
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
onReject()
|
||||
}
|
||||
setOnCancelListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
onReject()
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,14 +3,15 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
|||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcTransactionType
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.trustwallet.walletconnect.models.session.WCSession
|
||||
|
||||
object TransactionDialog {
|
||||
fun create(data: TransactionRequestDialogData, context: Context): AlertDialog {
|
||||
fun create(preparedData: WcPreparedRequest.EthTransaction, context: Context): AlertDialog {
|
||||
val data = preparedData.preparedRequestData.dialogData
|
||||
val message = context.getString(
|
||||
R.string.wallet_connect_create_tx_message,
|
||||
data.dAppName,
|
||||
|
|
@ -22,22 +23,23 @@ object TransactionDialog {
|
|||
)
|
||||
|
||||
val positiveButtonTitle = when (data.type) {
|
||||
WcTransactionType.EthSignTransaction -> context.getText(R.string.common_sign)
|
||||
WcTransactionType.EthSendTransaction -> context.getText(R.string.common_sign_and_send)
|
||||
WcEthTransactionType.EthSignTransaction -> context.getText(R.string.common_sign)
|
||||
WcEthTransactionType.EthSendTransaction -> context.getText(R.string.common_sign_and_send)
|
||||
}
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect_title))
|
||||
setMessage(message)
|
||||
setPositiveButton(positiveButtonTitle) { _, _ ->
|
||||
if (data.isEnoughFundsToSend) {
|
||||
store.dispatch(WalletConnectAction.SendTransaction(data.session))
|
||||
store.dispatch(WalletConnectAction.SendTransaction(data.topic))
|
||||
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
|
||||
} else {
|
||||
store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id))
|
||||
store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id))
|
||||
store.dispatch(WalletConnectAction.NotEnoughFunds)
|
||||
}
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id))
|
||||
store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
|
|
@ -54,7 +56,7 @@ data class TransactionRequestDialogData(
|
|||
val totalAmount: String,
|
||||
val balance: String,
|
||||
val isEnoughFundsToSend: Boolean,
|
||||
val session: WCSession,
|
||||
val topic: String,
|
||||
val id: Long,
|
||||
val type: WcTransactionType,
|
||||
val type: WcEthTransactionType,
|
||||
)
|
||||
|
|
@ -53,4 +53,17 @@ object SimpleOkDialog {
|
|||
),
|
||||
context = context,
|
||||
)
|
||||
|
||||
fun create(dialog: AppDialog.OkCancelDialogRes, context: Context): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(dialog.headerId))
|
||||
setMessage(dialog.messageId)
|
||||
setPositiveButton(dialog.okButton.title) { _, _ -> dialog.okButton.action?.invoke() }
|
||||
setNegativeButton(dialog.cancelButton.title) { _, _ -> dialog.cancelButton.action?.invoke() }
|
||||
setOnDismissListener {
|
||||
store.dispatchDialogHide()
|
||||
dialog.cancelButton.action?.invoke()
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.proxy.redux
|
|||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import com.tangem.features.wallet.navigation.WalletRouter
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed interface DaggerGraphAction : Action {
|
||||
|
|
@ -11,5 +12,6 @@ sealed interface DaggerGraphAction : Action {
|
|||
val testerRouter: TesterRouter,
|
||||
val scanCardUseCase: ScanCardUseCase,
|
||||
val walletRouter: WalletRouter,
|
||||
val walletConnectInteractor: WalletConnectInteractor,
|
||||
) : DaggerGraphAction
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ object DaggerGraphReducer {
|
|||
testerRouter = action.testerRouter,
|
||||
scanCardUseCase = action.scanCardUseCase,
|
||||
walletRouter = action.walletRouter,
|
||||
walletConnectInteractor = action.walletConnectInteractor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import com.tangem.domain.card.ScanCardUseCase
|
|||
import com.tangem.features.tester.api.TesterRouter
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.features.wallet.navigation.WalletRouter
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import org.rekotlin.StateType
|
||||
|
||||
|
|
@ -17,6 +20,9 @@ data class DaggerGraphState(
|
|||
val scanCardUseCase: ScanCardUseCase? = null,
|
||||
val walletFeatureToggles: WalletFeatureToggles? = null,
|
||||
val walletRouter: WalletRouter? = null,
|
||||
val walletConnectRepository: WalletConnectRepository? = null,
|
||||
val walletConnectSessionsRepository: WalletConnectSessionsRepository? = null,
|
||||
val walletConnectInteractor: WalletConnectInteractor? = null,
|
||||
) : StateType {
|
||||
|
||||
inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T {
|
||||
|
|
|
|||
0
buildSrc/src/main/java/Dependency.kt
Normal file
0
buildSrc/src/main/java/Dependency.kt
Normal file
|
|
@ -110,6 +110,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
shopify = configValues.shopifyShop,
|
||||
zendesk = configValues.zendesk,
|
||||
swapReferrerAccount = configValues.swapReferrerAccount,
|
||||
walletConnectProjectId = configValues.walletConnectProjectId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,4 +18,5 @@ data class Config(
|
|||
val shopify: ShopifyShop? = null,
|
||||
val zendesk: ZendeskConfig? = null,
|
||||
val swapReferrerAccount: SwapReferrerAccount? = null,
|
||||
val walletConnectProjectId: String = "",
|
||||
)
|
||||
|
|
@ -37,6 +37,7 @@ class ConfigValueModel(
|
|||
val amplitudeApiKey: String,
|
||||
val swapReferrerAccount: SwapReferrerAccount?,
|
||||
val kaspaSecondaryApiUrl: String,
|
||||
val walletConnectProjectId: String,
|
||||
)
|
||||
|
||||
data class AppsFlyer(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.files.AndroidFileReader
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface FilesModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindFileReader(androidFileReader: AndroidFileReader): FileReader
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.datasource.files
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class AndroidFileReader @Inject constructor(@ApplicationContext private val context: Context) : FileReader {
|
||||
override fun readFile(fileName: String): String {
|
||||
return context.openFileInput(fileName).bufferedReader().readText()
|
||||
}
|
||||
|
||||
override fun rewriteFile(content: String, fileName: String) {
|
||||
context.openFileOutput(fileName, Context.MODE_PRIVATE).use {
|
||||
it.write(content.toByteArray(), 0, content.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.datasource.files
|
||||
|
||||
interface FileReader {
|
||||
fun readFile(fileName: String): String
|
||||
fun rewriteFile(content: String, fileName: String)
|
||||
}
|
||||
|
|
@ -72,6 +72,8 @@ mviCore = "1.3.1"
|
|||
kotlinSerialization = "1.4.1"
|
||||
arrow = "1.2.0-RC"
|
||||
reactiveNetwork = "3.0.8"
|
||||
walletConnectCore = "1.15.0"
|
||||
walletConnectWeb3 = "1.8.0"
|
||||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
|
|
@ -216,4 +218,6 @@ kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-j
|
|||
arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" }
|
||||
arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" }
|
||||
reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" }
|
||||
walletConnectCore = { module = "com.walletconnect:android-core", version.ref = "walletConnectCore" }
|
||||
walletConnectWeb3 = { module = "com.walletconnect:web3wallet", version.ref = "walletConnectWeb3" }
|
||||
# endregion Other
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ internal object AppConfig {
|
|||
const val packageName = "com.tangem.wallet"
|
||||
const val versionCode = 1
|
||||
const val versionName = "1.0.0-SNAPSHOT"
|
||||
const val minSdkVersion = 21
|
||||
const val minSdkVersion = 23
|
||||
const val targetSdkVersion = 33
|
||||
const val compileSdkVersion = 33
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue