Skip to content

Commit

Permalink
safemode and nft manager
Browse files Browse the repository at this point in the history
  • Loading branch information
polstianka committed Nov 12, 2024
1 parent b8448e0 commit f358f57
Show file tree
Hide file tree
Showing 62 changed files with 1,245 additions and 119 deletions.
4 changes: 4 additions & 0 deletions apps/wallet/api/src/main/java/com/tonapps/wallet/api/API.kt
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,10 @@ class API(
}
}

suspend fun getScamDomains(): Array<String> = withContext(Dispatchers.IO) {
internalApi.getScamDomains()
}

fun loadChart(
token: String,
currency: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.util.Log
import com.tonapps.extensions.appVersionName
import com.tonapps.extensions.isDebug
import com.tonapps.extensions.locale
import com.tonapps.extensions.toUriOrNull
import com.tonapps.network.get
import com.tonapps.wallet.api.entity.ConfigEntity
import com.tonapps.wallet.api.entity.NotificationEntity
Expand Down Expand Up @@ -63,6 +64,24 @@ internal class InternalApi(
return list.toList()
}

fun getScamDomains(): Array<String> {
val array = withRetry {
okHttpClient.get("https://scam.tonkeeper.com/v1/scam/domains")
}?.let { JSONObject(it).getJSONArray("items") } ?: return emptyArray()

val domains = mutableListOf<String>()
for (i in 0 until array.length()) {
var url = array.getJSONObject(i).getString("url")
if (url.startsWith("www.")) {
url = url.substring(5)
} else if (url.startsWith("@")) {
continue
}
domains.add(url)
}
return domains.toTypedArray()
}

fun getBrowserApps(testnet: Boolean, locale: Locale): JSONObject {
val data = request("apps/popular", testnet, locale = locale)
return data.getJSONObject("data")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.tonapps.wallet.data.collectibles

import android.content.Context
import android.util.Log
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tonapps.wallet.api.API
import com.tonapps.wallet.data.collectibles.entities.NftEntity
import com.tonapps.wallet.data.collectibles.entities.NftListResult
Expand Down Expand Up @@ -45,7 +46,9 @@ class CollectiblesRepository(
val remote = getRemoteNftItems(address, testnet) ?: return@flow
emit(NftListResult(cache = false, list = remote))
}
} catch (ignored: Throwable) { }
} catch (e: Throwable) {
FirebaseCrashlytics.getInstance().recordException(e)
}
}.cancellable()

private fun getLocalNftItems(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.tonapps.wallet.data.settings

import android.content.Context
import android.icu.util.Currency
import android.util.Log
import androidx.core.os.LocaleListCompat
import com.tonapps.extensions.MutableEffectFlow
import com.tonapps.extensions.clear
Expand Down Expand Up @@ -52,8 +53,7 @@ class SettingsRepository(
private const val ENCRYPTED_COMMENT_MODAL_KEY = "encrypted_comment_modal"
private const val BATTERY_VIEWED_KEY = "battery_viewed"
private const val CHART_PERIOD_KEY = "chart_period"
private const val ONLY_VERIFY_NFTS_KEY = "only_verify_nfts"
private const val ONLY_VERIFY_TOKENS_KEY = "only_verify_tokens"
private const val SAFE_MODE_KEY = "safe_mode"
}

private val _currencyFlow = MutableEffectFlow<WalletCurrency>()
Expand Down Expand Up @@ -227,21 +227,11 @@ class SettingsRepository(
}


var onlyVerifyNFTs: Boolean = prefs.getBoolean(ONLY_VERIFY_NFTS_KEY, false)
var safeMode: Boolean = prefs.getBoolean(SAFE_MODE_KEY, true)
set(value) {
if (value != field) {
prefs.edit().putBoolean(ONLY_VERIFY_NFTS_KEY, value).apply()
prefs.edit().putBoolean(SAFE_MODE_KEY, value).apply()
field = value
walletPrefsFolder.notifyChanged()
}
}

var onlyVerifyTokens: Boolean = prefs.getBoolean(ONLY_VERIFY_TOKENS_KEY, false)
set(value) {
if (value != field) {
prefs.edit().putBoolean(ONLY_VERIFY_TOKENS_KEY, value).apply()
field = value
walletPrefsFolder.notifyChanged()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package com.tonapps.tonkeeper.client
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.tonapps.tonkeeper.client.safemode

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class BadDomainsEntity(
val array: Array<String>
): Parcelable {

val isEmpty: Boolean
get() = array.isEmpty()

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as BadDomainsEntity

return array.contentEquals(other.array)
}

override fun hashCode(): Int {
return array.contentHashCode()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.tonapps.tonkeeper.client.safemode

import android.content.Context
import android.net.Uri
import android.util.Log
import com.tonapps.icu.Coins
import com.tonapps.wallet.api.API
import com.tonapps.wallet.data.core.BlobDataSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import java.util.concurrent.ConcurrentHashMap

class SafeModeClient(
private val context: Context,
private val api: API,
private val scope: CoroutineScope
) {

private val scamDomains = ConcurrentHashMap<String, Boolean>(3, 1.0f, 2)
private val blobCache = BlobDataSource.simple<BadDomainsEntity>(context, "safemode")
private val badDomainsFlow = flow {
getCachedBadDomains()?.let {
emit(it)
}

loadBadDomains()?.let {
emit(it)
}
}.distinctUntilChanged()

init {
badDomainsFlow.onEach {
for (domain in it.array) {
scamDomains[domain] = true
}
}.launchIn(scope)
}

fun isHasScamUris(vararg uris: Uri): Boolean {
for (uri in uris) {
if (uri == Uri.EMPTY) {
continue
}
var host = uri.host ?: continue
if (host.startsWith("www.")) {
host = host.substring(4)
}
if (scamDomains.containsKey(host)) {
return true
}
}
return false
}

private fun getCachedBadDomains(): BadDomainsEntity? {
val entity = blobCache.getCache("scam_domains")
if (entity == null || entity.isEmpty) {
return null
}
return entity
}

private suspend fun loadBadDomains(): BadDomainsEntity? {
val domains = api.getScamDomains()
if (domains.isEmpty()) {
return null
}
val entity = BadDomainsEntity(domains)
blobCache.setCache("scam_domains", entity)
return entity
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.tonapps.tonkeeper.koin
import com.tonapps.network.NetworkMonitor
import com.tonapps.tonkeeper.Environment
import com.tonapps.tonkeeper.billing.BillingManager
import com.tonapps.tonkeeper.client.safemode.SafeModeClient
import com.tonapps.tonkeeper.manager.assets.AssetsManager
import com.tonapps.tonkeeper.manager.tx.TransactionManager
import com.tonapps.tonkeeper.core.history.HistoryHelper
Expand Down Expand Up @@ -55,6 +56,7 @@ val koinModel = module {
singleOf(::TransactionManager)
singleOf(::TonConnectManager)
singleOf(::PushManager)
singleOf(::SafeModeClient)

factoryOf(::SignUseCase)
factoryOf(::EmulationUseCase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.tonapps.tonkeeper.ui.screen.wallet.main.WalletViewModel
import com.tonapps.tonkeeper.ui.screen.settings.main.SettingsViewModel
import com.tonapps.tonkeeper.ui.screen.name.edit.EditNameViewModel
import com.tonapps.tonkeeper.ui.screen.events.EventsViewModel
import com.tonapps.tonkeeper.ui.screen.collectibles.CollectiblesViewModel
import com.tonapps.tonkeeper.ui.screen.collectibles.main.CollectiblesViewModel
import com.tonapps.tonkeeper.ui.screen.browser.explore.BrowserExploreViewModel
import com.tonapps.tonkeeper.ui.screen.browser.connected.BrowserConnectedViewModel
import com.tonapps.tonkeeper.ui.screen.browser.dapp.DAppViewModel
Expand All @@ -19,6 +19,8 @@ import com.tonapps.tonkeeper.ui.screen.token.picker.TokenPickerViewModel
import com.tonapps.tonkeeper.ui.screen.battery.settings.BatterySettingsViewModel
import com.tonapps.tonkeeper.ui.screen.battery.refill.BatteryRefillViewModel
import com.tonapps.tonkeeper.ui.screen.battery.recharge.BatteryRechargeViewModel
import com.tonapps.tonkeeper.ui.screen.collectibles.manage.CollectiblesManageScreen
import com.tonapps.tonkeeper.ui.screen.collectibles.manage.CollectiblesManageViewModel
import com.tonapps.tonkeeper.ui.screen.send.contacts.main.SendContactsViewModel
import com.tonapps.tonkeeper.ui.screen.purchase.PurchaseViewModel
import com.tonapps.tonkeeper.ui.screen.nft.NftViewModel
Expand All @@ -33,6 +35,7 @@ import com.tonapps.tonkeeper.ui.screen.staking.withdraw.StakeWithdrawViewModel
import org.koin.core.module.dsl.viewModel
import org.koin.core.module.dsl.viewModelOf


val viewModelWalletModule = module {
viewModelOf(::WalletViewModel)
viewModelOf(::SettingsViewModel)
Expand Down Expand Up @@ -63,4 +66,5 @@ val viewModelWalletModule = module {
viewModelOf(::AddContactViewModel)
viewModelOf(::EditContactViewModel)
viewModelOf(::AppsViewModel)
viewModelOf(::CollectiblesManageViewModel)
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ class AssetsManager(
currency: WalletCurrency = settingsRepository.currency,
refresh: Boolean,
): List<AssetsEntity.Token> {
val onlyVerifyTokens = settingsRepository.onlyVerifyTokens
val safeMode = settingsRepository.safeMode
val tokens = tokenRepository.get(currency, wallet.accountId, wallet.testnet, refresh) ?: return emptyList()
return if (onlyVerifyTokens) {
return if (safeMode) {
tokens.filter { it.verified }.map { AssetsEntity.Token(it) }
} else {
tokens.map { AssetsEntity.Token(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import com.tonapps.extensions.flatter
import com.tonapps.extensions.hasQuery
import com.tonapps.extensions.isEmptyQuery
import com.tonapps.extensions.mapList
import com.tonapps.extensions.toUriOrNull
import com.tonapps.network.simple
import com.tonapps.security.CryptoBox
import com.tonapps.tonkeeper.client.safemode.SafeModeClient
import com.tonapps.tonkeeper.core.DevSettings
import com.tonapps.tonkeeper.extensions.showToast
import com.tonapps.tonkeeper.manager.push.PushManager
Expand All @@ -27,13 +29,15 @@ import com.tonapps.tonkeeper.manager.tonconnect.bridge.model.BridgeError
import com.tonapps.tonkeeper.manager.tonconnect.bridge.model.BridgeEvent
import com.tonapps.tonkeeper.manager.tonconnect.bridge.model.BridgeMethod
import com.tonapps.tonkeeper.manager.tonconnect.exceptions.ManifestException
import com.tonapps.tonkeeper.ui.screen.tonconnect.TonConnectSafeModeDialog
import com.tonapps.tonkeeper.ui.screen.tonconnect.TonConnectScreen
import com.tonapps.tonkeeper.worker.DAppPushToggleWorker
import com.tonapps.wallet.api.API
import com.tonapps.wallet.data.account.entities.WalletEntity
import com.tonapps.wallet.data.dapps.DAppsRepository
import com.tonapps.wallet.data.dapps.entities.AppConnectEntity
import com.tonapps.wallet.data.dapps.entities.AppEntity
import com.tonapps.wallet.data.settings.SettingsRepository
import com.tonapps.wallet.localization.Localization
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -60,6 +64,8 @@ class TonConnectManager(
private val api: API,
private val dAppsRepository: DAppsRepository,
private val pushManager: PushManager,
private val safeModeClient: SafeModeClient,
private val settingsRepository: SettingsRepository,
) {

private val bridge: Bridge = Bridge(api)
Expand Down Expand Up @@ -228,6 +234,11 @@ class TonConnectManager(
returnUri = returnUri,
fromPackageName = fromPackageName
)

if (isScam(context, uri, normalizedUri, tonConnect.manifestUrl.toUri())) {
return null
}

scope.launch {
connectRemoteApp(activity, tonConnect)
}
Expand Down Expand Up @@ -311,6 +322,14 @@ class TonConnectManager(
}
}

fun isScam(context: Context, vararg uris: Uri): Boolean {
if (settingsRepository.safeMode && safeModeClient.isHasScamUris(*uris)) {
TonConnectSafeModeDialog(context).show()
return true
}
return false
}

private suspend fun readManifest(url: String): AppEntity {
return fetchManifest(url)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.webkit.WebResourceRequest
Expand All @@ -22,7 +21,6 @@ import com.tonapps.tonkeeper.deeplink.DeepLink
import com.tonapps.tonkeeper.deeplink.DeepLinkRoute
import com.tonapps.tonkeeper.extensions.copyToClipboard
import com.tonapps.tonkeeper.extensions.normalizeTONSites
import com.tonapps.tonkeeper.helper.BrowserHelper
import com.tonapps.tonkeeper.koin.walletViewModel
import com.tonapps.tonkeeper.manager.tonconnect.ConnectRequest
import com.tonapps.tonkeeper.manager.tonconnect.TonConnect
Expand All @@ -36,7 +34,6 @@ import com.tonapps.tonkeeper.popup.ActionSheet
import com.tonapps.tonkeeper.ui.base.WalletContextScreen
import com.tonapps.tonkeeper.ui.screen.root.RootViewModel
import com.tonapps.tonkeeper.ui.screen.send.transaction.SendTransactionScreen
import com.tonapps.tonkeeper.ui.screen.tonconnect.TonConnectScreen
import com.tonapps.tonkeeperx.R
import com.tonapps.uikit.color.tabBarActiveIconColor
import com.tonapps.uikit.icon.UIKitIcon
Expand Down Expand Up @@ -286,6 +283,10 @@ class DAppScreen(wallet: WalletEntity): WalletContextScreen(R.layout.fragment_da
return JsonBuilder.connectEventError(BridgeError.badRequest("Version $version is not supported"))
}
val activity = requireContext().activity ?: return JsonBuilder.connectEventError(BridgeError.unknown("internal client error"))
if (tonConnectManager.isScam(requireContext(), request.manifestUrl.toUri(), webView.url!!.toUri(), args.url)) {
return JsonBuilder.connectEventError(BridgeError.unknown("internal client error"))
}

return tonConnectManager.launchConnectFlow(
activity = activity,
tonConnect = TonConnect.fromJsInject(request, webView.url?.toUri()),
Expand Down
Loading

0 comments on commit f358f57

Please sign in to comment.