Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NUT-17: WebSocket updates #202

Open
wants to merge 60 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
714ac60
added Queue
Egge21M Mar 27, 2024
ac221bd
started implementation
Egge21M Mar 27, 2024
fbf6fb0
added listeners
Egge21M Mar 28, 2024
b61c963
added subscription
Egge21M Mar 28, 2024
4234e7a
added dev test
Egge21M Mar 28, 2024
28fb63f
fixed binding
Egge21M Mar 28, 2024
1c68297
added commands
Egge21M Mar 28, 2024
6efb153
added ws package
Egge21M Apr 5, 2024
6f21168
added unsub
Egge21M Apr 5, 2024
c1f4ed5
added jsonrpc types
Egge21M Apr 18, 2024
f3f044b
moved to json rpc
Egge21M Apr 18, 2024
12045f9
added send command
Egge21M Apr 18, 2024
28e0491
calles format
Egge21M Apr 21, 2024
a160369
export fixes and logs
Egge21M Apr 21, 2024
c767657
fixed typo
Egge21M Jun 18, 2024
72bda7d
fixed connection types and params
Egge21M Jun 18, 2024
703044f
added connection to mint
Egge21M Jun 18, 2024
e1780ff
more type fixes
Egge21M Jun 18, 2024
24fb18e
added waitOnQuotePaid API
Egge21M Jun 18, 2024
d49db5f
added unsub method
Egge21M Jun 18, 2024
7b351f2
added unsub to wallet
Egge21M Jun 18, 2024
03bf30b
clean up
Egge21M Jun 25, 2024
c965e9d
began tests
Egge21M Jun 25, 2024
5a84753
added error handling
Egge21M Jun 26, 2024
ecfd530
improved error handling
Egge21M Jun 28, 2024
d636bec
added some tests
Egge21M Jun 28, 2024
cf60ded
more tests
Egge21M Jun 30, 2024
ac51732
fixed notification test
Egge21M Jul 11, 2024
9c6992e
added onMintPaid test
Egge21M Jul 15, 2024
7f0fe81
updated naming
Egge21M Jul 17, 2024
2fbf05c
added onMeltQuote
Egge21M Jul 17, 2024
387867d
updated types
Egge21M Jul 17, 2024
5f8d159
ws: added disconnect method
Egge21M Sep 9, 2024
2a3589a
ws: updated test
Egge21M Sep 9, 2024
f4bf062
added integration test
Egge21M Sep 9, 2024
38c9dcb
added connection promise
Egge21M Sep 29, 2024
aeb7c39
typed API
Egge21M Sep 29, 2024
49e5beb
remove subid key if no listener is left
Egge21M Oct 7, 2024
b010897
added quote update method
Egge21M Oct 7, 2024
33221be
multiple ids integration test
Egge21M Oct 7, 2024
3339b7d
moved ws injection
Egge21M Oct 16, 2024
0d25233
tiny merge fixes
Egge21M Nov 4, 2024
b1fecea
fixed unsub test
Egge21M Nov 4, 2024
888a4a7
fixed integration import
Egge21M Nov 4, 2024
a86fab6
generalised methods
Egge21M Nov 4, 2024
4c315df
updated integration test
Egge21M Nov 4, 2024
71e2e47
added on proofStateUpdates
Egge21M Nov 4, 2024
406c54d
added proofmap to state updates
Egge21M Nov 4, 2024
589a6ac
fixed integration test (again)
Egge21M Nov 4, 2024
181a7c4
bumped nutshell integration version
Egge21M Nov 4, 2024
f9b3fd7
fixed integration test (rly...)
Egge21M Nov 4, 2024
fc94cb2
added onMintQuotePaid helper
Egge21M Nov 4, 2024
5d663bf
added onMeltQuotePaid helper
Egge21M Nov 4, 2024
681a6f6
added jsdocs
Egge21M Nov 4, 2024
acf3d9e
refactor onPaid handlers
Egge21M Nov 5, 2024
1a8cb47
fixed mint url
Egge21M Nov 5, 2024
6c83a5a
handle mint urls with base path
Egge21M Nov 8, 2024
0a1ec50
added ConnectionManager
Egge21M Nov 15, 2024
3387590
added ConnectionManager to CashuMint
Egge21M Nov 15, 2024
dfe515c
adjusted integraton test
Egge21M Nov 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/nutshell-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 65 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -37,14 +38,16 @@
"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",
"ts-jest": "^29.1.0",
"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",
Expand Down
43 changes: 43 additions & 0 deletions src/CashuMint.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ConnectionManager, WSConnection } from './WSConnection.js';
import type {
CheckStatePayload,
CheckStateResponse,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -438,6 +440,47 @@ class CashuMint {
}): Promise<PostRestoreResponse> {
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();
} 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 = ConnectionManager.getInstance().getConnection(
`${mintUrl.protocol === 'https:' ? 'wss' : 'ws'}://${mintUrl.host}${mintUrl.pathname}`
);
try {
await this.ws.connect();
} catch (e) {
console.log(e);
throw new Error('Failed to connect to WebSocket...');
}
}
}

/**
* Closes a websocket connection
*/
disconnectWebSocket() {
if (this.ws) {
this.ws.close();
}
}

get webSocketConnection() {
return this.ws;
}
}

export { CashuMint };
139 changes: 138 additions & 1 deletion src/CashuWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import {
GetInfoResponse,
OutputAmounts,
ProofState,
BlindingData
BlindingData,
MintQuoteResponse,
MintQuoteState,
MeltQuoteState
} from './model/types/index.js';
import { bytesToNumber, getDecodedToken, splitAmount, sumProofs, getKeepAmounts } from './utils.js';
import { hashToCurve, pointFromHex } from '@cashu/crypto/modules/common';
Expand All @@ -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.
Expand Down Expand Up @@ -863,6 +867,139 @@ 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<string>,
callback: (payload: MintQuoteResponse) => void,
errorCallback: (e: Error) => void
): Promise<SubscriptionCanceller> {
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);
};
}

/**
* 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,
errorCallback: (e: Error) => void
): Promise<SubscriptionCanceller> {
return this.onMeltQuoteUpdates(
[quoteId],
(p) => {
if (p.state === MeltQuoteState.PAID) {
callback(p);
}
},
errorCallback
);
}

/**
* 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,
errorCallback: (e: Error) => void
): Promise<SubscriptionCanceller> {
return this.onMintQuoteUpdates(
[quoteId],
(p) => {
if (p.state === MintQuoteState.PAID) {
callback(p);
}
},
errorCallback
);
}

/**
* 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<string>,
callback: (payload: MeltQuoteResponse) => void,
errorCallback: (e: Error) => void
): Promise<SubscriptionCanceller> {
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: quoteIds },
callback,
errorCallback
);
return () => {
this.mint.webSocketConnection?.cancelSubscription(subId, callback);
};
}

/**
* 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<Proof>,
callback: (payload: ProofState & { proof: Proof }) => void,
errorCallback: (e: Error) => void
): Promise<SubscriptionCanceller> {
await this.mint.connectWebSocket();
if (!this.mint.webSocketConnection) {
throw new Error('failed to establish WebSocket connection.');
}
const enc = new TextEncoder();
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 },
(p: ProofState) => {
callback({ ...p, proof: proofMap[p.Y] });
},
errorCallback
);
return () => {
this.mint.webSocketConnection?.cancelSubscription(subId, callback);
};
}

/**
* Creates blinded messages for a given amount
* @param amount amount to create blinded messages for
Expand Down
Loading
Loading