mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-23 03:58:49 +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 {
|
store: ProfileSectionStore {
|
||||||
property WalletStore walletStore: WalletStore {
|
property WalletStore walletStore: WalletStore {
|
||||||
accountSensitiveSettings: mockData.accountSettings
|
property var accountSensitiveSettings: mockData.accountSettings
|
||||||
dappList: dappsModel
|
property var dappList: dappsModel
|
||||||
|
|
||||||
function disconnect(dappName) {
|
function disconnect(dappName) {
|
||||||
for (let i = 0; i < dappsModel.count; i++) {
|
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.Components 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
import StatusQ.Popups.Dialog 0.1
|
import StatusQ.Popups.Dialog 0.1
|
||||||
|
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||||
|
|
||||||
import Models 1.0
|
import Models 1.0
|
||||||
import Storybook 1.0
|
import Storybook 1.0
|
||||||
@ -21,6 +22,7 @@ import AppLayouts.Wallet.services.dapps 1.0
|
|||||||
import SortFilterProxyModel 0.2
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
import AppLayouts.Wallet.panels 1.0
|
import AppLayouts.Wallet.panels 1.0
|
||||||
|
import AppLayouts.Profile.stores 1.0
|
||||||
|
|
||||||
import utils 1.0
|
import utils 1.0
|
||||||
import shared.stores 1.0
|
import shared.stores 1.0
|
||||||
@ -49,6 +51,8 @@ Item {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
|
wcService: walletConnectService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ColumnLayout {}
|
ColumnLayout {}
|
||||||
@ -62,11 +66,20 @@ Item {
|
|||||||
Text {
|
Text {
|
||||||
id: projectIdText
|
id: projectIdText
|
||||||
readonly property string projectId: SystemUtils.getEnvVar("WALLET_CONNECT_PROJECT_ID")
|
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
|
font.bold: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
|
||||||
|
text: "Testnet Mode"
|
||||||
|
checked: settings.testNetworks
|
||||||
|
onCheckedChanged: {
|
||||||
|
settings.testNetworks = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// spacer
|
// spacer
|
||||||
ColumnLayout {}
|
ColumnLayout {}
|
||||||
|
|
||||||
@ -87,7 +100,7 @@ Item {
|
|||||||
|
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: openPairCheckBox
|
|
||||||
text: "Open Pair"
|
text: "Open Pair"
|
||||||
checked: settings.openPair
|
checked: settings.openPair
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
@ -96,10 +109,11 @@ Item {
|
|||||||
d.startPairing()
|
d.startPairing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: dappsWorkflow
|
target: dappsWorkflow
|
||||||
|
|
||||||
// Open Pairing workflow if selected in the side bar
|
// If Open Pair workflow if selected in the side bar
|
||||||
function onDAppsListReady() {
|
function onDAppsListReady() {
|
||||||
if (!d.startPairingWorkflowActive)
|
if (!d.startPairingWorkflowActive)
|
||||||
return
|
return
|
||||||
@ -113,7 +127,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onConnectDappReady() {
|
function onPairWCReady() {
|
||||||
if (!d.startPairingWorkflowActive)
|
if (!d.startPairingWorkflowActive)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -121,25 +135,52 @@ Item {
|
|||||||
let items = InspectionUtils.findVisualsByTypeName(dappsWorkflow, "StatusBaseInput")
|
let items = InspectionUtils.findVisualsByTypeName(dappsWorkflow, "StatusBaseInput")
|
||||||
if (items.length === 1) {
|
if (items.length === 1) {
|
||||||
items[0].text = pairUriInput.text
|
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 {
|
WalletConnectService {
|
||||||
wCSDK: WalletConnectSDK {
|
id: walletConnectService
|
||||||
|
|
||||||
|
wcSDK: WalletConnectSDK {
|
||||||
active: true
|
active: true
|
||||||
|
|
||||||
projectId: projectIdText.projectId
|
projectId: projectIdText.projectId
|
||||||
|
}
|
||||||
|
|
||||||
onSessionRequestEvent: (details) => {
|
dappsStore: DAppsStore {
|
||||||
// TODO #14556
|
}
|
||||||
console.debug(`@dd onSessionRequestEvent: ${JSON.stringify(details)}`)
|
|
||||||
|
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
|
property bool startPairingWorkflowActive: false
|
||||||
|
|
||||||
function startPairing() {
|
function startPairing() {
|
||||||
startPairingWorkflowActive = true
|
d.startPairingWorkflowActive = true
|
||||||
if(root.visible) {
|
if(root.visible) {
|
||||||
dappsWorkflow.clicked()
|
dappsWorkflow.clicked()
|
||||||
}
|
}
|
||||||
@ -168,6 +209,7 @@ Item {
|
|||||||
|
|
||||||
property bool openPair: false
|
property bool openPair: false
|
||||||
property string pairUri: ""
|
property string pairUri: ""
|
||||||
|
property bool testNetworks: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +98,10 @@ SplitView {
|
|||||||
}
|
}
|
||||||
return prefChains
|
return prefChains
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addressWasShown(account) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,44 +7,128 @@ import QtQuick.Controls 2.15
|
|||||||
|
|
||||||
import Storybook 1.0
|
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 {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
width: 600
|
width: 600
|
||||||
height: 400
|
height: 400
|
||||||
|
|
||||||
Component {
|
// TODO: mock WalletConnectSDK
|
||||||
id: componentUnderTest
|
// Component {
|
||||||
DAppsWorkflow {
|
// 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 {
|
TestCase {
|
||||||
name: "DAppsWorkflow"
|
name: "ServiceHelpers"
|
||||||
when: windowShown
|
|
||||||
|
|
||||||
property DAppsWorkflow controlUnderTest: null
|
function test_extractChainsAndAccountsFromApprovedNamespaces() {
|
||||||
|
let res = Helpers.extractChainsAndAccountsFromApprovedNamespaces(JSON.parse(`{
|
||||||
function init() {
|
"eip155": {
|
||||||
controlUnderTest = createTemporaryObject(componentUnderTest, root)
|
"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() {
|
readonly property ListModel chainsModel: ListModel {
|
||||||
verify(!!controlUnderTest)
|
ListElement { chainId: 1 }
|
||||||
waitForRendering(controlUnderTest)
|
ListElement { chainId: 2 }
|
||||||
|
}
|
||||||
|
|
||||||
mouseClick(controlUnderTest, Qt.LeftButton)
|
readonly property ListModel accountsModel: ListModel {
|
||||||
waitForRendering(controlUnderTest)
|
ListElement { address: "0x1" }
|
||||||
|
ListElement { address: "0x2" }
|
||||||
|
}
|
||||||
|
|
||||||
let popup = findChild(controlUnderTest, "dappsPopup")
|
function test_buildSupportedNamespacesFromModels() {
|
||||||
verify(!!popup)
|
let resStr = Helpers.buildSupportedNamespacesFromModels(chainsModel, accountsModel)
|
||||||
verify(popup.opened)
|
let jsonObj = JSON.parse(resStr)
|
||||||
|
verify(jsonObj.hasOwnProperty("eip155"))
|
||||||
|
let eip155 = jsonObj.eip155
|
||||||
|
|
||||||
mouseClick(Overlay.overlay, Qt.LeftButton)
|
verify(eip155.hasOwnProperty("chains"))
|
||||||
waitForRendering(controlUnderTest)
|
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,
|
isEnabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
chainId: 5,
|
chainId: 11155111,
|
||||||
chainName: "Goerli",
|
chainName: "Sepolia Mainnet",
|
||||||
blockExplorerUrl: "https://goerli.etherscan.io/",
|
blockExplorerUrl: "https://sepolia.etherscan.io/",
|
||||||
iconUrl: "network/Network=Testnet",
|
iconUrl: "network/Network=Ethereum",
|
||||||
chainColor: "#939BA1",
|
chainColor: "#627EEA",
|
||||||
shortName: "goEth",
|
shortName: "eth",
|
||||||
nativeCurrencyName: "Ether",
|
nativeCurrencyName: "Ether",
|
||||||
nativeCurrencySymbol: "ETH",
|
nativeCurrencySymbol: "ETH",
|
||||||
nativeCurrencyDecimals: 18,
|
nativeCurrencyDecimals: 18,
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import QtQuick 2.14
|
import QtQuick 2.14
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
property var accountSensitiveSettings: ({})
|
|
||||||
property var dappList: []
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
|
|
||||||
import AppLayouts.Wallet.services.dapps 1.0
|
QtObject {}
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property WalletConnectSDK wCSDK
|
|
||||||
}
|
|
||||||
|
@ -5,12 +5,17 @@ import QtQuick.Layouts 1.15
|
|||||||
import AppLayouts.Wallet.controls 1.0
|
import AppLayouts.Wallet.controls 1.0
|
||||||
|
|
||||||
import shared.popups.walletconnect 1.0
|
import shared.popups.walletconnect 1.0
|
||||||
|
import AppLayouts.Wallet.services.dapps 1.0
|
||||||
|
|
||||||
|
import shared.stores 1.0
|
||||||
|
|
||||||
ConnectedDappsButton {
|
ConnectedDappsButton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
required property WalletConnectService wcService
|
||||||
|
|
||||||
signal dAppsListReady()
|
signal dAppsListReady()
|
||||||
signal connectDappReady()
|
signal pairWCReady()
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
dappsListLoader.active = true
|
dappsListLoader.active = true
|
||||||
@ -19,23 +24,23 @@ ConnectedDappsButton {
|
|||||||
highlighted: dappsListLoader.active
|
highlighted: dappsListLoader.active
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: connectDappLoader
|
id: pairWCLoader
|
||||||
|
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
item.open()
|
item.open()
|
||||||
root.connectDappReady()
|
root.pairWCReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceComponent: ConnectDappModal {
|
sourceComponent: PairWCModal {
|
||||||
visible: true
|
visible: true
|
||||||
|
|
||||||
onClosed: connectDappLoader.active = false
|
onClosed: pairWCLoader.active = false
|
||||||
|
|
||||||
onPair: (uri) => {
|
onPair: (uri) => {
|
||||||
this.close()
|
root.wcService.pair(uri)
|
||||||
console.debug(`TODO(#14556): ConnectionRequestDappModal with ${uri}`)
|
this.isPairing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,8 +58,8 @@ ConnectedDappsButton {
|
|||||||
sourceComponent: DAppsListPopup {
|
sourceComponent: DAppsListPopup {
|
||||||
visible: true
|
visible: true
|
||||||
|
|
||||||
onConnectDapp: {
|
onPairWCDapp: {
|
||||||
connectDappLoader.active = true
|
pairWCLoader.active = true
|
||||||
this.close()
|
this.close()
|
||||||
}
|
}
|
||||||
onOpened: {
|
onOpened: {
|
||||||
@ -64,4 +69,72 @@ ConnectedDappsButton {
|
|||||||
onClosed: dappsListLoader.active = false
|
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
|
spacing: 8
|
||||||
|
|
||||||
visible: !root.walletStore.showSavedAddresses && Global.featureFlags.dappsEnabled
|
visible: !root.walletStore.showSavedAddresses && Global.featureFlags.dappsEnabled
|
||||||
|
enabled: !!Global.walletConnectService
|
||||||
|
|
||||||
|
wcService: Global.walletConnectService
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusButton {
|
StatusButton {
|
||||||
|
@ -27,9 +27,11 @@ Item {
|
|||||||
|
|
||||||
signal statusChanged(string message)
|
signal statusChanged(string message)
|
||||||
signal sdkInit(bool success, var result)
|
signal sdkInit(bool success, var result)
|
||||||
|
signal pairResponse(bool success)
|
||||||
signal sessionProposal(var sessionProposal)
|
signal sessionProposal(var sessionProposal)
|
||||||
signal sessionProposalExpired()
|
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 rejectSessionResult(string error)
|
||||||
signal sessionRequestEvent(var sessionRequest)
|
signal sessionRequestEvent(var sessionRequest)
|
||||||
signal sessionRequestUserAnswerResult(bool accept, string error)
|
signal sessionRequestUserAnswerResult(bool accept, string error)
|
||||||
@ -40,6 +42,8 @@ Item {
|
|||||||
|
|
||||||
signal sessionDelete(var topic, string error)
|
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) {
|
function pair(pairLink) {
|
||||||
wcCalls.pair(pairLink)
|
wcCalls.pair(pairLink)
|
||||||
}
|
}
|
||||||
@ -64,6 +68,10 @@ Item {
|
|||||||
wcCalls.ping(topic)
|
wcCalls.ping(topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildApprovedNamespaces(params, supportedNamespaces) {
|
||||||
|
wcCalls.buildApprovedNamespaces(params, supportedNamespaces)
|
||||||
|
}
|
||||||
|
|
||||||
function approveSession(sessionProposal, supportedNamespaces) {
|
function approveSession(sessionProposal, supportedNamespaces) {
|
||||||
wcCalls.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) {
|
function approveSession(sessionProposal, supportedNamespaces) {
|
||||||
console.debug(`WC WalletConnectSDK.wcCall.approveSession; sessionProposal: ${JSON.stringify(sessionProposal)}, supportedNamespaces: ${JSON.stringify(supportedNamespaces)}`)
|
console.debug(`WC WalletConnectSDK.wcCall.approveSession; sessionProposal: ${JSON.stringify(sessionProposal)}, supportedNamespaces: ${JSON.stringify(supportedNamespaces)}`)
|
||||||
|
|
||||||
@ -413,6 +436,7 @@ Item {
|
|||||||
|
|
||||||
function onPairResponse(error) {
|
function onPairResponse(error) {
|
||||||
console.debug(`WC WalletConnectSDK.onPairResponse; error: ${error}`)
|
console.debug(`WC WalletConnectSDK.onPairResponse; error: ${error}`)
|
||||||
|
root.pairResponse(error == "")
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPingResponse(error) {
|
function onPingResponse(error) {
|
||||||
@ -430,6 +454,11 @@ Item {
|
|||||||
d.resetPairingsModel()
|
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) {
|
function onApproveSessionResponse(session, error) {
|
||||||
console.debug(`WC WalletConnectSDK.onApproveSessionResponse; sessionTopic: ${JSON.stringify(session, null, 2)}, error: ${error}`)
|
console.debug(`WC WalletConnectSDK.onApproveSessionResponse; sessionTopic: ${JSON.stringify(session, null, 2)}, error: ${error}`)
|
||||||
d.resetPairingsModel()
|
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
|
- or to update to the latest run `ncu -u; npm install` in here
|
||||||
- run `npm install -g npm-check-updates` for `ncu` command
|
- 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
|
- 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
|
- 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
|
- 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 });
|
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 { id, params } = sessionProposal;
|
||||||
|
|
||||||
const { relays } = params
|
const { relays } = params
|
||||||
|
|
||||||
const approvedNamespaces = buildApprovedNamespaces(
|
|
||||||
{
|
|
||||||
proposal: params,
|
|
||||||
supportedNamespaces: supportedNamespaces,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return await window.wc.web3wallet.approveSession(
|
return await window.wc.web3wallet.approveSession(
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
|
@ -78,18 +78,6 @@ Item {
|
|||||||
walletAssetStore: appMain.walletAssetsStore
|
walletAssetStore: appMain.walletAssetsStore
|
||||||
tokensStore: appMain.tokensStore
|
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
|
// set from main.qml
|
||||||
property var sysPalette
|
property var sysPalette
|
||||||
@ -2040,4 +2028,27 @@ Item {
|
|||||||
onClosed: userAgreementLoader.active = false
|
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
|
property int menuWidth: 312
|
||||||
|
|
||||||
signal connectDapp()
|
signal pairWCDapp()
|
||||||
|
|
||||||
contentWidth: root.menuWidth
|
contentWidth: root.menuWidth
|
||||||
contentHeight: list.height
|
contentHeight: list.height
|
||||||
@ -60,7 +60,7 @@ Popup {
|
|||||||
|
|
||||||
text: qsTr("Connect a dApp via WalletConnect")
|
text: qsTr("Connect a dApp via WalletConnect")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.connectDapp()
|
root.pairWCDapp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,18 @@ import StatusQ.Controls 0.1
|
|||||||
|
|
||||||
import utils 1.0
|
import utils 1.0
|
||||||
|
|
||||||
import "ConnectDappModal"
|
import "PairWCModal"
|
||||||
|
|
||||||
StatusDialog {
|
StatusDialog {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
signal pair(string uri)
|
|
||||||
|
|
||||||
width: 480
|
width: 480
|
||||||
implicitHeight: 633
|
implicitHeight: 633
|
||||||
|
|
||||||
|
property bool isPairing: false
|
||||||
|
|
||||||
|
signal pair(string uri)
|
||||||
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
|
|
||||||
title: qsTr("Connect a dApp via WalletConnect")
|
title: qsTr("Connect a dApp via WalletConnect")
|
||||||
@ -32,10 +34,12 @@ StatusDialog {
|
|||||||
|
|
||||||
WCUriInput {
|
WCUriInput {
|
||||||
id: uriInput
|
id: uriInput
|
||||||
|
|
||||||
|
onTextChanged: root.isPairing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spacer
|
// Spacer
|
||||||
ColumnLayout {}
|
Item { Layout.fillHeight: true }
|
||||||
|
|
||||||
StatusLinkText {
|
StatusLinkText {
|
||||||
text: qsTr("How to copy the dApp URI")
|
text: qsTr("How to copy the dApp URI")
|
||||||
@ -58,7 +62,7 @@ StatusDialog {
|
|||||||
height: 44
|
height: 44
|
||||||
text: qsTr("Done")
|
text: qsTr("Done")
|
||||||
|
|
||||||
enabled: uriInput.valid && uriInput.text.length > 0
|
enabled: uriInput.valid && !root.isPairing && uriInput.text.length > 0
|
||||||
|
|
||||||
onClicked: root.pair(uriInput.text)
|
onClicked: root.pair(uriInput.text)
|
||||||
}
|
}
|
@ -1,2 +1,3 @@
|
|||||||
ConnectDappModal 1.0 ConnectDappModal.qml
|
PairWCModal 1.0 PairWCModal.qml
|
||||||
DAppsListPopup 1.0 DAppsListPopup.qml
|
DAppsListPopup 1.0 DAppsListPopup.qml
|
||||||
|
ConnectDAppModal 1.0 ConnectDAppModal.qml
|
@ -1,11 +1,3 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
|
|
||||||
import AppLayouts.Wallet.services.dapps 1.0
|
QtObject {}
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property WalletConnectSDK wCSDK
|
|
||||||
|
|
||||||
// Here we will have business logic calls and expose connections history models
|
|
||||||
}
|
|
@ -14,6 +14,9 @@ QtObject {
|
|||||||
property var userProfile
|
property var userProfile
|
||||||
property bool appIsReady: false
|
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
|
// avoid lookup of context property in QML
|
||||||
readonly property var featureFlags: featureFlagsRootContextProperty
|
readonly property var featureFlags: featureFlagsRootContextProperty
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user