mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-16 17:45:15 +00:00
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:
parent
ea0c4dfdca
commit
112a6f3003
@ -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()
|
||||
|
@ -48,6 +48,3 @@ type
|
||||
addresses*: seq[string]
|
||||
|
||||
const MARK_WALLET_ADDRESSES_AS_SHOWN* = "markWalletAddressesAsShown"
|
||||
|
||||
|
||||
const WALLET_CONNECT_CHECK_PAIRINGS* = "walletConnectCheckPairings"
|
@ -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)
|
@ -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, "")
|
@ -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)
|
@ -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
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 == "")
|
||||
|
@ -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
|
||||
|
41
ui/StatusQ/include/StatusQ/networkchecker.h
Normal file
41
ui/StatusQ/include/StatusQ/networkchecker.h
Normal 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);
|
||||
};
|
@ -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
|
||||
}
|
||||
}
|
61
ui/StatusQ/src/networkchecker.cpp
Normal file
61
ui/StatusQ/src/networkchecker.cpp
Normal 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();
|
||||
}
|
||||
}
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
POCWalletConnect 1.0 POCWalletConnect.qml
|
||||
POCWalletConnectModal 1.0 POCWalletConnectModal.qml
|
||||
POCPairings 1.0 POCPairings.qml
|
@ -2185,7 +2185,7 @@ Item {
|
||||
id: walletConnectService
|
||||
|
||||
wcSDK: WalletConnectSDK {
|
||||
active: WalletStore.RootStore.walletSectionInst.walletReady
|
||||
enableSdk: WalletStore.RootStore.walletSectionInst.walletReady
|
||||
|
||||
projectId: WalletStore.RootStore.appSettings.walletConnectProjectID
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user