310 lines
11 KiB
QML
310 lines
11 KiB
QML
import QtQuick 2.15
|
|
|
|
import AppLayouts.Wallet.services.dapps 1.0
|
|
import AppLayouts.Wallet.services.dapps.plugins 1.0
|
|
import AppLayouts.Wallet.services.dapps.types 1.0
|
|
import AppLayouts.Wallet.stores 1.0 as WalletStore
|
|
|
|
import StatusQ.Core.Utils 0.1 as SQUtils
|
|
|
|
import shared.stores 1.0
|
|
import utils 1.0
|
|
|
|
/// Component that provides the dapps integration for the wallet.
|
|
/// It provides the following features:
|
|
/// - WalletConnect integration
|
|
/// - WalletConnect pairing
|
|
/// - WalletConnect sessions management
|
|
/// - WalletConnect signing requests
|
|
/// - WalletConnect SIWE
|
|
/// - WalletConnect online status
|
|
/// - BrowserConnect integration
|
|
/// - BrowserConnect pairing
|
|
/// - BrowserConnect - access to persistent sessions
|
|
/// - BrowserConnect - access to persistent signing requests
|
|
/// - BrowserConnect signing requests
|
|
/// - BrowserConnect online status
|
|
SQUtils.QObject {
|
|
id: root
|
|
|
|
// SDKs providing the DApps API
|
|
required property WalletConnectSDKBase wcSdk
|
|
required property WalletConnectSDKBase bcSdk
|
|
|
|
// DApps shared store - used for wc peristence and signing requests/transactions
|
|
required property DAppsStore store
|
|
required property CurrenciesStore currenciesStore
|
|
|
|
// Required roles: address
|
|
required property var accountsModel
|
|
// Required roles: chainId, layer, isOnline
|
|
required property var networksModel
|
|
// Required roles: tokenKey, balances
|
|
required property var groupedAccountAssetsModel
|
|
|
|
readonly property alias requestsModel: requests
|
|
readonly property alias dappsModel: dappConnections.dappsModel
|
|
readonly property bool enabled: wcSdk.enabled || bcSdk.enabled
|
|
readonly property bool isServiceOnline: chainsSupervisorPlugin.anyChainAvailable && (wcSdk.sdkReady || bcSdk.enabled)
|
|
|
|
// Connection signals
|
|
/// Emitted when a new DApp requests a connection
|
|
signal connectDApp(var chains, string dAppUrl, string dAppName, string dAppIcon, string key)
|
|
/// Emitted when a new DApp is connected
|
|
signal dappConnected(string proposalId, string newTopic, string url, int connectorId)
|
|
/// Emitted when a DApp is disconnected
|
|
signal dappDisconnected(string topic, string url)
|
|
/// Emitted when a new DApp fails to connect
|
|
signal newConnectionFailed(string key, string dappUrl, var error)
|
|
|
|
// Pairing signals
|
|
signal pairingValidated(int validationState)
|
|
signal pairingResponse(int state) // Maps to Pairing.errors
|
|
|
|
// Sign request signals
|
|
signal signCompleted(string topic, string id, bool userAccepted, string error)
|
|
signal siweCompleted(string topic, string id, string error)
|
|
|
|
/// WalletConnect pairing
|
|
/// @param uri - the pairing URI to pair
|
|
/// Result is emitted via the pairingResponse signal
|
|
/// A new session proposal is expected to be emitted if the pairing is successful
|
|
function pair(uri) {
|
|
return wcSdk.pair(uri)
|
|
}
|
|
|
|
/// Approves or rejects the session proposal. App response to `connectDApp`
|
|
/// @param key - the key of the session proposal
|
|
/// @param approvedChainIds - array containing the chainIds that the user approved
|
|
/// @param accountAddress - the address of the account that approved the session
|
|
function approvePairSession(key, approvedChainIds, accountAddress) {
|
|
if (siwePlugin.connectionRequests.has(key.toString())) {
|
|
siwePlugin.accept(key, approvedChainIds, accountAddress)
|
|
siwePlugin.connectionRequests.delete(key.toString())
|
|
return
|
|
}
|
|
dappConnections.connect(key, approvedChainIds, accountAddress)
|
|
}
|
|
|
|
/// Rejects the session proposal. App response to `connectDApp`
|
|
/// @param id - the id of the session proposal
|
|
function rejectPairSession(id) {
|
|
if (siwePlugin.connectionRequests.has(id.toString())) {
|
|
siwePlugin.reject(id)
|
|
siwePlugin.connectionRequests.delete(id.toString())
|
|
return
|
|
}
|
|
dappConnections.reject(id)
|
|
}
|
|
|
|
/// Disconnects the WC session with the given topic. Expected `dappDisconnected` signal
|
|
/// @param sessionTopic - the topic of the session to disconnect
|
|
function disconnectSession(sessionTopic) {
|
|
dappConnections.disconnect(sessionTopic)
|
|
}
|
|
|
|
/// Validates the pairing URI and emits the pairingValidated signal. Expected `pairingValidated` signal
|
|
/// Async function
|
|
/// @param uri - the pairing URI to validate
|
|
function validatePairingUri(uri){
|
|
const info = DAppsHelpers.extractInfoFromPairUri(uri)
|
|
wcSdk.getActiveSessions((sessions) => {
|
|
// Check if the URI is already paired
|
|
let validationState = Pairing.errors.uriOk
|
|
for (const key in sessions) {
|
|
if (sessions[key].pairingTopic === info.topic) {
|
|
validationState = Pairing.errors.alreadyUsed
|
|
break
|
|
}
|
|
}
|
|
|
|
// Check if expired
|
|
if (validationState === Pairing.errors.uriOk) {
|
|
const now = (new Date().getTime())/1000
|
|
if (info.expiry < now) {
|
|
validationState = Pairing.errors.expired
|
|
}
|
|
}
|
|
|
|
root.pairingValidated(validationState)
|
|
});
|
|
}
|
|
|
|
/// Returns the DApp with the given topic
|
|
/// @param topic - the topic of the DApp to return
|
|
/// @return the DApp with the given topic
|
|
/// DApp {
|
|
/// name: string
|
|
/// url: string
|
|
/// iconUrl: string
|
|
/// topic: string
|
|
/// connectorId: int
|
|
/// accountAddressses: [{address: string}]
|
|
/// chains: string
|
|
/// rawSessions: [{session: object}]
|
|
/// }
|
|
function getDApp(topic) {
|
|
return SQUtils.ModelUtils.getFirstModelEntryIf(dappsModel, (dapp) => {
|
|
return dapp.topic === topic
|
|
SQUtils.ModelUtils.getFirstModelEntryIf(dapp.rawSessions, (session) => {
|
|
return session.topic === topic
|
|
})
|
|
})
|
|
}
|
|
|
|
DAppConnectionsPlugin {
|
|
id: dappConnections
|
|
wcSDK: root.wcSdk
|
|
bcSDK: root.bcSdk
|
|
dappsStore: root.store
|
|
accountsModel: root.accountsModel
|
|
networksModel: root.networksModel
|
|
|
|
onConnected: (proposalId, topic, url, connectorId) => {
|
|
root.dappConnected(proposalId, topic, url, connectorId)
|
|
}
|
|
onDisconnected: (topic, url) => {
|
|
root.dappDisconnected(topic, url)
|
|
}
|
|
onNewConnectionProposed: (key, chains, dAppUrl, dAppName, dAppIcon) => {
|
|
root.connectDApp(chains, dAppUrl, dAppName, dAppIcon, key)
|
|
}
|
|
onNewConnectionFailed: (key, dappUrl, error) => {
|
|
root.newConnectionFailed(key, dappUrl, error)
|
|
}
|
|
}
|
|
|
|
SessionRequestsModel {
|
|
id: requests
|
|
}
|
|
|
|
ChainsSupervisorPlugin {
|
|
id: chainsSupervisorPlugin
|
|
|
|
sdk: root.wcSdk
|
|
networksModel: root.networksModel
|
|
}
|
|
|
|
Connections {
|
|
target: root.wcSdk
|
|
enabled: root.wcSdk.enabled
|
|
|
|
function onPairResponse(ok) {
|
|
root.pairingResponse(ok)
|
|
}
|
|
}
|
|
|
|
SiweRequestPlugin {
|
|
id: siwePlugin
|
|
|
|
readonly property var connectionRequests: new Map()
|
|
sdk: root.wcSdk
|
|
store: root.store
|
|
accountsModel: root.accountsModel
|
|
networksModel: root.networksModel
|
|
|
|
onRegisterSignRequest: (request) => {
|
|
requests.enqueue(request)
|
|
}
|
|
|
|
onUnregisterSignRequest: (requestId) => {
|
|
const request = requests.findById(requestId)
|
|
if (request === null) {
|
|
console.error("SiweRequestPlugin::onUnregisterSignRequest: Error finding event for requestId", requestId)
|
|
return
|
|
}
|
|
requests.removeRequest(request.topic, requestId)
|
|
}
|
|
|
|
onConnectDApp: (chains, dAppUrl, dAppName, dAppIcon, key) => {
|
|
siwePlugin.connectionRequests.set(key.toString(), {chains, dAppUrl, dAppName, dAppIcon})
|
|
root.connectDApp(chains, dAppUrl, dAppName, dAppIcon, key)
|
|
}
|
|
|
|
onSiweFailed: (id, error, topic) => {
|
|
root.siweCompleted(topic, id, error)
|
|
}
|
|
|
|
onSiweSuccessful: (id, topic) => {
|
|
d.lookupSession(topic, function(session) {
|
|
// Persist session
|
|
if(!root.store.addWalletConnectSession(JSON.stringify(session))) {
|
|
console.error("Failed to persist session")
|
|
}
|
|
|
|
root.siweCompleted(topic, id, "")
|
|
})
|
|
}
|
|
|
|
function accept(key, approvedChainIds, accountAddress) {
|
|
const approvedNamespaces = JSON.parse(
|
|
DAppsHelpers.buildSupportedNamespaces(approvedChainIds,
|
|
[accountAddress],
|
|
SessionRequest.getSupportedMethods()))
|
|
siwePlugin.connectionApproved(key, approvedNamespaces)
|
|
}
|
|
function reject(key) {
|
|
siwePlugin.connectionRejected(key)
|
|
}
|
|
}
|
|
|
|
SQUtils.QObject {
|
|
id: d
|
|
|
|
function lookupSession(topicToLookup, callback) {
|
|
wcSdk.getActiveSessions((res) => {
|
|
Object.keys(res).forEach((topic) => {
|
|
if (topic === topicToLookup) {
|
|
let session = res[topic]
|
|
callback(session)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
// bcSignRequestPlugin and wcSignRequestPlugin are used to handle sign requests
|
|
// Almost identical, and it's worth extracting in an inline component, but Qt5.15.2 doesn't support it
|
|
SignRequestPlugin {
|
|
id: bcSignRequestPlugin
|
|
|
|
sdk: root.bcSdk
|
|
groupedAccountAssetsModel: root.groupedAccountAssetsModel
|
|
networksModel: root.networksModel
|
|
accountsModel: root.accountsModel
|
|
currentCurrency: root.currenciesStore.currentCurrency
|
|
store: root.store
|
|
requests: root.requestsModel
|
|
dappsModel: root.dappsModel
|
|
|
|
getFiatValue: (value, currency) => {
|
|
return root.currenciesStore.getFiatValue(value, currency)
|
|
}
|
|
|
|
onSignCompleted: (topic, id, userAccepted, error) => {
|
|
root.signCompleted(topic, id, userAccepted, error)
|
|
}
|
|
}
|
|
|
|
SignRequestPlugin {
|
|
id: wcSignRequestPlugin
|
|
|
|
sdk: root.wcSdk
|
|
groupedAccountAssetsModel: root.groupedAccountAssetsModel
|
|
networksModel: root.networksModel
|
|
accountsModel: root.accountsModel
|
|
currentCurrency: root.currenciesStore.currentCurrency
|
|
store: root.store
|
|
requests: root.requestsModel
|
|
dappsModel: root.dappsModel
|
|
|
|
getFiatValue: (value, currency) => {
|
|
return root.currenciesStore.getFiatValue(value, currency)
|
|
}
|
|
|
|
onSignCompleted: (topic, id, userAccepted, error) => {
|
|
root.signCompleted(topic, id, userAccepted, error)
|
|
}
|
|
}
|
|
}
|