feat(wallet) Wallet Connect integration prototype

Implement a prototype of integrating [WalletConnect Web SDK]()

- integrate WalletConnect Web SDK using Node.js and packing it using
  [webpack](https://webpack.js.org/guides/getting-started/)
  - this way, we achieve the same versioning strategy for the SDK
- add WalletConnectSDK view
  - it is used to load the web SDK via a WebView (validated working on
  Mac and Windows)
- add new app dependency of WebView QT
  - also update vendor packages `Dotherside` and `nimqml` to add
    required WebView::initialize API used to initialize the WebView
    integration at the app start
- add WalletConnectPage to Storybook for quick prototyping
  - Also add dependency for WebView Qt lib
- index.js is the wrapper used to provide a simple stateful interface
  with the WC SDK
- Entry in ui/generate-rcc.go ensures the node_modules cache is excluded
  from the resource file

Notes:

- Added `com.apple.security.cs.allow-jit` entitlement when signing the
app package. This allows Execution of JIT-compiled Code Entitlement
required by the fast-path of the JavaScriptCore framework on MacOS
platforms.
- Keep some debugging entries expected to help debugging Linux package
- Removed outdated `DerivationPathInputRegressionTests` qml test

Closes #12301
This commit is contained in:
Stefan 2023-10-03 18:43:35 +03:00 committed by Stefan Dunca
parent fd0e4eff43
commit ccd8c5b65f
28 changed files with 5401 additions and 34 deletions

View File

@ -186,7 +186,7 @@ ifneq ($(detected_OS),Windows)
endif
DOTHERSIDE_LIBFILE := vendor/DOtherSide/build/lib/libDOtherSideStatic.a
# order matters here, due to "-Wl,-as-needed"
NIM_PARAMS += --passL:"$(DOTHERSIDE_LIBFILE)" --passL:"$(shell PKG_CONFIG_PATH="$(QT5_PCFILEDIR)" pkg-config --libs Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets Qt5Svg Qt5Multimedia)"
NIM_PARAMS += --passL:"$(DOTHERSIDE_LIBFILE)" --passL:"$(shell PKG_CONFIG_PATH="$(QT5_PCFILEDIR)" pkg-config --libs Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets Qt5Svg Qt5Multimedia Qt5WebView)"
else
NIM_EXTRA_PARAMS := --passL:"-lsetupapi -lhid"
endif

View File

@ -10,5 +10,9 @@
<string>applinks:status.app</string>
</array>
-->
<!-- Required by WalletConnect web SDK to run in WebView -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>

View File

@ -132,6 +132,9 @@ proc mainProc() =
let app = newQGuiApplication()
# Required by the WalletConnectSDK view right after creating the QGuiApplication instance
initializeWebView()
let singleInstance = newSingleInstance($toMD5(DATADIR), openUri)
let urlSchemeEvent = newStatusUrlSchemeEventObject()
# init url manager before app controller

View File

@ -20,7 +20,7 @@ endif()
find_package(
Qt5
COMPONENTS Core Gui Quick QuickControls2 Test QuickTest Qml
COMPONENTS Core Gui Quick QuickControls2 Test QuickTest Qml WebView
REQUIRED)
set(STATUSQ_BUILD_SANDBOX OFF)
@ -53,6 +53,7 @@ add_library(${PROJECT_LIB}
pagesmodel.h pagesmodel.cpp
sectionsdecoratormodel.cpp sectionsdecoratormodel.h
testsrunner.h testsrunner.cpp
systemutils.cpp systemutils.h
)
add_executable(
@ -72,7 +73,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
)
target_link_libraries(
${PROJECT_LIB} PUBLIC Qt5::Core Qt5::Gui Qt5::Quick Qt5::QuickControls2)
${PROJECT_LIB} PUBLIC Qt5::Core Qt5::Gui Qt5::Quick Qt5::QuickControls2 Qt5::WebView)
target_link_libraries(
${PROJECT_NAME} PRIVATE ${PROJECT_LIB})
@ -87,7 +88,7 @@ add_executable(
)
target_link_libraries(
PagesValidator PUBLIC Qt5::Core Qt5::Gui Qt5::Quick Qt5::QuickControls2)
PagesValidator PUBLIC Qt5::Core Qt5::Gui Qt5::Quick Qt5::QuickControls2 Qt5::WebView)
add_dependencies(PagesValidator StatusQ)

View File

@ -2,12 +2,15 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QtWebView>
#include "cachecleaner.h"
#include "directorieswatcher.h"
#include "figmalinks.h"
#include "pagesmodel.h"
#include "sectionsdecoratormodel.h"
#include "testsrunner.h"
#include "systemutils.h"
struct PagesModelInitialized : public PagesModel {
explicit PagesModelInitialized(QObject *parent = nullptr)
@ -16,6 +19,9 @@ struct PagesModelInitialized : public PagesModel {
int main(int argc, char *argv[])
{
// Required by the WalletConnectSDK view
QtWebView::initialize();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
@ -79,6 +85,11 @@ int main(int argc, char *argv[])
qmlRegisterSingletonType<TestsRunner>(
"Storybook", 1, 0, "TestsRunner", runnerFactory);
qmlRegisterSingletonType<SystemUtils>(
"Storybook", 1, 0, "SystemUtils", [](QQmlEngine*, QJSEngine*) {
return new SystemUtils;
});
#ifdef Q_OS_WIN
const QUrl url(QUrl::fromLocalFile(QML_IMPORT_ROOT + QStringLiteral("/main.qml")));
#else

View File

@ -0,0 +1,58 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups.Dialog 0.1
import Models 1.0
import Storybook 1.0
import AppLayouts.Wallet.views.walletconnect 1.0
import SortFilterProxyModel 0.2
import utils 1.0
Item {
id: root
// qml Splitter
SplitView {
anchors.fill: parent
WalletConnect {
id: walletConnect
SplitView.preferredWidth: 400
projectId: SystemUtils.getEnvVar("WALLET_CONNECT_PROJECT_ID")
backgroundColor: Theme.palette.statusAppLayout.backgroundColor
clip: true
}
ColumnLayout {
id: optionsSpace
RowLayout {
id: optionsHeader
Text { text: "projectId" }
Text {
text: walletConnect.projectId.substring(0, 3) + "..." + walletConnect.projectId.substring(walletConnect.projectId.length - 3)
font.bold: true
}
}
// spacer
ColumnLayout {}
}
}
}
// category: Popups

View File

@ -161,24 +161,4 @@ Item {
verify(/^<style>(?:\.[a-zA-Z]{[a-zA-Z0-9#:;]+})+<\/style>(?:<span\sclass=["']\w["']>[\/\d'm]+<\/span>)+$/.test(res), `The generated html is valid and optimum (no extra spaces or CSS long names) - "${res}"`)
}
}
TestCase {
name: "DerivationPathInputRegressionTests"
Component {
id: regressionControlComponent
DerivationPathInput {
}
}
property DerivationPathInput controller: null
// Controller.Component.onCompleted was initializing Component.d.referenceElements after DerivationPathInput.onCompleted was processing the DerivationPathInput.initialDerivationPath, hence the output was wrong (m/44'/60001)
function test_successfulInitializationOfControllerBeforeItem() {
skip("Failing test. Needs to be fixed or updated")
const control = createTemporaryObject(regressionControlComponent, root)
compare("m/44'/60'/0'/0/1", control.derivationPath)
}
}
}

View File

@ -0,0 +1,9 @@
#include "systemutils.h"
#include <QtGlobal>
SystemUtils::SystemUtils(QObject *parent) : QObject(parent) {}
QString SystemUtils::getEnvVar(const QString &varName) {
return qEnvironmentVariable(varName.toUtf8().constData(), "");
}

13
storybook/systemutils.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <QObject>
#include <QString>
class SystemUtils : public QObject
{
Q_OBJECT
public:
explicit SystemUtils(QObject *parent = nullptr);
Q_INVOKABLE QString getEnvVar(const QString &varName);
};

View File

@ -16,6 +16,18 @@ QString StringUtilsInternal::escapeHtml(const QString& unsafe) const
return unsafe.toHtmlEscaped();
}
QString resolveFileUsingQmlImportPaths(QQmlEngine *engine, const QString &relativeFilePath) {
QStringList importPaths = engine->importPathList();
for (const auto &path : importPaths) {
QString fullPath = path + "/" + relativeFilePath;
QFile file(fullPath);
if (file.exists()) {
return fullPath;
}
}
return "";
}
QString StringUtilsInternal::readTextFile(const QString& filePath) const
{
auto selector = QQmlFileSelector::get(m_engine);
@ -28,9 +40,17 @@ QString StringUtilsInternal::readTextFile(const QString& filePath) const
QFile file(resolvedFilePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << Q_FUNC_INFO << "Error opening" << resolvedFilePath << "for reading";
auto fileUrl = resolveFileUsingQmlImportPaths(m_engine, filePath);
if (fileUrl.isEmpty()) {
qWarning() << Q_FUNC_INFO << "Can't find file in QML import paths" << filePath;
return {};
}
file.setFileName(fileUrl);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << Q_FUNC_INFO << "Error opening existing file" << fileUrl << "for reading";
return {};
}
}
return file.readAll();
}

View File

@ -0,0 +1,141 @@
import QtQuick 2.15
import QtWebView 1.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
Item {
id: root
implicitWidth: Math.min(mainLayout.implicitWidth, 400)
implicitHeight: Math.min(mainLayout.implicitHeight, 700)
required property string projectId
required property color backgroundColor
property alias optionalSdkPath: sdkView.optionalSdkPath
ColumnLayout {
id: mainLayout
anchors.fill: parent
StatusBaseText {
text: qsTr("Debugging UX until design is ready")
}
StatusInput {
id: pairLinkInput
Layout.fillWidth: true
placeholderText: "Insert pair link"
}
RowLayout {
Layout.fillWidth: true
StatusButton {
text: "Pair"
onClicked: {
statusText.text = "Pairing..."
sdkView.pair(pairLinkInput.text)
}
enabled: pairLinkInput.text.length > 0 && sdkView.state === sdkView.disconnectedState
}
StatusButton {
text: "Auth"
onClicked: {
statusText.text = "Authenticating..."
sdkView.auth()
}
enabled: false && pairLinkInput.text.length > 0 && sdkView.state === sdkView.disconnectedState
}
StatusButton {
text: "Accept"
onClicked: {
sdkView.acceptPairing()
}
visible: sdkView.state === sdkView.waitingPairState
}
StatusButton {
text: "Reject"
onClicked: {
sdkView.rejectPairing()
}
visible: sdkView.state === sdkView.waitingPairState
}
}
RowLayout {
StatusBaseText {
id: statusText
text: "-"
}
}
// TODO: DEBUG JS Loading in DMG
// RowLayout {
// TextField {
// id: urlInput
// Layout.fillWidth: true
// placeholderText: "Insert URL here"
// }
// Button {
// text: "Set URL"
// onClicked: {
// sdkView.url = urlInput.text
// }
// }
// }
// Button {
// text: "Set HTML"
// onClicked: {
// sdkView.loadHtml(htmlContent.text, "http://status.im")
// }
// }
// StatusInput {
// id: htmlContent
// Layout.fillWidth: true
// Layout.minimumHeight: 200
// Layout.maximumHeight: 300
// text: `<!DOCTYPE html><html><head><title>TODO: Test</title>\n<!--<script src="http://127.0.0.1:8080/bundle.js" defer></script>-->\n<script type='text/javascript'>\n console.log("@dd loaded dummy script!")\n</script>\n</head><body style='background-color: ${root.backgroundColor.toString()};'></body></html>`
// multiline: true
// minimumHeight: Layout.minimumHeight
// maximumHeight: Layout.maximumHeight
// }
// END DEBUGGING
// Separator
ColumnLayout {}
WalletConnectSDK {
id: sdkView
projectId: root.projectId
backgroundColor: root.backgroundColor
Layout.fillWidth: true
// Note that a too smaller height might cause the webview to generate rendering errors
Layout.preferredHeight: 10
onStatusChanged: function(message) {
statusText.text = message
}
}
}
}

View File

@ -0,0 +1,208 @@
import QtQuick 2.15
import QtWebView 1.15
// TODO #12434: remove debugging WebEngineView code
// import QtWebEngine 1.10
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core.Utils 0.1 as SQUtils
// Control used to instantiate and run the the WalletConnect web SDK
// The view is not used to draw anything, but has to be visible to be able to run JS code
// Use the \c backgroundColor property to blend in with the background
// \warning A too smaller height might cause rendering errors
// TODO #12434: remove debugging WebEngineView code
WebView {
//WebEngineView {
id: root
implicitWidth: 1
implicitHeight: 1
required property string projectId
required property color backgroundColor
readonly property string notReadyState: "not-ready"
readonly property string disconnectedState: "disconnected"
readonly property string waitingPairState: "waiting_pairing"
readonly property string pairedState: "paired"
state: root.notReadyState
property string optionalSdkPath: ""
// TODO: proper report
signal statusChanged(string message)
function pair(pairLink) {
d.requestSdk(
"wcResult = {error: null}; try { wc.pair(\"" + pairLink + "\").then((sessionProposal) => { wcResult = {state: \"" + root.waitingPairState + "\", error: null, sessionProposal: sessionProposal}; }).catch((error) => { wcResult = {error: error}; }); } catch (e) { wcResult = {error: \"Exception: \" + e.message}; }; wcResult"
)
}
function acceptPairing() {
d.acceptPair(d.sessionProposal)
}
function rejectPairing() {
d.rejectPair(d.sessionProposal.id)
}
// TODO #12434: remove debugging WebEngineView code
onLoadingChanged: function(loadRequest) {
console.debug(`@dd WalletConnectSDK.onLoadingChanged; status: ${loadRequest.status}; error: ${loadRequest.errorString}`)
switch(loadRequest.status) {
case WebView.LoadSucceededStatus:
// case WebEngineView.LoadSucceededStatus:
d.init(root.projectId)
break
case WebView.LoadFailedStatus:
// case WebEngineView.LoadFailedStatus:
root.statusChanged(`<font color="red">Failed loading SDK JS code; error: "${loadRequest.errorString}"</font>`)
break
case WebView.LoadStartedStatus:
// case WebEngineView.LoadStartedStatus:
root.statusChanged(`<font color="blue">Loading SDK JS code</font>`)
break
// case WebEngineView.LoadStoppedStatus:
// root.statusChanged(`<font color="pink">STOPPED loading SDK JS code</font>`)
// break
}
}
Component.onCompleted: {
console.debug(`@dd WalletConnectSDK onCompleted`)
var scriptSrc = SQUtils.StringUtils.readTextFile(":/app/AppLayouts/Wallet/views/walletconnect/sdk/generated/bundle.js")
// Load bundle from disk if not found in resources (Storybook)
if (scriptSrc === "") {
scriptSrc = SQUtils.StringUtils.readTextFile("./AppLayouts/Wallet/views/walletconnect/sdk/generated/bundle.js")
if (scriptSrc === "") {
console.error("Failed to read WalletConnect SDK bundle")
return
}
}
let htmlSrc = `<!DOCTYPE html><html><head><!--<title>TODO: Test</title>--><script type='text/javascript'>${scriptSrc}</script></head><body style='background-color: ${root.backgroundColor.toString()};'></body></html>`
console.debug(`@dd WalletConnectSDK.loadHtml; htmlSrc len: ${htmlSrc.length}`)
root.loadHtml(htmlSrc, "http://status.im")
}
Timer {
id: timer
interval: 100
repeat: true
running: false
triggeredOnStart: true
property int errorCount: 0
onTriggered: {
root.runJavaScript(
"wcResult",
function(result) {
if (!result) {
return
}
let done = false
if (result.error) {
done = true
if (root.state === root.notReadyState) {
root.statusChanged(`<font color="red">[${timer.errorCount++}] Failed SDK init; error: ${result.error}</font>`)
} else {
root.state = root.disconnectedState
root.statusChanged(`<font color="red">[${timer.errorCount++}] Operation error: ${result.error}</font>`)
}
} else if (result.state) {
switch (result.state) {
case root.disconnectedState: {
root.statusChanged(`<font color="green">Ready to pair or auth</font>`)
break
}
case root.waitingPairState: {
d.sessionProposal = result.sessionProposal
root.statusChanged("Pair ID: " + result.sessionProposal.id + "; Topic: " + result.sessionProposal.params.pairingTopic)
break
}
case root.pairedState: {
d.sessionType = result.sessionType
root.statusChanged(`<font color="blue">Paired: ${JSON.stringify(result.sessionType)}</font>`)
break
}
case root.disconnectedState: {
root.statusChanged(`<font color="orange">User rejected PairID ${d.sessionProposal.id}</font>`)
break
}
default: {
root.statusChanged(`<font color="red">[${timer.errorCount++}] Unknown state: ${result.state}</font>`)
result.state = root.disconnectedState
}
}
root.state = result.state
done = true
}
if (done) {
timer.stop()
}
}
)
}
}
Timer {
id: responseTimeoutTimer
interval: 10000
repeat: false
running: timer.running
onTriggered: {
timer.stop()
root.state = root.disconnectedState
root.statusChanged(`<font color="red">Timeout waiting for response. The pairing might have been already attempted for the URI.</font>`)
}
}
QtObject {
id: d
property var sessionProposal: null
property var sessionType: null
function isWaitingForSdk() {
return timer.running
}
function requestSdk(jsCode) {
console.debug(`@dd WalletConnectSDK.requestSdk; jsCode: ${jsCode}`)
root.runJavaScript(jsCode,
function(result) {
console.debug(`@dd WalletConnectSDK.requestSdk; result: ${JSON.stringify(result)}`)
timer.restart()
}
)
}
function init(projectId) {
d.requestSdk(
"wcResult = {error: null}; try { wc.init(\"" + projectId + "\").then((wc) => { wcResult = {state: \"" + root.disconnectedState + "\", error: null}; }).catch((error) => { wcResult = {error: error}; }); } catch (e) { wcResult = {error: \"Exception: \" + e.message}; }; wcResult"
)
}
function acceptPair(sessionProposal) {
d.requestSdk(
"wcResult = {error: null}; try { wc.approveSession(" + JSON.stringify(sessionProposal) + ").then((sessionType) => { wcResult = {state: \"" + root.pairedState + "\", error: null, sessionType: sessionType}; }).catch((error) => { wcResult = {error: error}; }); } catch (e) { wcResult = {error: \"Exception: \" + e.message}; }; wcResult"
)
}
function rejectPair(id) {
d.requestSdk(
"wcResult = {error: null}; try { wc.rejectSession(" + JSON.stringify(id) + ").then(() => { wcResult = {state: \"" + root.disconnectedState + "\", error: null}; }).catch((error) => { wcResult = {error: error}; }); } catch (e) { wcResult = {error: \"Exception: \" + e.message}; }; wcResult"
)
}
}
}

View File

@ -0,0 +1 @@
WalletConnect 1.0 WalletConnect.qml

View File

@ -0,0 +1,58 @@
# Wallet Connect Integration
## WalletConnect SDK management
Install dependencies steps by executing commands in this directory:
- update the [`package.json`](./package.json) versions and run `npm install`
- alternatively
- use the command `npm install <package-name>@<version/latest> --save` for individual packages
- or to update to the latest run `npm update` in here
- these commands will also create or update a `package-lock.json` file and populate the `node_modules` directory
- update the [`bundle.js`](./dist/main.js) file by running `npm run build`
- the result will be embedded with the app and loaded by [`WalletConnectSDK.qml`](../WalletConnectSDK.qml) component
- add the newly generated files to index `git add --update .` to include in the commit
## Testing
Use the web demo test client https://react-app.walletconnect.com/ for wallet pairing and https://react-auth-dapp.walletconnect.com/ for authentication
## TODO
- [ ] test namespaces implementation https://se-sdk-dapp.vercel.app/
## Log
Initial setup
```sh
npm init -y
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save @walletconnect/web3wallet
npm run build
```
## Dev - to be removed
To test SDK loading add the following to `ui/app/mainui/AppMain.qml`
```qml
import AppLayouts.Wallet.views.walletconnect 1.0
// ...
StatusDialog {
id: wcHelperDialog
visible: true
WalletConnect {
SplitView.preferredWidth: 400
SplitView.preferredHeight: 600
projectId: "<Project ID>"
backgroundColor: wcHelperDialog.backgroundColor
}
clip: true
}
```

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,23 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/**
* [js-sha3]{@link https://github.com/emn178/js-sha3}
*
* @version 0.8.0
* @author Chen, Yi-Cyuan [emn178@gmail.com]
* @copyright Chen, Yi-Cyuan 2015-2018
* @license MIT
*/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
{
"name": "wallet_connect_integration",
"version": "0.1.0",
"description": "Wallet Connect Integration for status-desktop",
"private": true,
"devDependencies": {
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"license": "MPL-2.0",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"dependencies": {
"@walletconnect/auth-client": "^2.1.2",
"@walletconnect/web3wallet": "^1.9.2"
}
}

View File

@ -0,0 +1,197 @@
import { Core } from "@walletconnect/core";
import { Web3Wallet } from "@walletconnect/web3wallet";
import AuthClient from '@walletconnect/auth-client'
// import the builder util
import { buildApprovedNamespaces, getSdkError } from "@walletconnect/utils";
// "Export" API to window
// Workaround, tried using export via output.module: true in webpack.config.js, but it didn't work
window.wc = {
core: null,
web3wallet: null,
authClient: null,
init: function (projectId) {
return new Promise(async (resolve) => {
window.wc.core = new Core({
projectId: projectId,
});
window.wc.web3wallet = await Web3Wallet.init({
core: window.wc.core, // <- pass the shared `core` instance
metadata: {
// TODO: what values should be here?
name: "Prototype",
description: "Prototype Wallet/Peer",
url: "https://github.com/status-im/status-desktop",
icons: ['https://status.im/img/status-footer-logo.svg'],
},
});
window.wc.authClient = await AuthClient.init({
projectId: projectId,
metadata: window.wc.web3wallet.metadata,
})
resolve(window.wc)
})
},
alreadyPaired: new Error("Already paired"),
waitingForApproval: new Error("Waiting for approval"),
// TODO: there is a corner case when attempting to pair with a link that is already paired or was rejected won't trigger any event back
pair: function (uri) {
let pairingTopic = getPairingTopicFromPairingUrl(uri);
let pairPromise = window.wc.web3wallet
.pair({ uri: uri })
.catch((error) => console.error(error));
const pairings = window.wc.core.pairing.getPairings();
// Find pairing by topic
const pairing = pairings.find((p) => p.topic === pairingTopic);
if (pairing) {
if (pairing.active) {
return new Promise((_, reject) => {
reject(window.wc.alreadyPaired);
});
}
}
return new Promise((resolve, reject) => {
pairPromise
.then(() => {
window.wc.web3wallet.on("session_proposal", async (sessionProposal) => {
resolve(sessionProposal);
});
})
.catch((error) => {
reject(error);
});
});
},
registerForSessionRequest: function (callback) {
window.wc.web3wallet.on("session_request", callback);
},
// TODO: ensure if session requests only one account we don't provide all accounts
approveSession: function (sessionProposal) {
const { id, params } = sessionProposal;
// ------- namespaces builder util ------------ //
const approvedNamespaces = buildApprovedNamespaces({
proposal: params,
// TODO: source this from wallet
supportedNamespaces: {
eip155: {
chains: ["eip155:1", "eip155:5"],
methods: ["eth_sendTransaction", "personal_sign"],
events: ["accountsChanged", "chainChanged"],
accounts: [
"eip155:1:0x0000000000000000000000000000000000000001",
"eip155:5:0xe74E17D586227691Cb7b64ed78b1b7B14828B034",
],
},
},
});
// ------- end namespaces builder util ------------ //
const session = window.wc.web3wallet.approveSession({
id,
namespaces: approvedNamespaces,
});
return session;
},
rejectSession: function (id) {
return window.wc.web3wallet.rejectSession({
id: id,
reason: getSdkError("USER_REJECTED"), // TODO USER_REJECTED_METHODS, USER_REJECTED_CHAINS, USER_REJECTED_EVENTS
});
},
auth: function (uri) {
let pairingTopic = getPairingTopicFromPairingUrl(uri);
let pairPromise = window.wc.authClient.core.pairing
.pair({ uri })
.catch((error) => console.error(error));
const pairings = window.wc.core.pairing.getPairings();
// Find pairing by topic
const pairing = pairings.find((p) => p.topic === pairingTopic);
if (pairing) {
if (pairing.active) {
return new Promise((_, reject) => {
reject(window.wc.alreadyPaired);
});
}
}
return new Promise((resolve, reject) => {
pairPromise
.then(() => {
// TODO: check if we can separate using the URI info
window.wc.authClient.on("auth_request", async (authProposal) => {
resolve(authProposal);
});
})
.catch((error) => {
reject(error);
});
});
},
approveAuth: function (authProposal) {
const { id, params } = authProposal;
// TODO: source users address
const iss = `did:pkh:eip155:1:${"0x0123456789"}`;
// format the cacao payload with the users address
const message = window.wc.authClient.formatMessage(params.cacaoPayload, iss);
// TODO: signature
const signature = "0x123456789"
return window.wc.authClient.respond(
{
id: id,
signature: {
s: signature,
t: "eip191",
},
},
iss
);
},
rejectAuth: function (id) {
return window.wc.authClient.reject(id);
},
respondSessionRequest: function (topic, response) {
window.wc.web3wallet.respondSessionRequest({ topic, response });
},
disconnectAll: function () {
const pairings = window.wc.core.pairing.getPairings();
pairings.forEach((p) => {
window.wc.core.pairing.disconnect({ topic: p.topic });
});
},
};
// Returns null if not a pairing url
function getPairingTopicFromPairingUrl(url) {
if (!url.startsWith("wc:")) {
return null;
}
const atIndex = url.indexOf("@");
if (atIndex < 0) {
return null;
}
return url.slice(3, atIndex);
}

View File

@ -0,0 +1,22 @@
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'generated'),
module: true,
//clean: true,
},
devServer: {
static: path.join(__dirname, "."),
compress: true,
port: 9000,
client: {
overlay: false,
}
},
experiments: {
outputModule: true,
},
};

View File

@ -53,7 +53,7 @@ func main() {
if err != nil {
return err
}
if info.IsDir() && (info.Name() == "vendor" || info.Name() == "tests" || info.Name() == "StatusQ") {
if info.IsDir() && (info.Name() == "vendor" || info.Name() == "tests" || info.Name() == "StatusQ" || info.Name() == "node_modules") {
return filepath.SkipDir
}
if !info.IsDir() {

View File

@ -11,7 +11,7 @@ endif()
# Macro for merging common code between static and shared
macro(add_target name type)
find_package(Qt5 COMPONENTS Core Qml Gui Quick QuickControls2 Widgets Network Multimedia REQUIRED)
find_package(Qt5 COMPONENTS Core Qml Gui Quick QuickControls2 Widgets Network Multimedia WebView REQUIRED)
file(GLOB HEADERS include/DOtherSide/*.h include/DOtherSide/Status/*.h)
file(GLOB SOURCES src/*.cpp src/Status/*.cpp)
@ -35,7 +35,7 @@ macro(add_target name type)
target_include_directories(${name} PUBLIC include include/Qt)
target_link_libraries(${name} PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Qml Qt5::Quick Qt5::Network Qt5::Multimedia)
target_link_libraries(${name} PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Qml Qt5::Quick Qt5::Network Qt5::Multimedia Qt5::WebView)
target_compile_definitions(${name} PRIVATE $<$<CONFIG:Debug>:QT_QML_DEBUG>)
if(DEFINED QML_DEBUG_PORT)
@ -58,7 +58,7 @@ macro(add_target name type)
endif()
# for DOtherSide.pc
set(PC_REQUIRES "Qt5Core, Qt5Gui, Qt5Widgets, Qt5Qml, Qt5Quick, Qt5Network, Qt5DBus, Qt5Multimedia")
set(PC_REQUIRES "Qt5Core, Qt5Gui, Qt5Widgets, Qt5Qml, Qt5Quick, Qt5Network, Qt5DBus, Qt5Multimedia, Qt5WebView")
if (${Qt5QuickControls2_FOUND})
target_link_libraries(${name} PRIVATE Qt5::QuickControls2)
set(PC_REQUIRES "${PC_REQUIRES}, Qt5QuickControls2")

View File

@ -60,6 +60,8 @@ DOS_API void DOS_CALL dos_qguiapplication_enable_hdpi(const char *uiScaleFilePat
DOS_API void DOS_CALL dos_qguiapplication_initialize_opengl(void);
DOS_API void DOS_CALL dos_qtwebview_initialize(void);
DOS_API void DOS_CALL dos_qguiapplication_try_enable_threaded_renderer();
/// \brief Create a QGuiApplication

View File

@ -1,5 +1,5 @@
lib_version = '0.6.3'
lib_dependencies = dependency('qt5', modules : ['Core', 'Gui', 'Widgets', 'Quick', 'Qml'])
lib_dependencies = dependency('qt5', modules : ['Core', 'Gui', 'Widgets', 'Quick', 'Qml', 'WebView'])
lib_sources = [
'src/DOtherSide.cpp',
'src/DOtherSideTypesCpp.cpp',

View File

@ -56,6 +56,7 @@
#ifdef QT_QUICKCONTROLS2_LIB
#include <QtQuickControls2/QQuickStyle>
#endif
#include <QtWebView>
#include <stdio.h>
#include <stdlib.h>
@ -175,6 +176,11 @@ void dos_qguiapplication_initialize_opengl()
QGuiApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
}
void dos_qtwebview_initialize()
{
QtWebView::initialize();
}
void dos_qguiapplication_try_enable_threaded_renderer()
{
if(QSysInfo::kernelType() == "darwin" && QT_VERSION < QT_VERSION_CHECK(6, 0, 0))

View File

@ -22,7 +22,10 @@ continueUserActivity:(NSUserActivity *)userActivity
if (!url)
return FALSE;
QUrl deeplink = QUrl::fromNSURL(url);
// set it to nim
// TODO #12434: Check if WalletConnect link and redirect the workflow
qDebug() << "@dd deeplink " << deeplink;
// TODO #12245: set it to nim
return TRUE;
}
return FALSE;

2
vendor/nimqml vendored

@ -1 +1 @@
Subproject commit 90f19ac030c28bbb4f89bf014afba344ca2941cb
Subproject commit df74bf1e65d0c185740f17cb31e6e59d4bf8ad4a