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:
parent
fd0e4eff43
commit
ccd8c5b65f
2
Makefile
2
Makefile
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -55,7 +55,7 @@ if [[ -n "${MACOS_KEYCHAIN_FILE}" ]]; then
|
|||
echo -e "\n### Storing original keychain search list..."
|
||||
# We want to restore the normal keychains and ignore Jenkis created ones
|
||||
ORIG_KEYCHAIN_LIST=$(security list-keychains | grep -v -e "^/private" -e "secretFiles" | xargs)
|
||||
|
||||
|
||||
# The keychain file needs to be locked afterwards
|
||||
trap clean_up EXIT ERR
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(), "");
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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,8 +40,16 @@ 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";
|
||||
return {};
|
||||
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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
WalletConnect 1.0 WalletConnect.qml
|
|
@ -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
|
@ -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
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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 user’s address
|
||||
const iss = `did:pkh:eip155:1:${"0x0123456789"}`;
|
||||
|
||||
// format the cacao payload with the user’s 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);
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
};
|
|
@ -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() {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 90f19ac030c28bbb4f89bf014afba344ca2941cb
|
||||
Subproject commit df74bf1e65d0c185740f17cb31e6e59d4bf8ad4a
|
Loading…
Reference in New Issue