From d1c0acf96a354075ac521921a8550a916b3a3379 Mon Sep 17 00:00:00 2001 From: Egge Date: Sat, 16 Nov 2024 22:12:00 +0000 Subject: [PATCH 1/9] started Info Class --- src/model/MintInfo.ts | 86 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/model/MintInfo.ts diff --git a/src/model/MintInfo.ts b/src/model/MintInfo.ts new file mode 100644 index 00000000..eb686621 --- /dev/null +++ b/src/model/MintInfo.ts @@ -0,0 +1,86 @@ +import { GetInfoResponse, MPPMethod, WebSocketSupport } from './types'; + +type GetInfoResponse2 = { + name: string; + pubkey: string; + version: string; + description?: string; + description_long?: string; + contact: Array; + nuts: { + '4': { + // Minting + methods: Array; + disabled: boolean; + }; + '5': { + // Melting + methods: Array; + disabled: boolean; + }; + '7'?: { + // Token state check + supported: boolean; + }; + '8'?: { + // Overpaid melt fees + supported: boolean; + }; + '9'?: { + // Restore + supported: boolean; + }; + '10'?: { + // Spending conditions + supported: boolean; + }; + '11'?: { + // P2PK + supported: boolean; + }; + '12'?: { + // DLEQ + supported: boolean; + }; + '14'?: { + // HTLCs + supported: boolean; + }; + '15'?: { + // MPP + methods: Array; + }; + '17'?: { + // WebSockets + supported: Array; + }; + }; + motd?: string; +}; + +export class MintInfo { + private mintInfo: GetInfoResponse; + + constructor(info: GetInfoResponse) { + this.mintInfo = info; + } + + isSupported(num: 17): { supported: boolean; params?: Array }; + isSupported(num: 15): { supported: boolean; params?: Array }; + isSupported(num: number) { + switch (num) { + case 17: { + return this.checkNut17(); + } + case 15: { + } + } + } + private checkNut17() { + if (this.mintInfo.nuts['17'] && this.mintInfo.nuts[17].supported.length > 0) { + return { supported: true, params: this.mintInfo.nuts[17]?.supported }; + } + return { supported: false }; + } + private checkNut15() {} +} From 911b05cf062f2b66ef667d5a25327540d36f974c Mon Sep 17 00:00:00 2001 From: Egge Date: Sat, 16 Nov 2024 23:32:07 +0100 Subject: [PATCH 2/9] added optional nuts --- src/model/MintInfo.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/model/MintInfo.ts b/src/model/MintInfo.ts index eb686621..eb188b71 100644 --- a/src/model/MintInfo.ts +++ b/src/model/MintInfo.ts @@ -59,28 +59,50 @@ type GetInfoResponse2 = { }; export class MintInfo { - private mintInfo: GetInfoResponse; + private readonly mintInfo: GetInfoResponse; constructor(info: GetInfoResponse) { this.mintInfo = info; } + isSupported(num: 7 | 8 | 9 | 10 | 11 | 12 | 14): { supported: boolean }; isSupported(num: 17): { supported: boolean; params?: Array }; isSupported(num: 15): { supported: boolean; params?: Array }; isSupported(num: number) { switch (num) { + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 14: { + return this.checkGenericNut(num); + } case 17: { return this.checkNut17(); } case 15: { + return this.checkNut15(); } } } + private checkGenericNut(num: 7 | 8 | 9 | 10 | 11 | 12 | 14) { + if (this.mintInfo.nuts[num]?.supported) { + return { supported: true }; + } + return { supported: false }; + } private checkNut17() { if (this.mintInfo.nuts['17'] && this.mintInfo.nuts[17].supported.length > 0) { - return { supported: true, params: this.mintInfo.nuts[17]?.supported }; + return { supported: true, params: this.mintInfo.nuts[17].supported }; + } + return { supported: false }; + } + private checkNut15() { + if (this.mintInfo.nuts['15'] && this.mintInfo.nuts[15].methods.length > 0) { + return { supported: true, params: this.mintInfo.nuts[15].methods }; } return { supported: false }; } - private checkNut15() {} } From bbd384422c287cc7059b728fc1e4d60995c7381b Mon Sep 17 00:00:00 2001 From: Egge Date: Sat, 16 Nov 2024 23:41:15 +0100 Subject: [PATCH 3/9] added mint/melt --- src/model/MintInfo.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/model/MintInfo.ts b/src/model/MintInfo.ts index eb188b71..8106d60d 100644 --- a/src/model/MintInfo.ts +++ b/src/model/MintInfo.ts @@ -57,6 +57,7 @@ type GetInfoResponse2 = { }; motd?: string; }; +import { GetInfoResponse, MPPMethod, SwapMethod, WebSocketSupport } from './types'; export class MintInfo { private readonly mintInfo: GetInfoResponse; @@ -65,11 +66,16 @@ export class MintInfo { this.mintInfo = info; } + isSupported(num: 4 | 5): { disabled: boolean; params: Array }; isSupported(num: 7 | 8 | 9 | 10 | 11 | 12 | 14): { supported: boolean }; isSupported(num: 17): { supported: boolean; params?: Array }; isSupported(num: 15): { supported: boolean; params?: Array }; isSupported(num: number) { switch (num) { + case 4: + case 5: { + return this.checkMintMelt(num); + } case 7: case 8: case 9: @@ -93,6 +99,13 @@ export class MintInfo { } return { supported: false }; } + private checkMintMelt(num: 4 | 5) { + const mintMeltInfo = this.mintInfo.nuts[num]; + if (mintMeltInfo && mintMeltInfo.methods.length > 0 && !mintMeltInfo.disabled) { + return { disabled: false, params: mintMeltInfo.methods }; + } + return { disabled: true, params: mintMeltInfo.methods }; + } private checkNut17() { if (this.mintInfo.nuts['17'] && this.mintInfo.nuts[17].supported.length > 0) { return { supported: true, params: this.mintInfo.nuts[17].supported }; From cbfd9d2cb6630e2f35653d5e744a4b75d5431308 Mon Sep 17 00:00:00 2001 From: Egge Date: Sat, 16 Nov 2024 23:41:22 +0100 Subject: [PATCH 4/9] added getters --- src/model/MintInfo.ts | 87 ++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 59 deletions(-) diff --git a/src/model/MintInfo.ts b/src/model/MintInfo.ts index 8106d60d..18ec15cd 100644 --- a/src/model/MintInfo.ts +++ b/src/model/MintInfo.ts @@ -1,62 +1,3 @@ -import { GetInfoResponse, MPPMethod, WebSocketSupport } from './types'; - -type GetInfoResponse2 = { - name: string; - pubkey: string; - version: string; - description?: string; - description_long?: string; - contact: Array; - nuts: { - '4': { - // Minting - methods: Array; - disabled: boolean; - }; - '5': { - // Melting - methods: Array; - disabled: boolean; - }; - '7'?: { - // Token state check - supported: boolean; - }; - '8'?: { - // Overpaid melt fees - supported: boolean; - }; - '9'?: { - // Restore - supported: boolean; - }; - '10'?: { - // Spending conditions - supported: boolean; - }; - '11'?: { - // P2PK - supported: boolean; - }; - '12'?: { - // DLEQ - supported: boolean; - }; - '14'?: { - // HTLCs - supported: boolean; - }; - '15'?: { - // MPP - methods: Array; - }; - '17'?: { - // WebSockets - supported: Array; - }; - }; - motd?: string; -}; import { GetInfoResponse, MPPMethod, SwapMethod, WebSocketSupport } from './types'; export class MintInfo { @@ -118,4 +59,32 @@ export class MintInfo { } return { supported: false }; } + + get contact() { + return this.mintInfo.contact; + } + + get description() { + return this.mintInfo.description; + } + + get description_long() { + return this.mintInfo.description_long; + } + + get name() { + return this.mintInfo.name; + } + + get pubkey() { + return this.mintInfo.pubkey; + } + + get version() { + return this.mintInfo.version; + } + + get motd() { + return this.mintInfo.motd; + } } From 8eb42d254dcf29f94561f116510c0d51f80b6830 Mon Sep 17 00:00:00 2001 From: Egge Date: Sat, 16 Nov 2024 23:50:42 +0100 Subject: [PATCH 5/9] added class to wallet --- src/CashuWallet.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 3661202b..2db06203 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -46,6 +46,7 @@ import { createP2PKsecret, getSignedProofs } from '@cashu/crypto/modules/client/ import { type Proof as NUT11Proof, DLEQ } from '@cashu/crypto/modules/common/index'; import { SubscriptionCanceller } from './model/types/wallet/websocket.js'; import { verifyDLEQProof_reblind } from '@cashu/crypto/modules/client/NUT12'; +import { MintInfo } from './model/MintInfo.js'; /** * The default number of proofs per denomination to keep in a wallet. */ @@ -66,7 +67,7 @@ class CashuWallet { private _keysets: Array = []; private _seed: Uint8Array | undefined = undefined; private _unit = DEFAULT_UNIT; - private _mintInfo: GetInfoResponse | undefined = undefined; + private _mintInfo: MintInfo | undefined = undefined; private _denominationTarget = DEFAULT_DENOMINATION_TARGET; mint: CashuMint; @@ -102,7 +103,7 @@ class CashuWallet { if (keys) keys.forEach((key: MintKeys) => this._keys.set(key.id, key)); if (options?.unit) this._unit = options?.unit; if (options?.keysets) this._keysets = options.keysets; - if (options?.mintInfo) this._mintInfo = options.mintInfo; + if (options?.mintInfo) this._mintInfo = new MintInfo(options.mintInfo); if (options?.denominationTarget) { this._denominationTarget = options.denominationTarget; } @@ -134,7 +135,7 @@ class CashuWallet { get keysets(): Array { return this._keysets; } - get mintInfo(): GetInfoResponse { + get mintInfo(): MintInfo { if (!this._mintInfo) { throw new Error('Mint info not loaded'); } @@ -145,8 +146,9 @@ class CashuWallet { * Get information about the mint * @returns mint info */ - async getMintInfo(): Promise { - this._mintInfo = await this.mint.getInfo(); + async getMintInfo(): Promise { + const infoRes = await this.mint.getInfo(); + this._mintInfo = new MintInfo(infoRes); return this._mintInfo; } From e8e00b9b6fcf60fdd7b707b2602825c1b07af196 Mon Sep 17 00:00:00 2001 From: Egge Date: Sat, 16 Nov 2024 23:50:46 +0100 Subject: [PATCH 6/9] added tests --- test/wallet.test.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/wallet.test.ts b/test/wallet.test.ts index ba075d95..76c2f1dd 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -12,6 +12,7 @@ import { getDecodedToken } from '../src/utils.js'; import { Proof } from '@cashu/crypto/modules/common'; import { Server, WebSocket } from 'mock-socket'; import { injectWebSocketImpl } from '../src/ws.js'; +import { MintInfo } from '../src/model/MintInfo.js'; injectWebSocketImpl(WebSocket); @@ -68,8 +69,18 @@ describe('test info', () => { { method: 'twitter', info: '@me' }, { method: 'nostr', info: 'npub1337' } ]); - expect(info.nuts?.['17']).toEqual({ - supported: [ + expect(info.isSupported(10)).toEqual({ supported: true }); + expect(info.isSupported(5)).toEqual({ + disabled: false, + params: [ + { method: 'bolt11', unit: 'sat' }, + { method: 'bolt11', unit: 'usd' }, + { method: 'bolt11', unit: 'eur' } + ] + }); + expect(info.isSupported(17)).toEqual({ + supported: true, + params: [ { method: 'bolt11', unit: 'sat', @@ -87,7 +98,7 @@ describe('test info', () => { } ] }); - expect(info).toEqual(mintInfoResp); + expect(info).toEqual(new MintInfo(mintInfoResp)); }); test('test info with deprecated contact field', async () => { // mintInfoRespDeprecated is the same as mintInfoResp but with the contact field in the old format From 5a36b42c791c98c04955bad02a664733db06d552 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 18 Nov 2024 14:57:15 +0000 Subject: [PATCH 7/9] consisten property accessors --- src/model/MintInfo.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/MintInfo.ts b/src/model/MintInfo.ts index 18ec15cd..08f25bb8 100644 --- a/src/model/MintInfo.ts +++ b/src/model/MintInfo.ts @@ -48,13 +48,13 @@ export class MintInfo { return { disabled: true, params: mintMeltInfo.methods }; } private checkNut17() { - if (this.mintInfo.nuts['17'] && this.mintInfo.nuts[17].supported.length > 0) { + if (this.mintInfo.nuts[17] && this.mintInfo.nuts[17].supported.length > 0) { return { supported: true, params: this.mintInfo.nuts[17].supported }; } return { supported: false }; } private checkNut15() { - if (this.mintInfo.nuts['15'] && this.mintInfo.nuts[15].methods.length > 0) { + if (this.mintInfo.nuts[15] && this.mintInfo.nuts[15].methods.length > 0) { return { supported: true, params: this.mintInfo.nuts[15].methods }; } return { supported: false }; From b47fd61d45a52f61b147bad4c7cdbd92b491b1b6 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 18 Nov 2024 20:53:39 +0000 Subject: [PATCH 8/9] added default throw --- src/model/MintInfo.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/model/MintInfo.ts b/src/model/MintInfo.ts index 08f25bb8..c26ea445 100644 --- a/src/model/MintInfo.ts +++ b/src/model/MintInfo.ts @@ -32,6 +32,9 @@ export class MintInfo { case 15: { return this.checkNut15(); } + default: { + throw new Error('nut is not supported by cashu-ts'); + } } } private checkGenericNut(num: 7 | 8 | 9 | 10 | 11 | 12 | 14) { From f5d8b5106d0e4161511660d85242e28a0c4dc38d Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 18 Nov 2024 20:56:44 +0000 Subject: [PATCH 9/9] private fields --- src/model/MintInfo.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/model/MintInfo.ts b/src/model/MintInfo.ts index c26ea445..4aeba097 100644 --- a/src/model/MintInfo.ts +++ b/src/model/MintInfo.ts @@ -1,10 +1,10 @@ import { GetInfoResponse, MPPMethod, SwapMethod, WebSocketSupport } from './types'; export class MintInfo { - private readonly mintInfo: GetInfoResponse; + private readonly _mintInfo: GetInfoResponse; constructor(info: GetInfoResponse) { - this.mintInfo = info; + this._mintInfo = info; } isSupported(num: 4 | 5): { disabled: boolean; params: Array }; @@ -38,56 +38,56 @@ export class MintInfo { } } private checkGenericNut(num: 7 | 8 | 9 | 10 | 11 | 12 | 14) { - if (this.mintInfo.nuts[num]?.supported) { + if (this._mintInfo.nuts[num]?.supported) { return { supported: true }; } return { supported: false }; } private checkMintMelt(num: 4 | 5) { - const mintMeltInfo = this.mintInfo.nuts[num]; + const mintMeltInfo = this._mintInfo.nuts[num]; if (mintMeltInfo && mintMeltInfo.methods.length > 0 && !mintMeltInfo.disabled) { return { disabled: false, params: mintMeltInfo.methods }; } return { disabled: true, params: mintMeltInfo.methods }; } private checkNut17() { - if (this.mintInfo.nuts[17] && this.mintInfo.nuts[17].supported.length > 0) { - return { supported: true, params: this.mintInfo.nuts[17].supported }; + if (this._mintInfo.nuts[17] && this._mintInfo.nuts[17].supported.length > 0) { + return { supported: true, params: this._mintInfo.nuts[17].supported }; } return { supported: false }; } private checkNut15() { - if (this.mintInfo.nuts[15] && this.mintInfo.nuts[15].methods.length > 0) { - return { supported: true, params: this.mintInfo.nuts[15].methods }; + if (this._mintInfo.nuts[15] && this._mintInfo.nuts[15].methods.length > 0) { + return { supported: true, params: this._mintInfo.nuts[15].methods }; } return { supported: false }; } get contact() { - return this.mintInfo.contact; + return this._mintInfo.contact; } get description() { - return this.mintInfo.description; + return this._mintInfo.description; } get description_long() { - return this.mintInfo.description_long; + return this._mintInfo.description_long; } get name() { - return this.mintInfo.name; + return this._mintInfo.name; } get pubkey() { - return this.mintInfo.pubkey; + return this._mintInfo.pubkey; } get version() { - return this.mintInfo.version; + return this._mintInfo.version; } get motd() { - return this.mintInfo.motd; + return this._mintInfo.motd; } }