Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make small fixes to TonConnect #7949

Merged
merged 1 commit into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ dependencies {
debugImplementation leakCanary

// Wallet kits
implementation 'com.github.horizontalsystems:ton-kit-android:1cb4c1e'
implementation 'com.github.horizontalsystems:ton-kit-android:1ffc986'
implementation 'com.github.horizontalsystems:bitcoin-kit-android:ff7cfa4'
implementation 'com.github.horizontalsystems:ethereum-kit-android:d423acf'
implementation 'com.github.horizontalsystems:blockchain-fee-rate-kit-android:1d3bd49'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.tonapps.wallet.data.core.entity.SendRequestEntity
import com.tonapps.wallet.data.tonconnect.entities.DAppRequestEntity
import com.walletconnect.web3.wallet.client.Wallet
import io.horizontalsystems.bankwallet.core.App
Expand All @@ -18,6 +17,7 @@ import io.horizontalsystems.core.IKeyStoreManager
import io.horizontalsystems.core.IPinComponent
import io.horizontalsystems.core.ISystemInfoManager
import io.horizontalsystems.core.security.KeyStoreValidationError
import io.horizontalsystems.tonkit.models.SignTransaction
import kotlinx.coroutines.launch

class MainActivityViewModel(
Expand All @@ -32,7 +32,7 @@ class MainActivityViewModel(

val navigateToMainLiveData = MutableLiveData(false)
val wcEvent = MutableLiveData<Wallet.Model?>()
val tcSendRequest = MutableLiveData<SendRequestEntity?>()
val tcSendRequest = MutableLiveData<SignTransaction?>()
val tcDappRequest = MutableLiveData<DAppRequestEntity?>()
val intentLiveData = MutableLiveData<Intent?>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.horizontalsystems.bankwallet.modules.tonconnect
import androidx.lifecycle.viewModelScope
import com.tonapps.wallet.data.tonconnect.entities.DAppManifestEntity
import com.tonapps.wallet.data.tonconnect.entities.DAppRequestEntity
import com.tonapps.wallet.data.tonconnect.entities.reply.DAppConnectEventError
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.ViewModelUiState
import io.horizontalsystems.bankwallet.core.managers.toTonWalletFullAccess
Expand Down Expand Up @@ -80,8 +81,32 @@ class TonConnectNewViewModel(
}

fun reject() {
finish = true
emitState()
viewModelScope.launch {
try {
val manifest = manifest ?: throw NoManifestError()
val account = account ?: throw IllegalArgumentException("Empty account")
val dapp = tonConnectKit.newApp(
manifest,
account.id,
false,
requestEntity.id,
account.id,
false
)

val error = DAppConnectEventError(
id = System.currentTimeMillis().toString(),
errorCode = 300,
errorMessage = "User declined the transaction"
)

tonConnectKit.send(dapp, error.toJSON())
finish = true
} catch (e: Throwable) {
toast = e.message?.nullIfBlank() ?: e.javaClass.simpleName
}
emitState()
}
}

fun onToastShow() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.activity.ComponentActivity
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand Down Expand Up @@ -71,52 +72,66 @@ fun TonConnectSendRequestScreen(navController: NavController) {
val coroutineScope = rememberCoroutineScope()
val view = LocalView.current

var buttonEnabled by remember { mutableStateOf(true) }

ButtonPrimaryYellow(
modifier = Modifier.fillMaxWidth(),
title = stringResource(R.string.Button_Confirm),
enabled = uiState.confirmEnabled && buttonEnabled,
onClick = {
coroutineScope.launch {
buttonEnabled = false
HudHelper.showInProcessMessage(
view,
R.string.Send_Sending,
SnackbarDuration.INDEFINITE
)

try {
logger.info("click confirm button")
viewModel.confirm()
logger.info("success")

HudHelper.showSuccessMessage(view, R.string.Hud_Text_Done)
delay(1200)
} catch (t: Throwable) {
logger.warning("failed", t)
HudHelper.showErrorMessage(view, t.javaClass.simpleName)
if (uiState.error != null) {
ButtonPrimaryDefault(
modifier = Modifier.fillMaxWidth(),
title = stringResource(R.string.Button_Close),
enabled = true,
onClick = {
navController.popBackStack()
}
)
} else {
var buttonEnabled by remember { mutableStateOf(true) }

ButtonPrimaryYellow(
modifier = Modifier.fillMaxWidth(),
title = stringResource(R.string.Button_Confirm),
enabled = uiState.confirmEnabled && buttonEnabled,
onClick = {
coroutineScope.launch {
buttonEnabled = false
HudHelper.showInProcessMessage(
view,
R.string.Send_Sending,
SnackbarDuration.INDEFINITE
)

try {
logger.info("click confirm button")
viewModel.confirm()
logger.info("success")

HudHelper.showSuccessMessage(view, R.string.Hud_Text_Done)
delay(1200)
} catch (t: Throwable) {
logger.warning("failed", t)
HudHelper.showErrorMessage(view, t.javaClass.simpleName)
}

buttonEnabled = true
navController.popBackStack()
}

buttonEnabled = true
}
)
VSpacer(16.dp)
ButtonPrimaryDefault(
modifier = Modifier.fillMaxWidth(),
title = stringResource(R.string.Button_Reject),
enabled = uiState.rejectEnabled,
onClick = {
viewModel.reject()
navController.popBackStack()
}
}
)
VSpacer(16.dp)
ButtonPrimaryDefault(
modifier = Modifier.fillMaxWidth(),
title = stringResource(R.string.Button_Reject),
enabled = uiState.rejectEnabled,
onClick = {
viewModel.reject()
navController.popBackStack()
}
)
)
}
}
) {
uiState.error?.let { error ->
TextImportantError(text = error.message ?: error.javaClass.simpleName)
TextImportantError(
modifier = Modifier.padding(horizontal = 16.dp),
text = error.message ?: error.javaClass.simpleName
)
}

Crossfade(uiState.tonTransactionRecord) { record ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.horizontalsystems.bankwallet.modules.tonconnect

import androidx.lifecycle.viewModelScope
import com.tonapps.blockchain.ton.TonNetwork
import com.tonapps.extensions.equalsAddress
import com.tonapps.wallet.data.core.entity.RawMessageEntity
import com.tonapps.wallet.data.core.entity.SendRequestEntity
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IAccountManager
Expand All @@ -16,15 +19,18 @@ import io.horizontalsystems.marketkit.models.TokenQuery
import io.horizontalsystems.marketkit.models.TokenType
import io.horizontalsystems.tonkit.core.TonWallet
import io.horizontalsystems.tonkit.models.Event
import io.horizontalsystems.tonkit.models.SignTransaction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class TonConnectSendRequestViewModel(
private val sendRequestEntity: SendRequestEntity?,
private val signTransaction: SignTransaction?,
private val accountManager: IAccountManager,
private val tonConnectManager: TonConnectManager,
) : ViewModelUiState<TonConnectSendRequestUiState>() {

private val sendRequestEntity = signTransaction?.request
private var error: TonConnectSendRequestError? = null
private val transactionSigner = tonConnectManager.transactionSigner
private val tonConnectKit = App.tonConnectManager.kit
Expand Down Expand Up @@ -54,12 +60,58 @@ class TonConnectSendRequestViewModel(
return
}

val connectionAccountId = signTransaction?.dApp?.accountId
val requestAccountId = sendRequestEntity.fromAccountId

if (requestAccountId != null && connectionAccountId != null && !requestAccountId.equalsAddress(connectionAccountId)) {
error = TonConnectSendRequestError.InvalidData("Invalid \"from\" address. Specified wallet address not connected to this app.")

responseBadRequest(sendRequestEntity)
return
}

if (validUntilIsInvalid(sendRequestEntity)) {
error = TonConnectSendRequestError.InvalidData("Invalid validUntil field")

responseBadRequest(sendRequestEntity)
return
}

try {
val messages = sendRequestEntity.messages
if (addressIsRaw(messages)) {
error = TonConnectSendRequestError.InvalidData("Send to Raw address is not allowed")
responseBadRequest(sendRequestEntity)
return
}
}catch (e: Exception){
error = TonConnectSendRequestError.InvalidData("Failed to parse messages")
responseBadRequest(sendRequestEntity)
return
}

if (isTestnet(sendRequestEntity)) {
error = TonConnectSendRequestError.InvalidData("Send to Testnet is not allowed")
responseBadRequest(sendRequestEntity)
return
}

if (sendRequestEntity.messages.isEmpty()) {
error = TonConnectSendRequestError.InvalidData("Empty messages")
responseBadRequest(sendRequestEntity)
return
}

val (accountId, _) = sendRequestEntity.dAppId.split(":", limit = 2)
val account = accountManager.account(accountId)

if (account == null) {
error = TonConnectSendRequestError.AccountNotFound()
return
} else if (account != accountManager.activeAccount){
error = TonConnectSendRequestError.DifferentAccount("Incorrect account selected")
responseBadRequest(sendRequestEntity)
return
}

val tonWallet = account.type.toTonWalletFullAccess().also {
Expand All @@ -69,8 +121,14 @@ class TonConnectSendRequestViewModel(
tonKitWrapper = it
}

val tonEvent = transactionSigner.getDetails(sendRequestEntity, tonWallet).also {
tonEvent = it
val tonEvent = try {
val event = transactionSigner.getDetails(sendRequestEntity, tonWallet)
tonEvent = event
event
} catch (e: Exception) {
error = TonConnectSendRequestError.InvalidData("Failed to get details")
responseBadRequest(sendRequestEntity)
return
}

val token = App.coinManager.getToken(TokenQuery(BlockchainType.Ton, TokenType.Native))
Expand All @@ -89,6 +147,37 @@ class TonConnectSendRequestViewModel(
tonTransactionRecord = tonTransactionConverter?.createTransactionRecord(tonEvent)
}

private fun isTestnet(sendRequestEntity: SendRequestEntity): Boolean {
return sendRequestEntity.network == TonNetwork.TESTNET
}

private fun addressIsRaw(messages: List<RawMessageEntity>): Boolean {
messages.forEach { message ->
if (message.addressValue.contains(":", ignoreCase = true)) {
return true
}
}
return false
}

private suspend fun TonConnectSendRequestViewModel.responseBadRequest(entity: SendRequestEntity) {
withContext(Dispatchers.IO) {
tonConnectKit.badRequest(entity)
}
}

private fun validUntilIsInvalid(sendRequestEntity: SendRequestEntity): Boolean {
return try {
val result = sendRequestEntity.validUntil
if (result == 0L) {
throw IllegalArgumentException("Invalid validUntil")
}
false
} catch (e: IllegalArgumentException) {
true
}
}

fun confirm() {
val sendRequestEntity = sendRequestEntity ?: return
val tonWallet = tonWallet ?: return
Expand All @@ -111,8 +200,10 @@ class TonConnectSendRequestViewModel(
}

sealed class TonConnectSendRequestError : Error() {
class InvalidData(override val message: String) : TonConnectSendRequestError()
class EmptySendRequest : TonConnectSendRequestError()
class AccountNotFound : TonConnectSendRequestError()
class DifferentAccount(override val message: String) : TonConnectSendRequestError()
class Other(override val message: String) : TonConnectSendRequestError()
}

Expand Down