mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-09 22:06:25 +00:00
45835973e1
closes #16887 The pairing popup can be destroyed while the pairInstructions popup is active. As a result the pair instructions popup will misbehave. To fix this, the pair instructions component is moved outside of the pair popup scope (cherry picked from commit 007f75ad4c4823692abf9c12ee7c1a0806a87659)
434 lines
15 KiB
QML
434 lines
15 KiB
QML
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQuick.Layouts 1.15
|
|
|
|
import StatusQ 0.1
|
|
import StatusQ.Core.Theme 0.1
|
|
import StatusQ.Core.Utils 0.1 as SQUtils
|
|
import SortFilterProxyModel 0.2
|
|
|
|
import AppLayouts.Wallet 1.0
|
|
import AppLayouts.Wallet.controls 1.0
|
|
import AppLayouts.Wallet.services.dapps 1.0
|
|
import AppLayouts.Wallet.services.dapps.types 1.0
|
|
|
|
import shared.popups.walletconnect 1.0
|
|
|
|
import utils 1.0
|
|
|
|
SQUtils.QObject {
|
|
id: root
|
|
|
|
// Parent item for the popups
|
|
required property Item visualParent
|
|
// Whether the dapps can interract with the wallet
|
|
required property bool enabled
|
|
// Values mapped to Constants.LoginType
|
|
required property int loginType
|
|
/*
|
|
Accounts model
|
|
|
|
Expected model structure:
|
|
name [string] - account name e.g. "Piggy Bank"
|
|
address [string] - wallet account address e.g. "0x1234567890"
|
|
colorizedChainPrefixes [string] - chain prefixes with rich text colors e.g. "<font color=\"red\">eth:</font><font color=\"blue\">oeth:</font><font color=\"green\">arb:</font>"
|
|
emoji [string] - emoji for account e.g. "🐷"
|
|
colorId [string] - color id for account e.g. "1"
|
|
currencyBalance [var] - fiat currency balance
|
|
amount [number] - amount of currency e.g. 1234
|
|
symbol [string] - currency symbol e.g. "USD"
|
|
optDisplayDecimals [number] - optional number of decimals to display
|
|
stripTrailingZeroes [bool] - strip trailing zeroes
|
|
walletType [string] - wallet type e.g. Constants.watchWalletType. See `Constants` for possible values
|
|
migratedToKeycard [bool] - whether account is migrated to keycard
|
|
accountBalance [var] - account balance for a specific network
|
|
formattedBalance [string] - formatted balance e.g. "1234.56B"
|
|
balance [string] - balance e.g. "123456000000"
|
|
iconUrl [string] - icon url e.g. "network/Network=Hermez"
|
|
chainColor [string] - chain color e.g. "#FF0000"
|
|
*/
|
|
property var accountsModel
|
|
/*
|
|
Networks model
|
|
Expected model structure:
|
|
chainName [string] - chain long name. e.g. "Ethereum" or "Optimism"
|
|
chainId [int] - chain unique identifier
|
|
iconUrl [string] - SVG icon name. e.g. "network/Network=Ethereum"
|
|
layer [int] - chain layer. e.g. 1 or 2
|
|
isTest [bool] - true if the chain is a testnet
|
|
*/
|
|
property var networksModel
|
|
/*
|
|
ObjectModel containig session requests
|
|
requestId [string] - unique identifier for the request
|
|
requestItem [SessionRequestResolved] - request object
|
|
*/
|
|
property SessionRequestsModel sessionRequestsModel
|
|
/*
|
|
ObjectModel containing dApps
|
|
name [string] - dApp name
|
|
iconUrl [string] - dApp icon url
|
|
dAppUrl [string] - dApp url
|
|
topic [string] - dApp topic
|
|
*/
|
|
property var dAppsModel
|
|
|
|
property string selectedAccountAddress
|
|
|
|
property var formatBigNumber: (number, symbol, noSymbolOption) => console.error("formatBigNumber not set")
|
|
|
|
signal pairWCReady()
|
|
|
|
signal disconnectRequested(string connectionId)
|
|
signal pairingRequested(string uri)
|
|
signal pairingValidationRequested(string uri)
|
|
signal connectionAccepted(string pairingId, var chainIds, string selectedAccount)
|
|
signal connectionDeclined(string pairingId)
|
|
signal signRequestAccepted(string connectionId, string requestId)
|
|
signal signRequestRejected(string connectionId, string requestId)
|
|
signal signRequestIsLive(string connectionId, string requestId)
|
|
|
|
/// Response to pairingValidationRequested
|
|
function pairingValidated(validationState) {
|
|
if (pairWCLoader.item) {
|
|
pairWCLoader.item.pairingValidated(validationState)
|
|
}
|
|
}
|
|
|
|
/// Confirmation received on connectionAccepted
|
|
function connectionSuccessful(pairingId, newConnectionId) {
|
|
connectDappLoader.connectionSuccessful(pairingId, newConnectionId)
|
|
}
|
|
|
|
/// Confirmation received on connectionAccepted
|
|
function connectionFailed(pairingId) {
|
|
connectDappLoader.connectionFailed(pairingId)
|
|
}
|
|
|
|
/// Request to connect to a dApp
|
|
function connectDApp(dappChains, dappUrl, dappName, dappIcon, connectorIcon, pairingId) {
|
|
connectDappLoader.connect(dappChains, dappUrl, dappName, dappIcon, connectorIcon, pairingId)
|
|
}
|
|
|
|
function openPairing() {
|
|
pairWCLoader.active = true
|
|
}
|
|
|
|
function disconnectDapp(dappUrl) {
|
|
disconnectdAppDialogLoader.dAppUrl = dappUrl
|
|
disconnectdAppDialogLoader.active = true
|
|
}
|
|
|
|
Loader {
|
|
id: disconnectdAppDialogLoader
|
|
|
|
property string dAppUrl
|
|
|
|
active: false
|
|
parent: root.visualParent
|
|
|
|
onLoaded: {
|
|
const dApp = SQUtils.ModelUtils.getByKey(root.dAppsModel, "url", dAppUrl);
|
|
if (dApp) {
|
|
item.dappName = dApp.name;
|
|
item.dappIcon = dApp.iconUrl;
|
|
item.dappUrl = disconnectdAppDialogLoader.dAppUrl;
|
|
}
|
|
|
|
item.open();
|
|
}
|
|
|
|
sourceComponent: DAppConfirmDisconnectPopup {
|
|
|
|
visible: true
|
|
|
|
onClosed: {
|
|
disconnectdAppDialogLoader.active = false
|
|
}
|
|
|
|
onAccepted: {
|
|
SQUtils.ModelUtils.forEach(root.dAppsModel, (dApp) => {
|
|
if (dApp.url === dAppUrl) {
|
|
root.disconnectRequested(dApp.topic)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: pairWCLoader
|
|
|
|
active: false
|
|
parent: root.visualParent
|
|
|
|
onLoaded: {
|
|
item.open()
|
|
root.pairWCReady()
|
|
}
|
|
|
|
sourceComponent: PairWCModal {
|
|
visible: true
|
|
|
|
onClosed: pairWCLoader.active = false
|
|
onPair: (uri) => root.pairingRequested(uri)
|
|
onPairUriChanged: (uri) => root.pairingValidationRequested(uri)
|
|
onPairInstructionsRequested: pairInstructionsLoader.active = true
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: pairInstructionsLoader
|
|
|
|
active: false
|
|
parent: root.visualParent
|
|
|
|
sourceComponent: Component {
|
|
DAppsUriCopyInstructionsPopup{
|
|
visible: true
|
|
destroyOnClose: false
|
|
onClosed: pairInstructionsLoader.active = false
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: connectDappLoader
|
|
|
|
active: false
|
|
parent: root.visualParent
|
|
|
|
// Array of chaind ids
|
|
property var dappChains
|
|
property url dappUrl
|
|
property string dappName
|
|
property url dappIcon
|
|
property string connectorIcon
|
|
property var key
|
|
property var topic
|
|
|
|
property var connectionQueue: []
|
|
onActiveChanged: {
|
|
if (!active && connectionQueue.length > 0) {
|
|
connect(connectionQueue[0].dappChains,
|
|
connectionQueue[0].dappUrl,
|
|
connectionQueue[0].dappName,
|
|
connectionQueue[0].dappIcon,
|
|
connectionQueue[0].connectorIcon,
|
|
connectionQueue[0].key)
|
|
connectionQueue.shift()
|
|
}
|
|
}
|
|
|
|
function connect(dappChains, dappUrl, dappName, dappIcon, connectorIcon, key) {
|
|
if (connectDappLoader.active) {
|
|
connectionQueue.push({ dappChains, dappUrl, dappName, dappIcon, key, connectorIcon })
|
|
return
|
|
}
|
|
|
|
connectDappLoader.dappChains = dappChains
|
|
connectDappLoader.dappUrl = dappUrl
|
|
connectDappLoader.dappName = dappName
|
|
connectDappLoader.dappIcon = dappIcon
|
|
connectDappLoader.connectorIcon = connectorIcon
|
|
connectDappLoader.key = key
|
|
|
|
if (pairWCLoader.item) {
|
|
// Allow user to get the uri valid confirmation
|
|
pairWCLoader.item.pairingValidated(Pairing.errors.dappReadyForApproval)
|
|
connectDappTimer.start()
|
|
} else {
|
|
connectDappLoader.active = true
|
|
}
|
|
}
|
|
|
|
function connectionSuccessful(key, newTopic) {
|
|
if (connectDappLoader.key === key && connectDappLoader.item) {
|
|
connectDappLoader.topic = newTopic
|
|
connectDappLoader.item.pairSuccessful()
|
|
}
|
|
}
|
|
|
|
function connectionFailed(id) {
|
|
if (connectDappLoader.key === key && connectDappLoader.item) {
|
|
connectDappLoader.item.pairFailed()
|
|
}
|
|
}
|
|
|
|
sourceComponent: ConnectDAppModal {
|
|
visible: true
|
|
|
|
onClosed: connectDappLoader.active = false
|
|
accounts: root.accountsModel
|
|
flatNetworks: SortFilterProxyModel {
|
|
sourceModel: root.networksModel
|
|
filters: [
|
|
FastExpressionFilter {
|
|
inverted: true
|
|
expression: connectDappLoader.dappChains.indexOf(chainId) === -1
|
|
expectedRoles: ["chainId"]
|
|
}
|
|
]
|
|
}
|
|
selectedAccountAddress: root.selectedAccountAddress
|
|
|
|
dAppUrl: connectDappLoader.dappUrl
|
|
dAppName: connectDappLoader.dappName
|
|
dAppIconUrl: connectDappLoader.dappIcon
|
|
dAppConnectorBadge: connectDappLoader.connectorIcon
|
|
connectButtonEnabled: root.enabled
|
|
|
|
onConnect: {
|
|
if (!selectedAccount || !selectedAccount.address) {
|
|
console.error("Missing account selection")
|
|
return
|
|
}
|
|
if (!selectedChains || selectedChains.length === 0) {
|
|
console.error("Missing chain selection")
|
|
return
|
|
}
|
|
|
|
root.connectionAccepted(connectDappLoader.key, selectedChains, selectedAccount.address)
|
|
}
|
|
|
|
onDecline: {
|
|
root.connectionDeclined(connectDappLoader.key)
|
|
close()
|
|
}
|
|
|
|
onDisconnect: {
|
|
root.disconnectRequested(connectDappLoader.topic)
|
|
close()
|
|
}
|
|
}
|
|
}
|
|
|
|
Instantiator {
|
|
model: root.sessionRequestsModel
|
|
delegate: DAppSignRequestModal {
|
|
id: dappRequestModal
|
|
objectName: "dappsRequestModal"
|
|
|
|
required property var model
|
|
required property int index
|
|
|
|
readonly property var request: model.requestItem
|
|
readonly property var account: accountEntry.available ? accountEntry.item : {
|
|
name: "",
|
|
address: "",
|
|
emoji: "",
|
|
colorId: 0,
|
|
migratedToKeycard: false
|
|
}
|
|
|
|
readonly property var network: networkEntry.available ? networkEntry.item : {
|
|
chainName: "",
|
|
iconUrl: ""
|
|
}
|
|
property bool requestHandled: false
|
|
|
|
function rejectRequest() {
|
|
// Allow rejecting only once
|
|
if (requestHandled) {
|
|
return
|
|
}
|
|
requestHandled = true
|
|
root.signRequestRejected(request.topic, request.requestId)
|
|
}
|
|
|
|
parent: root.visualParent
|
|
|
|
loginType: account.migratedToKeycard ? Constants.LoginType.Keycard : root.loginType
|
|
formatBigNumber: root.formatBigNumber
|
|
|
|
visible: !!request.dappUrl
|
|
|
|
dappUrl: request.dappUrl
|
|
dappIcon: request.dappIcon
|
|
dappName: request.dappName
|
|
|
|
accountColor: Utils.getColorForId(account.colorId)
|
|
accountName: account.name
|
|
accountAddress: account.address
|
|
accountEmoji: account.emoji
|
|
|
|
networkName: network.chainName
|
|
networkIconPath: Theme.svg(network.iconUrl)
|
|
|
|
fiatFees: request.fiatMaxFees ? request.fiatMaxFees.toFixed() : ""
|
|
cryptoFees: request.ethMaxFees ? request.ethMaxFees.toFixed() : ""
|
|
estimatedTime: WalletUtils.getLabelForEstimatedTxTime(request.estimatedTimeCategory)
|
|
feesLoading: hasFees && (!fiatFees || !cryptoFees)
|
|
estimatedTimeLoading: request.estimatedTimeCategory === Constants.TransactionEstimatedTime.Unknown
|
|
hasFees: signingTransaction
|
|
enoughFundsForTransaction: request.haveEnoughFunds
|
|
enoughFundsForFees: request.haveEnoughFees
|
|
|
|
signButtonEnabled: ((!hasFees) || enoughFundsForTransaction && enoughFundsForFees) && root.enabled
|
|
signingTransaction: !!request.method && (request.method === SessionRequest.methods.signTransaction.name
|
|
|| request.method === SessionRequest.methods.sendTransaction.name)
|
|
requestPayload: {
|
|
try {
|
|
const data = JSON.parse(request.preparedData)
|
|
|
|
delete data.maxFeePerGas
|
|
delete data.maxPriorityFeePerGas
|
|
delete data.gasPrice
|
|
|
|
return JSON.stringify(data, null, 2)
|
|
} catch(_) {
|
|
return request.preparedData
|
|
}
|
|
}
|
|
expirationSeconds: request.expirationTimestamp ? request.expirationTimestamp - requestTimestamp.getTime() / 1000
|
|
: 0
|
|
hasExpiryDate: !!request.expirationTimestamp
|
|
|
|
onOpened: {
|
|
root.signRequestIsLive(request.topic, request.requestId)
|
|
}
|
|
|
|
onClosed: {
|
|
Qt.callLater(rejectRequest)
|
|
}
|
|
|
|
onAccepted: {
|
|
requestHandled = true
|
|
root.signRequestAccepted(request.topic, request.requestId)
|
|
}
|
|
|
|
onRejected: {
|
|
rejectRequest()
|
|
}
|
|
|
|
ModelEntry {
|
|
id: accountEntry
|
|
sourceModel: root.accountsModel
|
|
key: "address"
|
|
value: request.accountAddress
|
|
}
|
|
|
|
ModelEntry {
|
|
id: networkEntry
|
|
sourceModel: root.networksModel
|
|
key: "chainId"
|
|
value: request.chainId
|
|
}
|
|
}
|
|
}
|
|
|
|
// Used between transitioning from PairWCModal to ConnectDAppModal
|
|
Timer {
|
|
id: connectDappTimer
|
|
|
|
interval: 500
|
|
running: false
|
|
repeat: false
|
|
|
|
onTriggered: {
|
|
pairWCLoader.item.close()
|
|
connectDappLoader.active = true
|
|
}
|
|
}
|
|
}
|