Skip to content

Commit

Permalink
Add wallet connect session validation
Browse files Browse the repository at this point in the history
  • Loading branch information
omurovch committed May 8, 2024
1 parent 647dfd4 commit d55f567
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ private fun ColumnScope.WCSessionListContent(
Spacer(Modifier.height(12.dp))
TextImportantWarning(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringResource(it)
text = it
)
}
Spacer(Modifier.height(24.dp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ data class WCSessionUiState(
val closeEnabled: Boolean,
val connecting: Boolean,
val buttonStates: WCSessionButtonStates?,
val hint: Int?,
val hint: String?,
val showError: String?,
val status: Status?,
val pendingRequests: List<WCRequestViewItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.UnsupportedAccountException
import io.horizontalsystems.bankwallet.core.ViewModelUiState
import io.horizontalsystems.bankwallet.core.managers.ConnectivityManager
import io.horizontalsystems.bankwallet.core.providers.Translator
import io.horizontalsystems.bankwallet.entities.Account
import io.horizontalsystems.bankwallet.entities.AccountType
import io.horizontalsystems.bankwallet.modules.walletconnect.WCDelegate
Expand Down Expand Up @@ -43,11 +44,38 @@ class WCSessionViewModel(
private var closeEnabled = false
private var connecting = false
private var buttonStates: WCSessionButtonStates? = null
private var hint: Int? = null
private var hint: String? = null
private var showError: String? = null
private var status: Status? = null
private var pendingRequests = listOf<WCRequestViewItem>()

private val supportedChains = listOf(
Chain.Ethereum,
Chain.BinanceSmartChain,
Chain.Polygon,
Chain.Optimism,
Chain.ArbitrumOne,
Chain.Avalanche,
Chain.Gnosis,
)

private val supportedMethods = listOf(
"eth_sendTransaction",
"personal_sign",
// "eth_accounts",
// "eth_requestAccounts",
// "eth_call",
// "eth_getBalance",
// "eth_sendRawTransaction",
"eth_sign",
"eth_signTransaction",
"eth_signTypedData",
// "wallet_addEthereumChain",
// "wallet_switchEthereumChain"
)

private val supportedEvents = listOf("chainChanged", "accountsChanged" /*"connect", "disconnect", "message"*/)

override fun createState() = WCSessionUiState(
peerMeta = peerMeta,
closeEnabled = closeEnabled,
Expand Down Expand Up @@ -89,14 +117,15 @@ class WCSessionViewModel(
WCDelegate.walletEvents.collect { event ->
when (event) {
is Wallet.Model.SessionDelete -> {
when(event){
when (event) {
is Wallet.Model.SessionDelete.Success -> {
session?.topic?.let { topic ->
if (topic == event.topic) {
sessionServiceState = Killed
}
}
}

is Wallet.Model.SessionDelete.Error -> {
sessionServiceState = Invalid(event.error)
}
Expand Down Expand Up @@ -152,10 +181,34 @@ class WCSessionViewModel(
loadSessionProposal(topic)
}

private fun validate(proposal: Wallet.Model.SessionProposal): ValidationError? {
val chains = proposal.requiredNamespaces.mapNotNull { it.value.chains }.flatten()
val methods = proposal.requiredNamespaces.map { it.value.methods }.flatten()
val events = proposal.requiredNamespaces.map { it.value.events }.flatten()

val supportedChains = supportedChains.map { "eip155:${it.id}" }

val unsupportedChains = chains - supportedChains.toSet()
if (unsupportedChains.isNotEmpty()) {
return ValidationError.UnsupportedChains(unsupportedChains)
}

val unsupportedMethods = methods - supportedMethods.toSet()
if (unsupportedMethods.isNotEmpty()) {
return ValidationError.UnsupportedMethods(unsupportedMethods)
}

val unsupportedEvents = events - supportedEvents.toSet()
if (unsupportedEvents.isNotEmpty()) {
return ValidationError.UnsupportedEvents(unsupportedEvents)
}

return null
}

private fun loadSessionProposal(topic: String?) {
if (topic != null) {
val existingSession =
sessionManager.sessions.firstOrNull { it.topic == topic }
val existingSession = sessionManager.sessions.firstOrNull { it.topic == topic }
if (existingSession != null) {
peerMeta = existingSession.metaData?.let {
PeerMetaItem(
Expand All @@ -172,8 +225,7 @@ class WCSessionViewModel(
sessionServiceState = Ready
}
} else {
WCDelegate.sessionProposalEvent?.let {
val (sessionProposal, _) = it
WCDelegate.sessionProposalEvent?.let { (sessionProposal, _) ->
peerMeta = PeerMetaItem(
sessionProposal.name,
sessionProposal.url,
Expand All @@ -182,7 +234,7 @@ class WCSessionViewModel(
account?.name,
)
proposal = sessionProposal
sessionServiceState = WaitingForApproveSession
sessionServiceState = validate(sessionProposal)?.let { Invalid(it) } ?: WaitingForApproveSession
} ?: run {
sessionServiceState = Invalid(RequestNotFoundError)
}
Expand Down Expand Up @@ -397,34 +449,46 @@ class WCSessionViewModel(
private fun setError(
state: WCSessionServiceState
) {
val error: String? = when (state) {
is Invalid -> state.error.message ?: state.error::class.java.simpleName
val error: String? = when {
state is Invalid && (state.error !is ValidationError) -> state.error.message ?: state.error::class.java.simpleName
else -> null
}

showError = error
}

private fun getHint(connection: Boolean?, state: WCSessionServiceState): Int? {
private fun getHint(connection: Boolean?, state: WCSessionServiceState): String? {
when {
connection == false
&& (state == WaitingForApproveSession || state is Ready) -> {
return R.string.WalletConnect_Reconnect_Hint
return Translator.getString(R.string.WalletConnect_Reconnect_Hint)
}

connection == null -> return null
state is Invalid -> return getErrorMessage(state.error)
state == WaitingForApproveSession -> R.string.WalletConnect_Approve_Hint
state == WaitingForApproveSession -> Translator.getString(R.string.WalletConnect_Approve_Hint)
}
return null
}

private fun getErrorMessage(error: Throwable): Int? {
private fun getErrorMessage(error: Throwable): String? {
return when (error) {
is UnsupportedChainId -> R.string.WalletConnect_Error_UnsupportedChainId
is NoSuitableAccount -> R.string.WalletConnect_Error_NoSuitableAccount
is NoSuitableEvmKit -> R.string.WalletConnect_Error_NoSuitableEvmKit
is RequestNotFoundError -> R.string.WalletConnect_Error_RequestNotFoundError
is UnsupportedChainId -> Translator.getString(R.string.WalletConnect_Error_UnsupportedChainId)
is NoSuitableAccount -> Translator.getString(R.string.WalletConnect_Error_NoSuitableAccount)
is NoSuitableEvmKit -> Translator.getString(R.string.WalletConnect_Error_NoSuitableEvmKit)
is RequestNotFoundError -> Translator.getString(R.string.WalletConnect_Error_RequestNotFoundError)
is ValidationError.UnsupportedChains -> Translator.getString(
R.string.WalletConnect_Error_UnsupportedChains,
error.chains.joinToString { it })

is ValidationError.UnsupportedMethods -> Translator.getString(
R.string.WalletConnect_Error_UnsupportedMethods,
error.methods.joinToString { it })

is ValidationError.UnsupportedEvents -> Translator.getString(
R.string.WalletConnect_Error_UnsupportedEvents,
error.events.joinToString { it })

else -> null
}
}
Expand All @@ -447,54 +511,29 @@ class WCSessionViewModel(
else -> throw UnsupportedAccountException()
}

private fun getSupportedNamespaces(accounts: List<String>): Map<String, Wallet.Model.Namespace.Session> {
return mapOf(
"eip155" to Wallet.Model.Namespace.Session(
chains = listOf(
"eip155:1",
"eip155:56",
"eip155:137",
"eip155:10",
"eip155:42161",
"eip155:43114",
"eip155:100",
),
methods = listOf(
"eth_sendTransaction",
"personal_sign",
// "eth_accounts",
// "eth_requestAccounts",
// "eth_call",
// "eth_getBalance",
// "eth_sendRawTransaction",
"eth_sign",
"eth_signTransaction",
"eth_signTypedData"
),
events = listOf("chainChanged", "accountsChanged"),
accounts = accounts
),
private fun getSupportedNamespaces(accounts: List<String>) = mapOf(
"eip155" to Wallet.Model.Namespace.Session(
chains = supportedChains.map { "eip155:${it.id}" },
methods = supportedMethods,
events = supportedEvents,
accounts = accounts
)
}
)

private fun getSupportedBlockchains(account: Account): List<WCBlockchain> {
val chains = listOf(
Chain.Ethereum,
Chain.BinanceSmartChain,
Chain.Polygon,
Chain.Optimism,
Chain.ArbitrumOne,
Chain.Avalanche,
Chain.Gnosis,
)
return chains.map {
val address = getEvmAddress(account, it).eip55
WCBlockchain(it.id, it.name, address)
}
private fun getSupportedBlockchains(account: Account) = supportedChains.map {
val address = getEvmAddress(account, it).eip55
WCBlockchain(it.id, it.name, address)
}

fun setRequestToOpen(request: Wallet.Model.SessionRequest) {
WCDelegate.sessionRequestEvent = request
}

}

sealed class ValidationError : Throwable() {
class UnsupportedChains(val chains: List<String>) : ValidationError()
class UnsupportedMethods(val methods: List<String>) : ValidationError()
class UnsupportedEvents(val events: List<String>) : ValidationError()
}

3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,9 @@
<string name="WalletConnect_Error_NoSuitableAccount">No suitable account</string>
<string name="WalletConnect_Error_NoSuitableEvmKit">No suitable evm kit</string>
<string name="WalletConnect_Error_DataParsingError">Data parsing error</string>
<string name="WalletConnect_Error_UnsupportedChains">Unsupported chains: %s</string>
<string name="WalletConnect_Error_UnsupportedMethods">Unsupported methods: %s</string>
<string name="WalletConnect_Error_UnsupportedEvents">Unsupported events: %s</string>
<string name="WalletConnect_Error_RequestNotFoundError">Request not found error</string>
<string name="WalletConnect_NoConnection">You don\’t have any connected dapps</string>
<string name="WalletConnect_PendingRequests">Pending Requests</string>
Expand Down

0 comments on commit d55f567

Please sign in to comment.