From 714ac606209cf6a78852ac1b005e0014aeb74579 Mon Sep 17 00:00:00 2001 From: Egge Date: Wed, 27 Mar 2024 17:51:04 +0100 Subject: [PATCH 01/62] added Queue --- src/utils.ts | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/utils.ts b/src/utils.ts index 826e2705..90f58813 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -360,3 +360,90 @@ export function sumProofs(proofs: Array) { export function decodePaymentRequest(paymentRequest: string) { return PaymentRequest.fromEncodedRequest(paymentRequest); } + +export class MessageNode { + private _value: string; + private _next: MessageNode | null; + + public get value(): string { + return this._value; + } + public set value(message: string) { + this._value = message; + } + public get next(): MessageNode | null { + return this._next; + } + public set next(node: MessageNode | null) { + this._next = node; + } + + constructor(message: string) { + this._value = message; + this._next = null; + } +} + +export class MessageQueue { + private _first: MessageNode | null; + private _last: MessageNode | null; + + public get first(): MessageNode | null { + return this._first; + } + public set first(messageNode: MessageNode | null) { + this._first = messageNode; + } + public get last(): MessageNode | null { + return this._last; + } + public set last(messageNode: MessageNode | null) { + this._last = messageNode; + } + private _size: number; + public get size(): number { + return this._size; + } + public set size(v: number) { + this._size = v; + } + + constructor() { + this._first = null; + this._last = null; + this._size = 0; + } + enqueue(message: string): boolean { + const newNode = new MessageNode(message); + if (this._size === 0 || !this._last) { + this._first = newNode; + this._last = newNode; + } else { + this._last.next = newNode; + this._last = newNode; + } + this._size++; + return true; + } + dequeue(): string | null { + if (this._size === 0 || !this._first) return null; + + const prev = this._first; + this._first = prev.next; + prev.next = null; + + this._size--; + return prev.value; + } +} + +export { + bigIntStringify, + bytesToNumber, + getDecodedToken, + getEncodedToken, + getEncodedTokenV4, + hexToNumber, + splitAmount, + getDefaultAmountPreference +}; From ac221bdd11f2e1ed12fd10a23514fc0690586eed Mon Sep 17 00:00:00 2001 From: Egge Date: Wed, 27 Mar 2024 17:51:13 +0100 Subject: [PATCH 02/62] started implementation --- src/WSConnection.ts | 75 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/WSConnection.ts diff --git a/src/WSConnection.ts b/src/WSConnection.ts new file mode 100644 index 00000000..1707c2f9 --- /dev/null +++ b/src/WSConnection.ts @@ -0,0 +1,75 @@ +import { MessageQueue } from './utils'; + +let _WS: typeof WebSocket; + +if (WebSocket) { + _WS = WebSocket; +} + +export function injectWebSocketImpl(ws: any) { + _WS = ws; +} + +export class WSConnection { + public readonly url: URL; + private ws: WebSocket | undefined; + private listeners: { [reqId: string]: (e: any) => any } = {}; + private messageQueue: MessageQueue; + private handlingInterval?: NodeJS.Timer; + + constructor(url: string) { + this.url = new URL(url); + this.messageQueue = new MessageQueue(); + } + async connect() { + return new Promise((res, rej) => { + try { + this.ws = new _WS(this.url); + } catch (err) { + rej(err); + return; + } + this.ws.onopen = res; + this.ws.onerror = rej; + this.ws.onmessage = (e) => { + this.messageQueue.enqueue(e.data); + if (!this.handlingInterval) { + this.handlingInterval = setInterval(this.handleNextMesage, 0); + } + }; + }); + } + + handleNextMesage() { + if (this.messageQueue.size === 0) { + clearInterval(this.handlingInterval); + return; + } + const message = this.messageQueue.dequeue() as string; + let parsed; + try { + parsed = JSON.parse(message) as Array; + } catch (e) { + console.log(e); + return; + } + let subId: string; + let data: any; + switch (parsed.length) { + case 2: { + // Must be notice + // TODO: Implement NOTICED + return; + } + case 3: { + subId = parsed[1]; + data = parsed[3]; + break; + } + default: { + return; + } + } + const len = parsed.length; + } +} From fbf6fb0e6aa4cb8074b97e7d2a17acda9a135281 Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 28 Mar 2024 10:28:09 +0100 Subject: [PATCH 03/62] added listeners --- src/WSConnection.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 1707c2f9..b9987882 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -1,3 +1,4 @@ +import { listeners } from 'process'; import { MessageQueue } from './utils'; let _WS: typeof WebSocket; @@ -13,7 +14,7 @@ export function injectWebSocketImpl(ws: any) { export class WSConnection { public readonly url: URL; private ws: WebSocket | undefined; - private listeners: { [reqId: string]: (e: any) => any } = {}; + private listeners: { [reqId: string]: Array } = {}; private messageQueue: MessageQueue; private handlingInterval?: NodeJS.Timer; @@ -40,6 +41,14 @@ export class WSConnection { }); } + addListener(subId: string, callback: () => any) { + (this.listeners[subId] = this.listeners[subId] || []).push(callback); + } + + removeListener(subId: string, callback: () => any) { + (this.listeners[subId] = this.listeners[subId] || []).filter((fn) => fn !== callback); + } + handleNextMesage() { if (this.messageQueue.size === 0) { clearInterval(this.handlingInterval); @@ -58,12 +67,13 @@ export class WSConnection { switch (parsed.length) { case 2: { // Must be notice - // TODO: Implement NOTICED + // TODO: Implement NOTICE return; } case 3: { subId = parsed[1]; data = parsed[3]; + this.listeners[subId].forEach((cb) => cb(data)); break; } default: { From b61c9632e5d3cac614f59aa796ea7cd77dd55a30 Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 28 Mar 2024 10:46:52 +0100 Subject: [PATCH 04/62] added subscription --- src/WSConnection.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index b9987882..a65f7ff0 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -11,6 +11,19 @@ export function injectWebSocketImpl(ws: any) { _WS = ws; } +class Subscription { + private connection: WSConnection; + private subId: string; + constructor(conn: WSConnection) { + // HACK: There might be way better ways to create an random string, but I want to create something without dependecies frist + this.subId = Math.random().toString(36).slice(-5); + this.connection = conn; + } + onmessage(cb: () => any) { + this.connection.addListener(this.subId, cb); + } +} + export class WSConnection { public readonly url: URL; private ws: WebSocket | undefined; @@ -49,6 +62,12 @@ export class WSConnection { (this.listeners[subId] = this.listeners[subId] || []).filter((fn) => fn !== callback); } + async ensureConenction() { + if (this.ws?.readyState !== 1) { + await this.connect(); + } + } + handleNextMesage() { if (this.messageQueue.size === 0) { clearInterval(this.handlingInterval); @@ -80,6 +99,9 @@ export class WSConnection { return; } } - const len = parsed.length; + } + + subscribe() { + return new Subscription(this); } } From 4234e7a63eadec27e45db8b4902cec1ee4d2edf0 Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 28 Mar 2024 12:08:15 +0100 Subject: [PATCH 05/62] added dev test --- test/WSConnection.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 test/WSConnection.test.ts diff --git a/test/WSConnection.test.ts b/test/WSConnection.test.ts new file mode 100644 index 00000000..a6519261 --- /dev/null +++ b/test/WSConnection.test.ts @@ -0,0 +1,17 @@ +import { WSConnection, injectWebSocketImpl } from '../src/WSConnection'; + +describe('testing WSConnection', () => { + test('connecting...', async () => { + injectWebSocketImpl(require('ws')); + const ws = new WSConnection('https://echo.websocket.org/'); + await ws.connect(); + const sub = ws.subscribe(); + await new Promise((res) => { + // @ts-ignore + sub.onmessage((e) => { + console.log(e); + res(e); + }); + }); + }); +}); From 28fb63f8809f30beb048b1823e9d1233e67d8a64 Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 28 Mar 2024 12:08:41 +0100 Subject: [PATCH 06/62] fixed binding --- src/WSConnection.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index a65f7ff0..72eb3e80 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -3,7 +3,7 @@ import { MessageQueue } from './utils'; let _WS: typeof WebSocket; -if (WebSocket) { +if (typeof WebSocket !== 'undefined') { _WS = WebSocket; } @@ -48,7 +48,7 @@ export class WSConnection { this.ws.onmessage = (e) => { this.messageQueue.enqueue(e.data); if (!this.handlingInterval) { - this.handlingInterval = setInterval(this.handleNextMesage, 0); + this.handlingInterval = setInterval(this.handleNextMesage.bind(this), 0); } }; }); @@ -71,6 +71,7 @@ export class WSConnection { handleNextMesage() { if (this.messageQueue.size === 0) { clearInterval(this.handlingInterval); + this.handlingInterval = undefined; return; } const message = this.messageQueue.dequeue() as string; From 1c68297a676401e0e5d26e2f1157809d8dc505ee Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 28 Mar 2024 12:18:03 +0100 Subject: [PATCH 07/62] added commands --- src/WSConnection.ts | 9 ++++++++- test/WSConnection.test.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 72eb3e80..dd5f8de8 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -1,6 +1,8 @@ import { listeners } from 'process'; import { MessageQueue } from './utils'; +type Command = 'check_quote' | 'check_proof'; + let _WS: typeof WebSocket; if (typeof WebSocket !== 'undefined') { @@ -18,6 +20,7 @@ class Subscription { // HACK: There might be way better ways to create an random string, but I want to create something without dependecies frist this.subId = Math.random().toString(36).slice(-5); this.connection = conn; + conn.sendCommand('check_proof', this.subId, { test: true }); } onmessage(cb: () => any) { this.connection.addListener(this.subId, cb); @@ -54,6 +57,10 @@ export class WSConnection { }); } + sendCommand(cmd: Command, subId: string, params: any) { + this.ws?.send(JSON.stringify(['REQ', subId, cmd, params])); + } + addListener(subId: string, callback: () => any) { (this.listeners[subId] = this.listeners[subId] || []).push(callback); } @@ -102,7 +109,7 @@ export class WSConnection { } } - subscribe() { + subscribe(cmd: 'check_proof' | 'check_quote') { return new Subscription(this); } } diff --git a/test/WSConnection.test.ts b/test/WSConnection.test.ts index a6519261..4beedbfb 100644 --- a/test/WSConnection.test.ts +++ b/test/WSConnection.test.ts @@ -5,7 +5,7 @@ describe('testing WSConnection', () => { injectWebSocketImpl(require('ws')); const ws = new WSConnection('https://echo.websocket.org/'); await ws.connect(); - const sub = ws.subscribe(); + const sub = ws.subscribe('check_proof'); await new Promise((res) => { // @ts-ignore sub.onmessage((e) => { From 6efb1538ddf2ae0f2facd26ec5d4a0299aeee722 Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 5 Apr 2024 09:22:15 +0200 Subject: [PATCH 08/62] added ws package --- package-lock.json | 31 ++++++++++++++++++++++++++++++- package.json | 3 ++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f1d6e7cc..8b92724d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,8 @@ "ts-jest-resolver": "^2.0.1", "ts-node": "^10.9.1", "typedoc": "^0.24.7", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "ws": "^8.16.0" } }, "node_modules/@ampproject/remapping": { @@ -6327,6 +6328,27 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -10966,6 +10988,13 @@ "signal-exit": "^3.0.7" } }, + "ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "dev": true, + "requires": {} + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index b40a5680..61d4ef63 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "ts-jest-resolver": "^2.0.1", "ts-node": "^10.9.1", "typedoc": "^0.24.7", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "ws": "^8.16.0" }, "dependencies": { "@cashu/crypto": "^0.2.7", From 6f2116895deecf0cfff048faaefbc615091f83f4 Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 5 Apr 2024 09:22:24 +0200 Subject: [PATCH 09/62] added unsub --- src/WSConnection.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index dd5f8de8..09aeb0fe 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -25,6 +25,7 @@ class Subscription { onmessage(cb: () => any) { this.connection.addListener(this.subId, cb); } + unsub() {} } export class WSConnection { @@ -61,6 +62,10 @@ export class WSConnection { this.ws?.send(JSON.stringify(['REQ', subId, cmd, params])); } + closeSubscription(subId: string) { + this.ws?.send(JSON.stringify(['CLOSE', subId])); + } + addListener(subId: string, callback: () => any) { (this.listeners[subId] = this.listeners[subId] || []).push(callback); } From c1f4ed5367e1a059f33feb8c8b8d5024fd3a023d Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 18 Apr 2024 15:10:03 +0200 Subject: [PATCH 10/62] added jsonrpc types --- src/model/types/index.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 8203aee6..392c2df0 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -17,3 +17,40 @@ export type InvoiceData = { memo?: string; expiry?: number; }; + +export type RpcSubId = string | number | null; + +type JsonRpcParams = any; + +type JsonRpcSuccess = { + jsonrpc: '2.0'; + result: T; + id: RpcSubId; +}; + +export type JsonRpcErrorObject = { + code: number; + message: string; + data?: any; +}; + +type JsonRpcError = { + jsonrpc: '2.0'; + error: JsonRpcErrorObject; + id: RpcSubId; +}; + +type JsonRpcRequest = { + jsonrpc: '2.0'; + method: string; + params?: JsonRpcParams; + id: Exclude; +}; + +type JsonRpcNotification = { + jsonrpc: '2.0'; + method: string; + params?: JsonRpcParams; +}; + +export type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcSuccess | JsonRpcError; From f3f044bd4d82cb24e09e2dbd9e4a0029a64e5347 Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 18 Apr 2024 15:10:29 +0200 Subject: [PATCH 11/62] moved to json rpc --- src/WSConnection.ts | 119 ++++++++++++++++++++++++++++---------------- 1 file changed, 76 insertions(+), 43 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 09aeb0fe..de026e52 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -1,5 +1,12 @@ import { listeners } from 'process'; import { MessageQueue } from './utils'; +import { + JsonRpcErrorObject, + JsonRpcMessage, + JsonRpcRequest, + JsonRpcResponse, + RpcSubId +} from './model/types'; type Command = 'check_quote' | 'check_proof'; @@ -13,33 +20,21 @@ export function injectWebSocketImpl(ws: any) { _WS = ws; } -class Subscription { - private connection: WSConnection; - private subId: string; - constructor(conn: WSConnection) { - // HACK: There might be way better ways to create an random string, but I want to create something without dependecies frist - this.subId = Math.random().toString(36).slice(-5); - this.connection = conn; - conn.sendCommand('check_proof', this.subId, { test: true }); - } - onmessage(cb: () => any) { - this.connection.addListener(this.subId, cb); - } - unsub() {} -} - export class WSConnection { public readonly url: URL; private ws: WebSocket | undefined; - private listeners: { [reqId: string]: Array } = {}; + private subListeners: { [subId: string]: Array } = {}; + private rpcListeners: { [rpsSubId: string]: any } = {}; private messageQueue: MessageQueue; private handlingInterval?: NodeJS.Timer; + private rpcId = 0; constructor(url: string) { this.url = new URL(url); this.messageQueue = new MessageQueue(); } - async connect() { + + connect() { return new Promise((res, rej) => { try { this.ws = new _WS(this.url); @@ -58,20 +53,34 @@ export class WSConnection { }); } - sendCommand(cmd: Command, subId: string, params: any) { - this.ws?.send(JSON.stringify(['REQ', subId, cmd, params])); + sendRequest(cmd: Command, subId: string, params: any) { + const id = this.rpcId; + this.rpcId++; + this.ws?.send(JSON.stringify({ jsonrpc: '2.0', method: cmd, params: { subId }, id: id })); } closeSubscription(subId: string) { this.ws?.send(JSON.stringify(['CLOSE', subId])); } - addListener(subId: string, callback: () => any) { - (this.listeners[subId] = this.listeners[subId] || []).push(callback); + addSubListener(subId: string, callback: () => any) { + (this.subListeners[subId] = this.subListeners[subId] || []).push(callback); + } + + addRpcListener( + callback: () => any, + errorCallback: (e: JsonRpcErrorObject) => any, + id: Exclude + ) { + this.rpcListeners[id] = { callback, errorCallback }; + } + + removeRpcListener(id: Exclude) { + delete this.rpcListeners[id]; } removeListener(subId: string, callback: () => any) { - (this.listeners[subId] = this.listeners[subId] || []).filter((fn) => fn !== callback); + (this.subListeners[subId] = this.subListeners[subId] || []).filter((fn) => fn !== callback); } async ensureConenction() { @@ -89,32 +98,56 @@ export class WSConnection { const message = this.messageQueue.dequeue() as string; let parsed; try { - parsed = JSON.parse(message) as Array; + parsed = JSON.parse(message) as JsonRpcMessage; + if ('result' in parsed && parsed.id != undefined) { + if (this.rpcListeners[parsed.id]) { + this.rpcListeners[parsed.id].callback(); + this.removeRpcListener(parsed.id); + } + } else if ('error' in parsed && parsed.id != undefined) { + if (this.rpcListeners[parsed.id]) { + this.rpcListeners[parsed.id].errorCallback(parsed.error); + this.removeRpcListener(parsed.id); + } + } else if ('method' in parsed) { + if ('id' in parsed) { + // This is a request + // Do nothing as mints should not send requests + } else { + const subId = parsed.params.subId; + if (!subId) { + return; + } + if (this.subListeners[subId].length > 0) { + this.subListeners[subId].forEach((cb) => cb()); + } + // This is a notification + } + } } catch (e) { console.log(e); return; } - let subId: string; - let data: any; - switch (parsed.length) { - case 2: { - // Must be notice - // TODO: Implement NOTICE - return; - } - case 3: { - subId = parsed[1]; - data = parsed[3]; - this.listeners[subId].forEach((cb) => cb(data)); - break; - } - default: { - return; - } - } } - subscribe(cmd: 'check_proof' | 'check_quote') { - return new Subscription(this); + createSubscription( + cmd: 'check_proof' | 'check_quote', + callback: () => any, + errorCallback: (e: Error) => any + ) { + if (this.ws?.readyState === 1) { + return errorCallback(new Error('Socket is not open')); + } + const subId = (Math.random() + 1).toString(36).substring(7); + this.addRpcListener( + () => { + this.addSubListener(subId, callback); + }, + (e: JsonRpcErrorObject) => { + errorCallback(new Error(e.message)); + }, + this.rpcId + ); + this.rpcId++; } } From 12045f9d2bae384f6c5bd86caaddbafdf4896456 Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 18 Apr 2024 15:13:34 +0200 Subject: [PATCH 12/62] added send command --- src/WSConnection.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index de026e52..df2d50b5 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -1,12 +1,5 @@ -import { listeners } from 'process'; import { MessageQueue } from './utils'; -import { - JsonRpcErrorObject, - JsonRpcMessage, - JsonRpcRequest, - JsonRpcResponse, - RpcSubId -} from './model/types'; +import { JsonRpcErrorObject, JsonRpcMessage, RpcSubId } from './model/types'; type Command = 'check_quote' | 'check_proof'; @@ -132,6 +125,7 @@ export class WSConnection { createSubscription( cmd: 'check_proof' | 'check_quote', + params: any, callback: () => any, errorCallback: (e: Error) => any ) { @@ -149,5 +143,6 @@ export class WSConnection { this.rpcId ); this.rpcId++; + this.sendRequest(cmd, subId, params); } } From 28e04912a938ba0346acc8f893d9982dbdd3829f Mon Sep 17 00:00:00 2001 From: Egge Date: Sun, 21 Apr 2024 09:35:27 +0200 Subject: [PATCH 13/62] calles format --- src/WSConnection.ts | 11 +++++------ src/model/types/index.ts | 12 ++++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index df2d50b5..5233de0b 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -1,5 +1,5 @@ import { MessageQueue } from './utils'; -import { JsonRpcErrorObject, JsonRpcMessage, RpcSubId } from './model/types'; +import { JsonRpcErrorObject, JsonRpcMessage, JsonRpcReqParams, RpcSubId } from './model/types'; type Command = 'check_quote' | 'check_proof'; @@ -46,10 +46,10 @@ export class WSConnection { }); } - sendRequest(cmd: Command, subId: string, params: any) { + sendRequest(params: JsonRpcReqParams) { const id = this.rpcId; this.rpcId++; - this.ws?.send(JSON.stringify({ jsonrpc: '2.0', method: cmd, params: { subId }, id: id })); + this.ws?.send(JSON.stringify({ jsonrpc: '2.0', method: 'sub', params, id: id })); } closeSubscription(subId: string) { @@ -124,8 +124,7 @@ export class WSConnection { } createSubscription( - cmd: 'check_proof' | 'check_quote', - params: any, + params: JsonRpcReqParams, callback: () => any, errorCallback: (e: Error) => any ) { @@ -143,6 +142,6 @@ export class WSConnection { this.rpcId ); this.rpcId++; - this.sendRequest(cmd, subId, params); + this.sendRequest(params); } } diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 392c2df0..c3b8b897 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -18,10 +18,18 @@ export type InvoiceData = { expiry?: number; }; +type RpcSubKinds = 'bolt11_mint_quote' | 'bolt11_melt_quote' | 'proof_state'; + export type RpcSubId = string | number | null; type JsonRpcParams = any; +export type JsonRpcReqParams = { + kind: RpcSubKinds; + filter: Array; + subId: string; +}; + type JsonRpcSuccess = { jsonrpc: '2.0'; result: T; @@ -42,8 +50,8 @@ type JsonRpcError = { type JsonRpcRequest = { jsonrpc: '2.0'; - method: string; - params?: JsonRpcParams; + method: 'sub'; + params: JsonRpcReqParams; id: Exclude; }; From a1603693fb6ef3b8102c82739254121f7eb2d761 Mon Sep 17 00:00:00 2001 From: Egge Date: Sun, 21 Apr 2024 10:40:59 +0200 Subject: [PATCH 14/62] export fixes and logs --- src/WSConnection.ts | 9 +++++---- src/index.ts | 1 + src/model/types/index.ts | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 5233de0b..878ee116 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -1,8 +1,6 @@ import { MessageQueue } from './utils'; import { JsonRpcErrorObject, JsonRpcMessage, JsonRpcReqParams, RpcSubId } from './model/types'; -type Command = 'check_quote' | 'check_proof'; - let _WS: typeof WebSocket; if (typeof WebSocket !== 'undefined') { @@ -49,7 +47,9 @@ export class WSConnection { sendRequest(params: JsonRpcReqParams) { const id = this.rpcId; this.rpcId++; - this.ws?.send(JSON.stringify({ jsonrpc: '2.0', method: 'sub', params, id: id })); + const message = JSON.stringify({ jsonrpc: '2.0', method: 'sub', params, id }); + console.log(message); + this.ws?.send(message); } closeSubscription(subId: string) { @@ -92,6 +92,7 @@ export class WSConnection { let parsed; try { parsed = JSON.parse(message) as JsonRpcMessage; + console.log(parsed); if ('result' in parsed && parsed.id != undefined) { if (this.rpcListeners[parsed.id]) { this.rpcListeners[parsed.id].callback(); @@ -128,7 +129,7 @@ export class WSConnection { callback: () => any, errorCallback: (e: Error) => any ) { - if (this.ws?.readyState === 1) { + if (this.ws?.readyState !== 1) { return errorCallback(new Error('Socket is not open')); } const subId = (Math.random() + 1).toString(36).substring(7); diff --git a/src/index.ts b/src/index.ts index fad66d8e..cd9c6b3d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { CashuMint } from './CashuMint.js'; import { CashuWallet } from './CashuWallet.js'; import { PaymentRequest } from './model/PaymentRequest.js'; +import { WSConnection } from './WSConnection.js'; import { setGlobalRequestOptions } from './request.js'; import { getEncodedToken, diff --git a/src/model/types/index.ts b/src/model/types/index.ts index c3b8b897..72cb6cea 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -26,7 +26,7 @@ type JsonRpcParams = any; export type JsonRpcReqParams = { kind: RpcSubKinds; - filter: Array; + filters: Array; subId: string; }; From c7676574185fcbec09b1e50185f11138d1dde262 Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 18 Jun 2024 06:40:11 +0200 Subject: [PATCH 15/62] fixed typo --- src/WSConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 878ee116..1d4baa45 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -76,7 +76,7 @@ export class WSConnection { (this.subListeners[subId] = this.subListeners[subId] || []).filter((fn) => fn !== callback); } - async ensureConenction() { + async ensureConnection() { if (this.ws?.readyState !== 1) { await this.connect(); } From 72bda7dee27ca4a351e3e48dcee51cfee1309e8e Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 18 Jun 2024 06:40:26 +0200 Subject: [PATCH 16/62] fixed connection types and params --- src/WSConnection.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 1d4baa45..0269a6f3 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -1,5 +1,11 @@ import { MessageQueue } from './utils'; -import { JsonRpcErrorObject, JsonRpcMessage, JsonRpcReqParams, RpcSubId } from './model/types'; +import { + JsonRpcErrorObject, + JsonRpcMessage, + JsonRpcNotification, + JsonRpcReqParams, + RpcSubId +} from './model/types'; let _WS: typeof WebSocket; @@ -92,7 +98,6 @@ export class WSConnection { let parsed; try { parsed = JSON.parse(message) as JsonRpcMessage; - console.log(parsed); if ('result' in parsed && parsed.id != undefined) { if (this.rpcListeners[parsed.id]) { this.rpcListeners[parsed.id].callback(); @@ -113,7 +118,8 @@ export class WSConnection { return; } if (this.subListeners[subId].length > 0) { - this.subListeners[subId].forEach((cb) => cb()); + const notification = parsed as JsonRpcNotification; + this.subListeners[subId].forEach((cb) => cb(notification.params.payload)); } // This is a notification } @@ -125,7 +131,7 @@ export class WSConnection { } createSubscription( - params: JsonRpcReqParams, + params: Omit, callback: () => any, errorCallback: (e: Error) => any ) { @@ -143,6 +149,6 @@ export class WSConnection { this.rpcId ); this.rpcId++; - this.sendRequest(params); + this.sendRequest({ ...params, subId }); } } From 703044fad4bc2b5f9fea0aa7ef3ac2e1b3de45ca Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 18 Jun 2024 06:40:39 +0200 Subject: [PATCH 17/62] added connection to mint --- src/CashuMint.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index b86a07eb..482a3e51 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -1,3 +1,4 @@ +import { WSConnection } from './WSConnection.js'; import type { CheckStatePayload, CheckStateResponse, @@ -33,6 +34,7 @@ import { handleMintInfoContactFieldDeprecated } from './legacy/nut-06.js'; * Class represents Cashu Mint API. This class contains Lower level functions that are implemented by CashuWallet. */ class CashuMint { + private ws?: WSConnection; /** * @param _mintUrl requires mint URL to create this object * @param _customRequest if passed, use custom request implementation for network communication with the mint @@ -438,6 +440,21 @@ class CashuMint { }): Promise { return CashuMint.restore(this._mintUrl, restorePayload, this._customRequest); } + + async connectWebSocket() { + if (this.ws) { + await this.ws.ensureConnection(); + } else { + const mintUrl = new URL(this._mintUrl); + this.ws = new WSConnection( + `${mintUrl.protocol === 'https' ? 'wss' : 'ws'}://${mintUrl.host}/v1/ws` + ); + await this.ws.connect(); + } + } + get webSocketConnection() { + return this.ws; + } } export { CashuMint }; From e1780ff7eb2ede22511a610b88bda8b5f8ad62d4 Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 18 Jun 2024 06:42:40 +0200 Subject: [PATCH 18/62] more type fixes --- src/WSConnection.ts | 6 +++--- src/model/types/index.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 0269a6f3..b694bb65 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -20,7 +20,7 @@ export function injectWebSocketImpl(ws: any) { export class WSConnection { public readonly url: URL; private ws: WebSocket | undefined; - private subListeners: { [subId: string]: Array } = {}; + private subListeners: { [subId: string]: Array<(payload: any) => any> } = {}; private rpcListeners: { [rpsSubId: string]: any } = {}; private messageQueue: MessageQueue; private handlingInterval?: NodeJS.Timer; @@ -62,7 +62,7 @@ export class WSConnection { this.ws?.send(JSON.stringify(['CLOSE', subId])); } - addSubListener(subId: string, callback: () => any) { + addSubListener(subId: string, callback: (payload: any) => any) { (this.subListeners[subId] = this.subListeners[subId] || []).push(callback); } @@ -132,7 +132,7 @@ export class WSConnection { createSubscription( params: Omit, - callback: () => any, + callback: (payload: any) => any, errorCallback: (e: Error) => any ) { if (this.ws?.readyState !== 1) { diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 72cb6cea..3f1f819f 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -55,7 +55,7 @@ type JsonRpcRequest = { id: Exclude; }; -type JsonRpcNotification = { +export type JsonRpcNotification = { jsonrpc: '2.0'; method: string; params?: JsonRpcParams; From 24fb18e95a465f3c174546feb0b695f68f310c56 Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 18 Jun 2024 06:59:46 +0200 Subject: [PATCH 19/62] added waitOnQuotePaid API --- src/CashuWallet.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 1b1a1b8a..be92bbb3 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -863,6 +863,21 @@ class CashuWallet { return states; } + async waitOnQuotePaid(quoteId: string, callback: (payload: any) => any) { + await this.mint.connectWebSocket(); + if (!this.mint.webSocketConnection) { + throw new Error('failed to establish WebSocket connection.'); + } + this.mint.webSocketConnection.createSubscription( + { kind: 'bolt11_mint_quote', filters: [quoteId] }, + callback, + (e) => { + throw new Error(e.message); + } + ); + //TODO: Return unsub function + } + /** * Creates blinded messages for a given amount * @param amount amount to create blinded messages for From d49db5f32e6e1886c6aa187e6420598747150e3b Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 18 Jun 2024 10:05:44 +0200 Subject: [PATCH 20/62] added unsub method --- src/WSConnection.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index b694bb65..2426f4cb 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -49,11 +49,12 @@ export class WSConnection { }; }); } - - sendRequest(params: JsonRpcReqParams) { + sendRequest(method: 'subscribe', params: JsonRpcReqParams): void; + sendRequest(method: 'unsubscribe', params: { subId: string }): void; + sendRequest(method: 'subscribe' | 'unsubscribe', params: Partial) { const id = this.rpcId; this.rpcId++; - const message = JSON.stringify({ jsonrpc: '2.0', method: 'sub', params, id }); + const message = JSON.stringify({ jsonrpc: '2.0', method, params, id }); console.log(message); this.ws?.send(message); } @@ -149,6 +150,17 @@ export class WSConnection { this.rpcId ); this.rpcId++; - this.sendRequest({ ...params, subId }); + this.sendRequest('subscribe', { ...params, subId }); + } + + cancelSubscription(subId: string, callback: () => any, errorCallback: (e: Error) => any) { + this.removeListener(subId, callback); + this.addRpcListener( + callback, + (e: JsonRpcErrorObject) => errorCallback(new Error(e.message)), + this.rpcId + ); + this.rpcId++; + this.sendRequest('unsubscribe', { subId }); } } From 7b351f22fd93b699e1fefe3a2d05747e43e35e60 Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 18 Jun 2024 10:11:54 +0200 Subject: [PATCH 21/62] added unsub to wallet --- src/CashuWallet.ts | 6 ++++-- src/WSConnection.ts | 10 +++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index be92bbb3..84ece60f 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -868,14 +868,16 @@ class CashuWallet { if (!this.mint.webSocketConnection) { throw new Error('failed to establish WebSocket connection.'); } - this.mint.webSocketConnection.createSubscription( + const subId = this.mint.webSocketConnection.createSubscription( { kind: 'bolt11_mint_quote', filters: [quoteId] }, callback, (e) => { throw new Error(e.message); } ); - //TODO: Return unsub function + return () => { + this.mint.webSocketConnection?.cancelSubscription(subId, callback); + }; } /** diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 2426f4cb..cd0e5ca5 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -79,7 +79,7 @@ export class WSConnection { delete this.rpcListeners[id]; } - removeListener(subId: string, callback: () => any) { + removeListener(subId: string, callback: (payload: any) => any) { (this.subListeners[subId] = this.subListeners[subId] || []).filter((fn) => fn !== callback); } @@ -151,15 +151,11 @@ export class WSConnection { ); this.rpcId++; this.sendRequest('subscribe', { ...params, subId }); + return subId; } - cancelSubscription(subId: string, callback: () => any, errorCallback: (e: Error) => any) { + cancelSubscription(subId: string, callback: (payload: any) => any) { this.removeListener(subId, callback); - this.addRpcListener( - callback, - (e: JsonRpcErrorObject) => errorCallback(new Error(e.message)), - this.rpcId - ); this.rpcId++; this.sendRequest('unsubscribe', { subId }); } From 03bf30b4c3fe04e9b7fc6b7552828dc9f42e3e4c Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 25 Jun 2024 20:49:36 +0200 Subject: [PATCH 22/62] clean up --- src/CashuWallet.ts | 6 ++++-- src/WSConnection.ts | 13 +++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 84ece60f..9973fb4b 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -863,14 +863,16 @@ class CashuWallet { return states; } - async waitOnQuotePaid(quoteId: string, callback: (payload: any) => any) { + async onQuotePaid(quoteId: string, callback: (payload: any) => any) { await this.mint.connectWebSocket(); if (!this.mint.webSocketConnection) { throw new Error('failed to establish WebSocket connection.'); } const subId = this.mint.webSocketConnection.createSubscription( { kind: 'bolt11_mint_quote', filters: [quoteId] }, - callback, + (payload) => { + console.log(payload); + }, (e) => { throw new Error(e.message); } diff --git a/src/WSConnection.ts b/src/WSConnection.ts index cd0e5ca5..7c2dd98d 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -49,6 +49,7 @@ export class WSConnection { }; }); } + sendRequest(method: 'subscribe', params: JsonRpcReqParams): void; sendRequest(method: 'unsubscribe', params: { subId: string }): void; sendRequest(method: 'subscribe' | 'unsubscribe', params: Partial) { @@ -67,7 +68,8 @@ export class WSConnection { (this.subListeners[subId] = this.subListeners[subId] || []).push(callback); } - addRpcListener( + //TODO: Move to RPCManagerClass + private addRpcListener( callback: () => any, errorCallback: (e: JsonRpcErrorObject) => any, id: Exclude @@ -75,11 +77,12 @@ export class WSConnection { this.rpcListeners[id] = { callback, errorCallback }; } - removeRpcListener(id: Exclude) { + //TODO: Move to RPCManagerClass + private removeRpcListener(id: Exclude) { delete this.rpcListeners[id]; } - removeListener(subId: string, callback: (payload: any) => any) { + private removeListener(subId: string, callback: (payload: any) => any) { (this.subListeners[subId] = this.subListeners[subId] || []).filter((fn) => fn !== callback); } @@ -89,7 +92,7 @@ export class WSConnection { } } - handleNextMesage() { + private handleNextMesage() { if (this.messageQueue.size === 0) { clearInterval(this.handlingInterval); this.handlingInterval = undefined; @@ -111,7 +114,6 @@ export class WSConnection { } } else if ('method' in parsed) { if ('id' in parsed) { - // This is a request // Do nothing as mints should not send requests } else { const subId = parsed.params.subId; @@ -122,7 +124,6 @@ export class WSConnection { const notification = parsed as JsonRpcNotification; this.subListeners[subId].forEach((cb) => cb(notification.params.payload)); } - // This is a notification } } } catch (e) { From c965e9d5dbb2c50fb6f7c74bc31c67ab5fee0464 Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 25 Jun 2024 20:49:45 +0200 Subject: [PATCH 23/62] began tests --- test/WSConnection.test.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/test/WSConnection.test.ts b/test/WSConnection.test.ts index 4beedbfb..d086f21f 100644 --- a/test/WSConnection.test.ts +++ b/test/WSConnection.test.ts @@ -1,17 +1,11 @@ import { WSConnection, injectWebSocketImpl } from '../src/WSConnection'; +import { CashuMint, CashuWallet } from '../src/index'; describe('testing WSConnection', () => { test('connecting...', async () => { injectWebSocketImpl(require('ws')); - const ws = new WSConnection('https://echo.websocket.org/'); - await ws.connect(); - const sub = ws.subscribe('check_proof'); - await new Promise((res) => { - // @ts-ignore - sub.onmessage((e) => { - console.log(e); - res(e); - }); - }); + const wallet = new CashuWallet(new CashuMint('ws://localhost:3338')); + const quote = await wallet.getMintQuote(21); + console.log(quote); }); }); From 5a84753a94bc0610c96e1c4be8604f8bd4f202f0 Mon Sep 17 00:00:00 2001 From: Egge Date: Wed, 26 Jun 2024 17:37:53 +0200 Subject: [PATCH 24/62] added error handling --- src/CashuMint.ts | 6 +++++- src/CashuWallet.ts | 32 ++++++++++++++++++++++++-------- src/WSConnection.ts | 7 ++++--- test/WSConnection.test.ts | 17 ++++++++++++++--- 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 482a3e51..e51dcf0a 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -449,7 +449,11 @@ class CashuMint { this.ws = new WSConnection( `${mintUrl.protocol === 'https' ? 'wss' : 'ws'}://${mintUrl.host}/v1/ws` ); - await this.ws.connect(); + try { + await this.ws.connect(); + } catch (e) { + throw new Error('Failed to connect to WebSocket...'); + } } } get webSocketConnection() { diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 9973fb4b..2185de8f 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -863,22 +863,38 @@ class CashuWallet { return states; } - async onQuotePaid(quoteId: string, callback: (payload: any) => any) { - await this.mint.connectWebSocket(); + async onQuotePaid( + quoteId: string, + callback: (payload: any) => any, + errorCallback: (e: Error) => void + ) { + try { + await this.mint.connectWebSocket(); + } catch (e) { + console.log('caught in quote paid'); + if (e instanceof Error) { + return errorCallback(e); + } else if (e) { + return errorCallback(new Error('Something went wrong')); + } + } if (!this.mint.webSocketConnection) { throw new Error('failed to establish WebSocket connection.'); } + const subCallback = (payload: any) => { + if (payload.paid) { + callback(payload); + } + }; const subId = this.mint.webSocketConnection.createSubscription( { kind: 'bolt11_mint_quote', filters: [quoteId] }, - (payload) => { - console.log(payload); - }, - (e) => { - throw new Error(e.message); + subCallback, + (e: Error) => { + errorCallback(e); } ); return () => { - this.mint.webSocketConnection?.cancelSubscription(subId, callback); + this.mint.webSocketConnection?.cancelSubscription(subId, subCallback); }; } diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 7c2dd98d..fa601978 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -33,9 +33,11 @@ export class WSConnection { connect() { return new Promise((res, rej) => { + console.log('running connect'); try { this.ws = new _WS(this.url); } catch (err) { + console.log(err); rej(err); return; } @@ -56,7 +58,6 @@ export class WSConnection { const id = this.rpcId; this.rpcId++; const message = JSON.stringify({ jsonrpc: '2.0', method, params, id }); - console.log(message); this.ws?.send(message); } @@ -120,7 +121,7 @@ export class WSConnection { if (!subId) { return; } - if (this.subListeners[subId].length > 0) { + if (this.subListeners[subId]?.length > 0) { const notification = parsed as JsonRpcNotification; this.subListeners[subId].forEach((cb) => cb(notification.params.payload)); } @@ -150,8 +151,8 @@ export class WSConnection { }, this.rpcId ); - this.rpcId++; this.sendRequest('subscribe', { ...params, subId }); + this.rpcId++; return subId; } diff --git a/test/WSConnection.test.ts b/test/WSConnection.test.ts index d086f21f..f300348e 100644 --- a/test/WSConnection.test.ts +++ b/test/WSConnection.test.ts @@ -4,8 +4,19 @@ import { CashuMint, CashuWallet } from '../src/index'; describe('testing WSConnection', () => { test('connecting...', async () => { injectWebSocketImpl(require('ws')); - const wallet = new CashuWallet(new CashuMint('ws://localhost:3338')); - const quote = await wallet.getMintQuote(21); - console.log(quote); + const wallet = new CashuWallet(new CashuMint('http://localhost:3338')); + await new Promise((res, rej) => { + const unsub = wallet.onQuotePaid( + 'XCV', + (pa) => { + console.log(pa); + res(pa); + }, + (e: Error) => { + rej(e); + } + ); + }); + console.log('Ended'); }); }); From ecfd530488f4b5252121183a7d7bcf1a862388b9 Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 28 Jun 2024 09:12:51 +0200 Subject: [PATCH 25/62] improved error handling --- src/CashuWallet.ts | 1 - src/WSConnection.ts | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 2185de8f..1f633e65 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -871,7 +871,6 @@ class CashuWallet { try { await this.mint.connectWebSocket(); } catch (e) { - console.log('caught in quote paid'); if (e instanceof Error) { return errorCallback(e); } else if (e) { diff --git a/src/WSConnection.ts b/src/WSConnection.ts index fa601978..ce0e3f29 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -33,16 +33,16 @@ export class WSConnection { connect() { return new Promise((res, rej) => { - console.log('running connect'); try { this.ws = new _WS(this.url); } catch (err) { - console.log(err); rej(err); return; } this.ws.onopen = res; - this.ws.onerror = rej; + this.ws.onerror = (e) => { + rej(e); + }; this.ws.onmessage = (e) => { this.messageQueue.enqueue(e.data); if (!this.handlingInterval) { @@ -55,6 +55,9 @@ export class WSConnection { sendRequest(method: 'subscribe', params: JsonRpcReqParams): void; sendRequest(method: 'unsubscribe', params: { subId: string }): void; sendRequest(method: 'subscribe' | 'unsubscribe', params: Partial) { + if (this.ws?.readyState !== 1) { + throw new Error('Socket not open...'); + } const id = this.rpcId; this.rpcId++; const message = JSON.stringify({ jsonrpc: '2.0', method, params, id }); From d636becf3df3ba89dbe2eb76819b59bc540aacc2 Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 28 Jun 2024 09:13:00 +0200 Subject: [PATCH 26/62] added some tests --- package-lock.json | 35 ++++++++++++++++++ package.json | 2 + test/WSConnection.test.ts | 77 ++++++++++++++++++++++++++++++++------- 3 files changed, 100 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8b92724d..8b85330f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "devDependencies": { "@types/jest": "^29.5.1", "@types/node-fetch": "^2.6.4", + "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/parser": "^5.59.2", "eslint": "^8.39.0", @@ -26,6 +27,7 @@ "eslint-plugin-n": "^15.7.0", "eslint-plugin-promise": "^6.1.1", "jest": "^29.5.0", + "mock-socket": "^9.3.1", "nock": "^13.3.3", "node-fetch": "^2.7.0", "prettier": "^2.8.8", @@ -1429,6 +1431,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.17", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.17.tgz", @@ -4967,6 +4978,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mock-socket": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", + "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7512,6 +7532,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "17.0.17", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.17.tgz", @@ -10044,6 +10073,12 @@ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, + "mock-socket": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", + "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/package.json b/package.json index 61d4ef63..b5b877c0 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "devDependencies": { "@types/jest": "^29.5.1", "@types/node-fetch": "^2.6.4", + "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/parser": "^5.59.2", "eslint": "^8.39.0", @@ -37,6 +38,7 @@ "eslint-plugin-n": "^15.7.0", "eslint-plugin-promise": "^6.1.1", "jest": "^29.5.0", + "mock-socket": "^9.3.1", "nock": "^13.3.3", "node-fetch": "^2.7.0", "prettier": "^2.8.8", diff --git a/test/WSConnection.test.ts b/test/WSConnection.test.ts index f300348e..f69b26a8 100644 --- a/test/WSConnection.test.ts +++ b/test/WSConnection.test.ts @@ -1,22 +1,71 @@ import { WSConnection, injectWebSocketImpl } from '../src/WSConnection'; -import { CashuMint, CashuWallet } from '../src/index'; +import { Server, WebSocket } from 'mock-socket'; + +injectWebSocketImpl(WebSocket); describe('testing WSConnection', () => { test('connecting...', async () => { - injectWebSocketImpl(require('ws')); - const wallet = new CashuWallet(new CashuMint('http://localhost:3338')); - await new Promise((res, rej) => { - const unsub = wallet.onQuotePaid( - 'XCV', - (pa) => { - console.log(pa); - res(pa); - }, - (e: Error) => { - rej(e); - } + const fakeUrl = 'ws://localhost:3338/v1/ws'; + const server = new Server(fakeUrl, { mock: false }); + const connectionSpy = jest.fn(); + server.on('connection', connectionSpy); + const conn = new WSConnection(fakeUrl); + await conn.connect(); + expect(connectionSpy).toHaveBeenCalled(); + server.stop(); + }); + test('requesting subscription', async () => { + const fakeUrl = 'ws://localhost:3338/v1/ws'; + const server = new Server(fakeUrl, { mock: false }); + const message = (await new Promise(async (res) => { + server.on('connection', (socket) => { + socket.on('message', (m) => { + res(m.toString()); + }); + }); + const conn = new WSConnection(fakeUrl); + await conn.connect(); + + const callback = jest.fn(); + const errorCallback = jest.fn(); + conn.createSubscription( + { kind: 'bolt11_mint_quote', filters: ['12345'] }, + callback, + errorCallback + ); + })) as string; + expect(JSON.parse(message)).toMatchObject({ + jsonrpc: '2.0', + method: 'subscribe', + params: { kind: 'bolt11_mint_quote', filters: ['12345'] } + }); + server.stop(); + }); + test('unsubscribing', async () => { + const fakeUrl = 'ws://localhost:3338/v1/ws'; + const server = new Server(fakeUrl, { mock: false }); + const message = await new Promise(async (res) => { + server.on('connection', (socket) => { + socket.on('message', (m) => { + const parsed = JSON.parse(m.toString()); + if (parsed.method === 'unsubscribe') res(parsed); + }); + }); + const conn = new WSConnection(fakeUrl); + await conn.connect(); + + conn.sendRequest('subscribe', { + subId: '12345', + kind: 'bolt11_mint_quote', + filters: ['12345'] + }); + const callback = jest.fn(); + const errorCallback = jest.fn(); + conn.createSubscription( + { kind: 'bolt11_mint_quote', filters: ['123'] }, + callback, + errorCallback ); }); - console.log('Ended'); }); }); From cf60dedde106d1c3408e24b4935f0889ac528bf5 Mon Sep 17 00:00:00 2001 From: Egge Date: Sun, 30 Jun 2024 06:02:42 +0200 Subject: [PATCH 27/62] more tests --- test/WSConnection.test.ts | 50 ++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/test/WSConnection.test.ts b/test/WSConnection.test.ts index f69b26a8..1bc52af2 100644 --- a/test/WSConnection.test.ts +++ b/test/WSConnection.test.ts @@ -54,14 +54,52 @@ describe('testing WSConnection', () => { const conn = new WSConnection(fakeUrl); await conn.connect(); - conn.sendRequest('subscribe', { - subId: '12345', - kind: 'bolt11_mint_quote', - filters: ['12345'] - }); const callback = jest.fn(); const errorCallback = jest.fn(); - conn.createSubscription( + const subId = conn.createSubscription( + { kind: 'bolt11_mint_quote', filters: ['123'] }, + callback, + errorCallback + ); + //TODO: Add assertion for subListenerLength once SubscriptionManager is modularised + conn.cancelSubscription(subId, callback); + }); + expect(message).toMatchObject({ jsonrpc: '2.0', method: 'unsubscribe' }); + server.stop(); + }); + test('handing a notification', async () => { + const fakeUrl = 'ws://localhost:3338/v1/ws'; + const server = new Server(fakeUrl, { mock: false }); + server.on('connection', (socket) => { + socket.on('message', (m) => { + console.log(m); + try { + const parsed = JSON.parse(m.toString()); + if (parsed.method === 'subscribe') { + const message = `{"jsonrpc": "2.0", "result": {"status": "OK", "subId": "${parsed.params.subId}", "id": ${parsed.id}}}`; + console.log(message); + socket.send(message); + setTimeout(() => { + const message = `{"jsonrpc": "2.0", "method": "subscribe", "params": {"subId": "${parsed.params.subId}", "payload": {"quote": "123", "request": "456", "paid": true, "expiry": 123}}}`; + console.log(message); + socket.send(message); + }, 500); + } + } catch { + console.log('Server parsing failed...'); + } + }); + }); + const conn = new WSConnection(fakeUrl); + await conn.connect(); + + await new Promise((res) => { + const callback = jest.fn((p) => { + console.log('Payload received! ', p); + res(p); + }); + const errorCallback = jest.fn(); + const subId = conn.createSubscription( { kind: 'bolt11_mint_quote', filters: ['123'] }, callback, errorCallback From ac5173285377ac89410b202f9b6619fa6de0e880 Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 11 Jul 2024 17:11:20 +0200 Subject: [PATCH 28/62] fixed notification test --- test/WSConnection.test.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/WSConnection.test.ts b/test/WSConnection.test.ts index 1bc52af2..cf1981d4 100644 --- a/test/WSConnection.test.ts +++ b/test/WSConnection.test.ts @@ -72,16 +72,13 @@ describe('testing WSConnection', () => { const server = new Server(fakeUrl, { mock: false }); server.on('connection', (socket) => { socket.on('message', (m) => { - console.log(m); try { const parsed = JSON.parse(m.toString()); if (parsed.method === 'subscribe') { - const message = `{"jsonrpc": "2.0", "result": {"status": "OK", "subId": "${parsed.params.subId}", "id": ${parsed.id}}}`; - console.log(message); + const message = `{"jsonrpc": "2.0", "result": {"status": "OK", "subId": "${parsed.params.subId}"}, "id": ${parsed.id}}`; socket.send(message); setTimeout(() => { const message = `{"jsonrpc": "2.0", "method": "subscribe", "params": {"subId": "${parsed.params.subId}", "payload": {"quote": "123", "request": "456", "paid": true, "expiry": 123}}}`; - console.log(message); socket.send(message); }, 500); } @@ -93,17 +90,17 @@ describe('testing WSConnection', () => { const conn = new WSConnection(fakeUrl); await conn.connect(); - await new Promise((res) => { - const callback = jest.fn((p) => { - console.log('Payload received! ', p); + const payload = await new Promise((res) => { + const callback = jest.fn((p: any) => { res(p); }); const errorCallback = jest.fn(); - const subId = conn.createSubscription( + conn.createSubscription( { kind: 'bolt11_mint_quote', filters: ['123'] }, callback, errorCallback ); }); + expect(payload).toMatchObject({ quote: '123', request: '456', paid: true, expiry: 123 }); }); }); From 9c6992e4aa592dbb0706b5235dda25ec7213019f Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 15 Jul 2024 10:29:30 +0200 Subject: [PATCH 29/62] added onMintPaid test --- test/WSConnection.test.ts | 1 + test/wallet.test.ts | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/test/WSConnection.test.ts b/test/WSConnection.test.ts index cf1981d4..9c5b183d 100644 --- a/test/WSConnection.test.ts +++ b/test/WSConnection.test.ts @@ -102,5 +102,6 @@ describe('testing WSConnection', () => { ); }); expect(payload).toMatchObject({ quote: '123', request: '456', paid: true, expiry: 123 }); + server.stop(); }); }); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 7ca5c27b..e29251b0 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -3,6 +3,11 @@ import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; import { CheckStateEnum, MeltQuoteResponse } from '../src/model/types/index.js'; import { getDecodedToken } from '../src/utils.js'; +import { Proof } from '@cashu/crypto/modules/common'; +import { injectWebSocketImpl } from '../src/WSConnection.js'; +import { Server, WebSocket } from 'mock-socket'; + +injectWebSocketImpl(WebSocket); const dummyKeysResp = { keysets: [ @@ -550,3 +555,38 @@ describe('deterministic', () => { ); }); }); + +describe('WebSocket Updates', () => { + test('mint update', async () => { + const fakeUrl = 'ws://localhost:3338/v1/ws'; + const server = new Server(fakeUrl, { mock: false }); + server.on('connection', (socket) => { + socket.on('message', (m) => { + console.log(m); + try { + const parsed = JSON.parse(m.toString()); + if (parsed.method === 'subscribe') { + const message = `{"jsonrpc": "2.0", "result": {"status": "OK", "subId": "${parsed.params.subId}"}, "id": ${parsed.id}}`; + socket.send(message); + setTimeout(() => { + const message = `{"jsonrpc": "2.0", "method": "subscribe", "params": {"subId": "${parsed.params.subId}", "payload": {"quote": "123", "request": "456", "paid": true, "expiry": 123}}}`; + socket.send(message); + }, 500); + } + } catch { + console.log('Server parsing failed...'); + } + }); + }); + const wallet = new CashuWallet(mint); + await new Promise((res) => { + const callback = (p: any) => { + console.log(p); + res(p); + }; + const test = wallet.onQuotePaid('123', callback, () => { + console.log('error'); + }); + }); + }); +}); From 7f0fe81d1eb72afffe4ebc1ae7a5018e8785f99f Mon Sep 17 00:00:00 2001 From: Egge Date: Wed, 17 Jul 2024 09:36:17 +0200 Subject: [PATCH 30/62] updated naming --- src/CashuWallet.ts | 2 +- test/wallet.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 1f633e65..a89ef060 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -863,7 +863,7 @@ class CashuWallet { return states; } - async onQuotePaid( + async onMintQuotePaid( quoteId: string, callback: (payload: any) => any, errorCallback: (e: Error) => void diff --git a/test/wallet.test.ts b/test/wallet.test.ts index e29251b0..7fd9b0af 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -584,7 +584,7 @@ describe('WebSocket Updates', () => { console.log(p); res(p); }; - const test = wallet.onQuotePaid('123', callback, () => { + const test = wallet.onMintQuotePaid('123', callback, () => { console.log('error'); }); }); From 2fbf05cafbc6f6c6dbb0d235c5318ac0d5504eca Mon Sep 17 00:00:00 2001 From: Egge Date: Wed, 17 Jul 2024 09:44:55 +0200 Subject: [PATCH 31/62] added onMeltQuote --- src/CashuWallet.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index a89ef060..01060c8a 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -897,6 +897,40 @@ class CashuWallet { }; } + async onMeltQuotePaid( + quoteId: string, + callback: (payload: MeltQuoteResponse) => any, + errorCallback: (e: Error) => void + ) { + try { + await this.mint.connectWebSocket(); + } catch (e) { + if (e instanceof Error) { + return errorCallback(e); + } else if (e) { + return errorCallback(new Error('Something went wrong')); + } + } + if (!this.mint.webSocketConnection) { + throw new Error('failed to establish WebSocket connection.'); + } + const subCallback = (payload: MeltQuoteResponse) => { + if (payload.state === 'PAID') { + callback(payload); + } + }; + const subId = this.mint.webSocketConnection.createSubscription( + { kind: 'bolt11_melt_quote', filters: [quoteId] }, + subCallback, + (e: Error) => { + errorCallback(e); + } + ); + return () => { + this.mint.webSocketConnection?.cancelSubscription(subId, subCallback); + }; + } + /** * Creates blinded messages for a given amount * @param amount amount to create blinded messages for From 387867dd658d5b1d2c5c461f3be05cdbcc158704 Mon Sep 17 00:00:00 2001 From: Egge Date: Wed, 17 Jul 2024 10:42:00 +0200 Subject: [PATCH 32/62] updated types --- src/CashuWallet.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 01060c8a..993d22ac 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -865,7 +865,7 @@ class CashuWallet { async onMintQuotePaid( quoteId: string, - callback: (payload: any) => any, + callback: (payload: MintQuoteResponse) => any, errorCallback: (e: Error) => void ) { try { @@ -880,8 +880,8 @@ class CashuWallet { if (!this.mint.webSocketConnection) { throw new Error('failed to establish WebSocket connection.'); } - const subCallback = (payload: any) => { - if (payload.paid) { + const subCallback = (payload: MintQuoteResponse) => { + if (payload.state === 'PAID') { callback(payload); } }; From 5f8d159122cdd394de69ca76ee285325ef32bc29 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 9 Sep 2024 14:37:12 +0200 Subject: [PATCH 33/62] ws: added disconnect method --- src/CashuMint.ts | 8 ++++++++ src/WSConnection.ts | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index e51dcf0a..800eb0d5 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -452,10 +452,18 @@ class CashuMint { try { await this.ws.connect(); } catch (e) { + console.log(e); throw new Error('Failed to connect to WebSocket...'); } } } + + disconnectWebSocket() { + if (this.ws) { + this.ws.close(); + } + } + get webSocketConnection() { return this.ws; } diff --git a/src/WSConnection.ts b/src/WSConnection.ts index ce0e3f29..e2273ab4 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -164,4 +164,10 @@ export class WSConnection { this.rpcId++; this.sendRequest('unsubscribe', { subId }); } + + close() { + if (this.ws) { + this.ws?.close(); + } + } } From 2a3589a8cfca90fee5dec561348d6934da1af943 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 9 Sep 2024 14:37:25 +0200 Subject: [PATCH 34/62] ws: updated test --- test/wallet.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 7fd9b0af..802f92fa 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -569,7 +569,7 @@ describe('WebSocket Updates', () => { const message = `{"jsonrpc": "2.0", "result": {"status": "OK", "subId": "${parsed.params.subId}"}, "id": ${parsed.id}}`; socket.send(message); setTimeout(() => { - const message = `{"jsonrpc": "2.0", "method": "subscribe", "params": {"subId": "${parsed.params.subId}", "payload": {"quote": "123", "request": "456", "paid": true, "expiry": 123}}}`; + const message = `{"jsonrpc": "2.0", "method": "subscribe", "params": {"subId": "${parsed.params.subId}", "payload": {"quote": "123", "request": "456", "state": "PAID", "paid": true, "expiry": 123}}}`; socket.send(message); }, 500); } @@ -579,12 +579,13 @@ describe('WebSocket Updates', () => { }); }); const wallet = new CashuWallet(mint); - await new Promise((res) => { + await new Promise((res, rej) => { const callback = (p: any) => { console.log(p); res(p); }; const test = wallet.onMintQuotePaid('123', callback, () => { + rej(); console.log('error'); }); }); From f4bf0629725fb2e479181b0e3eb9205ede10f5cc Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 9 Sep 2024 14:37:30 +0200 Subject: [PATCH 35/62] added integration test --- test/integration.test.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/integration.test.ts b/test/integration.test.ts index d6528d82..a105c33a 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -6,6 +6,8 @@ import { deriveKeysetId, getEncodedToken, sumProofs } from '../src/utils.js'; import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/curves/abstract/utils'; import { CheckStateEnum, MeltQuoteState } from '../src/model/types/index.js'; +import { injectWebSocketImpl } from '../src/WSConnection.js'; +import ws from 'ws'; dns.setDefaultResultOrder('ipv4first'); const externalInvoice = @@ -15,6 +17,8 @@ let request: Record | undefined; const mintUrl = 'http://localhost:3338'; const unit = 'sat'; +injectWebSocketImpl(ws); + describe('mint api', () => { test('get keys', async () => { const mint = new CashuMint(mintUrl); @@ -253,4 +257,27 @@ describe('mint api', () => { expect(response).toBeDefined(); expect(response.quote.state == MeltQuoteState.PAID).toBe(true); }); + test('websocket updates', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + + const mintQuote = await wallet.createMintQuote(21); + const callback = jest.fn(); + const res = await new Promise((res, rej) => { + wallet.onMintQuotePaid( + mintQuote.quote, + () => { + callback(); + res(1); + }, + (e) => { + console.log(e); + rej(e); + } + ); + }); + mint.disconnectWebSocket(); + expect(res).toBe(1); + expect(callback).toBeCalled(); + }); }); From 38c9dcb1a1c62ecde8a9b0f769a5ed927a8ef005 Mon Sep 17 00:00:00 2001 From: Egge Date: Sun, 29 Sep 2024 11:46:19 +0200 Subject: [PATCH 36/62] added connection promise --- src/WSConnection.ts | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index e2273ab4..9e94a0d3 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -6,6 +6,7 @@ import { JsonRpcReqParams, RpcSubId } from './model/types'; +import { OnOpenError, OnOpenSuccess } from './model/types/wallet/websocket'; let _WS: typeof WebSocket; @@ -20,6 +21,7 @@ export function injectWebSocketImpl(ws: any) { export class WSConnection { public readonly url: URL; private ws: WebSocket | undefined; + private connectionPromise: Promise | undefined; private subListeners: { [subId: string]: Array<(payload: any) => any> } = {}; private rpcListeners: { [rpsSubId: string]: any } = {}; private messageQueue: MessageQueue; @@ -32,24 +34,29 @@ export class WSConnection { } connect() { - return new Promise((res, rej) => { - try { - this.ws = new _WS(this.url); - } catch (err) { - rej(err); - return; - } - this.ws.onopen = res; - this.ws.onerror = (e) => { - rej(e); - }; - this.ws.onmessage = (e) => { - this.messageQueue.enqueue(e.data); - if (!this.handlingInterval) { - this.handlingInterval = setInterval(this.handleNextMesage.bind(this), 0); + if (!this.connectionPromise) { + this.connectionPromise = new Promise((res: OnOpenSuccess, rej: OnOpenError) => { + try { + this.ws = new _WS(this.url); + } catch (err) { + rej(err); + return; } - }; - }); + this.ws.onopen = () => { + res(); + }; + this.ws.onerror = () => { + rej(new Error('Failed to open WebSocket')); + }; + this.ws.onmessage = (e: MessageEvent) => { + this.messageQueue.enqueue(e.data); + if (!this.handlingInterval) { + this.handlingInterval = setInterval(this.handleNextMesage.bind(this), 0); + } + }; + }); + } + return this.connectionPromise; } sendRequest(method: 'subscribe', params: JsonRpcReqParams): void; From aeb7c3909c6ff160c12edbb1f82c480c5cb1a934 Mon Sep 17 00:00:00 2001 From: Egge Date: Sun, 29 Sep 2024 11:46:27 +0200 Subject: [PATCH 37/62] typed API --- src/CashuWallet.ts | 12 ++++++++---- src/model/types/wallet/websocket.ts | 5 +++++ 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 src/model/types/wallet/websocket.ts diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 993d22ac..ebaabb45 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -20,6 +20,9 @@ import { OutputAmounts, ProofState, BlindingData + MeltQuoteState, + CheckStateEntry, + MintQuoteResponse } from './model/types/index.js'; import { bytesToNumber, getDecodedToken, splitAmount, sumProofs, getKeepAmounts } from './utils.js'; import { hashToCurve, pointFromHex } from '@cashu/crypto/modules/common'; @@ -31,6 +34,7 @@ import { import { deriveBlindingFactor, deriveSecret } from '@cashu/crypto/modules/client/NUT09'; import { createP2PKsecret, getSignedProofs } from '@cashu/crypto/modules/client/NUT11'; import { type Proof as NUT11Proof } from '@cashu/crypto/modules/common/index'; +import { SubscriptionCanceller } from './model/types/wallet/websocket.js'; /** * The default number of proofs per denomination to keep in a wallet. @@ -865,16 +869,16 @@ class CashuWallet { async onMintQuotePaid( quoteId: string, - callback: (payload: MintQuoteResponse) => any, + callback: (payload: MintQuoteResponse) => void, errorCallback: (e: Error) => void - ) { + ): Promise { try { await this.mint.connectWebSocket(); } catch (e) { if (e instanceof Error) { - return errorCallback(e); + throw e; } else if (e) { - return errorCallback(new Error('Something went wrong')); + throw new Error('Something went wrong'); } } if (!this.mint.webSocketConnection) { diff --git a/src/model/types/wallet/websocket.ts b/src/model/types/wallet/websocket.ts new file mode 100644 index 00000000..a9249fcd --- /dev/null +++ b/src/model/types/wallet/websocket.ts @@ -0,0 +1,5 @@ +export type OnOpenSuccess = () => void; + +export type OnOpenError = (err: unknown) => void; + +export type SubscriptionCanceller = () => void; From 49e5beb6fc20fd68b4bc87688da75fe8ab5c1ecb Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 7 Oct 2024 10:04:20 +0200 Subject: [PATCH 38/62] remove subid key if no listener is left --- src/WSConnection.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 9e94a0d3..bf0c90de 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -94,7 +94,11 @@ export class WSConnection { } private removeListener(subId: string, callback: (payload: any) => any) { - (this.subListeners[subId] = this.subListeners[subId] || []).filter((fn) => fn !== callback); + if (this.subListeners[subId].length === 1) { + delete this.subListeners[subId]; + return; + } + this.subListeners[subId] = this.subListeners[subId].filter((fn: any) => fn !== callback); } async ensureConnection() { @@ -172,6 +176,10 @@ export class WSConnection { this.sendRequest('unsubscribe', { subId }); } + get activeSubscriptions() { + return Object.keys(this.subListeners); + } + close() { if (this.ws) { this.ws?.close(); From b010897a3ef40779b6b4903e9f070167efe6a806 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 7 Oct 2024 10:04:52 +0200 Subject: [PATCH 39/62] added quote update method --- src/CashuWallet.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index ebaabb45..2b64ef59 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -867,20 +867,31 @@ class CashuWallet { return states; } + async onMintQuoteUpdates( + quoteIds: Array, + callback: (payload: MintQuoteResponse) => void, + errorCallback: (e: Error) => void + ): Promise { + await this.mint.connectWebSocket(); + if (!this.mint.webSocketConnection) { + throw new Error('failed to establish WebSocket connection.'); + } + const subId = this.mint.webSocketConnection.createSubscription( + { kind: 'bolt11_mint_quote', filters: quoteIds }, + callback, + errorCallback + ); + return () => { + this.mint.webSocketConnection?.cancelSubscription(subId, callback); + }; + } + async onMintQuotePaid( quoteId: string, callback: (payload: MintQuoteResponse) => void, errorCallback: (e: Error) => void ): Promise { - try { - await this.mint.connectWebSocket(); - } catch (e) { - if (e instanceof Error) { - throw e; - } else if (e) { - throw new Error('Something went wrong'); - } - } + await this.mint.connectWebSocket(); if (!this.mint.webSocketConnection) { throw new Error('failed to establish WebSocket connection.'); } @@ -892,9 +903,7 @@ class CashuWallet { const subId = this.mint.webSocketConnection.createSubscription( { kind: 'bolt11_mint_quote', filters: [quoteId] }, subCallback, - (e: Error) => { - errorCallback(e); - } + errorCallback ); return () => { this.mint.webSocketConnection?.cancelSubscription(subId, subCallback); From 33221be75b8aaf3c5ac8db4b7e85f08081e174b9 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 7 Oct 2024 10:05:07 +0200 Subject: [PATCH 40/62] multiple ids integration test --- test/integration.test.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/integration.test.ts b/test/integration.test.ts index a105c33a..76beabbe 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -280,4 +280,40 @@ describe('mint api', () => { expect(res).toBe(1); expect(callback).toBeCalled(); }); + test('websocket mint quote updates on multiple ids', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + + const mintQuote1 = await wallet.createMintQuote(21); + const mintQuote2 = await wallet.createMintQuote(22); + + const callbackRef = jest.fn(); + const res = await new Promise(async (res, rej) => { + let counter = 0; + const unsub = await wallet.onMintQuoteUpdates( + [mintQuote1.quote, mintQuote2.quote], + (p) => { + console.log(p); + counter++; + callbackRef(); + if (counter === 4) { + unsub(); + res(1); + } + }, + (e) => { + counter++; + console.log(e); + if (counter === 4) { + unsub(); + rej(); + } + } + ); + }); + mint.disconnectWebSocket(); + expect(res).toBe(1); + expect(callbackRef).toHaveBeenCalledTimes(4); + expect(mint.webSocketConnection?.activeSubscriptions.length).toBe(0); + }); }); From 3339b7dcd3deb9904011e44d27326ad5ae8bf515 Mon Sep 17 00:00:00 2001 From: Egge Date: Wed, 16 Oct 2024 06:11:15 +0200 Subject: [PATCH 41/62] moved ws injection --- src/WSConnection.ts | 15 ++++----------- src/ws.ts | 13 +++++++++++++ test/WSConnection.test.ts | 3 ++- test/wallet.test.ts | 2 +- 4 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 src/ws.ts diff --git a/src/WSConnection.ts b/src/WSConnection.ts index bf0c90de..dca31a68 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -7,19 +7,11 @@ import { RpcSubId } from './model/types'; import { OnOpenError, OnOpenSuccess } from './model/types/wallet/websocket'; - -let _WS: typeof WebSocket; - -if (typeof WebSocket !== 'undefined') { - _WS = WebSocket; -} - -export function injectWebSocketImpl(ws: any) { - _WS = ws; -} +import { getWebSocketImpl } from './ws'; export class WSConnection { public readonly url: URL; + private readonly _WS: typeof WebSocket; private ws: WebSocket | undefined; private connectionPromise: Promise | undefined; private subListeners: { [subId: string]: Array<(payload: any) => any> } = {}; @@ -29,6 +21,7 @@ export class WSConnection { private rpcId = 0; constructor(url: string) { + this._WS = getWebSocketImpl(); this.url = new URL(url); this.messageQueue = new MessageQueue(); } @@ -37,7 +30,7 @@ export class WSConnection { if (!this.connectionPromise) { this.connectionPromise = new Promise((res: OnOpenSuccess, rej: OnOpenError) => { try { - this.ws = new _WS(this.url); + this.ws = new this._WS(this.url); } catch (err) { rej(err); return; diff --git a/src/ws.ts b/src/ws.ts new file mode 100644 index 00000000..f94d7b98 --- /dev/null +++ b/src/ws.ts @@ -0,0 +1,13 @@ +let _WS: typeof WebSocket; + +if (typeof WebSocket !== 'undefined') { + _WS = WebSocket; +} + +export function injectWebSocketImpl(ws: any) { + _WS = ws; +} + +export function getWebSocketImpl() { + return _WS; +} diff --git a/test/WSConnection.test.ts b/test/WSConnection.test.ts index 9c5b183d..b564cf19 100644 --- a/test/WSConnection.test.ts +++ b/test/WSConnection.test.ts @@ -1,5 +1,6 @@ -import { WSConnection, injectWebSocketImpl } from '../src/WSConnection'; +import { WSConnection } from '../src/WSConnection'; import { Server, WebSocket } from 'mock-socket'; +import { injectWebSocketImpl } from '../src/ws'; injectWebSocketImpl(WebSocket); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 802f92fa..7e17328e 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -4,8 +4,8 @@ import { CashuWallet } from '../src/CashuWallet.js'; import { CheckStateEnum, MeltQuoteResponse } from '../src/model/types/index.js'; import { getDecodedToken } from '../src/utils.js'; import { Proof } from '@cashu/crypto/modules/common'; -import { injectWebSocketImpl } from '../src/WSConnection.js'; import { Server, WebSocket } from 'mock-socket'; +import { injectWebSocketImpl } from '../src/ws.js'; injectWebSocketImpl(WebSocket); From 0d25233e5004a7f966f857251d4138de4db20b84 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 13:25:03 +0000 Subject: [PATCH 42/62] tiny merge fixes --- src/CashuWallet.ts | 4 +--- src/utils.ts | 11 ----------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 2b64ef59..1eed65ed 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -19,9 +19,7 @@ import { GetInfoResponse, OutputAmounts, ProofState, - BlindingData - MeltQuoteState, - CheckStateEntry, + BlindingData, MintQuoteResponse } from './model/types/index.js'; import { bytesToNumber, getDecodedToken, splitAmount, sumProofs, getKeepAmounts } from './utils.js'; diff --git a/src/utils.ts b/src/utils.ts index 90f58813..db33e561 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -436,14 +436,3 @@ export class MessageQueue { return prev.value; } } - -export { - bigIntStringify, - bytesToNumber, - getDecodedToken, - getEncodedToken, - getEncodedTokenV4, - hexToNumber, - splitAmount, - getDefaultAmountPreference -}; From b1fecea1a1547dcb56ef359de3e08057cd425951 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 14:20:54 +0000 Subject: [PATCH 43/62] fixed unsub test --- test/WSConnection.test.ts | 43 ++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/test/WSConnection.test.ts b/test/WSConnection.test.ts index b564cf19..bb7eb832 100644 --- a/test/WSConnection.test.ts +++ b/test/WSConnection.test.ts @@ -1,5 +1,5 @@ import { WSConnection } from '../src/WSConnection'; -import { Server, WebSocket } from 'mock-socket'; +import { Client, Server, WebSocket } from 'mock-socket'; import { injectWebSocketImpl } from '../src/ws'; injectWebSocketImpl(WebSocket); @@ -45,25 +45,40 @@ describe('testing WSConnection', () => { test('unsubscribing', async () => { const fakeUrl = 'ws://localhost:3338/v1/ws'; const server = new Server(fakeUrl, { mock: false }); - const message = await new Promise(async (res) => { + let wsSocket: Client; + let subId: string; + const conn = new WSConnection(fakeUrl); + await new Promise(async (res) => { server.on('connection', (socket) => { - socket.on('message', (m) => { - const parsed = JSON.parse(m.toString()); - if (parsed.method === 'unsubscribe') res(parsed); - }); + wsSocket = socket; + res(); }); - const conn = new WSConnection(fakeUrl); - await conn.connect(); - - const callback = jest.fn(); - const errorCallback = jest.fn(); - const subId = conn.createSubscription( + conn.connect(); + }); + const callback = jest.fn(); + const errorCallback = jest.fn(); + await new Promise((res) => { + wsSocket.on('message', (m) => { + const parsed = JSON.parse(m.toString()); + if (parsed.method === 'subscribe') { + const message = `{"jsonrpc": "2.0", "result": {"status": "OK", "subId": "${parsed.params.subId}"}, "id": ${parsed.id}}`; + wsSocket.send(message); + setTimeout(res, 0); + } + }); + subId = conn.createSubscription( { kind: 'bolt11_mint_quote', filters: ['123'] }, callback, errorCallback ); - //TODO: Add assertion for subListenerLength once SubscriptionManager is modularised - conn.cancelSubscription(subId, callback); + }); + + const message = await new Promise(async (res) => { + wsSocket.on('message', (m) => { + const parsed = JSON.parse(m.toString()); + if (parsed.method === 'unsubscribe') res(parsed); + }); + conn.cancelSubscription(subId!, callback); }); expect(message).toMatchObject({ jsonrpc: '2.0', method: 'unsubscribe' }); server.stop(); From 888a4a76f739813f2f45749c3f58d215b86464db Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 14:52:46 +0000 Subject: [PATCH 44/62] fixed integration import --- test/integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index 76beabbe..1f065671 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -6,8 +6,8 @@ import { deriveKeysetId, getEncodedToken, sumProofs } from '../src/utils.js'; import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/curves/abstract/utils'; import { CheckStateEnum, MeltQuoteState } from '../src/model/types/index.js'; -import { injectWebSocketImpl } from '../src/WSConnection.js'; import ws from 'ws'; +import { injectWebSocketImpl } from '../src/ws.js'; dns.setDefaultResultOrder('ipv4first'); const externalInvoice = From a86fab6206816ad447fdb508ae20d031dfa4f413 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 15:29:24 +0000 Subject: [PATCH 45/62] generalised methods --- src/CashuWallet.ts | 51 +++++---------------------------------- src/WSConnection.ts | 3 +++ test/wallet.test.ts | 58 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 62 insertions(+), 50 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 1eed65ed..d547aa1b 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -884,61 +884,22 @@ class CashuWallet { }; } - async onMintQuotePaid( - quoteId: string, - callback: (payload: MintQuoteResponse) => void, + async onMeltQuoteUpdates( + quoteIds: Array, + callback: (payload: MeltQuoteResponse) => void, errorCallback: (e: Error) => void ): Promise { await this.mint.connectWebSocket(); if (!this.mint.webSocketConnection) { throw new Error('failed to establish WebSocket connection.'); } - const subCallback = (payload: MintQuoteResponse) => { - if (payload.state === 'PAID') { - callback(payload); - } - }; const subId = this.mint.webSocketConnection.createSubscription( - { kind: 'bolt11_mint_quote', filters: [quoteId] }, - subCallback, + { kind: 'bolt11_melt_quote', filters: quoteIds }, + callback, errorCallback ); return () => { - this.mint.webSocketConnection?.cancelSubscription(subId, subCallback); - }; - } - - async onMeltQuotePaid( - quoteId: string, - callback: (payload: MeltQuoteResponse) => any, - errorCallback: (e: Error) => void - ) { - try { - await this.mint.connectWebSocket(); - } catch (e) { - if (e instanceof Error) { - return errorCallback(e); - } else if (e) { - return errorCallback(new Error('Something went wrong')); - } - } - if (!this.mint.webSocketConnection) { - throw new Error('failed to establish WebSocket connection.'); - } - const subCallback = (payload: MeltQuoteResponse) => { - if (payload.state === 'PAID') { - callback(payload); - } - }; - const subId = this.mint.webSocketConnection.createSubscription( - { kind: 'bolt11_melt_quote', filters: [quoteId] }, - subCallback, - (e: Error) => { - errorCallback(e); - } - ); - return () => { - this.mint.webSocketConnection?.cancelSubscription(subId, subCallback); + this.mint.webSocketConnection?.cancelSubscription(subId, callback); }; } diff --git a/src/WSConnection.ts b/src/WSConnection.ts index dca31a68..16c20c8a 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -47,6 +47,9 @@ export class WSConnection { this.handlingInterval = setInterval(this.handleNextMesage.bind(this), 0); } }; + this.ws.onclose = () => { + this.connectionPromise = undefined; + }; }); } return this.connectionPromise; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 7e17328e..48c6884b 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -1,7 +1,13 @@ import nock from 'nock'; import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; -import { CheckStateEnum, MeltQuoteResponse } from '../src/model/types/index.js'; +import { + CheckStateEnum, + MeltQuoteResponse, + MeltQuoteState, + MintQuoteResponse, + MintQuoteState +} from '../src/model/types/index.js'; import { getDecodedToken } from '../src/utils.js'; import { Proof } from '@cashu/crypto/modules/common'; import { Server, WebSocket } from 'mock-socket'; @@ -579,15 +585,57 @@ describe('WebSocket Updates', () => { }); }); const wallet = new CashuWallet(mint); - await new Promise((res, rej) => { - const callback = (p: any) => { + const state = await new Promise(async (res, rej) => { + const callback = (p: MintQuoteResponse) => { + if (p.state === MintQuoteState.PAID) { + res(p); + } + }; + const test = await wallet.onMintQuoteUpdates(['123'], callback, () => { + rej(); + console.log('error'); + }); + }); + expect(state).toMatchObject({ quote: '123' }); + mint.disconnectWebSocket(); + server.close(); + }); + test('melt update', async () => { + const fakeUrl = 'ws://localhost:3338/v1/ws'; + const server = new Server(fakeUrl, { mock: false }); + server.on('connection', (socket) => { + socket.on('message', (m) => { + console.log(m); + try { + const parsed = JSON.parse(m.toString()); + if (parsed.method === 'subscribe') { + const message = `{"jsonrpc": "2.0", "result": {"status": "OK", "subId": "${parsed.params.subId}"}, "id": ${parsed.id}}`; + socket.send(message); + setTimeout(() => { + const message = `{"jsonrpc": "2.0", "method": "subscribe", "params": {"subId": "${parsed.params.subId}", "payload": {"quote": "123", "request": "456", "state": "PAID", "paid": true, "expiry": 123}}}`; + socket.send(message); + }, 500); + } + } catch { + console.log('Server parsing failed...'); + } + }); + }); + const wallet = new CashuWallet(mint); + const state = await new Promise(async (res, rej) => { + const callback = (p: MeltQuoteResponse) => { console.log(p); - res(p); + if (p.state === MeltQuoteState.PAID) { + res(p); + } }; - const test = wallet.onMintQuotePaid('123', callback, () => { + const test = await wallet.onMeltQuoteUpdates(['123'], callback, (e) => { + console.log(e); rej(); console.log('error'); }); }); + expect(state).toMatchObject({ quote: '123' }); + server.close(); }); }); From 4c315dfbd733a198188f94784c55175771c28e7b Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 15:34:33 +0000 Subject: [PATCH 46/62] updated integration test --- test/integration.test.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index 1f065671..601def52 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -5,7 +5,12 @@ import dns from 'node:dns'; import { deriveKeysetId, getEncodedToken, sumProofs } from '../src/utils.js'; import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/curves/abstract/utils'; -import { CheckStateEnum, MeltQuoteState } from '../src/model/types/index.js'; +import { + CheckStateEnum, + MeltQuoteState, + MintQuotePayload, + MintQuoteState +} from '../src/model/types/index.js'; import ws from 'ws'; import { injectWebSocketImpl } from '../src/ws.js'; dns.setDefaultResultOrder('ipv4first'); @@ -264,10 +269,10 @@ describe('mint api', () => { const mintQuote = await wallet.createMintQuote(21); const callback = jest.fn(); const res = await new Promise((res, rej) => { - wallet.onMintQuotePaid( - mintQuote.quote, - () => { - callback(); + wallet.onMintQuoteUpdates( + [mintQuote.quote], + (p) => { + if (p.state === MintQuoteState.PAID) callback(); res(1); }, (e) => { From 71e2e476dc40526a0c3994062b229aabadaab026 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 16:39:30 +0000 Subject: [PATCH 47/62] added on proofStateUpdates --- src/CashuWallet.ts | 21 +++++++++++++++++++++ test/integration.test.ts | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index d547aa1b..c3b9a630 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -903,6 +903,27 @@ class CashuWallet { }; } + async onProofStateUpdates( + proofs: Array, + callback: (payload: ProofState) => void, + errorCallback: (e: Error) => void + ): Promise { + await this.mint.connectWebSocket(); + if (!this.mint.webSocketConnection) { + throw new Error('failed to establish WebSocket connection.'); + } + const enc = new TextEncoder(); + const ys = proofs.map((p: Proof) => hashToCurve(enc.encode(p.secret)).toHex(true)); + const subId = this.mint.webSocketConnection.createSubscription( + { kind: 'proof_state', filters: ys }, + callback, + errorCallback + ); + return () => { + this.mint.webSocketConnection?.cancelSubscription(subId, callback); + }; + } + /** * Creates blinded messages for a given amount * @param amount amount to create blinded messages for diff --git a/test/integration.test.ts b/test/integration.test.ts index 601def52..389c9a8a 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -9,7 +9,9 @@ import { CheckStateEnum, MeltQuoteState, MintQuotePayload, - MintQuoteState + MintQuoteResponse, + MintQuoteState, + ProofState } from '../src/model/types/index.js'; import ws from 'ws'; import { injectWebSocketImpl } from '../src/ws.js'; @@ -321,4 +323,36 @@ describe('mint api', () => { expect(callbackRef).toHaveBeenCalledTimes(4); expect(mint.webSocketConnection?.activeSubscriptions.length).toBe(0); }); + test('websocket proof state + mint quote updates', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + + const quote = await wallet.createMintQuote(63); + await new Promise((res) => { + function handleUpdate(p: MintQuoteResponse) { + if (p.state === MintQuoteState.PAID) { + res(); + } + } + wallet.onMintQuoteUpdates([quote.quote], handleUpdate, (e) => { + console.log(e); + }); + }); + const { proofs } = await wallet.mintProofs(63, quote.quote); + const data = await new Promise((res) => { + wallet.onProofStateUpdates( + proofs, + (p) => { + if (p.state === CheckStateEnum.SPENT) { + res(p); + } + }, + (e) => { + console.log(e); + } + ); + wallet.swap(63, proofs); + }); + console.log('final ws boss', data); + }); }); From 406c54de75dc009e54bc94b408c93f8366ba4c7f Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 17:20:08 +0000 Subject: [PATCH 48/62] added proofmap to state updates --- src/CashuWallet.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index c3b9a630..d781df93 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -905,7 +905,7 @@ class CashuWallet { async onProofStateUpdates( proofs: Array, - callback: (payload: ProofState) => void, + callback: (payload: ProofState & { proof: Proof }) => void, errorCallback: (e: Error) => void ): Promise { await this.mint.connectWebSocket(); @@ -913,10 +913,17 @@ class CashuWallet { throw new Error('failed to establish WebSocket connection.'); } const enc = new TextEncoder(); - const ys = proofs.map((p: Proof) => hashToCurve(enc.encode(p.secret)).toHex(true)); + const proofMap: { [y: string]: Proof } = {}; + for (let i = 0; i < proofs.length; i++) { + const y = hashToCurve(enc.encode(proofs[i].secret)).toHex(true); + proofMap[y] = proofs[i]; + } + const ys = Object.keys(proofMap); const subId = this.mint.webSocketConnection.createSubscription( { kind: 'proof_state', filters: ys }, - callback, + (p: ProofState) => { + callback({ ...p, proof: proofMap[p.Y] }); + }, errorCallback ); return () => { From 589a6ac8e8e58ef2e4418f70dbcb3bedf34db5a2 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 17:20:17 +0000 Subject: [PATCH 49/62] fixed integration test (again) --- test/integration.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index 389c9a8a..edf3a57c 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -274,8 +274,10 @@ describe('mint api', () => { wallet.onMintQuoteUpdates( [mintQuote.quote], (p) => { - if (p.state === MintQuoteState.PAID) callback(); - res(1); + if (p.state === MintQuoteState.PAID) { + callback(); + res(1); + } }, (e) => { console.log(e); @@ -299,8 +301,7 @@ describe('mint api', () => { let counter = 0; const unsub = await wallet.onMintQuoteUpdates( [mintQuote1.quote, mintQuote2.quote], - (p) => { - console.log(p); + () => { counter++; callbackRef(); if (counter === 4) { @@ -308,9 +309,8 @@ describe('mint api', () => { res(1); } }, - (e) => { + () => { counter++; - console.log(e); if (counter === 4) { unsub(); rej(); @@ -353,6 +353,6 @@ describe('mint api', () => { ); wallet.swap(63, proofs); }); - console.log('final ws boss', data); - }); + mint.disconnectWebSocket(); + }, 10000); }); From 181a7c4c12743bcab869f6d4e23ee17644419909 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 17:49:37 +0000 Subject: [PATCH 50/62] bumped nutshell integration version --- .github/workflows/nutshell-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 3e881511..ef25b0c1 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -8,7 +8,7 @@ jobs: steps: - name: Pull and start mint run: | - docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_INPUT_FEE_PPK=100 -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.0 poetry run mint + docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_INPUT_FEE_PPK=100 -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.2 poetry run mint - name: Check running containers run: docker ps From f9b3fd7d1b385573c88b3e4e6ae00ac338753243 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 17:52:37 +0000 Subject: [PATCH 51/62] fixed integration test (rly...) --- test/integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index edf3a57c..d8a7522d 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -351,7 +351,7 @@ describe('mint api', () => { console.log(e); } ); - wallet.swap(63, proofs); + wallet.swap(21, proofs); }); mint.disconnectWebSocket(); }, 10000); From fc94cb265adb9525828b215963e507949a49409d Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 19:24:15 +0000 Subject: [PATCH 52/62] added onMintQuotePaid helper --- src/CashuWallet.ts | 26 +++++++++++++++++++++++++- test/integration.test.ts | 11 ++--------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index d781df93..77f320d8 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -20,7 +20,8 @@ import { OutputAmounts, ProofState, BlindingData, - MintQuoteResponse + MintQuoteResponse, + MintQuoteState } from './model/types/index.js'; import { bytesToNumber, getDecodedToken, splitAmount, sumProofs, getKeepAmounts } from './utils.js'; import { hashToCurve, pointFromHex } from '@cashu/crypto/modules/common'; @@ -884,6 +885,29 @@ class CashuWallet { }; } + async onMintQuotePaid( + quoteId: string, + callback: (payload: MintQuoteResponse) => void, + errorCallback: (e: Error) => void + ): Promise { + await this.mint.connectWebSocket(); + if (!this.mint.webSocketConnection) { + throw new Error('failed to establish WebSocket connection.'); + } + const subId = this.mint.webSocketConnection.createSubscription( + { kind: 'bolt11_mint_quote', filters: [quoteId] }, + (p: MintQuoteResponse) => { + if (p.state === MintQuoteState.PAID) { + callback(p); + } + }, + errorCallback + ); + return () => { + this.mint.webSocketConnection?.cancelSubscription(subId, callback); + }; + } + async onMeltQuoteUpdates( quoteIds: Array, callback: (payload: MeltQuoteResponse) => void, diff --git a/test/integration.test.ts b/test/integration.test.ts index d8a7522d..508371ad 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -328,15 +328,8 @@ describe('mint api', () => { const wallet = new CashuWallet(mint); const quote = await wallet.createMintQuote(63); - await new Promise((res) => { - function handleUpdate(p: MintQuoteResponse) { - if (p.state === MintQuoteState.PAID) { - res(); - } - } - wallet.onMintQuoteUpdates([quote.quote], handleUpdate, (e) => { - console.log(e); - }); + await new Promise((res, rej) => { + wallet.onMintQuotePaid(quote.quote, res, rej); }); const { proofs } = await wallet.mintProofs(63, quote.quote); const data = await new Promise((res) => { From 5d663bfcfac8e8af22867c042d0a4519d6a39c57 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 19:26:03 +0000 Subject: [PATCH 53/62] added onMeltQuotePaid helper --- src/CashuWallet.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 77f320d8..7615a27f 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -21,7 +21,8 @@ import { ProofState, BlindingData, MintQuoteResponse, - MintQuoteState + MintQuoteState, + MeltQuoteState } from './model/types/index.js'; import { bytesToNumber, getDecodedToken, splitAmount, sumProofs, getKeepAmounts } from './utils.js'; import { hashToCurve, pointFromHex } from '@cashu/crypto/modules/common'; @@ -885,6 +886,29 @@ class CashuWallet { }; } + async onMeltQuotePaid( + quoteId: string, + callback: (payload: MeltQuoteResponse) => void, + errorCallback: (e: Error) => void + ): Promise { + await this.mint.connectWebSocket(); + if (!this.mint.webSocketConnection) { + throw new Error('failed to establish WebSocket connection.'); + } + const subId = this.mint.webSocketConnection.createSubscription( + { kind: 'bolt11_melt_quote', filters: [quoteId] }, + (p: MeltQuoteResponse) => { + if (p.state === MeltQuoteState.PAID) { + callback(p); + } + }, + errorCallback + ); + return () => { + this.mint.webSocketConnection?.cancelSubscription(subId, callback); + }; + } + async onMintQuotePaid( quoteId: string, callback: (payload: MintQuoteResponse) => void, From 681a6f6dfd60520da799db77f5aac90cbc8060fe Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 4 Nov 2024 19:34:33 +0000 Subject: [PATCH 54/62] added jsdocs --- src/CashuMint.ts | 6 ++++++ src/CashuWallet.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 800eb0d5..6f02881e 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -441,6 +441,9 @@ class CashuMint { return CashuMint.restore(this._mintUrl, restorePayload, this._customRequest); } + /** + * Tries to establish a websocket connection with the websocket mint url according to NUT-17 + */ async connectWebSocket() { if (this.ws) { await this.ws.ensureConnection(); @@ -458,6 +461,9 @@ class CashuMint { } } + /** + * Closes a websocket connection + */ disconnectWebSocket() { if (this.ws) { this.ws.close(); diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 7615a27f..8280783a 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -867,6 +867,13 @@ class CashuWallet { return states; } + /** + * Register a callback to be called whenever a mint quote's state changes + * @param quoteIds List of mint quote IDs that should be subscribed to + * @param callback Callback function that will be called whenever a mint quote state changes + * @param errorCallback + * @returns + */ async onMintQuoteUpdates( quoteIds: Array, callback: (payload: MintQuoteResponse) => void, @@ -886,6 +893,13 @@ class CashuWallet { }; } + /** + * Register a callback to be called whenever a melt quote's state changes + * @param quoteIds List of melt quote IDs that should be subscribed to + * @param callback Callback function that will be called whenever a melt quote state changes + * @param errorCallback + * @returns + */ async onMeltQuotePaid( quoteId: string, callback: (payload: MeltQuoteResponse) => void, @@ -909,6 +923,13 @@ class CashuWallet { }; } + /** + * Register a callback to be called when a single mint quote gets paid + * @param quoteId Mint quote id that should be subscribed to + * @param callback Callback function that will be called when this mint quote gets paid + * @param errorCallback + * @returns + */ async onMintQuotePaid( quoteId: string, callback: (payload: MintQuoteResponse) => void, @@ -932,6 +953,13 @@ class CashuWallet { }; } + /** + * Register a callback to be called when a single melt quote gets paid + * @param quoteId Melt quote id that should be subscribed to + * @param callback Callback function that will be called when this melt quote gets paid + * @param errorCallback + * @returns + */ async onMeltQuoteUpdates( quoteIds: Array, callback: (payload: MeltQuoteResponse) => void, @@ -951,6 +979,13 @@ class CashuWallet { }; } + /** + * Register a callback to be called whenever a subscribed proof state changes + * @param proofs List of proofs that should be subscribed to + * @param callback Callback function that will be called whenever a proof's state changes + * @param errorCallback + * @returns + */ async onProofStateUpdates( proofs: Array, callback: (payload: ProofState & { proof: Proof }) => void, From acf3d9efd607da3cd7e82bdf09852dc31893903d Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 5 Nov 2024 14:20:24 +0000 Subject: [PATCH 55/62] refactor onPaid handlers --- src/CashuWallet.ts | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 8280783a..ad68a254 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -905,22 +905,15 @@ class CashuWallet { callback: (payload: MeltQuoteResponse) => void, errorCallback: (e: Error) => void ): Promise { - await this.mint.connectWebSocket(); - if (!this.mint.webSocketConnection) { - throw new Error('failed to establish WebSocket connection.'); - } - const subId = this.mint.webSocketConnection.createSubscription( - { kind: 'bolt11_melt_quote', filters: [quoteId] }, - (p: MeltQuoteResponse) => { + return this.onMeltQuoteUpdates( + [quoteId], + (p) => { if (p.state === MeltQuoteState.PAID) { callback(p); } }, errorCallback ); - return () => { - this.mint.webSocketConnection?.cancelSubscription(subId, callback); - }; } /** @@ -935,22 +928,15 @@ class CashuWallet { callback: (payload: MintQuoteResponse) => void, errorCallback: (e: Error) => void ): Promise { - await this.mint.connectWebSocket(); - if (!this.mint.webSocketConnection) { - throw new Error('failed to establish WebSocket connection.'); - } - const subId = this.mint.webSocketConnection.createSubscription( - { kind: 'bolt11_mint_quote', filters: [quoteId] }, - (p: MintQuoteResponse) => { + return this.onMintQuoteUpdates( + [quoteId], + (p) => { if (p.state === MintQuoteState.PAID) { callback(p); } }, errorCallback ); - return () => { - this.mint.webSocketConnection?.cancelSubscription(subId, callback); - }; } /** From 1a8cb47b228db17ce79b28a4927f5499e737e25c Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 5 Nov 2024 15:15:45 +0000 Subject: [PATCH 56/62] fixed mint url --- src/CashuMint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 6f02881e..12b9511e 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -450,7 +450,7 @@ class CashuMint { } else { const mintUrl = new URL(this._mintUrl); this.ws = new WSConnection( - `${mintUrl.protocol === 'https' ? 'wss' : 'ws'}://${mintUrl.host}/v1/ws` + `${mintUrl.protocol === 'https:' ? 'wss' : 'ws'}://${mintUrl.host}/v1/ws` ); try { await this.ws.connect(); From 6c83a5aa427082231b7e2905808776c4226614ba Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 8 Nov 2024 15:21:38 +0000 Subject: [PATCH 57/62] handle mint urls with base path --- src/CashuMint.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 12b9511e..0434aa34 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -449,8 +449,16 @@ class CashuMint { await this.ws.ensureConnection(); } else { const mintUrl = new URL(this._mintUrl); + const wsSegment = 'v1/ws'; + if (mintUrl.pathname) { + if (mintUrl.pathname.endsWith('/')) { + mintUrl.pathname += wsSegment; + } else { + mintUrl.pathname += '/' + wsSegment; + } + } this.ws = new WSConnection( - `${mintUrl.protocol === 'https:' ? 'wss' : 'ws'}://${mintUrl.host}/v1/ws` + `${mintUrl.protocol === 'https:' ? 'wss' : 'ws'}://${mintUrl.host}${mintUrl.pathname}` ); try { await this.ws.connect(); From 0a1ec50cba492c32d1aedb6eefe14cbe2ea1d183 Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 15 Nov 2024 10:42:07 +0000 Subject: [PATCH 58/62] added ConnectionManager --- src/WSConnection.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index 16c20c8a..ec2ff5db 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -9,6 +9,27 @@ import { import { OnOpenError, OnOpenSuccess } from './model/types/wallet/websocket'; import { getWebSocketImpl } from './ws'; +export class ConnectionManager { + static instace: ConnectionManager; + private connectionMap: Map = new Map(); + + static getInstance() { + if (!ConnectionManager.instace) { + ConnectionManager.instace = new ConnectionManager(); + } + return ConnectionManager.instace; + } + + getConnection(url: string): WSConnection { + if (this.connectionMap.has(url)) { + return this.connectionMap.get(url) as WSConnection; + } + const newConn = new WSConnection(url); + this.connectionMap.set(url, newConn); + return newConn; + } +} + export class WSConnection { public readonly url: URL; private readonly _WS: typeof WebSocket; From 338759056e2b197eee6cd3812153e88ba0a19f9a Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 15 Nov 2024 10:44:04 +0000 Subject: [PATCH 59/62] added ConnectionManager to CashuMint --- src/CashuMint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 0434aa34..7cb4f2cf 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -1,4 +1,4 @@ -import { WSConnection } from './WSConnection.js'; +import { ConnectionManager, WSConnection } from './WSConnection.js'; import type { CheckStatePayload, CheckStateResponse, @@ -457,7 +457,7 @@ class CashuMint { mintUrl.pathname += '/' + wsSegment; } } - this.ws = new WSConnection( + this.ws = ConnectionManager.getInstance().getConnection( `${mintUrl.protocol === 'https:' ? 'wss' : 'ws'}://${mintUrl.host}${mintUrl.pathname}` ); try { From dfe515ccb8a170b30e40e5657ae3eae3f7d9333e Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 15 Nov 2024 10:54:14 +0000 Subject: [PATCH 60/62] adjusted integraton test --- test/integration.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index 508371ad..09676653 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -2,14 +2,12 @@ import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; import dns from 'node:dns'; -import { deriveKeysetId, getEncodedToken, sumProofs } from '../src/utils.js'; +import { getEncodedToken, sumProofs } from '../src/utils.js'; import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/curves/abstract/utils'; import { CheckStateEnum, MeltQuoteState, - MintQuotePayload, - MintQuoteResponse, MintQuoteState, ProofState } from '../src/model/types/index.js'; @@ -270,18 +268,20 @@ describe('mint api', () => { const mintQuote = await wallet.createMintQuote(21); const callback = jest.fn(); - const res = await new Promise((res, rej) => { - wallet.onMintQuoteUpdates( + const res = await new Promise(async (res, rej) => { + const unsub = await wallet.onMintQuoteUpdates( [mintQuote.quote], (p) => { if (p.state === MintQuoteState.PAID) { callback(); res(1); + unsub(); } }, (e) => { console.log(e); rej(e); + unsub(); } ); }); From dde08a1d9cb6239446c512ed6b96376e0f4ee1af Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 15 Nov 2024 13:03:42 +0000 Subject: [PATCH 61/62] typos --- src/WSConnection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WSConnection.ts b/src/WSConnection.ts index ec2ff5db..a6a5b0e0 100644 --- a/src/WSConnection.ts +++ b/src/WSConnection.ts @@ -36,7 +36,7 @@ export class WSConnection { private ws: WebSocket | undefined; private connectionPromise: Promise | undefined; private subListeners: { [subId: string]: Array<(payload: any) => any> } = {}; - private rpcListeners: { [rpsSubId: string]: any } = {}; + private rpcListeners: { [rpcSubId: string]: any } = {}; private messageQueue: MessageQueue; private handlingInterval?: NodeJS.Timer; private rpcId = 0; @@ -159,7 +159,7 @@ export class WSConnection { } } } catch (e) { - console.log(e); + console.error(e); return; } } From aa5d3ee332b38935b56dd8bb9adc89b7a229cae6 Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 15 Nov 2024 13:53:29 +0000 Subject: [PATCH 62/62] fixed integration test --- test/integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index 821e7c14..982be47f 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -339,7 +339,7 @@ describe('mint api', () => { await new Promise((res, rej) => { wallet.onMintQuotePaid(quote.quote, res, rej); }); - const { proofs } = await wallet.mintProofs(63, quote.quote); + const proofs = await wallet.mintProofs(63, quote.quote); const data = await new Promise((res) => { wallet.onProofStateUpdates( proofs,