Connection fixes for v2.30.x (#15921)

* chore(dapps) remove the POC wallet connect

Updates: #15598

* fix(dapps) Wallet Connect internet connection reestablishing issue

Add a new NetworkChecker QObject to StatusQ to be used in checking
internet connection status. This is used by the WebEngineLoader
to only allow loading of web pages when there is an active internet
to cover for a corner case on MacOS where the internet connection is
not reestablished if the WebEngineView was loaded without an active
internet connection.

Closes: #15598, #15806

* chore(dapps) disable eth_signTransaction for Wallet Connect

Closes: #15661
This commit is contained in:
Stefan Dunca 2024-07-31 19:23:39 +02:00 committed by GitHub
parent ea0c4dfdca
commit 112a6f3003
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 192 additions and 1302 deletions

View File

@ -391,9 +391,6 @@ proc checkForStoringPasswordToKeychain(self: AppController) =
else:
self.keychainService.storeData(account.keyUid, self.startupModule.getPin())
proc chekForWalletConnectPairings(self: AppController) =
self.statusFoundation.events.emit(WALLET_CONNECT_CHECK_PAIRINGS, Args())
proc startupDidLoad*(self: AppController) =
singletonInstance.engine.setRootContextProperty("localAppSettings", self.localAppSettingsVariant)
singletonInstance.engine.setRootContextProperty("localAccountSettings", self.localAccountSettingsVariant)
@ -410,7 +407,6 @@ proc mainDidLoad*(self: AppController) =
self.applyNecessaryActionsAfterLoggingIn()
self.startupModule.moveToAppState()
self.checkForStoringPasswordToKeychain()
self.chekForWalletConnectPairings()
proc start*(self: AppController) =
self.keycardService.init()

View File

@ -48,6 +48,3 @@ type
addresses*: seq[string]
const MARK_WALLET_ADDRESSES_AS_SHOWN* = "markWalletAddressesAsShown"
const WALLET_CONNECT_CHECK_PAIRINGS* = "walletConnectCheckPairings"

View File

@ -1,262 +0,0 @@
################################################################################
# WalletConnect POC - to remove this file
################################################################################
import NimQml, strutils, json, chronicles
import backend/wallet as backend_wallet
import backend/poc_wallet_connect as backend_wallet_connect
import app/global/global_singleton
import app/global/app_signals
import app/core/eventemitter
import app/core/signals/types
import app/global/app_signals
import app_service/common/utils as common_utils
from app_service/service/transaction/dto import PendingTransactionTypeDto
import app_service/service/wallet_account/service as wallet_account_service
import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module
import constants
import tx_response_dto, helpers
const UNIQUE_WC_SESSION_REQUEST_SIGNING_IDENTIFIER* = "WalletConnect-SessionRequestSigning"
const UNIQUE_WC_AUTH_REQUEST_SIGNING_IDENTIFIER* = "WalletConnect-AuthRequestSigning"
logScope:
topics = "wallet-connect"
QtObject:
type
Controller* = ref object of QObject
events: EventEmitter
walletAccountService: wallet_account_service.Service
sessionRequestJson: JsonNode
txResponseDto: TxResponseDto
hasActivePairings: bool
## Forward declarations
proc invalidateData(self: Controller)
proc setHasActivePairings*(self: Controller, value: bool)
proc onDataSigned(self: Controller, keyUid: string, path: string, r: string, s: string, v: string, pin: string, identifier: string)
proc finishSessionRequest(self: Controller, signature: string)
proc finishAuthRequest(self: Controller, signature: string)
## signals
proc checkPairings*(self: Controller) {.signal.}
proc requestOpenWalletConnectPopup*(self: Controller, uri: string) {.signal.}
proc hasActivePairingsChanged*(self: Controller) {.signal.}
proc respondSessionProposal*(self: Controller, sessionProposalJson: string, supportedNamespacesJson: string, error: string) {.signal.}
proc respondSessionRequest*(self: Controller, sessionRequestJson: string, signedJson: string, error: bool) {.signal.}
proc respondAuthRequest*(self: Controller, signature: string, error: bool) {.signal.}
proc setup(self: Controller) =
self.QObject.setup
# Register for wallet events
self.events.on(SignalType.Wallet.event, proc(e: Args) =
# TODO #12434: async processing
discard
)
self.events.on(SIGNAL_STATUS_URL_ACTIVATED) do(e: Args):
var args = StatusUrlArgs(e)
let (found, wcUri) = extractAndCheckUriParameter(args.url)
if found:
self.requestOpenWalletConnectPopup(wcUri)
self.events.on(WALLET_CONNECT_CHECK_PAIRINGS) do(e: Args):
self.setHasActivePairings(true)
self.checkPairings()
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_DATA_SIGNED) do(e: Args):
let args = SharedKeycarModuleArgs(e)
if args.uniqueIdentifier != UNIQUE_WC_SESSION_REQUEST_SIGNING_IDENTIFIER and
args.uniqueIdentifier != UNIQUE_WC_AUTH_REQUEST_SIGNING_IDENTIFIER:
return
self.onDataSigned(args.keyUid, args.path, args.r, args.s, args.v, args.pin, args.uniqueIdentifier)
proc delete*(self: Controller) =
self.invalidateData()
self.QObject.delete
proc newController*(events: EventEmitter, walletAccountService: wallet_account_service.Service): Controller =
new(result, delete)
result.events = events
result.walletAccountService = walletAccountService
result.setup()
proc invalidateData(self: Controller) =
self.sessionRequestJson = nil
self.txResponseDto = nil
proc onDataSigned(self: Controller, keyUid: string, path: string, r: string, s: string, v: string, pin: string, identifier: string) =
if keyUid.len == 0 or path.len == 0 or r.len == 0 or s.len == 0 or v.len == 0 or pin.len == 0:
error "invalid data signed"
return
let signature = "0x" & r & s & v
if identifier == UNIQUE_WC_SESSION_REQUEST_SIGNING_IDENTIFIER:
self.finishSessionRequest(signature)
elif identifier == UNIQUE_WC_AUTH_REQUEST_SIGNING_IDENTIFIER:
self.finishAuthRequest(signature)
else:
error "Unknown identifier"
proc sessionProposal(self: Controller, sessionProposalJson: string) {.slot.} =
var
supportedNamespacesJson: string
error: string
try:
var res: JsonNode
let err = backend_wallet_connect.pair(res, sessionProposalJson)
if err.len > 0:
raise newException(CatchableError, err)
supportedNamespacesJson = if res.hasKey("supportedNamespaces"): $res["supportedNamespaces"] else: ""
except Exception as e:
error = e.msg
error "pairing", msg=error
self.respondSessionProposal(sessionProposalJson, supportedNamespacesJson, error)
proc saveOrUpdateSession(self: Controller, sessionJson: string) {.slot.} =
if not backend_wallet_connect.saveOrUpdateSession(sessionJson):
error "Failed to save/update session"
proc deleteSession(self: Controller, topic: string) {.slot.} =
if not backend_wallet_connect.deleteSession(topic):
error "Failed to delete session"
proc getHasActivePairings*(self: Controller): bool {.slot.} =
return self.hasActivePairings
proc setHasActivePairings*(self: Controller, value: bool) {.slot.} =
self.hasActivePairings = value
self.hasActivePairingsChanged()
QtProperty[bool] hasActivePairings:
read = getHasActivePairings
write = setHasActivePairings
notify = hasActivePairingsChanged
proc sendTransactionAndRespond(self: Controller, signature: string) =
let finalSignature = singletonInstance.utils.removeHexPrefix(signature)
var txResponse: JsonNode
let err = backend_wallet.sendTransactionWithSignature(txResponse, self.txResponseDto.chainId,
$PendingTransactionTypeDto.WalletConnectTransfer, $self.txResponseDto.txArgsJson, finalSignature)
if err.len > 0 or txResponse.isNil:
error "Failed to send tx"
return
let txHash = txResponse.getStr
self.respondSessionRequest($self.sessionRequestJson, txHash, false)
proc buildRawTransactionAndRespond(self: Controller, signature: string) =
let finalSignature = singletonInstance.utils.removeHexPrefix(signature)
var txResponse: JsonNode
let err = backend_wallet.buildRawTransaction(txResponse, self.txResponseDto.chainId, $self.txResponseDto.txArgsJson,
finalSignature)
if err.len > 0:
error "Failed to build raw tx"
return
let txResponseDto = txResponse.toTxResponseDto()
self.respondSessionRequest($self.sessionRequestJson, txResponseDto.rawTx, false)
proc finishSessionRequest(self: Controller, signature: string) =
if signature.len == 0:
self.respondSessionRequest($self.sessionRequestJson, "", true)
return
let requestMethod = getRequestMethod(self.sessionRequestJson)
if requestMethod == RequestMethod.SendTransaction:
self.sendTransactionAndRespond(signature)
elif requestMethod == RequestMethod.SignTransaction:
self.buildRawTransactionAndRespond(signature)
elif requestMethod == RequestMethod.PersonalSign:
self.respondSessionRequest($self.sessionRequestJson, signature, false)
elif requestMethod == RequestMethod.EthSign:
self.respondSessionRequest($self.sessionRequestJson, signature, false)
elif requestMethod == RequestMethod.SignTypedData or
requestMethod == RequestMethod.SignTypedDataV3 or
requestMethod == RequestMethod.SignTypedDataV4:
self.respondSessionRequest($self.sessionRequestJson, signature, false)
else:
error "Unknown request method"
self.respondSessionRequest($self.sessionRequestJson, "", true)
proc sessionRequest*(self: Controller, sessionRequestJson: string, password: string) {.slot.} =
var signature: string
try:
self.invalidateData()
self.sessionRequestJson = parseJson(sessionRequestJson)
var sessionRes: JsonNode
let err = backend_wallet_connect.sessionRequest(sessionRes, sessionRequestJson)
if err.len > 0:
raise newException(CatchableError, err)
self.txResponseDto = sessionRes.toTxResponseDto()
if self.txResponseDto.signOnKeycard:
let data = SharedKeycarModuleSigningArgs(uniqueIdentifier: UNIQUE_WC_SESSION_REQUEST_SIGNING_IDENTIFIER,
keyUid: self.txResponseDto.keyUid,
path: self.txResponseDto.addressPath,
dataToSign: singletonInstance.utils.removeHexPrefix(self.txResponseDto.messageToSign))
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_SIGN_DATA, data)
return
else:
let hashedPasssword = common_utils.hashPassword(password)
var signMsgRes: JsonNode
let err = backend_wallet.signMessage(signMsgRes,
self.txResponseDto.messageToSign,
self.txResponseDto.address,
hashedPasssword)
if err.len > 0:
raise newException(CatchableError, err)
signature = signMsgRes.getStr
except Exception as e:
error "session request", msg=e.msg
self.finishSessionRequest(signature)
proc getProjectId*(self: Controller): string {.slot.} =
return constants.WALLET_CONNECT_PROJECT_ID
QtProperty[string] projectId:
read = getProjectId
proc getWalletAccounts*(self: Controller): string {.slot.} =
let jsonObj = % self.walletAccountService.getWalletAccounts()
return $jsonObj
proc finishAuthRequest(self: Controller, signature: string) =
if signature.len == 0:
self.respondAuthRequest("", true)
return
self.respondAuthRequest(signature, false)
proc authRequest*(self: Controller, selectedAddress: string, authMessage: string, password: string) {.slot.} =
var signature: string
try:
self.invalidateData()
var sessionRes: JsonNode
let err = backend_wallet_connect.authRequest(sessionRes, selectedAddress, authMessage)
if err.len > 0:
raise newException(CatchableError, err)
self.txResponseDto = sessionRes.toTxResponseDto()
if self.txResponseDto.signOnKeycard:
let data = SharedKeycarModuleSigningArgs(uniqueIdentifier: UNIQUE_WC_AUTH_REQUEST_SIGNING_IDENTIFIER,
keyUid: self.txResponseDto.keyUid,
path: self.txResponseDto.addressPath,
dataToSign: singletonInstance.utils.removeHexPrefix(self.txResponseDto.messageToSign))
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_SIGN_DATA, data)
return
else:
let hashedPasssword = common_utils.hashPassword(password)
var signMsgRes: JsonNode
let err = backend_wallet.signMessage(signMsgRes,
self.txResponseDto.messageToSign,
self.txResponseDto.address,
hashedPasssword)
if err.len > 0:
raise newException(CatchableError, err)
signature = signMsgRes.getStr
except Exception as e:
error "auth request", msg=e.msg
self.finishAuthRequest(signature)

View File

@ -1,47 +0,0 @@
################################################################################
# WalletConnect POC - to remove this file
################################################################################
import json, strutils
import uri
include app_service/common/json_utils
type
RequestMethod* {.pure.} = enum
Unknown = "unknown"
SendTransaction = "eth_sendTransaction"
SignTransaction = "eth_signTransaction"
PersonalSign = "personal_sign"
EthSign = "eth_sign"
SignTypedData = "eth_signTypedData"
SignTypedDataV3 = "eth_signTypedData_v3"
SignTypedDataV4 = "eth_signTypedData_v4"
## provided json represents a `SessionRequest`
proc getRequestMethod*(jsonObj: JsonNode): RequestMethod =
var paramsJsonObj: JsonNode
if jsonObj.getProp("params", paramsJsonObj):
var requestJsonObj: JsonNode
if paramsJsonObj.getProp("request", requestJsonObj):
var requestMethod: string
discard requestJsonObj.getProp("method", requestMethod)
try:
return parseEnum[RequestMethod](requestMethod)
except:
discard
return RequestMethod.Unknown
# check and extract Wallet Connect URI parameter from a deep link updated URL
proc extractAndCheckUriParameter*(url: string): (bool, string) =
let parsedUrl = parseUri(url)
if parsedUrl.path != "/wc":
return (false, "")
for (key, value) in decodeQuery(parsedUrl.query):
if key.toLower == "uri":
if value.startsWith("wc:"):
return (true, value)
return (false, "")

View File

@ -1,31 +0,0 @@
################################################################################
# WalletConnect POC - to remove this file
################################################################################
import json
include app_service/common/json_utils
type
TxResponseDto* = ref object
keyUid*: string
address*: string
addressPath*: string
signOnKeycard*: bool
chainId*: int
messageToSign*: string
txArgsJson*: JsonNode
rawTx*: string
txHash*: string
proc toTxResponseDto*(jsonObj: JsonNode): TxResponseDto =
result = TxResponseDto()
discard jsonObj.getProp("keyUid", result.keyUid)
discard jsonObj.getProp("address", result.address)
discard jsonObj.getProp("addressPath", result.addressPath)
discard jsonObj.getProp("signOnKeycard", result.signOnKeycard)
discard jsonObj.getProp("chainId", result.chainId)
discard jsonObj.getProp("messageToSign", result.messageToSign)
discard jsonObj.getProp("txArgs", result.txArgsJson)
discard jsonObj.getProp("rawTx", result.rawTx)
discard jsonObj.getProp("txHash", result.txHash)

View File

@ -1,97 +0,0 @@
################################################################################
# WalletConnect POC - to remove this file
################################################################################
import options, logging
import json
import core, response_type
from gen import rpc
import backend
# Declared in services/wallet/walletconnect/walletconnect.go
const eventWCProposeUserPair*: string = "WalletConnectProposeUserPair"
# Declared in services/wallet/walletconnect/walletconnect.go
const ErrorChainsNotSupported*: string = "chains not supported"
rpc(wCSignMessage, "wallet"):
message: string
address: string
password: string
rpc(wCBuildRawTransaction, "wallet"):
signature: string
rpc(wCSendTransactionWithSignature, "wallet"):
signature: string
rpc(wCPairSessionProposal, "wallet"):
sessionProposalJson: string
rpc(wCSaveOrUpdateSession, "wallet"):
sessionJson: string
rpc(wCChangeSessionState, "wallet"):
topic: string
active: bool
rpc(wCSessionRequest, "wallet"):
sessionRequestJson: string
rpc(wCAuthRequest, "wallet"):
address: string
message: string
proc isErrorResponse(rpcResponse: RpcResponse[JsonNode]): bool =
return not rpcResponse.error.isNil
proc prepareResponse(res: var JsonNode, rpcResponse: RpcResponse[JsonNode]): string =
if isErrorResponse(rpcResponse):
return rpcResponse.error.message
if rpcResponse.result.isNil:
return "no result"
res = rpcResponse.result
# TODO #12434: async answer
proc pair*(res: var JsonNode, sessionProposalJson: string): string =
try:
let response = wCPairSessionProposal(sessionProposalJson)
return prepareResponse(res, response)
except Exception as e:
warn e.msg
return e.msg
proc saveOrUpdateSession*(sessionJson: string): bool =
try:
let response = wCSaveOrUpdateSession(sessionJson)
return not isErrorResponse(response)
except Exception as e:
warn e.msg
return false
proc deleteSession*(topic: string): bool =
try:
let response = wCChangeSessionState(topic, false)
return not isErrorResponse(response)
except Exception as e:
warn e.msg
return false
proc sessionRequest*(res: var JsonNode, sessionRequestJson: string): string =
try:
let response = wCSessionRequest(sessionRequestJson)
return prepareResponse(res, response)
except Exception as e:
warn e.msg
return e.msg
proc authRequest*(res: var JsonNode, address: string, authMessage: string): string =
try:
let response = wCAuthRequest(address, authMessage)
return prepareResponse(res, response)
except Exception as e:
warn e.msg
return e.msg

View File

@ -57,8 +57,7 @@ proc getActiveSessions*(validAtTimestamp: int): JsonNode =
return nil
let jsonResultStr = rpcRes.result.getStr()
if jsonResultStr == "null":
# nil means error
if jsonResultStr == "null" or jsonResultStr == "":
return newJArray()
if rpcRes.result.kind != JArray:

View File

@ -83,6 +83,15 @@ Item {
font.bold: true
}
}
RowLayout {
StatusBaseText { text: "SDK status:" }
Rectangle {
Layout.preferredWidth: 20
Layout.preferredHeight: Layout.preferredWidth
radius: Layout.preferredWidth / 2
color: walletConnectService.wcSDK.sdkReady ? "green" : "red"
}
}
CheckBox {
text: "Testnet Mode"
@ -289,7 +298,7 @@ Item {
id: walletConnectService
wcSDK: WalletConnectSDK {
active: settings.enableSDK
enableSdk: settings.enableSDK
projectId: projectIdText.projectId
}

View File

@ -1,7 +1,5 @@
import unittest
import app/modules/main/wallet_section/poc_wallet_connect/helpers
import app/modules/shared_modules/wallet_connect/helpers
suite "wallet connect":
@ -14,27 +12,3 @@ suite "wallet connect":
const feesInfoJson = "{\"maxFees\":\"24528.282681\",\"maxFeePerGas\":1.168013461,\"maxPriorityFeePerGas\":0.036572351,\"gasPrice\":\"1.168013461\"}"
check(convertFeesInfoToHex(feesInfoJson) == """{"maxFeePerGas":"0x459E7895","maxPriorityFeePerGas":"0x22E0CBF"}""")
test "parse deep link url":
const testUrl = "https://status.app/wc?uri=wc%3Aa4f32854428af0f5b6635fb7a3cb2cfe174eaad63b9d10d52ef1c686f8eab862%402%3Frelay-protocol%3Dirn%26symKey%3D4ccbae2b4c81c26fbf4a6acee9de2771705d467de9a1d24c80240e8be59de6be"
let (resOk, wcUri) = extractAndCheckUriParameter(testUrl)
check(resOk)
check(wcUri == "wc:a4f32854428af0f5b6635fb7a3cb2cfe174eaad63b9d10d52ef1c686f8eab862@2?relay-protocol=irn&symKey=4ccbae2b4c81c26fbf4a6acee9de2771705d467de9a1d24c80240e8be59de6be")
test "parse another valid deep link url":
const testUrl = "https://status.app/notwc?uri=lt%3Asomevalue"
let (resOk, wcUri) = extractAndCheckUriParameter(testUrl)
check(not resOk)
check(wcUri == "")
test "parse a WC no-prefix deeplink":
const testUrl = "https://status.app/wc?uri=w4%3Atest"
let (resOk, wcUri) = extractAndCheckUriParameter(testUrl)
check(not resOk)
check(wcUri == "")

View File

@ -108,6 +108,7 @@ add_library(StatusQ SHARED
include/StatusQ/modelsyncedcontainer.h
include/StatusQ/modelutilsinternal.h
include/StatusQ/movablemodel.h
include/StatusQ/networkchecker.h
include/StatusQ/objectproxymodel.h
include/StatusQ/permissionutilsinternal.h
include/StatusQ/rolesrenamingmodel.h
@ -137,6 +138,7 @@ add_library(StatusQ SHARED
src/modelentry.cpp
src/modelutilsinternal.cpp
src/movablemodel.cpp
src/networkchecker.cpp
src/objectproxymodel.cpp
src/permissionutilsinternal.cpp
src/plugin.cpp

View File

@ -0,0 +1,41 @@
#pragma once
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <QTimer>
#include <chrono>
using namespace std::chrono_literals;
/// Checks if the internet connection is available, when active.
/// It checks the connection every 30 seconds as long as the \c active property is \c true.
class NetworkChecker : public QObject
{
Q_OBJECT
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
public:
explicit NetworkChecker(QObject* parent = nullptr);
bool isOnline() const;
bool isActive() const;
void setActive(bool active);
signals:
void isOnlineChanged(bool online);
void activeChanged(bool active);
private:
QNetworkAccessManager manager;
QTimer timer;
bool online = false;
bool active = true;
constexpr static std::chrono::milliseconds checkInterval = 30s;
void checkNetwork();
void onFinished(QNetworkReply* reply);
void updateRegularCheck(bool active);
};

View File

@ -3,6 +3,8 @@ import QtQuick 2.15
import QtWebEngine 1.10
import QtWebChannel 1.15
import StatusQ 0.1
// Helper to load and setup an instance of \c WebEngineView
//
// The \c webChannelObjects property is used to register specific objects
@ -11,34 +13,33 @@ import QtWebChannel 1.15
// qrc:/StatusQ/Components/private/qwebchannel/helpers.js will provide
// access to window.statusq APIs used to exchange data between the internal
// web engine and the QML application
//
// It doesn't load the web engine until NetworkChecker detects and active internet
// connection to avoid the corner case of initializing the web engine without
// network connectivity. If the web engine is initialized without network connectivity
// it won't restore the connectivity when it's available on Mac OS
Item {
id: root
required property url url
required property var webChannelObjects
property alias active: loader.active
// Used to control the loading of the web engine
property bool active: false
// Useful to monitor the loading state of the web engine (depends on active and internet connectivity)
readonly property bool isActive: loader.active
property alias instance: loader.item
property bool waitForInternet: true
signal engineLoaded(WebEngineView instance)
signal engineUnloaded()
signal pageLoaded()
signal pageLoadingError(string errorString)
Loader {
id: loader
Component {
id: webEngineViewComponent
active: false
onStatusChanged: function() {
if (status === Loader.Ready) {
root.engineLoaded(loader.item)
} else if (status === Loader.Null) {
root.engineUnloaded()
}
}
sourceComponent: WebEngineView {
WebEngineView {
id: webEngineView
anchors.fill: parent
@ -65,4 +66,35 @@ Item {
}
}
}
Loader {
id: loader
active: root.active && (!root.waitForInternet || (d.passedFirstTimeInitialization || networkChecker.isOnline))
onStatusChanged: function() {
if (status === Loader.Ready) {
root.engineLoaded(loader.item)
d.passedFirstTimeInitialization = true
} else if (status === Loader.Null) {
root.engineUnloaded()
}
}
sourceComponent: webEngineViewComponent
}
NetworkChecker {
id: networkChecker
// Deactivate searching for network connectivity after the web engine is loaded
active: !d.passedFirstTimeInitialization
}
QtObject {
id: d
// Used to hold the loading of the web engine until internet connectivity is available
property bool passedFirstTimeInitialization: false
}
}

View File

@ -0,0 +1,61 @@
#include "StatusQ/networkchecker.h"
NetworkChecker::NetworkChecker(QObject* parent)
: QObject(parent)
{
connect(&manager, &QNetworkAccessManager::finished, this, &NetworkChecker::onFinished);
connect(&timer, &QTimer::timeout, this, &NetworkChecker::checkNetwork);
updateRegularCheck(active);
}
bool NetworkChecker::isOnline() const
{
return online;
}
void NetworkChecker::checkNetwork()
{
QNetworkRequest request(QUrl("https://fedoraproject.org/static/hotspot.txt"));
manager.get(request);
}
void NetworkChecker::onFinished(QNetworkReply* reply)
{
bool wasOnline = online;
online = (reply->error() == QNetworkReply::NoError);
reply->deleteLater();
if(wasOnline != online)
{
emit isOnlineChanged(online);
}
}
bool NetworkChecker::isActive() const
{
return active;
}
void NetworkChecker::setActive(bool active)
{
if(active == this->active) return;
this->active = active;
emit activeChanged(active);
updateRegularCheck(active);
}
void NetworkChecker::updateRegularCheck(bool active)
{
if(active)
{
checkNetwork();
timer.start(checkInterval);
}
else
{
timer.stop();
}
}

View File

@ -17,6 +17,7 @@
#include "StatusQ/modelentry.h"
#include "StatusQ/modelutilsinternal.h"
#include "StatusQ/movablemodel.h"
#include "StatusQ/networkchecker.h"
#include "StatusQ/objectproxymodel.h"
#include "StatusQ/permissionutilsinternal.h"
#include "StatusQ/rolesrenamingmodel.h"
@ -58,6 +59,7 @@ public:
qmlRegisterType<SourceModel>("StatusQ", 0, 1, "SourceModel");
qmlRegisterType<ConcatModel>("StatusQ", 0, 1, "ConcatModel");
qmlRegisterType<MovableModel>("StatusQ", 0, 1, "MovableModel");
qmlRegisterType<NetworkChecker>("StatusQ", 0, 1, "NetworkChecker");
qmlRegisterType<FastExpressionFilter>("StatusQ", 0, 1, "FastExpressionFilter");
qmlRegisterType<FastExpressionRole>("StatusQ", 0, 1, "FastExpressionRole");

View File

@ -38,6 +38,8 @@ TestCase {
sourceComponent: WebEngineLoader {
url: "./WebEngineLoader/test.html"
webChannelObjects: [testObject]
waitForInternet: false
}
}
SignalSpy { id: loadedSpy; target: loader; signalName: "loaded" }
@ -67,13 +69,13 @@ TestCase {
compare(webEngine.instance, null, "By default the engine is not loaded")
webEngine.active = true
webEngineLoadedSpy.wait(1000);
webEngineLoadedSpy.wait(1000)
verify(webEngine.instance !== null , "The WebEngineView should be available")
if (Qt.platform.os === "linux") {
skip("fails to load page on linux")
}
pageLoadedSpy.wait(1000);
pageLoadedSpy.wait(1000)
webEngine.active = false
engineUnloadedSpy.wait(1000);

View File

@ -24,11 +24,6 @@ import "../controls"
import "../popups"
import "../panels"
/////////////////////////////////////////////////////
// WalletConnect POC - to remove
import AppLayouts.Wallet.views.pocwalletconnect 1.0
/////////////////////////////////////////////////////
SettingsContentBase {
id: root
@ -205,20 +200,6 @@ SettingsContentBase {
}
}
/////////////////////////////////////////////////////
// WalletConnect POC - to remove
StatusSettingsLineButton {
anchors.leftMargin: 0
anchors.rightMargin: 0
text: qsTr("POC Wallet Connect")
visible: root.advancedStore.isDebugEnabled
onClicked: {
Global.popupWalletConnect()
}
}
/////////////////////////////////////////////////////
Separator {
width: parent.width
}

View File

@ -14,10 +14,10 @@ WalletConnectSDKBase {
id: root
readonly property alias sdkReady: d.sdkReady
readonly property alias webEngineLoader: loader
property alias active: loader.active
property alias url: loader.url
// Enable the WalletConnect SDK
property alias enableSdk: loader.active
readonly property alias url: loader.url
implicitWidth: 1
implicitHeight: 1
@ -97,15 +97,15 @@ WalletConnectSDKBase {
function init() {
console.debug(`WC WalletConnectSDK.wcCall.init; root.projectId: ${root.projectId}`)
d.engine.runJavaScript(`wc.init("${root.projectId}").catch((error) => {wc.statusObject.sdkInitialized("SDK init error: "+error);})`, function(result) {
console.debug(`WC WalletConnectSDK.wcCall.init; response: ${JSON.stringify(result)}`)
if (result && !!result.error)
{
console.error("init: ", result.error)
}
})
d.engine.runJavaScript(`
wc.init("${root.projectId}")
.then(()=> {
wc.statusObject.sdkInitialized("");
})
.catch((error) => {
wc.statusObject.sdkInitialized("SDK init error: "+error)
})
`)
}
function getPairings(callback) {
@ -330,7 +330,7 @@ WalletConnectSDKBase {
WebChannel.id: "statusObject"
function bubbleConsoleMessage(type, message) {
function echo(type, message) {
if (type === "warn") {
console.warn(message)
} else if (type === "debug") {
@ -343,7 +343,7 @@ WalletConnectSDKBase {
}
function sdkInitialized(error) {
console.debug(`WC WalletConnectSDK.sdkInitialized; error: ${error}`)
console.debug(`WC WalletConnectSDK.sdkInitialized: ${!!error ? "error: " + error : "success"}`)
d.sdkReady = !error
root.sdkInit(d.sdkReady, error)
}

View File

@ -54,8 +54,8 @@ function buildSupportedNamespacesFromModels(chainsModel, accountsModel, methods)
}
function buildSupportedNamespaces(chainIds, addresses, methods) {
var eipChainIds = []
var eipAddresses = []
let eipChainIds = []
let eipAddresses = []
for (let i = 0; i < chainIds.length; i++) {
let chainId = chainIds[i]
eipChainIds.push(`"eip155:${chainId}"`)
@ -65,7 +65,13 @@ function buildSupportedNamespaces(chainIds, addresses, methods) {
}
let methodsStr = methods.map(method => `"${method}"`).join(',')
return `{
"eip155":{"chains": [${eipChainIds.join(',')}],"methods": [${methodsStr}],"events": ["accountsChanged", "chainChanged"],"accounts": [${eipAddresses.join(',')}]}}`
"eip155":{
"chains": [${eipChainIds.join(',')}],
"methods": [${methodsStr}],
"events": ["accountsChanged", "chainChanged"],
"accounts": [${eipAddresses.join(',')}]
}
}`
}
function validURI(uri) {

View File

@ -59,7 +59,7 @@ QtObject {
function buildDataObject(tx) { return {tx}}
function getTxObjFromData(data) { return data.tx }
}
readonly property var all: [personalSign, sign, signTypedData_v4, signTypedData, signTransaction, sendTransaction]
readonly property var all: [personalSign, sign, signTypedData_v4, signTypedData, sendTransaction]
}
function getSupportedMethods() {

View File

@ -1,49 +0,0 @@
/////////////////////////////////////////////////////
// WalletConnect POC - to remove this file
/////////////////////////////////////////////////////
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
ListView {
id: root
signal disconnect(string topic)
spacing: 32
delegate: Item {
implicitWidth: delegateLayout.implicitWidth
implicitHeight: delegateLayout.implicitHeight
RowLayout {
id: delegateLayout
width: root.width
StatusIcon {
icon: model.peerMetadata.icons.length > 0 ? model.peerMetadata.icons[0] : ""
visible: !!icon
}
StatusBaseText {
text: `${model.peerMetadata.name}\n${model.peerMetadata.url}\nTopic: ${SQUtils.Utils.elideText(model.topic, 6, 6)}\nExpire: ${new Date(model.expiry * 1000).toLocaleString()}`
color: model.active ? "green" : "orange"
}
StatusButton {
text: "Disconnect"
visible: model.active
onClicked: {
root.disconnect(model.topic)
}
}
}
}
}

View File

@ -1,36 +0,0 @@
/////////////////////////////////////////////////////
// WalletConnect POC - to remove this file
/////////////////////////////////////////////////////
import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import utils 1.0
import "../../../Profile/controls"
ListView {
id: root
property ButtonGroup buttonGroup
signal accountSelected(string address)
delegate: WalletAccountDelegate {
implicitWidth: root.width
nextIconVisible: false
account: model
totalCount: ListView.view.count
components: StatusRadioButton {
ButtonGroup.group: root.buttonGroup
onClicked: {
root.accountSelected(model.address)
}
}
}
}

View File

@ -1,159 +0,0 @@
/////////////////////////////////////////////////////
// WalletConnect POC - to remove this file
/////////////////////////////////////////////////////
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
ListView {
id: root
signal disconnect(string topic)
signal ping(string topic)
spacing: 48
delegate: Item {
implicitWidth: delegateLayout.implicitWidth
implicitHeight: delegateLayout.implicitHeight
ListModel {
id: namespacesListModel
}
Component.onCompleted: {
for (var key of Object.keys(model.namespaces)) {
let namespace = model.namespaces[key]
let obj = {
"eip": "",
"chain": "",
"methods": namespace.methods.join(", "),
"events": namespace.events.join(", ")
}
if (namespace.chains.length > 0) {
let data = namespace.chains[0].split(":")
if (data.length === 2) {
obj["eip"] = data[0]
obj["chain"] = data[1]
}
}
namespacesListModel.append(obj)
}
}
ColumnLayout {
id: delegateLayout
width: root.width
spacing: 8
StatusIcon {
icon: model.peer.metadata.icons.length > 0? model.peer.metadata.icons[0] : ""
visible: !!icon
}
StatusBaseText {
text: `Pairing topic:${SQUtils.Utils.elideText(model.pairingTopic, 6, 6)}\n${model.peer.metadata.name}\n${model.peer.metadata.url}`
}
StatusBaseText {
text: `Session topic:${SQUtils.Utils.elideText(model.topic, 6, 6)}\nExpire:${new Date(model.expiry * 1000).toLocaleString()}`
}
Rectangle {
color: "transparent"
border.color: "grey"
border.width: 1
Layout.fillWidth: true
Layout.preferredHeight: allNamespaces.implicitHeight
ColumnLayout {
id: allNamespaces
Repeater {
model: namespacesListModel
delegate: Rectangle {
id: namespaceDelegateRoot
property bool expanded: false
color: "transparent"
border.color: "grey"
border.width: 1
Layout.fillWidth: true
Layout.preferredHeight: namespace.implicitHeight
ColumnLayout {
id: namespace
spacing: 8
RowLayout {
StatusBaseText {
text: `Review ${model.eip} permissions`
}
StatusIcon {
icon: namespaceDelegateRoot.expanded? "chevron-up" : "chevron-down"
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
namespaceDelegateRoot.expanded = !namespaceDelegateRoot.expanded
}
}
}
}
StatusBaseText {
Layout.fillWidth: true
visible: namespaceDelegateRoot.expanded
text: `Chain ${model.chain}`
}
StatusBaseText {
Layout.fillWidth: true
visible: namespaceDelegateRoot.expanded
text: `Methods: ${model.methods}\nEvents: ${model.events}`
}
}
}
}
}
}
RowLayout {
StatusButton {
text: "Disconnect"
onClicked: {
root.disconnect(model.topic)
}
}
StatusButton {
text: "Ping"
onClicked: {
root.ping(model.topic)
}
}
}
}
}
}

View File

@ -1,523 +0,0 @@
/////////////////////////////////////////////////////
// WalletConnect POC - to remove this file
/////////////////////////////////////////////////////
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Popups 0.1
import shared.popups.walletconnect 1.0
Popup {
id: root
implicitWidth: 500
implicitHeight: Math.min(mainLayout.implicitHeight * 2, 700)
required property WalletConnectSDK sdk
parent: Overlay.overlay
anchors.centerIn: parent
clip: true
// wallet_connect.Controller \see wallet_section/wallet_connect/controller.nim
required property var controller
function openWithSessionRequestEvent(sessionRequest) {
d.setStatusText("Approve session request")
d.setDetailsText(JSON.stringify(sessionRequest, null, 2))
d.sessionRequest = sessionRequest
d.state = d.waitingUserResponseToSessionRequest
root.open()
}
function openWithUri(uri) {
pairLinkInput.text = uri
root.open()
if (root.sdk.sdkReady) {
d.setStatusText("Pairing from deeplink ...")
sdk.pair(uri)
} else {
d.pairModalUriWhenReady = uri
}
}
Flickable {
id: flickable
anchors.fill: parent
contentWidth: mainLayout.implicitWidth
contentHeight: mainLayout.implicitHeight
interactive: contentHeight > height || contentWidth > width
ColumnLayout {
id: mainLayout
spacing: 8
StatusBaseText {
text: qsTr("Debugging UX until design is ready")
font.bold: true
}
StatusTabBar {
id: tabBar
Layout.fillWidth: true
StatusTabButton {
width: implicitWidth
text: qsTr("WalletConnect")
}
StatusTabButton {
width: implicitWidth
text: qsTr("Sessions")
}
StatusTabButton {
width: implicitWidth
text: qsTr("Pairings")
}
}
StackLayout {
Layout.fillWidth: true
currentIndex: tabBar.currentIndex
ColumnLayout {
StatusSwitch {
id: testAuthentication
checkable: true
text: qsTr("Test Authentication")
}
StatusInput {
id: pairLinkInput
Layout.fillWidth: true
placeholderText: "Insert pair link"
}
RowLayout {
Layout.fillWidth: true
StatusButton {
text: testAuthentication.checked? "Authentication" : "Pair"
onClicked: {
d.setStatusText("")
d.setDetailsText("")
d.state = ""
accountsModel.clear()
if (testAuthentication.checked) {
d.setStatusText("Authenticating...")
root.sdk.auth(pairLinkInput.text)
return
}
d.setStatusText("Pairing...")
root.sdk.pair(pairLinkInput.text)
}
enabled: pairLinkInput.text.length > 0 && root.sdk.sdkReady
}
StatusButton {
text: "Accept"
onClicked: {
root.sdk.approveSession(d.observedData, d.supportedNamespaces)
}
visible: d.state === d.waitingPairState
}
StatusButton {
text: "Reject"
onClicked: {
root.sdk.rejectSession(d.observedData.id)
}
visible: d.state === d.waitingPairState
}
}
ButtonGroup {
id: selAccBtnGroup
}
POCSelectAccount {
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
model: accountsModel
buttonGroup: selAccBtnGroup
onAccountSelected: {
root.sdk.formatAuthMessage(d.observedData.params.cacaoPayload, address)
}
}
RowLayout {
StatusButton {
text: "Accept"
onClicked: {
if (testAuthentication.checked) {
root.controller.authRequest(d.selectedAddress, d.authMessage, passwordInput.text)
return
}
root.controller.sessionRequest(JSON.stringify(d.sessionRequest), passwordInput.text)
}
visible: d.state === d.waitingUserResponseToSessionRequest ||
d.state === d.waitingUserResponseToAuthRequest
}
StatusButton {
text: "Reject"
onClicked: {
if (testAuthentication.checked) {
root.sdk.authReject(d.observedData.id, d.selectedAddress)
return
}
root.sdk.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, false)
}
visible: d.state === d.waitingUserResponseToSessionRequest ||
d.state === d.waitingUserResponseToAuthRequest
}
StatusInput {
id: passwordInput
text: "1234567890"
placeholderText: "Insert account password"
visible: d.state === d.waitingUserResponseToSessionRequest ||
d.state === d.waitingUserResponseToAuthRequest
}
}
}
ColumnLayout {
Layout.fillWidth: true
POCPairings {
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
model: root.sdk.dappsModel
onDisconnect: function (topic) {
root.sdk.disconnectPairing(topic)
}
}
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: 32
}
ColumnLayout {
StatusBaseText {
text: qsTr("Tracking details...")
font.bold: true
}
StatusBaseText {
id: statusText
text: "-"
font.bold: true
}
StatusBaseText {
id: detailsText
text: ""
visible: text.length > 0
color: "#FF00FF"
}
}
}
ScrollBar.vertical: ScrollBar {}
clip: true
}
Connections {
target: root.sdk
function onSdkReadyChanged() {
if (root.sdk.sdkReady && d.pairModalUriWhenReady) {
d.setStatusText("Lazy pairing from deeplink ...")
sdk.pair(d.pairModalUriWhenReady)
d.pairModalUriWhenReady = ""
}
d.checkForPairings()
}
function onSdkInit(success, info) {
d.setDetailsText(info)
if (success) {
d.setStatusText("Ready to pair or auth")
} else {
d.setStatusText("SDK Error", "red")
}
d.state = ""
}
function onSessionProposal(sessionProposal) {
d.setDetailsText(sessionProposal)
d.setStatusText("Pair ID: " + sessionProposal.id + "; Topic: " + sessionProposal.params.pairingTopic)
root.controller.sessionProposal(JSON.stringify(sessionProposal))
}
function onSessionDelete(topic, error) {
if (!!error) {
d.setStatusText(`Error deleting session: ${error}`, "red")
d.setDetailsText("")
return
}
root.controller.deleteSession(topic)
}
function onSessionRequestEvent(sessionRequest) {
d.setStatusText("Approve session request")
d.setDetailsText(JSON.stringify(sessionRequest, null, 2))
d.sessionRequest = sessionRequest
root.state = d.waitingUserResponseToSessionRequest
}
function onApproveSessionResult(session, error) {
d.setDetailsText("")
if (!error) {
d.setStatusText("Pairing OK")
d.state = d.pairedState
root.sdk.getActiveSessions((activeSession) => {
root.controller.saveOrUpdateSession(JSON.stringify(session))
})
} else {
d.setStatusText("Pairing error", "red")
d.state = ""
}
}
function onRejectSessionResult(error) {
d.setDetailsText("")
d.state = ""
if (!error) {
d.setStatusText("Pairing rejected")
} else {
d.setStatusText("Rejecting pairing error", "red")
}
}
function onSessionRequestUserAnswerResult(accept, error) {
if (error) {
d.setStatusText(`Session Request ${accept ? "Accept" : "Reject"} error`, "red")
return
}
d.state = d.pairedState
if (accept) {
d.setStatusText(`Session Request accepted`)
} else {
d.setStatusText(`Session Request rejected`)
}
}
function onSessionProposalExpired() {
d.setStatusText(`Timeout waiting for response. Reusing URI?`, "red")
}
function onStatusChanged(message) {
d.setStatusText(message)
}
function onAuthRequest(request) {
d.observedData = request
d.setStatusText("Select the address you want to sign in with:")
accountsModel.clear()
let walletAccounts = root.controller.getWalletAccounts()
try {
let walletAccountsJsonArr = JSON.parse(walletAccounts)
for (let i = 0; i < walletAccountsJsonArr.length; i++) {
let obj = {
preferredSharingChainIds: ""
}
for (var key in walletAccountsJsonArr[i]) {
obj[key] = walletAccountsJsonArr[i][key]
}
accountsModel.append(obj)
}
} catch (e) {
console.error("error parsing wallet accounts, error: ", e)
d.setStatusText("error parsing walelt accounts", "red")
return
}
}
function onAuthMessageFormated(formatedMessage, address) {
let details = ""
if (!!d.observedData.verifyContext.verified.isScam) {
details = "This website you`re trying to connect is flagged as malicious by multiple security providers.\nApproving may lead to loss of funds."
} else {
if (d.observedData.verifyContext.verified.validation === "UNKNOWN")
details = "Website is Unverified"
else if (d.observedData.verifyContext.verified.validation === "INVALID")
details = "Website is Mismatched"
else
details = "Website is Valid"
}
d.selectedAddress = address
d.authMessage = formatedMessage
d.setDetailsText(`${details}\n\n${formatedMessage}`)
d.state = d.waitingUserResponseToAuthRequest
}
function onAuthRequestUserAnswerResult(accept, error) {
if (error) {
d.setStatusText(`Auth Request ${accept ? "Accept" : "Reject"} error`, "red")
return
}
if (accept) {
d.setStatusText(`Auth Request completed`)
} else {
d.setStatusText(`Auth Request aborted`)
}
}
}
QtObject {
id: d
property bool checkPairings: false
property string selectedAddress: ""
property var observedData: null
property var authMessage: null
property var supportedNamespaces: null
property var sessionRequest: null
property var signedData: null
property string pairModalUriWhenReady: ""
property string state: ""
readonly property string waitingPairState: "waiting_pairing"
readonly property string waitingUserResponseToSessionRequest: "waiting_user_response_to_session_request"
readonly property string waitingUserResponseToAuthRequest: "waiting_user_response_to_auth_request"
readonly property string pairedState: "paired"
function checkForPairings() {
if (!d.checkPairings || !root.sdk.sdkReady) {
return
}
d.checkPairings = false;
root.sdk.getPairings((pairings) => {
for (let i = 0; i < pairings.length; i++) {
if (pairings[i].active) {
// if there is at least a single active pairing we leave wallet connect sdk loaded
return;
}
}
// if there are no active pairings, we unload loaded sdk
root.controller.hasActivePairings = false;
})
}
function setStatusText(message, textColor) {
statusText.text = message
if (textColor === undefined) {
textColor = "green"
}
statusText.color = textColor
}
function setDetailsText(message) {
if (message === undefined) {
message = "undefined"
} else if (typeof message !== "string") {
message = JSON.stringify(message, null, 2)
}
detailsText.text = message
}
}
ListModel {
id: accountsModel
}
Connections {
target: root.controller
function onRespondSessionProposal(sessionProposalJson, supportedNamespacesJson, error) {
if (error) {
d.setStatusText(`Error: ${error}`, "red")
d.setDetailsText("")
return
}
d.setStatusText("Waiting user accept")
d.observedData = JSON.parse(sessionProposalJson)
d.supportedNamespaces = JSON.parse(supportedNamespacesJson)
d.setDetailsText(JSON.stringify(d.supportedNamespaces, null, 2))
d.state = d.waitingPairState
}
function onRespondSessionRequest(sessionRequestJson, signedData, error) {
console.log("WC respondSessionRequest", sessionRequestJson, " signedData", signedData, " error: ", error)
if (error) {
d.setStatusText("Session Request error", "red")
root.sdk.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, true)
return
}
d.sessionRequest = JSON.parse(sessionRequestJson)
d.signedData = signedData
root.sdk.acceptSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, d.signedData)
d.state = d.pairedState
d.setStatusText("Session Request accepted")
d.setDetailsText(d.signedData)
}
function onRespondAuthRequest(signature, error) {
console.log("WC signature", signature, " error: ", error)
if (error) {
d.setStatusText("Session Request error", "red")
root.sdk.authReject(d.observedData.id, d.selectedAddress)
return
}
root.sdk.authApprove(d.observedData, d.selectedAddress, signature)
}
function onCheckPairings() {
d.checkPairings = true
d.checkForPairings()
}
}
}

View File

@ -1,3 +0,0 @@
POCWalletConnect 1.0 POCWalletConnect.qml
POCWalletConnectModal 1.0 POCWalletConnectModal.qml
POCPairings 1.0 POCPairings.qml

View File

@ -2185,7 +2185,7 @@ Item {
id: walletConnectService
wcSDK: WalletConnectSDK {
active: WalletStore.RootStore.walletSectionInst.walletReady
enableSdk: WalletStore.RootStore.walletSectionInst.walletReady
projectId: WalletStore.RootStore.appSettings.walletConnectProjectID
}

View File

@ -112,11 +112,6 @@ QtObject {
// Metrics
signal openMetricsEnablePopupRequested(bool isOnboarding, var cb)
/////////////////////////////////////////////////////
// WalletConnect POC - to remove
signal popupWalletConnect()
/////////////////////////////////////////////////////
signal openAddEditSavedAddressesPopup(var params)
signal openDeleteSavedAddressesPopup(var params)
signal openShowQRPopup(var params)