mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-22 19:48:52 +00:00
feat(dapps) add DAppsService component and ConnectDAppModal
Implement the initial Pairing user workflow and disconnect option for the first session. Also - rename pairing modal accordingly (`PairWCModal.qml`) to make room for the proper `ConnectDAppModal.qml` - basic tests for service helpers - update storybook to reflect the new user workflows Closes #14607
This commit is contained in:
parent
4771f0d77f
commit
ee72ec7aee
@ -56,8 +56,8 @@ SplitView {
|
||||
|
||||
store: ProfileSectionStore {
|
||||
property WalletStore walletStore: WalletStore {
|
||||
accountSensitiveSettings: mockData.accountSettings
|
||||
dappList: dappsModel
|
||||
property var accountSensitiveSettings: mockData.accountSettings
|
||||
property var dappList: dappsModel
|
||||
|
||||
function disconnect(dappName) {
|
||||
for (let i = 0; i < dappsModel.count; i++) {
|
||||
|
126
storybook/pages/ConnectDAppModalPage.qml
Normal file
126
storybook/pages/ConnectDAppModalPage.qml
Normal file
@ -0,0 +1,126 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml 2.15
|
||||
import Qt.labs.settings 1.0
|
||||
import QtTest 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
|
||||
import Models 1.0
|
||||
import Storybook 1.0
|
||||
|
||||
import shared.popups.walletconnect 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import AppLayouts.Wallet.panels 1.0
|
||||
|
||||
import utils 1.0
|
||||
import shared.stores 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
function openModal() {
|
||||
modal.openWithFilter([1, 42161], JSON.parse(`{
|
||||
"metadata": {
|
||||
"description": "React App for WalletConnect",
|
||||
"icons": [
|
||||
"https://avatars.githubusercontent.com/u/37784886"
|
||||
],
|
||||
"name": "React App",
|
||||
"url": "https://react-app.walletconnect.com",
|
||||
"verifyUrl": "https://verify.walletconnect.com"
|
||||
},
|
||||
"publicKey": "300a6a1df4cb0cd73eb652f11845f35a318541eb18ab369860be85c0c2ada54a"
|
||||
}`))
|
||||
if (pairedCheckbox.checked) {
|
||||
pairedResultTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
// qml Splitter
|
||||
SplitView {
|
||||
anchors.fill: parent
|
||||
|
||||
ColumnLayout {
|
||||
SplitView.fillWidth: true
|
||||
|
||||
Component.onCompleted: root.openModal()
|
||||
|
||||
StatusButton {
|
||||
id: openButton
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.margins: 20
|
||||
|
||||
text: "Open ConnectDAppModal"
|
||||
|
||||
onClicked: root.openModal()
|
||||
}
|
||||
|
||||
ConnectDAppModal {
|
||||
id: modal
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
spacing: 8
|
||||
|
||||
accounts: WalletAccountsModel{
|
||||
}
|
||||
|
||||
flatNetworks: SortFilterProxyModel {
|
||||
sourceModel: NetworksModel.flatNetworks
|
||||
filters: ValueFilter { roleName: "isTest"; value: false; }
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: optionsSpace
|
||||
|
||||
CheckBox {
|
||||
id: pairedCheckbox
|
||||
|
||||
text: "Report Paired"
|
||||
|
||||
checked: true
|
||||
}
|
||||
CheckBox {
|
||||
id: pairedStatusCheckbox
|
||||
|
||||
text: "Paired Successful"
|
||||
|
||||
checked: true
|
||||
}
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pairedResultTimer
|
||||
|
||||
interval: 1000
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (pairedCheckbox.checked) {
|
||||
if (pairedStatusCheckbox.checked) {
|
||||
modal.pairSuccessful(null)
|
||||
} else {
|
||||
modal.pairFailed(null, "Pairing failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// category: Wallet
|
@ -11,6 +11,7 @@ import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||
|
||||
import Models 1.0
|
||||
import Storybook 1.0
|
||||
@ -21,6 +22,7 @@ import AppLayouts.Wallet.services.dapps 1.0
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import AppLayouts.Wallet.panels 1.0
|
||||
import AppLayouts.Profile.stores 1.0
|
||||
|
||||
import utils 1.0
|
||||
import shared.stores 1.0
|
||||
@ -49,6 +51,8 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
|
||||
spacing: 8
|
||||
|
||||
wcService: walletConnectService
|
||||
}
|
||||
}
|
||||
ColumnLayout {}
|
||||
@ -62,11 +66,20 @@ Item {
|
||||
Text {
|
||||
id: projectIdText
|
||||
readonly property string projectId: SystemUtils.getEnvVar("WALLET_CONNECT_PROJECT_ID")
|
||||
text: projectId.substring(0, 3) + "..." + projectId.substring(projectId.length - 3)
|
||||
text: SQUtils.Utils.elideText(projectId, 3)
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
|
||||
text: "Testnet Mode"
|
||||
checked: settings.testNetworks
|
||||
onCheckedChanged: {
|
||||
settings.testNetworks = checked
|
||||
}
|
||||
}
|
||||
|
||||
// spacer
|
||||
ColumnLayout {}
|
||||
|
||||
@ -87,7 +100,7 @@ Item {
|
||||
|
||||
|
||||
CheckBox {
|
||||
id: openPairCheckBox
|
||||
|
||||
text: "Open Pair"
|
||||
checked: settings.openPair
|
||||
onCheckedChanged: {
|
||||
@ -96,10 +109,11 @@ Item {
|
||||
d.startPairing()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: dappsWorkflow
|
||||
|
||||
// Open Pairing workflow if selected in the side bar
|
||||
// If Open Pair workflow if selected in the side bar
|
||||
function onDAppsListReady() {
|
||||
if (!d.startPairingWorkflowActive)
|
||||
return
|
||||
@ -113,7 +127,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
function onConnectDappReady() {
|
||||
function onPairWCReady() {
|
||||
if (!d.startPairingWorkflowActive)
|
||||
return
|
||||
|
||||
@ -121,25 +135,52 @@ Item {
|
||||
let items = InspectionUtils.findVisualsByTypeName(dappsWorkflow, "StatusBaseInput")
|
||||
if (items.length === 1) {
|
||||
items[0].text = pairUriInput.text
|
||||
|
||||
clickDoneIfSDKReady()
|
||||
}
|
||||
}
|
||||
d.startPairingWorkflowActive = false
|
||||
}
|
||||
|
||||
function clickDoneIfSDKReady() {
|
||||
if (!d.startPairingWorkflowActive) {
|
||||
return
|
||||
}
|
||||
|
||||
let modals = InspectionUtils.findVisualsByTypeName(dappsWorkflow, "PairWCModal")
|
||||
if (modals.length === 1) {
|
||||
let buttons = InspectionUtils.findVisualsByTypeName(modals[0].footer, "StatusButton")
|
||||
if (buttons.length === 1 && walletConnectService.wcSDK.sdkReady) {
|
||||
d.startPairingWorkflowActive = false
|
||||
buttons[0].clicked()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Backpressure.debounce(dappsWorkflow, 250, clickDoneIfSDKReady)()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DAppsStore {
|
||||
wCSDK: WalletConnectSDK {
|
||||
WalletConnectService {
|
||||
id: walletConnectService
|
||||
|
||||
wcSDK: WalletConnectSDK {
|
||||
active: true
|
||||
|
||||
projectId: projectIdText.projectId
|
||||
}
|
||||
|
||||
onSessionRequestEvent: (details) => {
|
||||
// TODO #14556
|
||||
console.debug(`@dd onSessionRequestEvent: ${JSON.stringify(details)}`)
|
||||
dappsStore: DAppsStore {
|
||||
}
|
||||
|
||||
walletStore: WalletStore {
|
||||
property var flatNetworks: SortFilterProxyModel {
|
||||
sourceModel: NetworksModel.flatNetworks
|
||||
filters: ValueFilter { roleName: "isTest"; value: settings.testNetworks; }
|
||||
}
|
||||
property var accounts: WalletAccountsModel{}
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,7 +191,7 @@ Item {
|
||||
property bool startPairingWorkflowActive: false
|
||||
|
||||
function startPairing() {
|
||||
startPairingWorkflowActive = true
|
||||
d.startPairingWorkflowActive = true
|
||||
if(root.visible) {
|
||||
dappsWorkflow.clicked()
|
||||
}
|
||||
@ -168,6 +209,7 @@ Item {
|
||||
|
||||
property bool openPair: false
|
||||
property string pairUri: ""
|
||||
property bool testNetworks: false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,6 +98,10 @@ SplitView {
|
||||
}
|
||||
return prefChains
|
||||
}
|
||||
|
||||
function addressWasShown(account) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,44 +7,128 @@ import QtQuick.Controls 2.15
|
||||
|
||||
import Storybook 1.0
|
||||
|
||||
import AppLayouts.Wallet.controls 1.0
|
||||
//import AppLayouts.Wallet.panels 1.0
|
||||
|
||||
import AppLayouts.Wallet.services.dapps 1.0
|
||||
|
||||
import QtQml.Models 2.15
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: 600
|
||||
height: 400
|
||||
|
||||
Component {
|
||||
id: componentUnderTest
|
||||
DAppsWorkflow {
|
||||
}
|
||||
}
|
||||
// TODO: mock WalletConnectSDK
|
||||
// Component {
|
||||
// id: componentUnderTest
|
||||
// DAppsWorkflow {
|
||||
// }
|
||||
// }
|
||||
|
||||
// TestCase {
|
||||
// name: "DAppsWorkflow"
|
||||
// when: windowShown
|
||||
|
||||
// property DAppsWorkflow controlUnderTest: null
|
||||
|
||||
// function init() {
|
||||
// controlUnderTest = createTemporaryObject(componentUnderTest, root)
|
||||
// }
|
||||
|
||||
// function test_ClickToOpenAndClosePopup() {
|
||||
// verify(!!controlUnderTest)
|
||||
// waitForRendering(controlUnderTest)
|
||||
|
||||
// mouseClick(controlUnderTest, Qt.LeftButton)
|
||||
// waitForRendering(controlUnderTest)
|
||||
|
||||
// let popup = findChild(controlUnderTest, "dappsPopup")
|
||||
// verify(!!popup)
|
||||
// verify(popup.opened)
|
||||
|
||||
// mouseClick(Overlay.overlay, Qt.LeftButton)
|
||||
// waitForRendering(controlUnderTest)
|
||||
|
||||
// verify(!popup.opened)
|
||||
// }
|
||||
// }
|
||||
|
||||
TestCase {
|
||||
name: "DAppsWorkflow"
|
||||
when: windowShown
|
||||
name: "ServiceHelpers"
|
||||
|
||||
property DAppsWorkflow controlUnderTest: null
|
||||
|
||||
function init() {
|
||||
controlUnderTest = createTemporaryObject(componentUnderTest, root)
|
||||
function test_extractChainsAndAccountsFromApprovedNamespaces() {
|
||||
let res = Helpers.extractChainsAndAccountsFromApprovedNamespaces(JSON.parse(`{
|
||||
"eip155": {
|
||||
"accounts": [
|
||||
"eip155:1:0x1",
|
||||
"eip155:1:0x2",
|
||||
"eip155:2:0x1",
|
||||
"eip155:2:0x2"
|
||||
],
|
||||
"chains": [
|
||||
"eip155:1",
|
||||
"eip155:2"
|
||||
],
|
||||
"events": [
|
||||
"accountsChanged",
|
||||
"chainChanged"
|
||||
],
|
||||
"methods": [
|
||||
"eth_sendTransaction",
|
||||
"personal_sign"
|
||||
]
|
||||
}
|
||||
}`))
|
||||
verify(res.chains.length === 2)
|
||||
verify(res.accounts.length === 2)
|
||||
verify(res.chains[0] === 1)
|
||||
verify(res.chains[1] === 2)
|
||||
verify(res.accounts[0] === "0x1")
|
||||
verify(res.accounts[1] === "0x2")
|
||||
}
|
||||
|
||||
function test_ClickToOpenAndClosePopup() {
|
||||
verify(!!controlUnderTest)
|
||||
waitForRendering(controlUnderTest)
|
||||
readonly property ListModel chainsModel: ListModel {
|
||||
ListElement { chainId: 1 }
|
||||
ListElement { chainId: 2 }
|
||||
}
|
||||
|
||||
mouseClick(controlUnderTest, Qt.LeftButton)
|
||||
waitForRendering(controlUnderTest)
|
||||
readonly property ListModel accountsModel: ListModel {
|
||||
ListElement { address: "0x1" }
|
||||
ListElement { address: "0x2" }
|
||||
}
|
||||
|
||||
let popup = findChild(controlUnderTest, "dappsPopup")
|
||||
verify(!!popup)
|
||||
verify(popup.opened)
|
||||
function test_buildSupportedNamespacesFromModels() {
|
||||
let resStr = Helpers.buildSupportedNamespacesFromModels(chainsModel, accountsModel)
|
||||
let jsonObj = JSON.parse(resStr)
|
||||
verify(jsonObj.hasOwnProperty("eip155"))
|
||||
let eip155 = jsonObj.eip155
|
||||
|
||||
mouseClick(Overlay.overlay, Qt.LeftButton)
|
||||
waitForRendering(controlUnderTest)
|
||||
verify(eip155.hasOwnProperty("chains"))
|
||||
let chains = eip155.chains
|
||||
verify(chains.length === 2)
|
||||
verify(chains[0] === "eip155:1")
|
||||
verify(chains[1] === "eip155:2")
|
||||
|
||||
verify(!popup.opened)
|
||||
verify(eip155.hasOwnProperty("accounts"))
|
||||
let accounts = eip155.accounts
|
||||
verify(accounts.length === 4)
|
||||
for (let chainI = 0; chainI < chainsModel.count; chainI++) {
|
||||
for (let accountI = 0; accountI < chainsModel.count; accountI++) {
|
||||
var found = false
|
||||
for (let entry of accounts) {
|
||||
if(entry === `eip155:${chainsModel.get(chainI).chainId}:${accountsModel.get(accountI).address}`) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
verify(found, `found ${accountsModel.get(accountI).address} for chain ${chainsModel.get(chainI).chainId}`)
|
||||
}
|
||||
}
|
||||
|
||||
verify(eip155.hasOwnProperty("methods"))
|
||||
verify(eip155.methods.length > 0)
|
||||
verify(eip155.hasOwnProperty("events"))
|
||||
verify(eip155.events.length > 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,12 +76,12 @@ QtObject {
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
chainId: 5,
|
||||
chainName: "Goerli",
|
||||
blockExplorerUrl: "https://goerli.etherscan.io/",
|
||||
iconUrl: "network/Network=Testnet",
|
||||
chainColor: "#939BA1",
|
||||
shortName: "goEth",
|
||||
chainId: 11155111,
|
||||
chainName: "Sepolia Mainnet",
|
||||
blockExplorerUrl: "https://sepolia.etherscan.io/",
|
||||
iconUrl: "network/Network=Ethereum",
|
||||
chainColor: "#627EEA",
|
||||
shortName: "eth",
|
||||
nativeCurrencyName: "Ether",
|
||||
nativeCurrencySymbol: "ETH",
|
||||
nativeCurrencyDecimals: 18,
|
||||
|
@ -1,6 +1,4 @@
|
||||
import QtQuick 2.14
|
||||
|
||||
QtObject {
|
||||
property var accountSensitiveSettings: ({})
|
||||
property var dappList: []
|
||||
}
|
||||
|
@ -1,9 +1,3 @@
|
||||
import QtQuick 2.15
|
||||
|
||||
import AppLayouts.Wallet.services.dapps 1.0
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
required property WalletConnectSDK wCSDK
|
||||
}
|
||||
QtObject {}
|
||||
|
@ -5,12 +5,17 @@ import QtQuick.Layouts 1.15
|
||||
import AppLayouts.Wallet.controls 1.0
|
||||
|
||||
import shared.popups.walletconnect 1.0
|
||||
import AppLayouts.Wallet.services.dapps 1.0
|
||||
|
||||
import shared.stores 1.0
|
||||
|
||||
ConnectedDappsButton {
|
||||
id: root
|
||||
|
||||
required property WalletConnectService wcService
|
||||
|
||||
signal dAppsListReady()
|
||||
signal connectDappReady()
|
||||
signal pairWCReady()
|
||||
|
||||
onClicked: {
|
||||
dappsListLoader.active = true
|
||||
@ -19,23 +24,23 @@ ConnectedDappsButton {
|
||||
highlighted: dappsListLoader.active
|
||||
|
||||
Loader {
|
||||
id: connectDappLoader
|
||||
id: pairWCLoader
|
||||
|
||||
active: false
|
||||
|
||||
onLoaded: {
|
||||
item.open()
|
||||
root.connectDappReady()
|
||||
root.pairWCReady()
|
||||
}
|
||||
|
||||
sourceComponent: ConnectDappModal {
|
||||
sourceComponent: PairWCModal {
|
||||
visible: true
|
||||
|
||||
onClosed: connectDappLoader.active = false
|
||||
onClosed: pairWCLoader.active = false
|
||||
|
||||
onPair: (uri) => {
|
||||
this.close()
|
||||
console.debug(`TODO(#14556): ConnectionRequestDappModal with ${uri}`)
|
||||
root.wcService.pair(uri)
|
||||
this.isPairing = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,8 +58,8 @@ ConnectedDappsButton {
|
||||
sourceComponent: DAppsListPopup {
|
||||
visible: true
|
||||
|
||||
onConnectDapp: {
|
||||
connectDappLoader.active = true
|
||||
onPairWCDapp: {
|
||||
pairWCLoader.active = true
|
||||
this.close()
|
||||
}
|
||||
onOpened: {
|
||||
@ -64,4 +69,72 @@ ConnectedDappsButton {
|
||||
onClosed: dappsListLoader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: connectDappLoader
|
||||
|
||||
active: false
|
||||
|
||||
onLoaded: item.openWithFilter(dappChains, sessionProposal.params.proposer)
|
||||
|
||||
property var dappChains: []
|
||||
property var sessionProposal: null
|
||||
property var availableNamespaces: null
|
||||
property var sessionTopic: null
|
||||
|
||||
sourceComponent: ConnectDAppModal {
|
||||
visible: true
|
||||
|
||||
onClosed: connectDappLoader.active = false
|
||||
accounts: wcService.validAccounts
|
||||
flatNetworks: wcService.flatNetworks
|
||||
|
||||
onConnect: {
|
||||
root.wcService.approvePairSession(sessionProposal, dappChains, selectedAccount)
|
||||
}
|
||||
|
||||
onDecline: {
|
||||
connectDappLoader.active = false
|
||||
root.wcService.rejectPairSession(sessionProposal.id)
|
||||
}
|
||||
|
||||
onDisconnect: {
|
||||
connectDappLoader.active = false
|
||||
root.wcService.disconnectDapp(sessionTopic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.wcService
|
||||
|
||||
function onConnectDApp(dappChains, sessionProposal, availableNamespaces) {
|
||||
connectDappLoader.dappChains = dappChains
|
||||
connectDappLoader.sessionProposal = sessionProposal
|
||||
connectDappLoader.availableNamespaces = availableNamespaces
|
||||
connectDappLoader.sessionTopic = null
|
||||
|
||||
if (pairWCLoader.item) {
|
||||
pairWCLoader.item.close()
|
||||
}
|
||||
|
||||
connectDappLoader.active = true
|
||||
}
|
||||
|
||||
function onApproveSessionResult(session, err) {
|
||||
connectDappLoader.dappChains = []
|
||||
connectDappLoader.sessionProposal = null
|
||||
connectDappLoader.availableNamespaces = null
|
||||
connectDappLoader.sessionTopic = session.topic
|
||||
|
||||
let modal = connectDappLoader.item
|
||||
if (!!modal) {
|
||||
if (err) {
|
||||
modal.pairFailed(session, err)
|
||||
} else {
|
||||
modal.pairSuccessful(session)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,9 @@ Item {
|
||||
spacing: 8
|
||||
|
||||
visible: !root.walletStore.showSavedAddresses && Global.featureFlags.dappsEnabled
|
||||
enabled: !!Global.walletConnectService
|
||||
|
||||
wcService: Global.walletConnectService
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
|
@ -27,9 +27,11 @@ Item {
|
||||
|
||||
signal statusChanged(string message)
|
||||
signal sdkInit(bool success, var result)
|
||||
signal pairResponse(bool success)
|
||||
signal sessionProposal(var sessionProposal)
|
||||
signal sessionProposalExpired()
|
||||
signal approveSessionResult(var session, string error)
|
||||
signal buildApprovedNamespacesResult(var session, string error)
|
||||
signal approveSessionResult(var approvedNamespaces, string error)
|
||||
signal rejectSessionResult(string error)
|
||||
signal sessionRequestEvent(var sessionRequest)
|
||||
signal sessionRequestUserAnswerResult(bool accept, string error)
|
||||
@ -40,6 +42,8 @@ Item {
|
||||
|
||||
signal sessionDelete(var topic, string error)
|
||||
|
||||
/// Generates \c pairResponse signal and expects to receive
|
||||
/// a \c sessionProposal signal with the sessionProposal object
|
||||
function pair(pairLink) {
|
||||
wcCalls.pair(pairLink)
|
||||
}
|
||||
@ -64,6 +68,10 @@ Item {
|
||||
wcCalls.ping(topic)
|
||||
}
|
||||
|
||||
function buildApprovedNamespaces(params, supportedNamespaces) {
|
||||
wcCalls.buildApprovedNamespaces(params, supportedNamespaces)
|
||||
}
|
||||
|
||||
function approveSession(sessionProposal, supportedNamespaces) {
|
||||
wcCalls.approveSession(sessionProposal, supportedNamespaces)
|
||||
}
|
||||
@ -227,6 +235,21 @@ Item {
|
||||
)
|
||||
}
|
||||
|
||||
function buildApprovedNamespaces(params, supportedNamespaces) {
|
||||
console.debug(`WC WalletConnectSDK.wcCall.buildApprovedNamespaces; params: ${JSON.stringify(params)}, supportedNamespaces: ${JSON.stringify(supportedNamespaces)}`)
|
||||
|
||||
d.engine.runJavaScript(`
|
||||
wc.buildApprovedNamespaces(${JSON.stringify(params)}, ${JSON.stringify(supportedNamespaces)})
|
||||
.then((approvedNamespaces) => {
|
||||
wc.statusObject.onBuildApprovedNamespacesResponse(approvedNamespaces, "")
|
||||
})
|
||||
.catch((e) => {
|
||||
wc.statusObject.onBuildApprovedNamespacesResponse("", e.message)
|
||||
})
|
||||
`
|
||||
)
|
||||
}
|
||||
|
||||
function approveSession(sessionProposal, supportedNamespaces) {
|
||||
console.debug(`WC WalletConnectSDK.wcCall.approveSession; sessionProposal: ${JSON.stringify(sessionProposal)}, supportedNamespaces: ${JSON.stringify(supportedNamespaces)}`)
|
||||
|
||||
@ -413,6 +436,7 @@ Item {
|
||||
|
||||
function onPairResponse(error) {
|
||||
console.debug(`WC WalletConnectSDK.onPairResponse; error: ${error}`)
|
||||
root.pairResponse(error == "")
|
||||
}
|
||||
|
||||
function onPingResponse(error) {
|
||||
@ -430,6 +454,11 @@ Item {
|
||||
d.resetPairingsModel()
|
||||
}
|
||||
|
||||
function onBuildApprovedNamespacesResponse(approvedNamespaces, error) {
|
||||
console.debug(`WC WalletConnectSDK.onBuildApprovedNamespacesResponse; approvedNamespaces: ${approvedNamespaces ? JSON.stringify(approvedNamespaces, null, 2) : "-"}, error: ${error}`)
|
||||
root.buildApprovedNamespacesResult(approvedNamespaces, error)
|
||||
}
|
||||
|
||||
function onApproveSessionResponse(session, error) {
|
||||
console.debug(`WC WalletConnectSDK.onApproveSessionResponse; sessionTopic: ${JSON.stringify(session, null, 2)}, error: ${error}`)
|
||||
d.resetPairingsModel()
|
||||
|
102
ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml
Normal file
102
ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml
Normal file
@ -0,0 +1,102 @@
|
||||
import QtQuick 2.15
|
||||
|
||||
import AppLayouts.Wallet.services.dapps 1.0
|
||||
import AppLayouts.Profile.stores 1.0
|
||||
import shared.stores 1.0
|
||||
import shared.popups.walletconnect 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
import utils 1.0
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
required property WalletConnectSDK wcSDK
|
||||
required property DAppsStore dappsStore
|
||||
required property WalletStore walletStore
|
||||
|
||||
readonly property var validAccounts: SortFilterProxyModel {
|
||||
sourceModel: walletStore.accounts
|
||||
filters: ValueFilter {
|
||||
roleName: "walletType"
|
||||
value: Constants.watchWalletType
|
||||
inverted: true
|
||||
}
|
||||
}
|
||||
readonly property var flatNetworks: walletStore.flatNetworks
|
||||
|
||||
function pair(uri) {
|
||||
_d.acceptedSessionProposal = null
|
||||
wcSDK.pair(uri)
|
||||
}
|
||||
|
||||
function approvePairSession(sessionProposal, approvedChainIds, approvedAccount) {
|
||||
_d.acceptedSessionProposal = sessionProposal
|
||||
let approvedNamespaces = JSON.parse(Helpers.buildSupportedNamespaces(approvedChainIds, [approvedAccount.address]))
|
||||
wcSDK.buildApprovedNamespaces(sessionProposal.params, approvedNamespaces)
|
||||
}
|
||||
|
||||
function rejectPairSession(id) {
|
||||
wcSDK.rejectSession(id)
|
||||
}
|
||||
|
||||
function disconnectDapp(sessionTopic) {
|
||||
wcSDK.disconnectSession(sessionTopic)
|
||||
}
|
||||
|
||||
signal connectDApp(var dappChains, var sessionProposal, var approvedNamespaces)
|
||||
signal approveSessionResult(var session, var error)
|
||||
|
||||
readonly property Connections sdkConnections: Connections {
|
||||
target: wcSDK
|
||||
|
||||
function onSessionProposal(sessionProposal) {
|
||||
_d.currentSessionProposal = sessionProposal
|
||||
|
||||
let supportedNamespacesStr = Helpers.buildSupportedNamespacesFromModels(root.flatNetworks, root.validAccounts)
|
||||
wcSDK.buildApprovedNamespaces(sessionProposal.params, JSON.parse(supportedNamespacesStr))
|
||||
}
|
||||
|
||||
function onBuildApprovedNamespacesResult(approvedNamespaces, error) {
|
||||
if(error) {
|
||||
// TODO: error reporting
|
||||
return
|
||||
}
|
||||
|
||||
if (_d.acceptedSessionProposal) {
|
||||
wcSDK.approveSession(_d.acceptedSessionProposal, approvedNamespaces)
|
||||
} else {
|
||||
let res = Helpers.extractChainsAndAccountsFromApprovedNamespaces(approvedNamespaces)
|
||||
|
||||
root.connectDApp(res.chains, _d.currentSessionProposal, approvedNamespaces)
|
||||
}
|
||||
}
|
||||
|
||||
function onApproveSessionResult(session, err) {
|
||||
root.approveSessionResult(session, err)
|
||||
}
|
||||
|
||||
function onRejectSessionResult(err) {
|
||||
let app_url = _d.currentSessionProposal ? _d.currentSessionProposal.params.proposer.url : "-"
|
||||
if(err) {
|
||||
console.debug(`TODO #14556: show a notification "Failed to reject connection request for ${app_url}"`)
|
||||
} else {
|
||||
console.debug(`TODO #14556: show a notification "Connection request for ${app_url} was rejected"`)
|
||||
}
|
||||
}
|
||||
|
||||
function onSessionDelete(topic, error) {
|
||||
let app_url = _d.currentSessionProposal ? _d.currentSessionProposal.params.proposer.url : "-"
|
||||
if(error) {
|
||||
console.debug(`TODO #14556: show a notification "Failed to disconnect from ${app_url}"`)
|
||||
} else {
|
||||
console.debug(`TODO #14556: show a notification "Disconnected from ${app_url}"`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property QtObject _d: QtObject {
|
||||
property var currentSessionProposal: null
|
||||
property var acceptedSessionProposal: null
|
||||
}
|
||||
}
|
39
ui/app/AppLayouts/Wallet/services/dapps/helpers.js
Normal file
39
ui/app/AppLayouts/Wallet/services/dapps/helpers.js
Normal file
@ -0,0 +1,39 @@
|
||||
.import StatusQ.Core.Utils 0.1 as SQUtils
|
||||
|
||||
function extractChainsAndAccountsFromApprovedNamespaces(approvedNamespaces) {
|
||||
const eip155Data = approvedNamespaces.eip155;
|
||||
const chains = eip155Data.chains.map(chain => parseInt(chain.split(':').pop().trim(), 10));
|
||||
const accountSet = new Set(
|
||||
eip155Data.accounts.map(account => account.split(':').pop().trim())
|
||||
);
|
||||
const uniqueAccounts = Array.from(accountSet);
|
||||
return { chains, accounts: uniqueAccounts };
|
||||
}
|
||||
|
||||
function buildSupportedNamespacesFromModels(chainsModel, accountsModel) {
|
||||
var chainIds = []
|
||||
var addresses = []
|
||||
for (let i = 0; i < chainsModel.count; i++) {
|
||||
let entry = SQUtils.ModelUtils.get(chainsModel, i)
|
||||
chainIds.push(parseInt(entry.chainId))
|
||||
}
|
||||
for (let i = 0; i < accountsModel.count; i++) {
|
||||
let entry = SQUtils.ModelUtils.get(accountsModel, i)
|
||||
addresses.push(entry.address)
|
||||
}
|
||||
return buildSupportedNamespaces(chainIds, addresses)
|
||||
}
|
||||
|
||||
function buildSupportedNamespaces(chainIds, addresses) {
|
||||
var eipChainIds = []
|
||||
var eipAddresses = []
|
||||
for (let i = 0; i < chainIds.length; i++) {
|
||||
let chainId = chainIds[i]
|
||||
eipChainIds.push(`"eip155:${chainId}"`)
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
eipAddresses.push(`"eip155:${chainId}:${addresses[i]}"`)
|
||||
}
|
||||
}
|
||||
return `{
|
||||
"eip155":{"chains": [${eipChainIds.join(',')}],"methods": ["eth_sendTransaction", "personal_sign"],"events": ["accountsChanged", "chainChanged"],"accounts": [${eipAddresses.join(',')}]}}`
|
||||
}
|
@ -1 +1,4 @@
|
||||
WalletConnectSDK 1.0 WalletConnectSDK.qml
|
||||
WalletConnectSDK 1.0 WalletConnectSDK.qml
|
||||
WalletConnectService 1.0 WalletConnectService.qml
|
||||
|
||||
Helpers 1.0 helpers.js
|
@ -17,7 +17,7 @@ Install dependencies steps by executing commands in this directory:
|
||||
- or to update to the latest run `ncu -u; npm install` in here
|
||||
- run `npm install -g npm-check-updates` for `ncu` command
|
||||
- these commands will also create or update a `package-lock.json` file and populate the `node_modules` directory
|
||||
- update the [`bundle.js`](./dist/main.js) file by running `npm run build`
|
||||
- update the [`bundle.js`](./generated/bundle.js) file by running `npm run build`
|
||||
- the result will be embedded with the app and loaded by [`WalletConnectSDK.qml`](../WalletConnectSDK.qml) component
|
||||
- add the newly generated files to index `git add --update .` to include in the commit
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -129,18 +129,18 @@ window.wc = {
|
||||
await window.wc.web3wallet.engine.signClient.ping({ topic });
|
||||
},
|
||||
|
||||
approveSession: async function (sessionProposal, supportedNamespaces) {
|
||||
buildApprovedNamespaces: async function (params, supportedNamespaces) {
|
||||
return buildApprovedNamespaces({
|
||||
proposal: params,
|
||||
supportedNamespaces: supportedNamespaces,
|
||||
});
|
||||
},
|
||||
|
||||
approveSession: async function (sessionProposal, approvedNamespaces) {
|
||||
const { id, params } = sessionProposal;
|
||||
|
||||
const { relays } = params
|
||||
|
||||
const approvedNamespaces = buildApprovedNamespaces(
|
||||
{
|
||||
proposal: params,
|
||||
supportedNamespaces: supportedNamespaces,
|
||||
}
|
||||
);
|
||||
|
||||
return await window.wc.web3wallet.approveSession(
|
||||
{
|
||||
id,
|
||||
|
@ -78,18 +78,6 @@ Item {
|
||||
walletAssetStore: appMain.walletAssetsStore
|
||||
tokensStore: appMain.tokensStore
|
||||
}
|
||||
readonly property DAppsStore dappsStore: DAppsStore {
|
||||
wCSDK: WalletConnectSDK {
|
||||
active: WalletStore.RootStore.walletSectionInst.walletReady
|
||||
|
||||
projectId: WalletStore.RootStore.appSettings.walletConnectProjectID
|
||||
|
||||
onSessionRequestEvent: (details) => {
|
||||
// TODO #14556
|
||||
console.debug(`@dd onSessionRequestEvent: ${JSON.stringify(details)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set from main.qml
|
||||
property var sysPalette
|
||||
@ -2040,4 +2028,27 @@ Item {
|
||||
onClosed: userAgreementLoader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: walletConnectServiceLoader
|
||||
|
||||
active: Global.featureFlags.dappsEnabled
|
||||
|
||||
sourceComponent: WalletConnectService {
|
||||
id: walletConnectService
|
||||
|
||||
wcSDK: WalletConnectSDK {
|
||||
active: WalletStore.RootStore.walletSectionInst.walletReady
|
||||
|
||||
projectId: WalletStore.RootStore.appSettings.walletConnectProjectID
|
||||
}
|
||||
dappsStore: DAppsStore {
|
||||
}
|
||||
walletStore: appMain.rootStore.profileSectionStore.walletStore
|
||||
|
||||
Component.onCompleted: {
|
||||
Global.walletConnectService = walletConnectService
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
380
ui/imports/shared/popups/walletconnect/ConnectDAppModal.qml
Normal file
380
ui/imports/shared/popups/walletconnect/ConnectDAppModal.qml
Normal file
@ -0,0 +1,380 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml.Models 2.14
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import StatusQ 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
// TODO extract the components to StatusQ
|
||||
import shared.popups.send.controls 1.0
|
||||
|
||||
import AppLayouts.Wallet.controls 1.0
|
||||
|
||||
import utils 1.0
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
width: 480
|
||||
implicitHeight: d.connectionStatus === root.notConnectedStatus ? 633 : 681
|
||||
|
||||
required property var accounts
|
||||
required property var flatNetworks
|
||||
|
||||
readonly property alias selectedAccount: d.selectedAccount
|
||||
|
||||
readonly property int notConnectedStatus: 0
|
||||
readonly property int connectionSuccessfulStatus: 1
|
||||
readonly property int connectionFailedStatus: 2
|
||||
|
||||
function openWithFilter(dappChains, proposer) {
|
||||
d.connectionStatus = root.notConnectedStatus
|
||||
d.afterTwoSecondsFromStatus = false
|
||||
|
||||
let m = proposer.metadata
|
||||
dappCard.name = m.name
|
||||
dappCard.url = m.url
|
||||
if(m.icons.length > 0) {
|
||||
dappCard.icon = m.icons[0]
|
||||
}
|
||||
|
||||
d.dappChains.clear()
|
||||
for (let i = 0; i < dappChains.length; i++) {
|
||||
// Convert to int
|
||||
d.dappChains.append({ chainId: parseInt(dappChains[i]) })
|
||||
}
|
||||
|
||||
root.open()
|
||||
}
|
||||
|
||||
function pairSuccessful(session) {
|
||||
d.connectionStatus = root.connectionSuccessfulStatus
|
||||
closeAndRetryTimer.start()
|
||||
}
|
||||
function pairFailed(session, err) {
|
||||
d.connectionStatus = root.connectionFailedStatus
|
||||
closeAndRetryTimer.start()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeAndRetryTimer
|
||||
|
||||
interval: 2000
|
||||
running: false
|
||||
repeat: false
|
||||
|
||||
onTriggered: {
|
||||
d.afterTwoSecondsFromStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
signal connect()
|
||||
signal decline()
|
||||
signal disconnect()
|
||||
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
title: qsTr("Connection request")
|
||||
|
||||
padding: 20
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 20
|
||||
clip: true
|
||||
|
||||
DAppCard {
|
||||
id: dappCard
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.leftMargin: 12
|
||||
Layout.rightMargin: Layout.leftMargin
|
||||
Layout.topMargin: 20
|
||||
Layout.bottomMargin: Layout.topMargin
|
||||
}
|
||||
|
||||
ContextCard {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
PermissionsCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.leftMargin: 12
|
||||
Layout.rightMargin: Layout.leftMargin
|
||||
Layout.topMargin: 20
|
||||
Layout.bottomMargin: Layout.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
footer: StatusDialogFooter {
|
||||
id: footer
|
||||
rightButtons: ObjectModel {
|
||||
StatusButton {
|
||||
height: 44
|
||||
text: qsTr("Decline")
|
||||
|
||||
visible: d.connectionStatus === root.notConnectedStatus
|
||||
|
||||
onClicked: root.decline()
|
||||
}
|
||||
StatusButton {
|
||||
height: 44
|
||||
text: qsTr("Disconnect")
|
||||
|
||||
visible: d.connectionStatus === root.connectionSuccessfulStatus
|
||||
|
||||
type: StatusBaseButton.Type.Danger
|
||||
|
||||
onClicked: root.disconnect()
|
||||
}
|
||||
StatusButton {
|
||||
height: 44
|
||||
text: d.connectionStatus === root.notConnectedStatus
|
||||
? qsTr("Connect")
|
||||
: qsTr("Close")
|
||||
|
||||
onClicked: {
|
||||
if (d.connectionStatus === root.notConnectedStatus)
|
||||
root.connect()
|
||||
else
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ContextCard: Rectangle {
|
||||
id: contextCard
|
||||
|
||||
implicitWidth: contextLayout.implicitWidth
|
||||
implicitHeight: contextLayout.implicitHeight
|
||||
|
||||
radius: 8
|
||||
// TODO: the color matched the design color (grey4); It is also matching the intention or we should add some another color to the theme? (e.g. sectionBorder)?
|
||||
border.color: Theme.palette.baseColor2
|
||||
border.width: 1
|
||||
color: "transparent"
|
||||
|
||||
ColumnLayout {
|
||||
id: contextLayout
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
RowLayout {
|
||||
Layout.margins: 16
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Connect with")
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// TODO: have a reusable component for this
|
||||
AccountsModalHeader {
|
||||
id: accountsDropdown
|
||||
|
||||
Layout.preferredWidth: 204
|
||||
|
||||
control.enabled: d.connectionStatus === root.notConnectedStatus && count > 1
|
||||
model: d.accountsProxy
|
||||
|
||||
onCountChanged: {
|
||||
if (count > 0) {
|
||||
selectedAccount = d.accountsProxy.get(0)
|
||||
}
|
||||
}
|
||||
|
||||
selectedAccount: d.accountsProxy.get(0)
|
||||
onSelectedAccountChanged: d.selectedAccount = selectedAccount
|
||||
onSelectedIndexChanged: {
|
||||
d.selectedAccount = model.get(selectedIndex)
|
||||
selectedAccount = d.selectedAccount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: contextCard.border.color
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.margins: 16
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("On")
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// TODO: replace with a specialized network selection control
|
||||
NetworkFilter {
|
||||
Layout.preferredWidth: accountsDropdown.Layout.preferredWidth
|
||||
|
||||
flatNetworks: d.filteredChains
|
||||
showAllSelectedText: false
|
||||
showCheckboxes: false
|
||||
enabled: d.connectionStatus === root.notConnectedStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component DAppCard: ColumnLayout {
|
||||
property alias name: appNameText.text
|
||||
property alias url: appUrlText.text
|
||||
property alias icon: iconDisplay.asset.source
|
||||
|
||||
// TODO: this doesn't work as expected, the icon is not displayed properly
|
||||
// TODO: set a fallback icon for when the provided icon is not available
|
||||
StatusRoundIcon {
|
||||
id: iconDisplay
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
width: 72
|
||||
height: 72
|
||||
|
||||
asset.width: width
|
||||
asset.height: height
|
||||
asset.color: "transparent"
|
||||
asset.bgColor: "transparent"
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: appNameText
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: 4
|
||||
|
||||
font.bold: true
|
||||
font.pixelSize: 17
|
||||
}
|
||||
|
||||
// TODO replace with the proper URL control
|
||||
StatusLinkText {
|
||||
id: appUrlText
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: pairingStatusLayout.implicitWidth + 32
|
||||
Layout.preferredHeight: pairingStatusLayout.implicitHeight + 14
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 16
|
||||
|
||||
visible: d.connectionStatus !== root.notConnectedStatus
|
||||
|
||||
color: d.connectionStatus === root.connectionSuccessfulStatus
|
||||
? d.afterTwoSecondsFromStatus
|
||||
? Theme.palette.successColor2
|
||||
: Theme.palette.successColor3
|
||||
: d.afterTwoSecondsFromStatus
|
||||
? "transparent"
|
||||
: Theme.palette.dangerColor3
|
||||
border.color: d.connectionStatus === root.connectionSuccessfulStatus
|
||||
? Theme.palette.successColor2
|
||||
: Theme.palette.dangerColor2
|
||||
border.width: 1
|
||||
radius: height / 2
|
||||
|
||||
RowLayout {
|
||||
id: pairingStatusLayout
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
spacing: 8
|
||||
|
||||
Rectangle {
|
||||
width: 6
|
||||
height: 6
|
||||
radius: width / 2
|
||||
|
||||
visible: d.connectionStatus === root.connectionSuccessfulStatus
|
||||
color: Theme.palette.successColor1
|
||||
}
|
||||
|
||||
StatusIcon {
|
||||
Layout.preferredWidth: 16
|
||||
Layout.preferredHeight: 16
|
||||
|
||||
visible: d.connectionStatus !== root.connectionSuccessfulStatus
|
||||
|
||||
color: Theme.palette.dangerColor1
|
||||
icon: "warning"
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
text: {
|
||||
if (d.connectionStatus === root.connectionSuccessfulStatus)
|
||||
return qsTr("Connected. You can now go back to the dApp.")
|
||||
else if (d.connectionStatus === root.connectionFailedStatus)
|
||||
return qsTr("Error connecting to dApp. Close and try again")
|
||||
return ""
|
||||
}
|
||||
|
||||
font.pixelSize: 12
|
||||
color: d.connectionStatus === root.connectionSuccessfulStatus ? Theme.palette.directColor1 : Theme.palette.dangerColor1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component PermissionsCard: ColumnLayout {
|
||||
spacing: 8
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Uniswap Interface will be able to:")
|
||||
|
||||
font.pixelSize: 13
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Check your account balance and activity")
|
||||
|
||||
font.pixelSize: 13
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Request transactions and message signing")
|
||||
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property SortFilterProxyModel accountsProxy: SortFilterProxyModel {
|
||||
sourceModel: root.accounts
|
||||
|
||||
sorters: RoleSorter { roleName: "position"; sortOrder: Qt.AscendingOrder }
|
||||
}
|
||||
|
||||
property var selectedAccount: accountsProxy.count > 0 ? accountsProxy.get(0) : null
|
||||
|
||||
readonly property var filteredChains: LeftJoinModel {
|
||||
leftModel: d.dappChains
|
||||
rightModel: root.flatNetworks
|
||||
|
||||
joinRole: "chainId"
|
||||
}
|
||||
|
||||
readonly property var dappChains: ListModel {}
|
||||
|
||||
property int connectionStatus: notConnectedStatus
|
||||
property bool afterTwoSecondsFromStatus: false
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ Popup {
|
||||
|
||||
property int menuWidth: 312
|
||||
|
||||
signal connectDapp()
|
||||
signal pairWCDapp()
|
||||
|
||||
contentWidth: root.menuWidth
|
||||
contentHeight: list.height
|
||||
@ -60,7 +60,7 @@ Popup {
|
||||
|
||||
text: qsTr("Connect a dApp via WalletConnect")
|
||||
onClicked: {
|
||||
root.connectDapp()
|
||||
root.pairWCDapp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,16 +9,18 @@ import StatusQ.Controls 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import "ConnectDappModal"
|
||||
import "PairWCModal"
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
signal pair(string uri)
|
||||
|
||||
width: 480
|
||||
implicitHeight: 633
|
||||
|
||||
property bool isPairing: false
|
||||
|
||||
signal pair(string uri)
|
||||
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
title: qsTr("Connect a dApp via WalletConnect")
|
||||
@ -32,10 +34,12 @@ StatusDialog {
|
||||
|
||||
WCUriInput {
|
||||
id: uriInput
|
||||
|
||||
onTextChanged: root.isPairing = false
|
||||
}
|
||||
|
||||
// Spacer
|
||||
ColumnLayout {}
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
StatusLinkText {
|
||||
text: qsTr("How to copy the dApp URI")
|
||||
@ -58,7 +62,7 @@ StatusDialog {
|
||||
height: 44
|
||||
text: qsTr("Done")
|
||||
|
||||
enabled: uriInput.valid && uriInput.text.length > 0
|
||||
enabled: uriInput.valid && !root.isPairing && uriInput.text.length > 0
|
||||
|
||||
onClicked: root.pair(uriInput.text)
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
ConnectDappModal 1.0 ConnectDappModal.qml
|
||||
DAppsListPopup 1.0 DAppsListPopup.qml
|
||||
PairWCModal 1.0 PairWCModal.qml
|
||||
DAppsListPopup 1.0 DAppsListPopup.qml
|
||||
ConnectDAppModal 1.0 ConnectDAppModal.qml
|
@ -1,11 +1,3 @@
|
||||
import QtQuick 2.15
|
||||
|
||||
import AppLayouts.Wallet.services.dapps 1.0
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
required property WalletConnectSDK wCSDK
|
||||
|
||||
// Here we will have business logic calls and expose connections history models
|
||||
}
|
||||
QtObject {}
|
@ -14,6 +14,9 @@ QtObject {
|
||||
property var userProfile
|
||||
property bool appIsReady: false
|
||||
|
||||
// use the generic var as type to break the cyclic dependency
|
||||
property var walletConnectService: null
|
||||
|
||||
// avoid lookup of context property in QML
|
||||
readonly property var featureFlags: featureFlagsRootContextProperty
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user