feat(wallet) Wallet Connect: process delete_session

Requires the specific status-go changes that brings WCChangePairingState

Process delete session and update internal pairing history state

Updated testing while fighting for the issue of not deleting the session
Found out that the client requests a different topic in the delete
session request.

Also:

- update debugging UX to support session events
- update storybook to support mocking session events
- fix go test utility to account for refactoring

Updates #12858
This commit is contained in:
Stefan 2023-11-24 14:51:36 +02:00 committed by Stefan Dunca
parent a12f96c311
commit 17c7e46917
19 changed files with 1818 additions and 756 deletions

View File

@ -74,6 +74,13 @@ QtObject:
if not self.hasActivePairings.get(false):
self.hasActivePairings = some(true)
proc deletePairing(self: Controller, topic: string) {.slot.} =
if backend.deletePairing(topic):
if self.hasActivePairings.get(false):
self.hasActivePairings = some(backend.hasActivePairings())
else:
error "Failed to delete pairing"
proc getHasActivePairings*(self: Controller): bool {.slot.} =
if self.hasActivePairings.isNone:
self.hasActivePairings = some(backend.hasActivePairings())

View File

@ -31,6 +31,10 @@ rpc(wCPairSessionProposal, "wallet"):
rpc(wCRecordSuccessfulPairing, "wallet"):
sessionProposalJson: string
rpc(wCChangePairingState, "wallet"):
topic: string
active: bool
rpc(wCHasActivePairings, "wallet"):
discard
@ -39,9 +43,7 @@ rpc(wCSessionRequest, "wallet"):
proc isErrorResponse(rpcResponse: RpcResponse[JsonNode]): bool =
if not rpcResponse.error.isNil:
return true
return false
return not rpcResponse.error.isNil
proc prepareResponse(res: var JsonNode, rpcResponse: RpcResponse[JsonNode]): string =
if isErrorResponse(rpcResponse):
@ -99,6 +101,14 @@ proc recordSuccessfulPairing*(sessionProposalJson: string): bool =
warn e.msg
return false
proc deletePairing*(topic: string): bool =
try:
let response = wCChangePairingState(topic, false)
return not isErrorResponse(response)
except Exception as e:
warn e.msg
return false
proc hasActivePairings*(): bool =
try:
let response = wCHasActivePairings()

View File

@ -34,9 +34,46 @@ Item {
controller: WalletConnectController {
pairSessionProposal: function(sessionProposalJson) {
proposeUserPair(sessionProposalJson, `{"eip155":{"methods":["eth_sendTransaction","personal_sign"],"chains":["eip155:5"],"events":["accountsChanged","chainChanged"],"accounts":["eip155:5:0x53780d79E83876dAA21beB8AFa87fd64CC29990b","eip155:5:0xBd54A96c0Ae19a220C8E1234f54c940DFAB34639","eip155:5:0x5D7905390b77A937Ae8c444aA8BF7Fa9a6A7DBA0"]}}`)
proposeUserPair(sessionProposalJson, `{"eip155":{"methods":[
"personal_sign",
"eth_sendTransaction",
"eth_signTransaction",
"eth_sign",
"eth_signTypedData",
"eth_signTypedData_v4"
],
"chains":["eip155:5"],
"events":[
"chainChanged",
"accountsChanged"
],
"accounts":["eip155:5:0xBd54A96c0Ae19a220C8E1234f54c940DFAB34639"]}}`)
}
recordSuccessfulPairing: function(sessionProposalJson) {
const sessionProposal = JSON.parse(sessionProposalJson)
pairingsModel.append({
topic: sessionProposal.params.pairingTopic,
expiry: sessionProposal.params.expiry,
active: true,
})
root.saveListModel(pairingsModel)
}
deletePairing: function(pairingTopic) {
var found = false
for (var i = 0; i < pairingsModel.count; i++) {
if (pairingsModel.get(i).topic === pairingTopic) {
pairingsModel.get.active = false
found = true
break;
}
}
if (!found) {
console.error("Mock Controller: pairing not found", pairingTopic)
}
root.saveListModel(pairingsModel)
}
sessionRequest: function(sessionRequestJson, password) {
const signedJson = "0x1234567890"
this.respondSessionRequest(sessionRequestJson, signedJson, respondError.checked)
@ -74,6 +111,7 @@ Item {
checked: wc.sdk.webEngineLoader.active
enabled: false
}
RowLayout {
id: optionsHeader
@ -95,14 +133,64 @@ Item {
checked: false
}
StatusBaseText { text: "Pairings History"; font.bold: true }
StatusButton {
text: "Clear"
onClicked: { pairingsModel.clear(); root.saveListModel(pairingsModel); }
}
Pairings {
id: pairingsView
Layout.fillWidth: true
Layout.minimumWidth: count > 0 ? 400 : 0
Layout.preferredHeight: contentHeight
Layout.maximumHeight: 300
onDisconnect: function(pairingTopic) {
wc.sdk.disconnectPairing(pairingTopic)
}
model: ListModel {
id: pairingsModel
}
clip: true
}
// spacer
ColumnLayout {}
}
}
Settings {
id: settings
property bool hasActivePairings: hasActivePairingsCheckBox.checked
property string pairingsHistory: ""
}
Component.onCompleted: {
loadListModel(pairingsModel)
}
function saveListModel(model) {
var listArray = [];
for (var i = 0; i < model.count; i++) {
listArray.push(model.get(i));
}
settings.pairingsHistory = JSON.stringify(listArray);
}
function loadListModel(model) {
pairingsModel.clear();
if (!settings.pairingsHistory) {
return;
}
var listArray = JSON.parse(settings.pairingsHistory);
listArray.forEach(function(entry) {
pairingsModel.append(entry);
});
}
}

View File

@ -10,6 +10,10 @@ Item {
// function pairSessionProposal(/*string*/ sessionProposalJson)
required property var pairSessionProposal
// function pairSessionRequest(/*string*/ sessionRequestJson)
required property var recordSuccessfulPairing
// function deletePairing(/*string*/ topic)
required property var deletePairing
signal respondSessionRequest(string sessionRequestJson, string signedJson, bool error)

View File

@ -1988,8 +1988,8 @@ github.com/status-im/gomoji v1.1.3-0.20220213022530-e5ac4a8732d4/go.mod h1:hmpnZ
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 h1:Oo2KZNP70KE0+IUJSidPj/BFS/RXNHmKIJOdckzml2E=
github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
github.com/status-im/markdown v0.0.0-20230314100416-26c6f74522d5 h1:Mie/nQtRrI/+qqyV4j5ydesjoPh6v6KF583NOPLKFQY=
github.com/status-im/markdown v0.0.0-20230314100416-26c6f74522d5/go.mod h1:5rjPyv3KffPNVbFjnsVy0NGj9+JeW40WvXLdxH1VKuE=
github.com/status-im/markdown v0.0.0-20231114210825-6c2d15b5dc57 h1:AuJFXERIFVzUjf9rrTb8vamFubB6Ks/e8aUasDr4pOM=
github.com/status-im/markdown v0.0.0-20231114210825-6c2d15b5dc57/go.mod h1:5rjPyv3KffPNVbFjnsVy0NGj9+JeW40WvXLdxH1VKuE=
github.com/status-im/migrate/v4 v4.6.2-status.2/go.mod h1:c/kc90n47GZu/58nnz1OMLTf7uE4Da4gZP5qmU+A/v8=
github.com/status-im/migrate/v4 v4.6.2-status.3 h1:Khwjb59NzniloUr5i9s9AtkEyqBbQFt1lkoAu66sAu0=
github.com/status-im/migrate/v4 v4.6.2-status.3/go.mod h1:c/kc90n47GZu/58nnz1OMLTf7uE4Da4gZP5qmU+A/v8=

View File

@ -91,8 +91,9 @@ func callPrivateMethod(method string, params []interface{}) string {
}
type Config struct {
HashedPassword string `json:"hashedPassword"`
NodeConfigFile string `json:"nodeConfigFile"`
HashedPassword string `json:"hashedPassword"`
NodeConfigFile string `json:"nodeConfigFile"`
DataDir *string `json:"dataDir,omitempty"`
}
func processConfigArgs() (config *Config, nodeConfigJson string, userFolder string, err error) {
@ -128,6 +129,11 @@ func processConfigArgs() (config *Config, nodeConfigJson string, userFolder stri
if err == nil {
nodeConfigJson = string(nodeConfigData)
}
if config.DataDir != nil {
userFolder = *config.DataDir
}
return
}

View File

@ -9,6 +9,8 @@
<div id="log"></div>
<script src="bundle.js" type="module"></script>
<script>
// Helper functions
//
function goEcho(message) {
window.echo(message);
}
@ -47,39 +49,158 @@
return addHtmlEntry(`${componentHtml}${statusHtml}`, null, entry);
}
// SDK initialization
//
const statusGoEntry = logComponentStatusChange("status-go", "Initializing...");
var initEventCount = 0;
var eventCount = 0;
const readyToPairEventName = "readyToPair";
async function initializeSDK() {
try {
const sdkEntry = logComponentStatusChange("SDK", "Initializing...");
const conf = await window.getConfiguration();
const wc = await window.wc.init(conf.projectId);
await window.wc.init(conf.projectId);
logComponentStatusChange("SDK", "Initialized", "green", sdkEntry);
eventCount++;
initEventCount++;
} catch (error) {
logComponentStatusChange("SDK", "FAIL initializing ${error.message}", "red", sdkEntry);
goEcho(`SDK init error: ${error}`);
logComponentStatusChange("SDK", "FAIL initializing ${error}", "red", sdkEntry);
}
}
// Simulate statusObject
window.statusq = {
channel: {
objects: {
statusObject: {
sdkInitialized: function (error) {
window.statusObject_sdkInitialized(error);
},
onSessionProposal: function (sessionProposal) {
window.statusObject_onSessionProposal(JSON.stringify(sessionProposal)).then((success) => {
if (!success) {
goEcho(`statusObject: onSessionProposal call failed ${sessionProposal.id}`);
return;
}
});
},
onSessionRequest: function (sessionRequest) {
eventCount++;
logComponentStatusChange("SDK", `received "session_request" event`, "green");
addLogEntry(`Data: ${JSON.stringify(sessionRequest)}`);
addHtmlEntry(
`<button id="acceptSessionButton${eventCount}">Accept</button> <button id="rejectSessionButton${eventCount}">Reject</button>`
);
const acceptSessionButton = document.getElementById(`acceptSessionButton${eventCount}`);
const rejectSessionButton = document.getElementById(`rejectSessionButton${eventCount}`);
acceptSessionButton.addEventListener("click", function () {
const sessionReqEntry = logComponentStatusChange("status-go", `sessionRequest called`, "orange");
window.sessionRequest(JSON.stringify(sessionRequest), hashedPasswordInput.value).then((success) => {
acceptSessionButton.disabled = true;
rejectSessionButton.disabled = true;
if (success) {
logComponentStatusChange("status-go", `sessionRequest OK`, "green", sessionReqEntry);
// waiting for "sessionRequestResult" event
} else {
logComponentStatusChange(
"status-go",
`sessionRequest call failed for topic ${sessionRequest.topic}`,
"red",
sessionReqEntry
);
window.wc.rejectSessionRequest(sessionRequest.topic, sessionRequest.id, true);
setStatus(`Session ${sessionRequest.id} rejected, internal error`, "purple");
}
});
});
rejectSessionButton.addEventListener("click", function () {
acceptSessionButton.disabled = true;
rejectSessionButton.disabled = true;
window.wc.rejectSessionRequest(sessionRequest.topic, sessionRequest.id).then(
() => {
addLogEntry(`Session ${sessionRequest.id} rejected`);
},
(err) => {
addLogEntry(`Session ${sessionRequest.id} reject error: ${err.message}`, "red");
}
);
});
},
onSessionDelete: function (deletePayload) {
goEcho(`statusObject: onSessionDelete ${JSON.stringify(deletePayload)}`);
},
onSessionExpire: function (expirePayload) {
goEcho(`statusObject: onSessionExpire ${JSON.stringify(expirePayload)}`);
},
onSessionUpdate: function (updatePayload) {
goEcho(`statusObject: onSessionUpdate ${JSON.stringify(updatePayload)}`);
},
onSessionExtend: function (extendPayload) {
goEcho(`statusObject: onSessionExtend ${JSON.stringify(extendPayload)}`);
},
onSessionPing: function (pingPayload) {
goEcho(`statusObject: onSessionPing ${JSON.stringify(pingPayload)}`);
},
onSessionEvent: function (eventPayload) {
goEcho(`statusObject: onSessionEvent ${JSON.stringify(eventPayload)}`);
},
onSessionRequest: function (sessionRequestPayload) {
goEcho(`statusObject: onSessionRequest ${JSON.stringify(sessionRequestPayload)}`);
},
onSessionRequestSent: function (sessionRequestSentPayload) {
goEcho(`statusObject: onSessionRequestSent ${JSON.stringify(sessionRequestSentPayload)}`);
},
onProposalExpire: function (proposalExpirePayload) {
goEcho(`statusObject: onProposalExpire ${JSON.stringify(proposalExpirePayload)}`);
},
},
},
},
};
var pairLinkInput = null;
var pairButton = null;
function newPairWorkflow() {
// Remove all the previous entries
if (logEntries) {
for (let i = 0; i < logEntries.length; i++) {
logEntries[i].remove();
}
}
logEntries = [];
eventCount++;
// Add session reset and password input
addHtmlEntry(`<button id="newSessionButton" style="display: none;">New Session</button>`);
newSessionButton = document.getElementById("newSessionButton");
newSessionButton.addEventListener("click", function () {
newPairWorkflow();
});
addHtmlEntry(
`<input type="text" id="hashedPasswordInput" placeholder="Insert hashed password" value="0x38301fb0b5fcf3aaa4b97c4771bb6c75546e313b4ce7057c51a8cc6a3ace9d7e"/>`
);
hashedPasswordInput = document.getElementById(`hashedPasswordInput`);
addHtmlEntry(
`<input type="text" id="pairLinkInput" placeholder="Insert pair link" /><button id="pairButton" disabled>Pair</button>`
);
// List existing pairing sessions
const pairings = window.wc.getPairings();
const pairingsRes = window.wc.getPairings();
let pairings = [];
if (pairingsRes) {
if (!!pairingsRes.error) {
goEcho(`getPairings() error: ${pairingsRes.error}`);
return;
} else if (pairingsRes.result) {
pairings = pairingsRes.result;
}
}
if (pairings.length > 0) {
addHtmlEntry(`Existing pairings:`, "fuchsia");
}
@ -88,24 +209,23 @@
const disconnectEntry = addHtmlEntry(
`[${i + 1}] <span style="color: ${p.active ? "green" : "orange"};">${
p.active ? "ACTIVE" : "INACTIVE"
}</span> <span class="elide-text">${
p.topic
}</span>; Expires: ${timestampToStr(p.expiry)} <button id="unpairButton${i}">Disconnect</button>`
}</span> <span class="elide-text">${p.topic}</span>; Expires: ${timestampToStr(
p.expiry
)} <button id="unpairButton${i}">Disconnect</button>`
);
const unpairButton = document.getElementById(`unpairButton${i}`);
unpairButton.addEventListener("click", function () {
window.wc.disconnect(p.topic).then(
() => {
addLogEntry(`Pairing ${p.topic} disconnected`, "green", disconnectEntry);
unpairButton.remove();
},
(err) => {
addLogEntry(`Pairing ${p.topic} disconnect error: ${err.message}`, "red", disconnectEntry);
}
);
const res = window.wc.disconnect(p.topic);
if (res && !!res.error) {
addLogEntry(`Pairing ${p.topic} disconnect error: ${err.message}`, "red", disconnectEntry);
return;
}
addLogEntry(`Pairing ${p.topic} disconnected`, "green", disconnectEntry);
unpairButton.remove();
});
}
// Add pairing options
pairLinkInput = document.getElementById(`pairLinkInput`);
pairButton = document.getElementById(`pairButton`);
pairLinkInput.addEventListener("input", function () {
@ -117,139 +237,73 @@
pairLinkInput.disabled = true;
const sdkEntry = logComponentStatusChange("SDK", "Pairing...");
window.wc
.pair(pairLinkInput.value)
.then((sessionProposal) => {
logComponentStatusChange("SDK", "got Pair session proposal", "green", sdkEntry);
addLogEntry(`Pair ID: ${sessionProposal.id} ; Topic: ${sessionProposal.params.pairingTopic}`);
const goSession = logComponentStatusChange("GO.pairSessionProposal", "waiting status-go", "pink");
const result = window.wc.pair(pairLinkInput.value);
if (result && !!result.error) {
goEcho("pair() error: ", result.error);
logComponentStatusChange("SDK", `Pairing error ${error.message}`, "red", sdkEntry);
return;
}
document.addEventListener(`proposeUserPair`, function (event) {
pairProposalEntry = logComponentStatusChange(
"GO.proposeUserPair",
`received "proposeUserPair"`,
"green"
);
addLogEntry(JSON.stringify(event.detail.supportedNamespaces));
addHtmlEntry(
`<button id="acceptPairButton">Accept</button><button id="rejectPairButton">Reject</button>`
);
const acceptPairButton = document.getElementById(`acceptPairButton`);
const rejectPairButton = document.getElementById(`rejectPairButton`);
acceptPairButton.addEventListener("click", function () {
window.wc.approvePairSession(sessionProposal, event.detail.supportedNamespaces).then(
() => {
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} approved`,
"green",
pairProposalEntry
);
acceptPairButton.remove();
rejectPairButton.remove();
},
(err) => {
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} approve error: ${err.message}`,
"red",
pairProposalEntry
);
}
);
});
rejectPairButton.addEventListener("click", function () {
window.wc.rejectPairSession(sessionProposal.id).then(
() => {
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} rejected`,
"green",
pairProposalEntry
);
acceptPairButton.remove();
rejectPairButton.remove();
},
(err) => {
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} reject error: ${err.message}`,
"red",
pairProposalEntry
);
}
);
});
});
window.pairSessionProposal(JSON.stringify(sessionProposal)).then((success) => {
if (!success) {
logComponentStatusChange(
"GO.pairSessionProposal",
`call failed ${sessionProposal.id}`,
"red",
goSession
);
return;
}
logComponentStatusChange("GO.pairSessionProposal", `waiting for "proposeUserPair"`, "black", goSession);
});
})
.catch((error) => {
logComponentStatusChange("SDK", `Pairing error ${error.message}`, "red", sdkEntry);
});
logComponentStatusChange("SDK", "got Pair session proposal", "green", sdkEntry);
const goSessionEntry = logComponentStatusChange("GO.pairSessionProposal", "waiting status-go", "pink");
});
}
function sdkReady() {
window.wc.registerForSessionRequest((event) => {
eventCount++;
logComponentStatusChange("SDK", `received "session_request" event`, "green");
addLogEntry(`Event: ${JSON.stringify(event)}`);
addHtmlEntry(
`<button id="acceptSessionButton${eventCount}">Accept</button> <button id="rejectSessionButton${eventCount}">Reject</button>`
);
const acceptSessionButton = document.getElementById(`acceptSessionButton${eventCount}`);
const rejectSessionButton = document.getElementById(`rejectSessionButton${eventCount}`);
acceptSessionButton.addEventListener("click", function () {
const sessionReqEntry = logComponentStatusChange("status-go", `sessionRequest called`, "orange");
window.sessionRequest(JSON.stringify(event), hashedPasswordInput.value).then((success) => {
acceptSessionButton.disabled = true;
rejectSessionButton.disabled = true;
if (success) {
logComponentStatusChange("status-go", `sessionRequest OK`, "green", sessionReqEntry);
// waiting for "sessionRequestResult" event
} else {
logComponentStatusChange(
"status-go",
`sessionRequest call failed for topic ${event.topic}`,
"red",
sessionReqEntry
);
window.wc.rejectSessionRequest(event.topic, event.id, true);
setStatus(`Session ${event.id} rejected, internal error`, "purple");
}
});
});
rejectSessionButton.addEventListener("click", function () {
acceptSessionButton.disabled = true;
rejectSessionButton.disabled = true;
window.wc.rejectSessionRequest(event.topic, event.id).then(
() => {
addLogEntry(`Session ${event.id} rejected`);
},
(err) => {
addLogEntry(`Session ${event.id} reject error: ${err.message}`, "red");
}
document.addEventListener(`proposeUserPair`, function (event) {
logComponentStatusChange("GO.proposeUserPair", `received "proposeUserPair"`, "green");
addLogEntry(JSON.stringify(event.detail.supportedNamespaces));
if (!document.getElementById(`acceptPairButton`)) {
addHtmlEntry(`<button id="acceptPairButton">Accept</button><button id="rejectPairButton">Reject</button>`);
}
const acceptPairButton = document.getElementById(`acceptPairButton`);
const rejectPairButton = document.getElementById(`rejectPairButton`);
const sessionProposal = event.detail.sessionProposal;
acceptPairButton.addEventListener("click", function () {
const result = window.wc.approvePairSession(sessionProposal, event.detail.supportedNamespaces);
if (result && !!result.error) {
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} approve error: ${result.error}`,
"red",
goSessionEntry
);
});
return;
}
acceptPairButton.remove();
rejectPairButton.remove();
root.controller_recordSuccessfulPairing(JSON.stringify(sessionProposal));
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} approved`,
"green",
goSessionEntry
);
});
}
rejectPairButton.addEventListener("click", function () {
const result = window.wc.rejectPairSession(sessionProposal.id);
if (result && !!result.error) {
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} reject error: ${result.error}`,
"red",
goSessionEntry
);
return;
}
acceptPairButton.remove();
rejectPairButton.remove();
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} rejected`,
"green",
goSessionEntry
);
});
});
document.addEventListener("sessionRequestResult", function (event) {
let req = event.detail.sessionRequest;
@ -262,16 +316,6 @@
// Add start from scratch option
document.addEventListener(readyToPairEventName, function () {
addHtmlEntry(`<button id="newSessionButton" style="display: none;">New Session</button>`);
newSessionButton = document.getElementById("newSessionButton");
newSessionButton.addEventListener("click", function () {
newPairWorkflow();
});
addHtmlEntry(
`<input type="text" id="hashedPasswordInput" placeholder="Insert hashed password" value="0x38301fb0b5fcf3aaa4b97c4771bb6c75546e313b4ce7057c51a8cc6a3ace9d7e"/>`
);
hashedPasswordInput = document.getElementById(`hashedPasswordInput`);
sdkReady();
newPairWorkflow();
});
@ -282,12 +326,12 @@
switch (event.name) {
case "nodeReady":
logComponentStatusChange("status-go", "Ready", "green", statusGoEntry);
eventCount++;
initEventCount++;
break;
default:
// Handle status-go and SDK bootstrap events
if (eventCount == 2) {
eventCount++;
if (initEventCount == 2) {
initEventCount++;
document.dispatchEvent(new CustomEvent(readyToPairEventName, {}));
} else if (event.name != "") {
goEcho(`GO event: ${event.name}`);
@ -315,7 +359,7 @@
const readableDate = date.toLocaleDateString();
const readableTime = date.toLocaleTimeString();
return `${readableDate} - ${readableTime}`
return `${readableDate} - ${readableTime}`;
}
</script>
</body>

View File

@ -63,9 +63,9 @@ func signalHandler(jsonEvent string) {
return
}
// TODO: continue from here
if walletEvent.Type == "WalletConnectProposeUserPair" {
eventQueue <- GoEvent{Name: "proposeUserPair", Payload: walletEvent.Message}
}
// if walletEvent.Type == "WalletConnectProposeUserPair" {
// eventQueue <- GoEvent{Name: "proposeUserPair", Payload: walletEvent.Message}
// }
}
}
@ -91,30 +91,31 @@ func main() {
w.SetTitle("WC status-go test")
w.SetSize(1280, 1024, webview.HintNone)
w.Bind("pairSessionProposal", func(sessionProposalJson string) bool {
sessionProposalRes := callPrivateMethod("wallet_wCPairSessionProposal", []interface{}{sessionProposalJson})
var apiResponse wc.PairSessionResponse
err = getRPCAPIResponse(sessionProposalRes, &apiResponse)
if err != nil {
l.Error("Error parsing the API response", "error", err)
return false
}
go func() {
eventQueue <- GoEvent{Name: "proposeUserPair", Payload: apiResponse}
}()
return true
})
w.Bind("sessionRequest", func(sessionRequestJson, hashedPassword string) bool {
sessionReqRes := callPrivateMethod("wallet_wCSessionRequest", []interface{}{sessionRequestJson, hashedPassword})
fmt.Println("sessionRequestJson:", sessionRequestJson)
sessionReqRes := callPrivateMethod("wallet_wCSessionRequest", []interface{}{sessionRequestJson})
fmt.Println("sessionReqRes:", sessionReqRes)
var apiResponse wc.SessionRequestResponse
err = getRPCAPIResponse(sessionReqRes, &apiResponse)
if err != nil {
l.Error("Error parsing the API response", "error", err)
l.Error("Error parsing wallet_wCSessionRequest response", "error", err)
return false
}
if apiResponse.SignOnKeycard {
l.Error("SignOnKeycard is not supported in this test")
return false
}
sessionReqRes = callPrivateMethod("wallet_wCSignMessage", []interface{}{apiResponse.MessageToSign, apiResponse.Address, hashedPassword})
fmt.Println("sessionReqRes:", sessionReqRes)
var signature string
err = getRPCAPIResponse(sessionReqRes, &signature)
if err != nil {
l.Error("Error parsing wallet_wCSignMessage response", "error", err)
return false
}
// TODO: process the request type ...
go func() {
eventQueue <- GoEvent{Name: "sessionRequestResult", Payload: apiResponse}
@ -146,6 +147,9 @@ func main() {
}
})
mockStatusObject(w)
mockController(w)
// Start a local server to serve the files
http.HandleFunc("/bundle.js", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
@ -162,3 +166,64 @@ func main() {
w.Navigate("http://localhost:8080")
w.Run()
}
func mockController(w webview.WebView) {
w.Bind("controller_recordSuccessfulPairing", func(sessionProposalJson string) {
fmt.Println("controller_recordSuccessfulPairing:", sessionProposalJson)
sessionProposalRes := callPrivateMethod("wallet_wCRecordSuccessfulPairing", []interface{}{sessionProposalJson})
var apiResponse wc.PairSessionResponse
err := getRPCAPIResponse(sessionProposalRes, &apiResponse)
if err != nil {
l.Error("Error parsing the API response", "error", err)
}
})
w.Bind("controller_changePairingState", func(topic string, active bool) {
sessionProposalRes := callPrivateMethod("wallet_wCChangePairingState", []interface{}{topic, active})
var apiResponse wc.PairSessionResponse
err := getRPCAPIResponse(sessionProposalRes, &apiResponse)
if err != nil {
l.Error("Error parsing the API response", "error", err)
}
})
}
func mockStatusObject(w webview.WebView) {
w.Bind("statusObject_sdkInitialized", func(error string) {
// All OK here
})
w.Bind("statusObject_onSessionProposal", func(sessionProposalJson string) bool {
sessionProposalRes := callPrivateMethod("wallet_wCPairSessionProposal", []interface{}{sessionProposalJson})
var apiResponse wc.PairSessionResponse
err := getRPCAPIResponse(sessionProposalRes, &apiResponse)
if err != nil {
l.Error("Error parsing the API response", "error", err)
return false
}
go func() {
eventQueue <- GoEvent{Name: "proposeUserPair", Payload: apiResponse}
}()
return true
})
w.Bind("statusObject_onSessionRequest", func(sessionRequestJson string) bool {
sessionReqRes := callPrivateMethod("wallet_wCSessionRequest", []interface{}{sessionRequestJson})
var apiResponse wc.SessionRequestResponse
err := getRPCAPIResponse(sessionReqRes, &apiResponse)
if err != nil {
l.Error("Error parsing the API response", "error", err)
return false
}
return true
})
// function onSessionUpdate(details)
// function onSessionExtend(details)
// function onSessionPing(details)
// function onSessionDelete(details)
// function onSessionExpire(details)
// function onSessionRequestSent(details)
// function onSessionEvent(details)
// function onProposalExpire(details)
}

View File

@ -0,0 +1,37 @@
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)
delegate: Item {
implicitWidth: delegateLayout.implicitWidth
implicitHeight: delegateLayout.implicitHeight
RowLayout {
id: delegateLayout
width: root.width
StatusBaseText {
text: `${SQUtils.Utils.elideText(model.topic, 6, 6)}\n${new Date(model.expiry * 1000).toLocaleString()}`
color: model.active ? "green" : "orange"
}
StatusButton {
text: "Disconnect"
visible: model.active
onClicked: {
root.disconnect(model.topic)
}
}
}
}
}

View File

@ -28,5 +28,8 @@ Item {
onSessionRequestEvent: (details) => {
modal.openWithSessionRequestEvent(details)
}
onSessionDelete: (deletePayload) => {
root.controller.deletePairing(deletePayload.topic)
}
}
}

View File

@ -4,14 +4,13 @@ import QtQuick.Layouts 1.15
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Popups 0.1
Popup {
id: root
implicitWidth: Math.min(mainLayout.implicitWidth, 400)
implicitHeight: Math.min(mainLayout.implicitHeight, 700)
implicitWidth: 500
implicitHeight: Math.min(mainLayout.implicitHeight * 2, 700)
required property WalletConnectSDK sdk
@ -38,89 +37,87 @@ Popup {
anchors.fill: parent
contentWidth: mainLayout.width
contentHeight: mainLayout.height
contentWidth: mainLayout.implicitWidth
contentHeight: mainLayout.implicitHeight
interactive: contentHeight > height || contentWidth > width
ColumnLayout {
id: mainLayout
id: mainLayout
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..."
sdk.pair(pairLinkInput.text)
}
enabled: pairLinkInput.text.length > 0 && sdk.sdkReady
}
StatusButton {
text: "Auth"
onClicked: {
statusText.text = "Authenticating..."
sdk.auth()
}
enabled: false && pairLinkInput.text.length > 0 && sdk.sdkReady
}
StatusButton {
text: "Accept"
onClicked: {
sdk.approvePairSession(d.sessionProposal, d.supportedNamespaces)
}
visible: d.state === d.waitingPairState
}
StatusButton {
text: "Reject"
onClicked: {
sdk.rejectPairSession(d.sessionProposal.id)
}
visible: d.state === d.waitingPairState
}
}
ColumnLayout {
StatusBaseText {
id: statusText
text: "-"
text: qsTr("Debugging UX until design is ready")
}
StatusBaseText {
text: "Pairings"
visible: sdk.pairingsModel.count > 0
}
StatusListView {
StatusInput {
id: pairLinkInput
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Layout.maximumHeight: 200
model: sdk.pairingsModel
placeholderText: "Insert pair link"
}
delegate: StatusBaseText {
text: `${SQUtils.Utils.elideText(topic, 6, 6)} - ${new Date(expiry * 1000).toLocaleString()}`
color: active ? "green" : "orange"
RowLayout {
Layout.fillWidth: true
StatusButton {
text: "Pair"
onClicked: {
statusText.text = "Pairing..."
sdk.pair(pairLinkInput.text)
}
enabled: pairLinkInput.text.length > 0 && sdk.sdkReady
}
StatusButton {
text: "Auth"
onClicked: {
statusText.text = "Authenticating..."
sdk.auth()
}
enabled: false && pairLinkInput.text.length > 0 && sdk.sdkReady
}
StatusButton {
text: "Accept"
onClicked: {
sdk.approvePairSession(d.sessionProposal, d.supportedNamespaces)
// Will trigger an onPairAcceptedResult if successful
}
visible: d.state === d.waitingPairState
}
StatusButton {
text: "Reject"
onClicked: {
sdk.rejectPairSession(d.sessionProposal.id)
}
visible: d.state === d.waitingPairState
}
}
Flickable {
Layout.fillWidth: true
Layout.preferredHeight: 200
Layout.maximumHeight: 400
contentWidth: detailsText.width
contentHeight: detailsText.height
ColumnLayout {
StatusBaseText {
id: statusText
text: "-"
}
StatusBaseText {
text: "Pairings"
visible: sdk.pairingsModel.count > 0
}
Pairings {
Layout.fillWidth: true
Layout.minimumWidth: count > 0 ? 400 : 0
Layout.preferredHeight: contentHeight
Layout.maximumHeight: 300
model: sdk.pairingsModel
onDisconnect: function (topic) {
sdk.disconnectPairing(topic)
}
}
StatusBaseText {
id: detailsText
@ -130,42 +127,37 @@ Popup {
color: "#FF00FF"
}
ScrollBar.vertical: ScrollBar {}
RowLayout {
StatusButton {
text: "Accept"
onClicked: {
root.controller.sessionRequest(JSON.stringify(d.sessionRequest), passwordInput.text)
}
visible: d.state === d.waitingUserResponseToSessionRequest
}
StatusButton {
text: "Reject"
onClicked: {
sdk.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, false)
}
visible: d.state === d.waitingUserResponseToSessionRequest
}
StatusInput {
id: passwordInput
clip: true
text: "1234567890"
placeholderText: "Insert account password"
visible: d.state === d.waitingUserResponseToSessionRequest
}
}
ColumnLayout { /* spacer */ }
}
RowLayout {
StatusButton {
text: "Accept"
onClicked: {
root.controller.sessionRequest(JSON.stringify(d.sessionRequest), passwordInput.text)
}
visible: d.state === d.waitingUserResponseToSessionRequest
}
StatusButton {
text: "Reject"
onClicked: {
sdk.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, false)
}
visible: d.state === d.waitingUserResponseToSessionRequest
}
StatusInput {
id: passwordInput
text: "1234567890"
placeholderText: "Insert account password"
visible: d.state === d.waitingUserResponseToSessionRequest
}
}
ColumnLayout { /* spacer */ }
// Separator
ColumnLayout {}
}
// Separator
ColumnLayout {}
}
ScrollBar.vertical: ScrollBar {}
clip: true
@ -190,7 +182,7 @@ Popup {
if (success) {
d.setStatusText("Pair ID: " + sessionProposal.id + "; Topic: " + sessionProposal.params.pairingTopic)
root.controller.pairSessionProposal(JSON.stringify(sessionProposal))
// Expecting signal onProposeUserPair from controller
// Expecting signal onProposeUserPair(..., true, ...) from controller
} else {
d.setStatusText("Pairing error", "red")
}

View File

@ -31,11 +31,18 @@ Item {
signal sessionRequestEvent(var sessionRequest)
signal sessionRequestUserAnswerResult(bool accept, string error)
signal sessionDelete(var deletePayload)
function pair(pairLink)
{
wcCalls.pair(pairLink)
}
function disconnectPairing(topic)
{
wcCalls.disconnectPairing(topic)
}
function approvePairSession(sessionProposal, supportedNamespaces)
{
wcCalls.approvePairSession(sessionProposal, supportedNamespaces)
@ -108,7 +115,7 @@ Item {
function init() {
console.debug(`WC WalletConnectSDK.wcCall.init; root.projectId: ${root.projectId}`)
d.engine.runJavaScript(`wc.init("${root.projectId}")`, function(result) {
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, null, 2)}`)
@ -252,6 +259,22 @@ Item {
d.resetPairingsModel()
})
}
function disconnectPairing(topic) {
console.debug(`WC WalletConnectSDK.wcCall.disconnectPairing; topic: "${topic}"`)
d.engine.runJavaScript(`wc.disconnect("${topic}")`, function(result) {
console.debug(`WC WalletConnectSDK.wcCall.disconnect; response: ${JSON.stringify(result, null, 2)}`)
if (result) {
if (!!result.error) {
console.error("disconnect: ", result.error)
return
}
}
d.resetPairingsModel()
})
}
}
QtObject {
@ -300,7 +323,8 @@ Item {
function onSessionDelete(details)
{
console.debug(`WC TODO WalletConnectSDK.onSessionDelete; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC WalletConnectSDK.onSessionDelete; details: ${JSON.stringify(details, null, 2)}`)
root.sessionDelete(details)
}
function onSessionExpire(details)

View File

@ -1,3 +1,4 @@
WalletConnect 1.0 WalletConnect.qml
WalletConnectModal 1.0 WalletConnectModal.qml
WalletConnectSDK 1.0 WalletConnectSDK.qml
WalletConnectSDK 1.0 WalletConnectSDK.qml
Pairings 1.0 Pairings.qml

View File

@ -18,7 +18,8 @@ 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
- or to update to the latest run `ncu -u; npm install` in here
- run `npm install -g npm-check-updates` for `ncu` command
- these commands will also create or update a `package-lock.json` file and populate the `node_modules` directory
- 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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
{
"name": "wallet_connect_integration",
"version": "0.1.0",
"description": "Wallet Connect Integration for status-desktop",
"description": "Wallet Connect integration for status-desktop",
"private": true,
"devDependencies": {
"webpack": "^5.88.2",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
@ -16,7 +16,6 @@
"start": "webpack serve --mode development --open"
},
"dependencies": {
"@walletconnect/auth-client": "^2.1.2",
"@walletconnect/web3wallet": "^1.9.2"
"@walletconnect/web3wallet": "^1.9.4"
}
}

View File

@ -1,7 +1,8 @@
import { Core } from "@walletconnect/core";
import { Web3Wallet } from "@walletconnect/web3wallet";
import AuthClient from '@walletconnect/auth-client'
// Disabled for now to debug the issue with wrong pairing topic
//import AuthClient from '@walletconnect/auth-client'
// import the builder util
import { buildApprovedNamespaces, getSdkError } from "@walletconnect/utils";
@ -14,21 +15,24 @@ window.wc = {
statusObject: null,
init: function (projectId) {
(async () => {
return new Promise(async (resolve, reject) => {
if (!window.statusq) {
console.error('missing window.statusq! Forgot to execute "ui/StatusQ/src/StatusQ/Components/private/qwebchannel/helpers.js" first?');
return;
const errMsg = 'missing window.statusq! Forgot to execute "ui/StatusQ/src/StatusQ/Components/private/qwebchannel/helpers.js" first?'
console.error(errMsg);
reject(errMsg);
}
if (window.statusq.error) {
console.error("Failed initializing WebChannel: " + window.statusq.error);
return;
const errMsg = "Failed initializing WebChannel: " + window.statusq.error
console.error(errMsg);
reject(errMsg);
}
wc.statusObject = window.statusq.channel.objects.statusObject;
if (!wc.statusObject) {
console.error("Failed initializing WebChannel or initialization not run");
return;
const errMsg = "Failed initializing WebChannel or initialization not run"
console.error(errMsg);
reject(errMsg);
}
window.wc.core = new Core({
@ -45,10 +49,10 @@ window.wc = {
},
});
window.wc.authClient = await AuthClient.init({
projectId: projectId,
metadata: window.wc.web3wallet.metadata,
});
//window.wc.authClient = await AuthClient.init({
// projectId: projectId,
// metadata: window.wc.web3wallet.metadata,
//});
// connect session responses https://specs.walletconnect.com/2.0/specs/clients/sign/session-events#events
window.wc.web3wallet.on("session_proposal", async (details) => {
@ -92,9 +96,8 @@ window.wc = {
});
wc.statusObject.sdkInitialized("");
})();
return { result: "ok", error: "" };
resolve("");
});
},
// 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
@ -127,6 +130,7 @@ window.wc = {
supportedNamespaces: supportedNamespaces,
});
wc.statusObject.bubbleConsoleMessage("debug", `web3wallet.approveSession id: ${id} ${JSON.stringify(approvedNamespaces, null, 2)}`)
return {
result: window.wc.web3wallet.approveSession({
id,
@ -145,45 +149,45 @@ window.wc = {
};
},
auth: function (uri) {
return {
result: window.wc.authClient.core.pairing.pair({ uri }),
error: ""
};
},
// auth: function (uri) {
// return {
// result: window.wc.authClient.core.pairing.pair({ uri }),
// error: ""
// };
// },
approveAuth: function (authProposal) {
const { id, params } = authProposal;
// approveAuth: function (authProposal) {
// const { id, params } = authProposal;
// TODO: source users address
const iss = `did:pkh:eip155:1:${"0x0123456789"}`;
// // 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);
// // format the cacao payload with the users address
// const message = window.wc.authClient.formatMessage(params.cacaoPayload, iss);
// TODO: signature
const signature = "0x123456789"
// // TODO: signature
// const signature = "0x123456789"
return {
result: window.wc.authClient.respond(
{
id: id,
signature: {
s: signature,
t: "eip191",
},
},
iss),
error: ""
};
},
// return {
// result: window.wc.authClient.respond(
// {
// id: id,
// signature: {
// s: signature,
// t: "eip191",
// },
// },
// iss),
// error: ""
// };
// },
rejectAuth: function (id) {
return {
result: window.wc.authClient.reject(id),
error: ""
};
},
// rejectAuth: function (id) {
// return {
// result: window.wc.authClient.reject(id),
// error: ""
// };
// },
respondSessionRequest: function (topic, id, signature) {
const response = formatJsonRpcResult(id, signature)

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 317ad2f906dfd97b9ad620e6481a72801dde713d
Subproject commit c433908834ad2d55e40b67da1cbd88014319315f