From 2b8352ceb8f0ac2ad5e26c56503c44554aae62d5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:48:42 -0300 Subject: [PATCH 001/175] adjust getKeys --- src/CashuMint.ts | 34 +++++++++++++++++++++-------- src/CashuWallet.ts | 10 +++++---- src/DHKE.ts | 4 ++-- src/model/types/index.ts | 46 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 77 insertions(+), 17 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 529ff4af9..5dbe20562 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -5,6 +5,8 @@ import { MeltPayload, MeltResponse, MintKeys, + MintActiveKeys, + MintAllKeysets, RequestMintResponse, SerializedBlindedMessage, SerializedBlindedSignature, @@ -21,7 +23,7 @@ class CashuMint { /** * @param _mintUrl requires mint URL to create this object */ - constructor(private _mintUrl: string) {} + constructor(private _mintUrl: string) { } get mintUrl() { return this._mintUrl; @@ -98,37 +100,51 @@ class CashuMint { * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from the active keyset are fetched * @returns */ - public static async getKeys(mintUrl: string, keysetId?: string): Promise { + public static async getKeys(mintUrl: string, keysetId?: string): Promise { + // backwards compatibility for base64 encoded keyset ids if (keysetId) { // make the keysetId url safe keysetId = keysetId.replace(/\//g, '_').replace(/\+/g, '-'); } - return request({ - endpoint: keysetId ? joinUrls(mintUrl, 'keys', keysetId) : joinUrls(mintUrl, 'keys') + + const data = await request({ + endpoint: keysetId ? joinUrls(mintUrl, '/v1/keys', keysetId) : joinUrls(mintUrl, 'keys') }); + + if (!isObj(data) || !Array.isArray(data)) { + throw new Error('bad response'); + } + + return data; } /** * Get the mints public keys * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from the active keyset are fetched * @returns the mints public keys */ - async getKeys(keysetId?: string): Promise { - return CashuMint.getKeys(this._mintUrl, keysetId); + async getKeys(keysetId?: string, mintUrl?: string): Promise { + const allKeys = CashuMint.getKeys(mintUrl || this._mintUrl, keysetId); + // find keyset with unit 'sat' + const satKeys = (await allKeys).find((keys) => keys.unit === 'sat'); + if (!satKeys) { + throw new Error('No keyset with unit "sat" found'); + } + return satKeys } /** * Get the mints keysets in no specific order * @param mintUrl * @returns all the mints past and current keysets. */ - public static async getKeySets(mintUrl: string): Promise<{ keysets: Array }> { - return request<{ keysets: Array }>({ endpoint: joinUrls(mintUrl, 'keysets') }); + public static async getKeySets(mintUrl: string): Promise { + return request({ endpoint: joinUrls(mintUrl, '/v1/keysets') }); } /** * Get the mints keysets in no specific order * @returns all the mints past and current keysets. */ - async getKeySets(): Promise<{ keysets: Array }> { + async getKeySets(): Promise { return CashuMint.getKeySets(this._mintUrl); } diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 55c45ba3d..6320a7802 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -7,6 +7,7 @@ import { BlindedMessageData, BlindedTransaction, MintKeys, + MintKeyset, PayLnInvoiceResponse, PaymentPayload, Proof, @@ -31,7 +32,7 @@ import { * This class should act as the entry point for this library */ class CashuWallet { - private _keys: MintKeys; + private _keys = {} as MintKeys; private _keysetId = ''; mint: CashuMint; @@ -40,9 +41,9 @@ class CashuWallet { * @param mint Cashu mint instance is used to make api calls */ constructor(mint: CashuMint, keys?: MintKeys) { - this._keys = keys || {}; this.mint = mint; if (keys) { + this._keys = keys; this._keysetId = deriveKeysetId(this._keys); } } @@ -357,8 +358,9 @@ class CashuWallet { const keys = !mint || mint === this.mint.mintUrl - ? await this.mint.getKeys(arr[0].id) - : await CashuMint.getKeys(mint, arr[0].id); + ? await this.mint.getKeys(keysetId) + : await this.mint.getKeys(keysetId, mint); + return keys; } diff --git a/src/DHKE.ts b/src/DHKE.ts index ca37559de..a26f7cb7e 100644 --- a/src/DHKE.ts +++ b/src/DHKE.ts @@ -51,11 +51,11 @@ function constructProofs( promises: Array, rs: Array, secrets: Array, - keys: MintKeys + keyset: MintKeys ): Array { return promises.map((p: SerializedBlindedSignature, i: number) => { const C_ = pointFromHex(p.C_); - const A = pointFromHex(keys[p.amount]); + const A = pointFromHex(keyset.keys[p.amount]); const C = unblindSignature(C_, rs[i], A); const proof = { id: p.id, diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 1fd13cdcb..e7bb1b507 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -21,9 +21,51 @@ export type Proof = { }; /** - * A mints publickey-set. + * An array of mint keysets */ -export type MintKeys = { [k: number]: string }; +export type MintActiveKeys = Array; + +/** + * A mint keyset. + */ +export type MintKeys = { + /** + * Keyset ID + */ + id: string; + /** + * Unit of the keyset. + */ + unit: string; + /** + * Public keys are a dictionary of number and string. The number represents the amount that the key signs for. + */ + keys: { [amount: number]: string }; + +}; + +/** + * An array of mint keyset entries. + */ +export type MintAllKeysets = Array; + +/** + * A mint keyset entry. + */ +export type MintKeyset = { + /** + * Keyset ID + */ + id: string; + /** + * Unit of the keyset. + */ + unit: string; + /** + * Whether the keyset is active or not. + */ + active: boolean; +}; /** * response when after receiving a single TokenEntry From a5affdc00626d31d83c9c4902e330072b27037ee Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 00:28:21 -0300 Subject: [PATCH 002/175] mint works --- src/CashuMint.ts | 38 +++++++++++++----------- src/CashuWallet.ts | 33 ++++++++++++++------- src/model/BlindedMessage.ts | 6 ++-- src/model/types/index.ts | 59 ++++++++++++++++++++++++++++++++++--- 4 files changed, 103 insertions(+), 33 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 5dbe20562..ada943181 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -11,7 +11,10 @@ import { SerializedBlindedMessage, SerializedBlindedSignature, SplitPayload, - SplitResponse + SplitResponse, + RequestMintPayload, + PostMintPayload, + PostMintResponse } from './model/types/index.js'; import request from './request.js'; import { isObj, joinUrls } from './utils.js'; @@ -33,7 +36,7 @@ class CashuMint { * @param mintUrl */ public static async getInfo(mintUrl: string): Promise { - return request({ endpoint: joinUrls(mintUrl, 'info') }); + return request({ endpoint: joinUrls(mintUrl, '/v1/info') }); } /** * fetches mints info at the /info endpoint @@ -47,9 +50,11 @@ class CashuMint { * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ - public static async requestMint(mintUrl: string, amount: number): Promise { + public static async requestMint(mintUrl: string, requestMintPayload: RequestMintPayload): Promise { return request({ - endpoint: `${joinUrls(mintUrl, 'mint')}?amount=${amount}` + endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11'), + method: 'POST', + requestBody: requestMintPayload }); } @@ -58,8 +63,8 @@ class CashuMint { * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ - async requestMint(amount: number): Promise { - return CashuMint.requestMint(this._mintUrl, amount); + async requestMint(requestMintPayload: RequestMintPayload): Promise { + return CashuMint.requestMint(this._mintUrl, requestMintPayload); } /** * Requests the mint to perform token minting after the LN invoice has been paid @@ -70,16 +75,15 @@ class CashuMint { */ public static async mint( mintUrl: string, - payloads: { outputs: Array }, - hash: string + mintPayload: PostMintPayload, ) { - const data = await request<{ promises: Array }>({ - endpoint: `${joinUrls(mintUrl, 'mint')}?hash=${hash}`, + const data = await request({ + endpoint: joinUrls(mintUrl, '/v1/mint/bolt11'), method: 'POST', - requestBody: payloads + requestBody: mintPayload }); - if (!isObj(data) || !Array.isArray(data?.promises)) { + if (!isObj(data) || !Array.isArray(data?.signatures)) { throw new Error('bad response'); } @@ -91,8 +95,8 @@ class CashuMint { * @param hash hash (id) used for by the mint to keep track of wether the invoice has been paid yet * @returns serialized blinded signatures */ - async mint(payloads: { outputs: Array }, hash: string) { - return CashuMint.mint(this._mintUrl, payloads, hash); + async mint(mintPayload: PostMintPayload) { + return CashuMint.mint(this._mintUrl, mintPayload); } /** * Get the mints public keys @@ -108,10 +112,10 @@ class CashuMint { } const data = await request({ - endpoint: keysetId ? joinUrls(mintUrl, '/v1/keys', keysetId) : joinUrls(mintUrl, 'keys') + endpoint: keysetId ? joinUrls(mintUrl, '/v1/keys', keysetId) : joinUrls(mintUrl, '/v1/keys') }); - if (!isObj(data) || !Array.isArray(data)) { + if (!isObj(data) || !Array.isArray(data.keysets)) { throw new Error('bad response'); } @@ -125,7 +129,7 @@ class CashuMint { async getKeys(keysetId?: string, mintUrl?: string): Promise { const allKeys = CashuMint.getKeys(mintUrl || this._mintUrl, keysetId); // find keyset with unit 'sat' - const satKeys = (await allKeys).find((keys) => keys.unit === 'sat'); + const satKeys = ((await allKeys).keysets).find((keys) => keys.unit === 'sat'); if (!satKeys) { throw new Error('No keyset with unit "sat" found'); } diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 6320a7802..ced913d46 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -10,9 +10,11 @@ import { MintKeyset, PayLnInvoiceResponse, PaymentPayload, + PostMintPayload, Proof, ReceiveResponse, ReceiveTokenEntryResponse, + RequestMintPayload, SendResponse, SerializedBlindedMessage, SerializedBlindedSignature, @@ -77,7 +79,11 @@ class CashuWallet { * @returns the mint will create and return a Lightning invoice for the specified amount */ requestMint(amount: number) { - return this.mint.requestMint(amount); + const requestMintPayload: RequestMintPayload = { + unit: 'sat', + amount: amount + } + return this.mint.requestMint(requestMintPayload); } /** @@ -298,15 +304,20 @@ class CashuWallet { hash: string, AmountPreference?: Array ): Promise<{ proofs: Array; newKeys?: MintKeys }> { + await this.initKeys(); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, + this.keysetId, AmountPreference ); - const payloads = { outputs: blindedMessages }; - const { promises } = await this.mint.mint(payloads, hash); + const postMintPayload: PostMintPayload = { + outputs: blindedMessages, + quote: hash + }; + const { signatures } = await this.mint.mint(postMintPayload); return { - proofs: dhke.constructProofs(promises, rs, secrets, await this.getKeys(promises)), - newKeys: await this.changedKeys(promises) + proofs: dhke.constructProofs(signatures, rs, secrets, await this.getKeys(signatures)), + newKeys: await this.changedKeys(signatures) }; } @@ -316,7 +327,8 @@ class CashuWallet { private async initKeys() { if (!this.keysetId || !Object.keys(this.keys).length) { this.keys = await this.mint.getKeys(); - this._keysetId = deriveKeysetId(this.keys); + // this._keysetId = deriveKeysetId(this.keys); + this._keysetId = this.keys.id; } } @@ -380,8 +392,8 @@ class CashuWallet { blindedMessages: BlindedTransaction; } { const totalAmount = proofsToSend.reduce((total, curr) => total + curr.amount, 0); - const keepBlindedMessages = this.createRandomBlindedMessages(totalAmount - amount); - const sendBlindedMessages = this.createRandomBlindedMessages(amount, preference); + const keepBlindedMessages = this.createRandomBlindedMessages(totalAmount - amount, this.keysetId); + const sendBlindedMessages = this.createRandomBlindedMessages(amount, this.keysetId, preference); // join keepBlindedMessages and sendBlindedMessages const blindedMessages: BlindedTransaction = { @@ -416,6 +428,7 @@ class CashuWallet { */ private createRandomBlindedMessages( amount: number, + keysetId: string, amountPreference?: Array ): BlindedMessageData & { amounts: Array } { const blindedMessages: Array = []; @@ -427,7 +440,7 @@ class CashuWallet { secrets.push(secret); const { B_, r } = dhke.blindMessage(secret); rs.push(r); - const blindedMessage = new BlindedMessage(amounts[i], B_); + const blindedMessage = new BlindedMessage(amounts[i], B_, keysetId); blindedMessages.push(blindedMessage.getSerializedBlindedMessage()); } return { blindedMessages, secrets, rs, amounts }; @@ -449,7 +462,7 @@ class CashuWallet { secrets.push(secret); const { B_, r } = dhke.blindMessage(secret); rs.push(r); - const blindedMessage = new BlindedMessage(0, B_); + const blindedMessage = new BlindedMessage(0, B_, ""); blindedMessages.push(blindedMessage.getSerializedBlindedMessage()); } diff --git a/src/model/BlindedMessage.ts b/src/model/BlindedMessage.ts index 5a3988303..9bd02f3c9 100644 --- a/src/model/BlindedMessage.ts +++ b/src/model/BlindedMessage.ts @@ -4,12 +4,14 @@ import { ProjPointType } from '@noble/curves/abstract/weierstrass'; class BlindedMessage { amount: number; B_: ProjPointType; - constructor(amount: number, B_: ProjPointType) { + id: string; + constructor(amount: number, B_: ProjPointType, id: string) { this.amount = amount; this.B_ = B_; + this.id = id; } getSerializedBlindedMessage(): SerializedBlindedMessage { - return { amount: this.amount, B_: this.B_.toHex(true) }; + return { amount: this.amount, B_: this.B_.toHex(true), id: this.id }; } } export { BlindedMessage }; diff --git a/src/model/types/index.ts b/src/model/types/index.ts index e7bb1b507..08067ec96 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -23,7 +23,12 @@ export type Proof = { /** * An array of mint keysets */ -export type MintActiveKeys = Array; +export type MintActiveKeys = { + /** + * Keysets + */ + keysets: Array; +}; /** * A mint keyset. @@ -47,7 +52,12 @@ export type MintKeys = { /** * An array of mint keyset entries. */ -export type MintAllKeysets = Array; +export type MintAllKeysets = { + /** + * Keysets + */ + keysets: Array; +}; /** * A mint keyset entry. @@ -233,11 +243,48 @@ export type ApiError = { detail?: string; }; +/** + * Payload that needs to be sent to the mint when requesting a mint + */ +export type RequestMintPayload = { + /** + * Unit to be minted + */ + unit: string; + /** + * Amount to be minted + */ + amount: number; +}; +/** + * Response from the mint after requesting a mint + */ export type RequestMintResponse = { - pr: string; - hash: string; + request: string; + quote: string; +} & ApiError; + +/** + * Payload that needs to be sent to the mint when requesting a mint + */ +export type PostMintPayload = { + /** + * Quote ID received from the mint. + */ + quote: string; + /** + * Outputs (blinded messages) to be signed by the mint. + */ + outputs: Array +}; +/** + * Response from the mint after requesting a mint + */ +export type PostMintResponse = { + signatures: Array; } & ApiError; + /** * Payload that needs to be sent to the mint when checking for spendable proofs */ @@ -270,6 +317,10 @@ export type SerializedBlindedMessage = { * Blinded message */ B_: string; + /** + * Keyset id + */ + id: string; }; /** * Blinded signature as it is received from the mint From f790539cad9a48aec388794fbc48d9a603eb0b9b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 00:46:40 -0300 Subject: [PATCH 003/175] add some tests --- .github/workflows/nutshell-integration.yml | 42 ++++++++ test/integration.test.ts | 119 +++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 .github/workflows/nutshell-integration.yml create mode 100644 test/integration.test.ts diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml new file mode 100644 index 000000000..be2ef1446 --- /dev/null +++ b/.github/workflows/nutshell-integration.yml @@ -0,0 +1,42 @@ +name: CI Test Workflow + +on: [push, pull_request] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + services: + docker: + image: docker:19.03.12 + ports: + - 3338:3338 + options: --privileged + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + repository: 'cashubtc/nutshell' + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Build and Run Docker Container + run: | + docker build -t nutshell-app . + docker run -d -e MINT_LIGHTNING_BACKEND=FakeWallet -p 3338:3338 nutshell-app + + - name: Execute Command Inside the Container + run: | + docker exec $(docker ps -q) poetry run mint --port 3338 --host 0.0.0.0 + + - name: Run Tests + uses: actions/setup-node@v3 + with: + node-version: 20x + cache: 'npm' + - run: npm ci + - run: npm run compile + - run: npm test diff --git a/test/integration.test.ts b/test/integration.test.ts new file mode 100644 index 000000000..036065bf1 --- /dev/null +++ b/test/integration.test.ts @@ -0,0 +1,119 @@ +import { CashuMint } from '../src/CashuMint.js'; +import { CashuWallet } from '../src/CashuWallet.js'; + +import dns from 'node:dns'; +dns.setDefaultResultOrder('ipv4first'); + +const externalInvoice = + 'lnbc20u1p3u27nppp5pm074ffk6m42lvae8c6847z7xuvhyknwgkk7pzdce47grf2ksqwsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzpgxqyz5vqsp5sw6n7cztudpl5m5jv3z6dtqpt2zhd3q6dwgftey9qxv09w82rgjq9qyyssqhtfl8wv7scwp5flqvmgjjh20nf6utvv5daw5h43h69yqfwjch7wnra3cn94qkscgewa33wvfh7guz76rzsfg9pwlk8mqd27wavf2udsq3yeuju'; + +let request: Record | undefined; +const mintUrl = 'http://localhost:3338'; + +describe('mint api', () => { + test('get keys', async () => { + const mint = new CashuMint(mintUrl); + const keys = await mint.getKeys(); + expect(keys).toBeDefined(); + }); + test('get keysets', async () => { + const mint = new CashuMint(mintUrl); + const keysets = await mint.getKeySets(); + expect(keysets).toBeDefined(); + expect(keysets.keysets).toBeDefined(); + expect(keysets.keysets.length).toBeGreaterThan(0); + }); + + test('get info', async () => { + const mint = new CashuMint(mintUrl); + const info = await mint.getInfo(); + expect(info).toBeDefined(); + }); + test('request mint', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + const request = await wallet.requestMint(100); + expect(request).toBeDefined(); + }); + test('mint tokens', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + const request = await wallet.requestMint(1337); + expect(request).toBeDefined(); + expect(request.request).toContain('lnbc1337'); + const tokens = await wallet.requestTokens(1337, request.quote); + expect(tokens).toBeDefined(); + // expect that the sum of all tokens.proofs.amount is equal to the requested amount + expect(tokens.proofs.reduce((a, b) => a + b.amount, 0)).toBe(1337); + }); + test('get fee for local invoice', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + const request = await wallet.requestMint(100); + const fee = await wallet.getFee(request.request); + expect(fee).toBeDefined(); + // because local invoice, fee should be 0 + expect(fee).toBe(0); + }); + test('get fee for external invoice', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + const fee = await wallet.getFee(externalInvoice); + expect(fee).toBeDefined(); + // because external invoice, fee should be > 0 + expect(fee).toBeGreaterThan(0); + }); + test('pay local invoice', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + const request = await wallet.requestMint(100); + const tokens = await wallet.requestTokens(100, request.quote); + + // expect no fee because local invoice + const requestToPay = await wallet.requestMint(10); + const fee = await wallet.getFee(requestToPay.request); + expect(fee).toBe(0); + + const sendResponse = await wallet.send(10, tokens.proofs); + const response = await wallet.payLnInvoice(requestToPay.request, sendResponse.send); + expect(response).toBeDefined(); + // expect that we have received the fee back, since it was internal + expect(response.change.reduce((a, b) => a + b.amount, 0)).toBe(fee); + + // check states of spent and kept proofs after payment + const sentProofsSpent = await wallet.checkProofsSpent(sendResponse.send) + expect(sentProofsSpent).toBeDefined(); + // expect that all proofs are spent, i.e. sendProofsSpent == sendResponse.send + expect(sentProofsSpent).toEqual(sendResponse.send); + // expect none of the sendResponse.returnChange to be spent + const returnChangeSpent = await wallet.checkProofsSpent(sendResponse.returnChange) + expect(returnChangeSpent).toBeDefined(); + expect(returnChangeSpent).toEqual([]); + }); + test('pay external invoice', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + const request = await wallet.requestMint(3000); + const tokens = await wallet.requestTokens(3000, request.quote); + + const fee = await wallet.getFee(externalInvoice); + expect(fee).toBeGreaterThan(0); + + const sendResponse = await wallet.send(2000 + fee, tokens.proofs); + const response = await wallet.payLnInvoice(externalInvoice, sendResponse.send); + + expect(response).toBeDefined(); + // expect that we have received the fee back, since it was internal + expect(response.change.reduce((a, b) => a + b.amount, 0)).toBe(fee); + + // check states of spent and kept proofs after payment + const sentProofsSpent = await wallet.checkProofsSpent(sendResponse.send) + expect(sentProofsSpent).toBeDefined(); + // expect that all proofs are spent, i.e. sendProofsSpent == sendResponse.send + expect(sentProofsSpent).toEqual(sendResponse.send); + // expect none of the sendResponse.returnChange to be spent + const returnChangeSpent = await wallet.checkProofsSpent(sendResponse.returnChange) + expect(returnChangeSpent).toBeDefined(); + expect(returnChangeSpent).toEqual([]); + }); +}); From 35a7455c9f5e1c8d4419f98c5807524b9d712c02 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 00:52:33 -0300 Subject: [PATCH 004/175] try docker-compose --- .github/workflows/nutshell-integration.yml | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index be2ef1446..4f26b350e 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -6,13 +6,6 @@ jobs: build-and-test: runs-on: ubuntu-latest - services: - docker: - image: docker:19.03.12 - ports: - - 3338:3338 - options: --privileged - steps: - name: Checkout repository uses: actions/checkout@v2 @@ -20,17 +13,16 @@ jobs: repository: 'cashubtc/nutshell' token: ${{ secrets.GITHUB_TOKEN }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Build and Run Docker Container + - name: Set up Docker Compose run: | - docker build -t nutshell-app . - docker run -d -e MINT_LIGHTNING_BACKEND=FakeWallet -p 3338:3338 nutshell-app + sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose - - name: Execute Command Inside the Container + - name: Build and start services + env: + MINT_LIGHTNING_BACKEND: FakeWallet run: | - docker exec $(docker ps -q) poetry run mint --port 3338 --host 0.0.0.0 + docker-compose up -d - name: Run Tests uses: actions/setup-node@v3 From d0719523d9c1f13076af62b923f5315bd6c4119d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 00:54:13 -0300 Subject: [PATCH 005/175] remove node version --- .github/workflows/nutshell-integration.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 4f26b350e..378e87ca8 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -27,7 +27,6 @@ jobs: - name: Run Tests uses: actions/setup-node@v3 with: - node-version: 20x cache: 'npm' - run: npm ci - run: npm run compile From 00f1c0d81ccdc044d513b2fee7b2f9ee31b36916 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 00:57:07 -0300 Subject: [PATCH 006/175] try again --- .github/workflows/nutshell-integration.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 378e87ca8..8cdfc9d8c 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -24,6 +24,10 @@ jobs: run: | docker-compose up -d + - name: Navigate to the parent directory + run: cd .. + - uses: actions/checkout@v3 + - name: Run Tests uses: actions/setup-node@v3 with: From aaf7842c7bf599d525041cbbceb3bbf516fa32ec Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 02:18:06 -0300 Subject: [PATCH 007/175] remove newkeys --- src/CashuMint.ts | 45 ++++++++-- src/CashuWallet.ts | 133 +++++++++++++--------------- src/model/types/index.ts | 42 +++++++-- test/integration.test.ts | 11 +-- test/wallet.test.ts | 181 ++++++++++++--------------------------- 5 files changed, 196 insertions(+), 216 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index ada943181..2caf082f9 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -14,7 +14,9 @@ import { SplitResponse, RequestMintPayload, PostMintPayload, - PostMintResponse + PostMintResponse, + MeltQuotePayload, + MeltQuoteResponse } from './model/types/index.js'; import request from './request.js'; import { isObj, joinUrls } from './utils.js'; @@ -50,7 +52,7 @@ class CashuMint { * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ - public static async requestMint(mintUrl: string, requestMintPayload: RequestMintPayload): Promise { + public static async mintQuote(mintUrl: string, requestMintPayload: RequestMintPayload): Promise { return request({ endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11'), method: 'POST', @@ -63,8 +65,8 @@ class CashuMint { * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ - async requestMint(requestMintPayload: RequestMintPayload): Promise { - return CashuMint.requestMint(this._mintUrl, requestMintPayload); + async mintQuote(requestMintPayload: RequestMintPayload): Promise { + return CashuMint.mintQuote(this._mintUrl, requestMintPayload); } /** * Requests the mint to perform token minting after the LN invoice has been paid @@ -126,10 +128,10 @@ class CashuMint { * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from the active keyset are fetched * @returns the mints public keys */ - async getKeys(keysetId?: string, mintUrl?: string): Promise { + async getKeys(keysetId?: string, mintUrl?: string, unit?: string): Promise { const allKeys = CashuMint.getKeys(mintUrl || this._mintUrl, keysetId); // find keyset with unit 'sat' - const satKeys = ((await allKeys).keysets).find((keys) => keys.unit === 'sat'); + const satKeys = ((await allKeys).keysets).find((keys) => keys.unit === unit ? unit : 'sat'); if (!satKeys) { throw new Error('No keyset with unit "sat" found'); } @@ -179,6 +181,33 @@ class CashuMint { async split(splitPayload: SplitPayload): Promise { return CashuMint.split(this._mintUrl, splitPayload); } + /** + * Asks the mint for a melt quote + * @param mintUrl + * @param MeltQuotePayload + * @returns + */ + public static async meltQuote(mintUrl: string, meltQuotePayload: MeltQuotePayload): Promise { + const data = await request({ + endpoint: joinUrls(mintUrl, '/v1/melt/quote/bolt11'), + method: 'POST', + requestBody: meltQuotePayload + }); + + if (!isObj(data) || typeof data?.amount !== 'number' || typeof data?.fee_reserve !== 'number' || typeof data?.quote !== 'string') { + throw new Error('bad response'); + } + + return data; + } + /** + * Asks the mint for a melt quote + * @param MeltQuotePayload + * @returns + */ + async meltQuote(meltQuotePayload: MeltQuotePayload): Promise { + return CashuMint.meltQuote(this._mintUrl, meltQuotePayload); + } /** * Ask mint to perform a melt operation. This pays a lightning invoice and destroys tokens matching its amount + fees * @param mintUrl @@ -187,7 +216,7 @@ class CashuMint { */ public static async melt(mintUrl: string, meltPayload: MeltPayload): Promise { const data = await request({ - endpoint: joinUrls(mintUrl, 'melt'), + endpoint: joinUrls(mintUrl, '/v1/melt/bolt11'), method: 'POST', requestBody: meltPayload }); @@ -195,7 +224,7 @@ class CashuMint { if ( !isObj(data) || typeof data?.paid !== 'boolean' || - (data?.preimage !== null && typeof data?.preimage !== 'string') + (data?.proof !== null && typeof data?.proof !== 'string') ) { throw new Error('bad response'); } diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index ced913d46..490020c84 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -6,6 +6,8 @@ import { AmountPreference, BlindedMessageData, BlindedTransaction, + MeltPayload, + MeltQuoteResponse, MintKeys, MintKeyset, PayLnInvoiceResponse, @@ -36,7 +38,9 @@ import { class CashuWallet { private _keys = {} as MintKeys; private _keysetId = ''; + mint: CashuMint; + unit = 'sat'; /** * @param keys public keys from the mint @@ -46,7 +50,8 @@ class CashuWallet { this.mint = mint; if (keys) { this._keys = keys; - this._keysetId = deriveKeysetId(this._keys); + // this._keysetId = deriveKeysetId(this._keys); + this._keysetId = keys.id; } } @@ -55,7 +60,8 @@ class CashuWallet { } set keys(keys: MintKeys) { this._keys = keys; - this._keysetId = deriveKeysetId(this._keys); + // this._keysetId = deriveKeysetId(this._keys); + this._keysetId = keys.id; } get keysetId(): string { return this._keysetId; @@ -80,59 +86,72 @@ class CashuWallet { */ requestMint(amount: number) { const requestMintPayload: RequestMintPayload = { - unit: 'sat', + unit: this.unit, amount: amount } - return this.mint.requestMint(requestMintPayload); + return this.mint.mintQuote(requestMintPayload); } + /** + * Requests a quote for a LN payment. Response returns amount and fees for a given LN invoice. + * @param invoice LN invoice that needs to get a fee estimate + * @returns estimated Fee + */ + async getMeltQuote(invoice: string): Promise { + const meltQuote = await this.mint.meltQuote({ unit: this.unit, request: invoice }); + // const { fee } = await this.mint.checkFees({ pr: invoice }); + return meltQuote; + } /** * Executes a payment of an invoice on the Lightning network. * The combined amount of Proofs has to match the payment amount including fees. * @param invoice * @param proofsToSend the exact amount to send including fees - * @param feeReserve? optionally set LN routing fee reserve. If not set, fee reserve will get fetched at mint + * @param meltQuote melt quote for the invoice */ async payLnInvoice( invoice: string, proofsToSend: Array, - feeReserve?: number + meltQuote?: MeltQuoteResponse ): Promise { - const paymentPayload = this.createPaymentPayload(invoice, proofsToSend); - if (!feeReserve) { - feeReserve = await this.getFee(invoice); + if (!meltQuote) { + meltQuote = await this.mint.meltQuote({ unit: this.unit, request: invoice }); } - const { blindedMessages, secrets, rs } = this.createBlankOutputs(feeReserve); - const payData = await this.mint.melt({ - ...paymentPayload, - outputs: blindedMessages - }); - return { - isPaid: payData.paid ?? false, - preimage: payData.preimage, - change: payData?.change - ? dhke.constructProofs(payData.change, rs, secrets, await this.getKeys(payData.change)) - : [], - newKeys: await this.changedKeys(payData?.change) - }; + return await this.payMeltQuote(meltQuote, proofsToSend); + } /** - * Estimate fees for a given LN invoice - * @param invoice LN invoice that needs to get a fee estimate - * @returns estimated Fee + * Pays an LN quote. + * @param meltQuote + * @param proofsToSend the exact amount to send including fees + * @returns */ - async getFee(invoice: string): Promise { - const { fee } = await this.mint.checkFees({ pr: invoice }); - return fee; - } + async payMeltQuote( + meltQuote: MeltQuoteResponse, + proofsToSend: Array + ): Promise { + const { blindedMessages, secrets, rs } = this.createBlankOutputs(meltQuote.fee_reserve); + const meltPayload: MeltPayload = { + quote: meltQuote.quote, + inputs: proofsToSend, + outputs: [...blindedMessages] + }; + const meltResponse = await this.mint.melt(meltPayload); - createPaymentPayload(invoice: string, proofs: Array): PaymentPayload { return { - pr: invoice, - proofs: proofs + isPaid: meltResponse.paid ?? false, + preimage: meltResponse.proof, + change: meltResponse?.change + ? dhke.constructProofs(meltResponse.change, rs, secrets, await this.getKeys(meltResponse.change)) + : [] }; } + + + /** + * NOTE: This method is a helper. Should be moved. + * * Use a cashu token to pay an ln invoice * @param invoice Lightning invoice * @param token cashu token @@ -148,13 +167,12 @@ class CashuWallet { * Receive an encoded Cashu token * @param encodedToken Cashu token * @param preference optional preference for splitting proofs into specific amounts - * @returns New token with newly created proofs, token entries that had errors, and newKeys if they have changed + * @returns New token with newly created proofs, token entries that had errors */ async receive(encodedToken: string, preference?: Array): Promise { const { token } = cleanToken(getDecodedToken(encodedToken)); const tokenEntries: Array = []; const tokenEntriesWithError: Array = []; - let newKeys: MintKeys | undefined; for (const tokenEntry of token) { if (!tokenEntry?.proofs?.length) { continue; @@ -163,16 +181,12 @@ class CashuWallet { const { proofsWithError, proofs, - newKeys: newKeysFromReceive } = await this.receiveTokenEntry(tokenEntry, preference); if (proofsWithError?.length) { tokenEntriesWithError.push(tokenEntry); continue; } tokenEntries.push({ mint: tokenEntry.mint, proofs: [...proofs] }); - if (!newKeys) { - newKeys = newKeysFromReceive; - } } catch (error) { console.error(error); tokenEntriesWithError.push(tokenEntry); @@ -181,7 +195,6 @@ class CashuWallet { return { token: { token: tokenEntries }, tokensWithErrors: tokenEntriesWithError.length ? { token: tokenEntriesWithError } : undefined, - newKeys }; } @@ -189,12 +202,11 @@ class CashuWallet { * Receive a single cashu token entry * @param tokenEntry a single entry of a cashu token * @param preference optional preference for splitting proofs into specific amounts. - * @returns New token entry with newly created proofs, proofs that had errors, and newKeys if they have changed + * @returns New token entry with newly created proofs, proofs that had errors */ async receiveTokenEntry(tokenEntry: TokenEntry, preference?: Array): Promise { const proofsWithError: Array = []; const proofs: Array = []; - let newKeys: MintKeys | undefined; try { const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); if (!preference) { @@ -213,18 +225,13 @@ class CashuWallet { await this.getKeys(promises, tokenEntry.mint) ); proofs.push(...newProofs); - newKeys = - tokenEntry.mint === this.mint.mintUrl - ? await this.changedKeys([...(promises || [])]) - : undefined; } catch (error) { console.error(error); proofsWithError.push(...tokenEntry.proofs); } return { proofs, - proofsWithError: proofsWithError.length ? proofsWithError : undefined, - newKeys + proofsWithError: proofsWithError.length ? proofsWithError : undefined }; } @@ -286,8 +293,7 @@ class CashuWallet { }); return { returnChange: [...splitProofsToKeep, ...proofsToKeep], - send: splitProofsToSend, - newKeys: await this.changedKeys([...(promises || [])]) + send: splitProofsToSend }; } return { returnChange: proofsToKeep, send: proofsToSend }; @@ -297,13 +303,13 @@ class CashuWallet { * Request tokens from the mint * @param amount amount to request * @param hash hash to use to identify the request - * @returns proofs and newKeys if they have changed + * @returns proofs */ async requestTokens( amount: number, hash: string, AmountPreference?: Array - ): Promise<{ proofs: Array; newKeys?: MintKeys }> { + ): Promise<{ proofs: Array }> { await this.initKeys(); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, @@ -316,8 +322,7 @@ class CashuWallet { }; const { signatures } = await this.mint.mint(postMintPayload); return { - proofs: dhke.constructProofs(signatures, rs, secrets, await this.getKeys(signatures)), - newKeys: await this.changedKeys(signatures) + proofs: dhke.constructProofs(signatures, rs, secrets, await this.getKeys(signatures)) }; } @@ -332,26 +337,6 @@ class CashuWallet { } } - /** - * Check if the keysetId has changed and return the new keys - * @param promises array of promises to check - * @returns new keys if they have changed - */ - private async changedKeys( - promises: Array = [] - ): Promise { - await this.initKeys(); - if (!promises?.length) { - return undefined; - } - if (!promises.some((x) => x.id !== this.keysetId)) { - return undefined; - } - const maybeNewKeys = await this.mint.getKeys(); - const keysetId = deriveKeysetId(maybeNewKeys); - return keysetId === this.keysetId ? undefined : maybeNewKeys; - } - /** * Get the mint's public keys for a given set of proofs * @param arr array of proofs @@ -462,7 +447,7 @@ class CashuWallet { secrets.push(secret); const { B_, r } = dhke.blindMessage(secret); rs.push(r); - const blindedMessage = new BlindedMessage(0, B_, ""); + const blindedMessage = new BlindedMessage(0, B_, this.keysetId); blindedMessages.push(blindedMessage.getSerializedBlindedMessage()); } diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 08067ec96..a2c95b784 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -144,18 +144,50 @@ export type PaymentPayload = { proofs: Array; }; +/** + * Payload that needs to be send to the mint to request a melt quote + */ +export type MeltQuotePayload = { + /** + * Unit to be melted + */ + unit: string; + /** + * Request to be melted to + */ + request: string; +}; + +/** + * Response from the mint after requesting a melt quote + */ +export type MeltQuoteResponse = { + /** + * Quote ID + */ + quote: string; + /** + * Amount to be melted + */ + amount: number; + /** + * Fee reserve to be added to the amount + */ + fee_reserve: number; +} & ApiError; + /** * Payload that needs to be sent to the mint when melting. Includes Return for overpaid fees */ export type MeltPayload = { /** - * Payment request/Lighting invoice that should get paid by the mint. + * ID of the melt quote */ - pr: string; + quote: string; /** - * Proofs, matching Lightning invoices amount + fees. + * Inputs (Proofs) to be melted */ - proofs: Array; + inputs: Array; /** * Blank outputs (blinded messages) that can be filled by the mint to return overpaid fees */ @@ -173,7 +205,7 @@ export type MeltResponse = { /** * preimage of the paid invoice. can be null, depending on which LN-backend the mint uses */ - preimage: string | null; + proof: string | null; /** * Return/Change from overpaid fees. This happens due to Lighting fee estimation being inaccurate */ diff --git a/test/integration.test.ts b/test/integration.test.ts index 036065bf1..e591b2238 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -50,7 +50,7 @@ describe('mint api', () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); const request = await wallet.requestMint(100); - const fee = await wallet.getFee(request.request); + const fee = (await wallet.getLNQuote(request.request)).fee_reserve; expect(fee).toBeDefined(); // because local invoice, fee should be 0 expect(fee).toBe(0); @@ -58,7 +58,7 @@ describe('mint api', () => { test('get fee for external invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const fee = await wallet.getFee(externalInvoice); + const fee = (await wallet.getLNQuote(externalInvoice)).fee_reserve; expect(fee).toBeDefined(); // because external invoice, fee should be > 0 expect(fee).toBeGreaterThan(0); @@ -71,11 +71,12 @@ describe('mint api', () => { // expect no fee because local invoice const requestToPay = await wallet.requestMint(10); - const fee = await wallet.getFee(requestToPay.request); + const quote = await wallet.getLNQuote(requestToPay.request); + const fee = quote.fee_reserve expect(fee).toBe(0); const sendResponse = await wallet.send(10, tokens.proofs); - const response = await wallet.payLnInvoice(requestToPay.request, sendResponse.send); + const response = await wallet.payLnInvoice(requestToPay.request, sendResponse.send, quote); expect(response).toBeDefined(); // expect that we have received the fee back, since it was internal expect(response.change.reduce((a, b) => a + b.amount, 0)).toBe(fee); @@ -96,7 +97,7 @@ describe('mint api', () => { const request = await wallet.requestMint(3000); const tokens = await wallet.requestTokens(3000, request.quote); - const fee = await wallet.getFee(externalInvoice); + const fee = (await wallet.getLNQuote(externalInvoice)).fee_reserve; expect(fee).toBeGreaterThan(0); const sendResponse = await wallet.send(2000 + fee, tokens.proofs); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index b375661cb..099356a5f 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -33,67 +33,67 @@ describe('test fees', () => { describe('receive', () => { const tokenInput = 'eyJwcm9vZnMiOlt7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifV0sIm1pbnRzIjpbeyJ1cmwiOiJodHRwczovL2xlZ2VuZC5sbmJpdHMuY29tL2Nhc2h1L2FwaS92MS80Z3I5WGNtejNYRWtVTndpQmlRR29DIiwiaWRzIjpbIi91WUIvNndXbllrVSJdfV19'; - test('test receive', async () => { - nock(mintUrl) - .post('/split') - .reply(200, { - promises: [ - { - id: 'z32vUtKgNCm1', - amount: 1, - C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - } - ] - }); - const wallet = new CashuWallet(mint); - - const { token: t, tokensWithErrors } = await wallet.receive(tokenInput); - - expect(t.token).toHaveLength(1); - expect(t.token[0].proofs).toHaveLength(1); - expect(t.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: 'z32vUtKgNCm1' }], - mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' + test('test receive', async () => { + nock(mintUrl) + .post('/split') + .reply(200, { + promises: [ + { + id: 'z32vUtKgNCm1', + amount: 1, + C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' + } + ] }); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); - expect(tokensWithErrors).toBe(undefined); + const wallet = new CashuWallet(mint); + + const { token: t, tokensWithErrors } = await wallet.receive(tokenInput); + + expect(t.token).toHaveLength(1); + expect(t.token[0].proofs).toHaveLength(1); + expect(t.token[0]).toMatchObject({ + proofs: [{ amount: 1, id: 'z32vUtKgNCm1' }], + mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' }); - test('test receive custom split', async () => { - nock(mintUrl) - .post('/split') - .reply(200, { - promises: [ - { - id: 'z32vUtKgNCm1', - amount: 1, - C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - }, - { - id: 'z32vUtKgNCm1', - amount: 1, - C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - }, - { - id: 'z32vUtKgNCm1', - amount: 1, - C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - } - ] - }); - const wallet = new CashuWallet(mint); - const token3sat = 'eyJwcm9vZnMiOlt7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifSx7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifSx7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifV0sIm1pbnRzIjpbeyJ1cmwiOiJodHRwczovL2xlZ2VuZC5sbmJpdHMuY29tL2Nhc2h1L2FwaS92MS80Z3I5WGNtejNYRWtVTndpQmlRR29DIiwiaWRzIjpbIi91WUIvNndXbllrVSJdfV19' - const { token: t, tokensWithErrors } = await wallet.receive(token3sat, [{amount:1, count:3}]); - - expect(t.token).toHaveLength(1); - expect(t.token[0].proofs).toHaveLength(3); - expect(t.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: 'z32vUtKgNCm1' },{ amount: 1, id: 'z32vUtKgNCm1' },{ amount: 1, id: 'z32vUtKgNCm1' }], + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); + expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + expect(tokensWithErrors).toBe(undefined); + }); + test('test receive custom split', async () => { + nock(mintUrl) + .post('/split') + .reply(200, { + promises: [ + { + id: 'z32vUtKgNCm1', + amount: 1, + C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' + }, + { + id: 'z32vUtKgNCm1', + amount: 1, + C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' + }, + { + id: 'z32vUtKgNCm1', + amount: 1, + C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' + } + ] }); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); - expect(tokensWithErrors).toBe(undefined); + const wallet = new CashuWallet(mint); + const token3sat = 'eyJwcm9vZnMiOlt7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifSx7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifSx7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifV0sIm1pbnRzIjpbeyJ1cmwiOiJodHRwczovL2xlZ2VuZC5sbmJpdHMuY29tL2Nhc2h1L2FwaS92MS80Z3I5WGNtejNYRWtVTndpQmlRR29DIiwiaWRzIjpbIi91WUIvNndXbllrVSJdfV19' + const { token: t, tokensWithErrors } = await wallet.receive(token3sat, [{ amount: 1, count: 3 }]); + + expect(t.token).toHaveLength(1); + expect(t.token[0].proofs).toHaveLength(3); + expect(t.token[0]).toMatchObject({ + proofs: [{ amount: 1, id: 'z32vUtKgNCm1' }, { amount: 1, id: 'z32vUtKgNCm1' }, { amount: 1, id: 'z32vUtKgNCm1' }], }); + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); + expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + expect(tokensWithErrors).toBe(undefined); + }); test('test receive tokens already spent', async () => { const msg = 'tokens already spent. Secret: oEpEuViVHUV2vQH81INUbq++Yv2w3u5H0LhaqXJKeR0='; nock(mintUrl).post('/split').reply(200, { detail: msg }); @@ -129,41 +129,6 @@ describe('receive', () => { expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); }); - test('test receive keys changed', async () => { - nock(mintUrl) - .post('/split') - .reply(200, { - promises: [ - { - id: 'test', - amount: 1, - C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - } - ] - }); - nock(mintUrl).get('/keys/test').reply(200, { - 1: '0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad' - }); - nock(mintUrl).get('/keys').reply(200, { - 1: '0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad' - }); - const wallet = new CashuWallet(mint); - - const { - token: { token }, - tokensWithErrors, - newKeys - } = await wallet.receive(tokenInput); - - expect(token[0].proofs).toHaveLength(1); - expect(token[0].proofs[0]).toMatchObject({ amount: 1, id: 'test' }); - expect(/[0-9a-f]{64}/.test(token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(token[0].proofs[0].secret)).toBe(true); - expect(tokensWithErrors).toBe(undefined); - expect(newKeys).toStrictEqual({ - 1: '0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad' - }); - }); }); describe('checkProofsSpent', () => { @@ -241,38 +206,6 @@ describe('payLnInvoice', () => { expect(result).toEqual(new Error('bad response')); }); - test('test payLnInvoice key changed', async () => { - nock(mintUrl).post('/checkfees').reply(200, { fee: 2 }); - nock(mintUrl) - .post('/melt') - .reply(200, { - paid: true, - preimage: '', - change: [ - { - id: 'test', - amount: 1, - C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - } - ] - }); - nock(mintUrl).get('/keys/test').reply(200, { - 1: '0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad' - }); - nock(mintUrl).get('/keys').reply(200, { - 1: '0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad' - }); - const wallet = new CashuWallet(mint); - - const { isPaid, preimage, change, newKeys } = await wallet.payLnInvoice(invoice, proofs); - - expect(isPaid).toEqual(true); - expect(preimage).toEqual(''); - expect(change).toHaveLength(1); - expect(newKeys).toStrictEqual({ - 1: '0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad' - }); - }); }); describe('requestTokens', () => { From 6d04486f34b7beb48c6af920f0b7ee98053de014 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 09:23:15 -0300 Subject: [PATCH 008/175] slowly fixing tests --- src/CashuMint.ts | 62 +++++++------- src/CashuWallet.ts | 15 ++-- src/model/types/index.ts | 24 +----- test/integration.test.ts | 37 ++++++++- test/wallet.test.ts | 172 ++++++++++++++++++++------------------- 5 files changed, 163 insertions(+), 147 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 2caf082f9..f4d9b35ef 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -162,12 +162,12 @@ class CashuMint { */ public static async split(mintUrl: string, splitPayload: SplitPayload): Promise { const data = await request({ - endpoint: joinUrls(mintUrl, 'split'), + endpoint: joinUrls(mintUrl, '/v1/split'), method: 'POST', requestBody: splitPayload }); - if (!isObj(data) || !Array.isArray(data?.promises)) { + if (!isObj(data) || !Array.isArray(data?.signatures)) { throw new Error('bad response'); } @@ -239,37 +239,37 @@ class CashuMint { async melt(meltPayload: MeltPayload): Promise { return CashuMint.melt(this._mintUrl, meltPayload); } - /** - * Estimate fees for a given LN invoice - * @param mintUrl - * @param checkfeesPayload Payload containing LN invoice that needs to get a fee estimate - * @returns estimated Fee - */ - public static async checkFees( - mintUrl: string, - checkfeesPayload: { pr: string } - ): Promise<{ fee: number }> { - const data = await request<{ fee: number }>({ - endpoint: joinUrls(mintUrl, 'checkfees'), - method: 'POST', - requestBody: checkfeesPayload - }); + // /** + // * Estimate fees for a given LN invoice + // * @param mintUrl + // * @param checkfeesPayload Payload containing LN invoice that needs to get a fee estimate + // * @returns estimated Fee + // */ + // public static async checkFees( + // mintUrl: string, + // checkfeesPayload: { pr: string } + // ): Promise<{ fee: number }> { + // const data = await request<{ fee: number }>({ + // endpoint: joinUrls(mintUrl, 'checkfees'), + // method: 'POST', + // requestBody: checkfeesPayload + // }); - if (!isObj(data) || typeof data?.fee !== 'number') { - throw new Error('bad response'); - } + // if (!isObj(data) || typeof data?.fee !== 'number') { + // throw new Error('bad response'); + // } - return data; - } - /** - * Estimate fees for a given LN invoice - * @param mintUrl - * @param checkfeesPayload Payload containing LN invoice that needs to get a fee estimate - * @returns estimated Fee - */ - async checkFees(checkfeesPayload: { pr: string }): Promise<{ fee: number }> { - return CashuMint.checkFees(this._mintUrl, checkfeesPayload); - } + // return data; + // } + // /** + // * Estimate fees for a given LN invoice + // * @param mintUrl + // * @param checkfeesPayload Payload containing LN invoice that needs to get a fee estimate + // * @returns estimated Fee + // */ + // async checkFees(checkfeesPayload: { pr: string }): Promise<{ fee: number }> { + // return CashuMint.checkFees(this._mintUrl, checkfeesPayload); + // } /** * Checks if specific proofs have already been redeemed * @param mintUrl diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 490020c84..df3e3223e 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -99,7 +99,6 @@ class CashuWallet { */ async getMeltQuote(invoice: string): Promise { const meltQuote = await this.mint.meltQuote({ unit: this.unit, request: invoice }); - // const { fee } = await this.mint.checkFees({ pr: invoice }); return meltQuote; } /** @@ -217,12 +216,12 @@ class CashuWallet { tokenEntry.proofs, preference ); - const { promises, error } = await CashuMint.split(tokenEntry.mint, payload); + const { signatures, error } = await CashuMint.split(tokenEntry.mint, payload); const newProofs = dhke.constructProofs( - promises, + signatures, blindedMessages.rs, blindedMessages.secrets, - await this.getKeys(promises, tokenEntry.mint) + await this.getKeys(signatures, tokenEntry.mint) ); proofs.push(...newProofs); } catch (error) { @@ -271,12 +270,12 @@ class CashuWallet { if (amount < amountAvailable || preference) { const { amountKeep, amountSend } = this.splitReceive(amount, amountAvailable); const { payload, blindedMessages } = this.createSplitPayload(amountSend, proofsToSend, preference); - const { promises } = await this.mint.split(payload); + const { signatures } = await this.mint.split(payload); const proofs = dhke.constructProofs( - promises, + signatures, blindedMessages.rs, blindedMessages.secrets, - await this.getKeys(promises) + await this.getKeys(signatures) ); // sum up proofs until amount2 is reached const splitProofsToKeep: Array = []; @@ -392,7 +391,7 @@ class CashuWallet { }; const payload = { - proofs: proofsToSend, + inputs: proofsToSend, outputs: [...blindedMessages.blindedMessages] }; return { payload, blindedMessages }; diff --git a/src/model/types/index.ts b/src/model/types/index.ts index a2c95b784..b5104b2b2 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -89,10 +89,6 @@ export type ReceiveTokenEntryResponse = { * Proofs that could not be received. Doesn't throw an error, but if this field is populated it should be handled by the implementation accordingly */ proofsWithError: Array | undefined; - /** - * If the mint has rotated keys, this field will be populated with the new keys. - */ - newKeys?: MintKeys; }; /** @@ -107,10 +103,6 @@ export type SendResponse = { * Proofs to be sent, matching the chosen amount */ send: Array; - /** - * If the mint has rotated keys, this field will be populated with the new keys. - */ - newKeys?: MintKeys; }; /** * Response when receiving a complete token. @@ -124,10 +116,6 @@ export type ReceiveResponse = { * TokenEntries that had errors. No error will be thrown, but clients can choose to handle tokens with errors accordingly. */ tokensWithErrors: Token | undefined; - /** - * If the mint has rotated keys, this field will be populated with the new keys. - */ - newKeys?: MintKeys; }; /** @@ -228,10 +216,6 @@ export type PayLnInvoiceResponse = { * Return/Change from overpaid fees. This happens due to Lighting fee estimation being inaccurate */ change: Array; - /** - * If the mint has rotated keys, this field will be populated with the new keys. - */ - newKeys?: MintKeys; }; /** @@ -239,11 +223,11 @@ export type PayLnInvoiceResponse = { */ export type SplitPayload = { /** - * Proofs to be split + * Inputs to the split operation */ - proofs: Array; + inputs: Array; /** - * Fresh blinded messages to be signed by the mint to create the split proofs + * Outputs (blinded messages) to be signed by the mint */ outputs: Array; }; @@ -254,7 +238,7 @@ export type SplitResponse = { /** * represents the outputs after the split */ - promises: Array; + signatures: Array; } & ApiError; /** diff --git a/test/integration.test.ts b/test/integration.test.ts index e591b2238..ef47c61e7 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -2,6 +2,7 @@ import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; import dns from 'node:dns'; +import { getEncodedToken } from '../src/utils.js'; dns.setDefaultResultOrder('ipv4first'); const externalInvoice = @@ -50,7 +51,7 @@ describe('mint api', () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); const request = await wallet.requestMint(100); - const fee = (await wallet.getLNQuote(request.request)).fee_reserve; + const fee = (await wallet.getMeltQuote(request.request)).fee_reserve; expect(fee).toBeDefined(); // because local invoice, fee should be 0 expect(fee).toBe(0); @@ -58,7 +59,7 @@ describe('mint api', () => { test('get fee for external invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const fee = (await wallet.getLNQuote(externalInvoice)).fee_reserve; + const fee = (await wallet.getMeltQuote(externalInvoice)).fee_reserve; expect(fee).toBeDefined(); // because external invoice, fee should be > 0 expect(fee).toBeGreaterThan(0); @@ -71,7 +72,7 @@ describe('mint api', () => { // expect no fee because local invoice const requestToPay = await wallet.requestMint(10); - const quote = await wallet.getLNQuote(requestToPay.request); + const quote = await wallet.getMeltQuote(requestToPay.request); const fee = quote.fee_reserve expect(fee).toBe(0); @@ -97,7 +98,7 @@ describe('mint api', () => { const request = await wallet.requestMint(3000); const tokens = await wallet.requestTokens(3000, request.quote); - const fee = (await wallet.getLNQuote(externalInvoice)).fee_reserve; + const fee = (await wallet.getMeltQuote(externalInvoice)).fee_reserve; expect(fee).toBeGreaterThan(0); const sendResponse = await wallet.send(2000 + fee, tokens.proofs); @@ -117,4 +118,32 @@ describe('mint api', () => { expect(returnChangeSpent).toBeDefined(); expect(returnChangeSpent).toEqual([]); }); + test('test send tokens', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + const request = await wallet.requestMint(100); + const tokens = await wallet.requestTokens(100, request.quote); + + const sendResponse = await wallet.send(10, tokens.proofs); + expect(sendResponse).toBeDefined(); + expect(sendResponse.send).toBeDefined(); + expect(sendResponse.returnChange).toBeDefined(); + expect(sendResponse.send.length).toBe(2); + expect(sendResponse.returnChange.length).toBe(4); + }); + test('receive tokens', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + const request = await wallet.requestMint(100); + const tokens = await wallet.requestTokens(100, request.quote); + + const sendResponse = await wallet.send(10, tokens.proofs); + const encoded = getEncodedToken({ + token: [{ mint: mintUrl, proofs: sendResponse.send }] + }); + const response = await wallet.receive(encoded); + expect(response).toBeDefined(); + expect(response.token).toBeDefined(); + expect(response.tokensWithErrors).toBeUndefined(); + }); }); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 099356a5f..57003bc9a 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -19,14 +19,18 @@ beforeEach(() => { }); describe('test fees', () => { - test('test get fees', async () => { - nock(mintUrl).post('/checkfees').reply(200, { fee: 20 }); + test('test melt quote fees', async () => { + nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, { + quote: 'test_melt_quote_id', + amount: 2000, + fee_reserve: 20 + }); const wallet = new CashuWallet(mint); - const fee = await wallet.getFee(invoice); + const fee = await wallet.getMeltQuote(invoice); const amount = decode(invoice).sections[2].value / 1000; - expect(fee + amount).toEqual(2020); + expect(fee.fee_reserve + amount).toEqual(2020); }); }); @@ -35,9 +39,9 @@ describe('receive', () => { 'eyJwcm9vZnMiOlt7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifV0sIm1pbnRzIjpbeyJ1cmwiOiJodHRwczovL2xlZ2VuZC5sbmJpdHMuY29tL2Nhc2h1L2FwaS92MS80Z3I5WGNtejNYRWtVTndpQmlRR29DIiwiaWRzIjpbIi91WUIvNndXbllrVSJdfV19'; test('test receive', async () => { nock(mintUrl) - .post('/split') + .post('/v1/split') .reply(200, { - promises: [ + signatures: [ { id: 'z32vUtKgNCm1', amount: 1, @@ -47,88 +51,88 @@ describe('receive', () => { }); const wallet = new CashuWallet(mint); - const { token: t, tokensWithErrors } = await wallet.receive(tokenInput); + const response = await wallet.receive(tokenInput); - expect(t.token).toHaveLength(1); - expect(t.token[0].proofs).toHaveLength(1); - expect(t.token[0]).toMatchObject({ + expect(response.token).toHaveLength(1); + expect(response.token[0].proofs).toHaveLength(1); + expect(response.token[0]).toMatchObject({ proofs: [{ amount: 1, id: 'z32vUtKgNCm1' }], mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' }); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); - expect(tokensWithErrors).toBe(undefined); - }); - test('test receive custom split', async () => { - nock(mintUrl) - .post('/split') - .reply(200, { - promises: [ - { - id: 'z32vUtKgNCm1', - amount: 1, - C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - }, - { - id: 'z32vUtKgNCm1', - amount: 1, - C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - }, - { - id: 'z32vUtKgNCm1', - amount: 1, - C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - } - ] - }); - const wallet = new CashuWallet(mint); - const token3sat = 'eyJwcm9vZnMiOlt7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifSx7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifSx7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifV0sIm1pbnRzIjpbeyJ1cmwiOiJodHRwczovL2xlZ2VuZC5sbmJpdHMuY29tL2Nhc2h1L2FwaS92MS80Z3I5WGNtejNYRWtVTndpQmlRR29DIiwiaWRzIjpbIi91WUIvNndXbllrVSJdfV19' - const { token: t, tokensWithErrors } = await wallet.receive(token3sat, [{ amount: 1, count: 3 }]); - - expect(t.token).toHaveLength(1); - expect(t.token[0].proofs).toHaveLength(3); - expect(t.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: 'z32vUtKgNCm1' }, { amount: 1, id: 'z32vUtKgNCm1' }, { amount: 1, id: 'z32vUtKgNCm1' }], - }); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); - expect(tokensWithErrors).toBe(undefined); - }); - test('test receive tokens already spent', async () => { - const msg = 'tokens already spent. Secret: oEpEuViVHUV2vQH81INUbq++Yv2w3u5H0LhaqXJKeR0='; - nock(mintUrl).post('/split').reply(200, { detail: msg }); - const wallet = new CashuWallet(mint); - - const { tokensWithErrors } = await wallet.receive(tokenInput); - const t = tokensWithErrors!; - - expect(tokensWithErrors).toBeDefined(); - expect(t.token).toHaveLength(1); - expect(t.token[0].proofs).toHaveLength(1); - expect(t.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: '/uYB/6wWnYkU' }], - mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' - }); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); - }); - test('test receive could not verify proofs', async () => { - nock(mintUrl).post('/split').reply(200, { code: 0, error: 'could not verify proofs.' }); - const wallet = new CashuWallet(mint); - - const { tokensWithErrors } = await wallet.receive(tokenInput); - const t = tokensWithErrors!; - - expect(tokensWithErrors).toBeDefined(); - expect(t.token).toHaveLength(1); - expect(t.token[0].proofs).toHaveLength(1); - expect(t.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: '/uYB/6wWnYkU' }], - mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' - }); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(response.token[0].proofs[0].C)).toBe(true); + // expect(/[A-Za-z0-9+/]{43}=/.test(response.token[0].proofs[0].secret)).toBe(true); + expect(response.tokensWithErrors).toBe(undefined); }); + // test('test receive custom split', async () => { + // nock(mintUrl) + // .post('/split') + // .reply(200, { + // promises: [ + // { + // id: 'z32vUtKgNCm1', + // amount: 1, + // C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' + // }, + // { + // id: 'z32vUtKgNCm1', + // amount: 1, + // C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' + // }, + // { + // id: 'z32vUtKgNCm1', + // amount: 1, + // C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' + // } + // ] + // }); + // const wallet = new CashuWallet(mint); + // const token3sat = 'eyJwcm9vZnMiOlt7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifSx7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifSx7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifV0sIm1pbnRzIjpbeyJ1cmwiOiJodHRwczovL2xlZ2VuZC5sbmJpdHMuY29tL2Nhc2h1L2FwaS92MS80Z3I5WGNtejNYRWtVTndpQmlRR29DIiwiaWRzIjpbIi91WUIvNndXbllrVSJdfV19' + // const { token: t, tokensWithErrors } = await wallet.receive(token3sat, [{ amount: 1, count: 3 }]); + + // expect(t.token).toHaveLength(1); + // expect(t.token[0].proofs).toHaveLength(3); + // expect(t.token[0]).toMatchObject({ + // proofs: [{ amount: 1, id: 'z32vUtKgNCm1' }, { amount: 1, id: 'z32vUtKgNCm1' }, { amount: 1, id: 'z32vUtKgNCm1' }], + // }); + // expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); + // expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + // expect(tokensWithErrors).toBe(undefined); + // }); + // test('test receive tokens already spent', async () => { + // const msg = 'tokens already spent. Secret: oEpEuViVHUV2vQH81INUbq++Yv2w3u5H0LhaqXJKeR0='; + // nock(mintUrl).post('/split').reply(200, { detail: msg }); + // const wallet = new CashuWallet(mint); + + // const { tokensWithErrors } = await wallet.receive(tokenInput); + // const t = tokensWithErrors!; + + // expect(tokensWithErrors).toBeDefined(); + // expect(t.token).toHaveLength(1); + // expect(t.token[0].proofs).toHaveLength(1); + // expect(t.token[0]).toMatchObject({ + // proofs: [{ amount: 1, id: '/uYB/6wWnYkU' }], + // mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' + // }); + // expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); + // expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + // }); + // test('test receive could not verify proofs', async () => { + // nock(mintUrl).post('/split').reply(200, { code: 0, error: 'could not verify proofs.' }); + // const wallet = new CashuWallet(mint); + + // const { tokensWithErrors } = await wallet.receive(tokenInput); + // const t = tokensWithErrors!; + + // expect(tokensWithErrors).toBeDefined(); + // expect(t.token).toHaveLength(1); + // expect(t.token[0].proofs).toHaveLength(1); + // expect(t.token[0]).toMatchObject({ + // proofs: [{ amount: 1, id: '/uYB/6wWnYkU' }], + // mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' + // }); + // expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); + // expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + // }); }); describe('checkProofsSpent', () => { @@ -211,7 +215,7 @@ describe('payLnInvoice', () => { describe('requestTokens', () => { test('test requestTokens', async () => { nock(mintUrl) - .post('/mint?hash=') + .post('/v1/mint/bolt11') .reply(200, { promises: [ { From 7630d74f2c4546512142a6abc5328f4887074225 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:36:47 -0300 Subject: [PATCH 009/175] generate only hex secrets --- src/CashuMint.ts | 4 +- src/CashuWallet.ts | 27 ++-- src/DHKE.ts | 8 +- test/request.test.ts | 2 +- test/utils.test.ts | 2 +- test/wallet.test.ts | 330 ++++++++++++++++++++++--------------------- 6 files changed, 196 insertions(+), 177 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index f4d9b35ef..6dde3b313 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -129,9 +129,9 @@ class CashuMint { * @returns the mints public keys */ async getKeys(keysetId?: string, mintUrl?: string, unit?: string): Promise { - const allKeys = CashuMint.getKeys(mintUrl || this._mintUrl, keysetId); + const allKeys = await CashuMint.getKeys(mintUrl || this._mintUrl, keysetId); // find keyset with unit 'sat' - const satKeys = ((await allKeys).keysets).find((keys) => keys.unit === unit ? unit : 'sat'); + const satKeys = (allKeys.keysets).find((keys) => keys.unit === unit ? unit : 'sat'); if (!satKeys) { throw new Error('No keyset with unit "sat" found'); } diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index df3e3223e..94f0b48d2 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -211,6 +211,7 @@ class CashuWallet { if (!preference) { preference = getDefaultAmountPreference(amount) } + const keyset = await this.initKeys() const { payload, blindedMessages } = this.createSplitPayload( amount, tokenEntry.proofs, @@ -221,7 +222,7 @@ class CashuWallet { signatures, blindedMessages.rs, blindedMessages.secrets, - await this.getKeys(signatures, tokenEntry.mint) + keyset ); proofs.push(...newProofs); } catch (error) { @@ -309,10 +310,10 @@ class CashuWallet { hash: string, AmountPreference?: Array ): Promise<{ proofs: Array }> { - await this.initKeys(); + const keyset = await this.initKeys(); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, - this.keysetId, + keyset, AmountPreference ); const postMintPayload: PostMintPayload = { @@ -328,12 +329,13 @@ class CashuWallet { /** * Initialize the wallet with the mints public keys */ - private async initKeys() { + private async initKeys(): Promise { if (!this.keysetId || !Object.keys(this.keys).length) { this.keys = await this.mint.getKeys(); // this._keysetId = deriveKeysetId(this.keys); this._keysetId = this.keys.id; } + return this.keys; } /** @@ -370,14 +372,21 @@ class CashuWallet { private createSplitPayload( amount: number, proofsToSend: Array, - preference?: Array + preference?: Array, + keyset?: MintKeys ): { payload: SplitPayload; blindedMessages: BlindedTransaction; } { + if (!keyset) { + if (!this.keys) { + throw new Error('No keyset available'); + } + keyset = this.keys; + } const totalAmount = proofsToSend.reduce((total, curr) => total + curr.amount, 0); - const keepBlindedMessages = this.createRandomBlindedMessages(totalAmount - amount, this.keysetId); - const sendBlindedMessages = this.createRandomBlindedMessages(amount, this.keysetId, preference); + const keepBlindedMessages = this.createRandomBlindedMessages(totalAmount - amount, keyset); + const sendBlindedMessages = this.createRandomBlindedMessages(amount, keyset, preference); // join keepBlindedMessages and sendBlindedMessages const blindedMessages: BlindedTransaction = { @@ -412,7 +421,7 @@ class CashuWallet { */ private createRandomBlindedMessages( amount: number, - keysetId: string, + keyset: MintKeys, amountPreference?: Array ): BlindedMessageData & { amounts: Array } { const blindedMessages: Array = []; @@ -424,7 +433,7 @@ class CashuWallet { secrets.push(secret); const { B_, r } = dhke.blindMessage(secret); rs.push(r); - const blindedMessage = new BlindedMessage(amounts[i], B_, keysetId); + const blindedMessage = new BlindedMessage(amounts[i], B_, keyset.id); blindedMessages.push(blindedMessage.getSerializedBlindedMessage()); } return { blindedMessages, secrets, rs, amounts }; diff --git a/src/DHKE.ts b/src/DHKE.ts index a26f7cb7e..865177c8d 100644 --- a/src/DHKE.ts +++ b/src/DHKE.ts @@ -27,9 +27,7 @@ export function pointFromHex(hex: string) { return secp256k1.ProjectivePoint.fromAffine(h2c.toAffine()); } */ function blindMessage(secret: Uint8Array, r?: bigint): { B_: ProjPointType; r: bigint } { - const secretMessageBase64 = encodeUint8toBase64(secret); - const secretMessage = new TextEncoder().encode(secretMessageBase64); - const Y = hashToCurve(secretMessage); + const Y = hashToCurve(secret); if (!r) { r = bytesToNumber(secp256k1.utils.randomPrivateKey()); } @@ -57,10 +55,12 @@ function constructProofs( const C_ = pointFromHex(p.C_); const A = pointFromHex(keyset.keys[p.amount]); const C = unblindSignature(C_, rs[i], A); + // Encode Uint8Array byte array as hex string: + const secret = Buffer.from(secrets[i]).toString('hex'); const proof = { id: p.id, amount: p.amount, - secret: encodeUint8toBase64(secrets[i]), + secret: secret, C: C.toHex(true) }; return proof; diff --git a/test/request.test.ts b/test/request.test.ts index 44e5fc4fc..5c503b7b3 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -4,7 +4,7 @@ import { CashuWallet } from '../src/CashuWallet.js'; import { setGlobalRequestOptions } from '../src/request.js'; let request: Record | undefined; -const mintUrl = 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC'; +const mintUrl = 'https://localhost:3338'; const invoice = 'lnbc20u1p3u27nppp5pm074ffk6m42lvae8c6847z7xuvhyknwgkk7pzdce47grf2ksqwsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzpgxqyz5vqsp5sw6n7cztudpl5m5jv3z6dtqpt2zhd3q6dwgftey9qxv09w82rgjq9qyyssqhtfl8wv7scwp5flqvmgjjh20nf6utvv5daw5h43h69yqfwjch7wnra3cn94qkscgewa33wvfh7guz76rzsfg9pwlk8mqd27wavf2udsq3yeuju'; diff --git a/test/utils.test.ts b/test/utils.test.ts index 3795a91a0..3fe6f02f5 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -93,7 +93,7 @@ describe('test decode token', () => { C: '02a42140d1bbd59ca4c5ba9eb73d020f31f68f4e3078fd3af1ee64ebece52b6eda' } ], - mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' + mint: 'https://localhost:3338' } ] }); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 57003bc9a..b0aae5f1b 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -2,9 +2,14 @@ import { decode } from '@gandlaf21/bolt11-decode'; import nock from 'nock'; import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; - -const dummyKeysResp = { 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181' }; -const mintUrl = 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC'; +import { ReceiveResponse } from '../src/model/types/index.js'; + +const dummyKeysResp = { + keysets: [{ + id: '009a1f293253e41e', unit: 'sat', keys: { 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181' } + },] +}; +const mintUrl = 'http://localhost:3338'; const mint = new CashuMint(mintUrl); const invoice = 'lnbc20u1p3u27nppp5pm074ffk6m42lvae8c6847z7xuvhyknwgkk7pzdce47grf2ksqwsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzpgxqyz5vqsp5sw6n7cztudpl5m5jv3z6dtqpt2zhd3q6dwgftey9qxv09w82rgjq9qyyssqhtfl8wv7scwp5flqvmgjjh20nf6utvv5daw5h43h69yqfwjch7wnra3cn94qkscgewa33wvfh7guz76rzsfg9pwlk8mqd27wavf2udsq3yeuju'; @@ -15,7 +20,8 @@ beforeAll(() => { beforeEach(() => { nock.cleanAll(); - nock(mintUrl).get('/keys').reply(200, dummyKeysResp); + nock(mintUrl).get('/v1/keys').reply(200, dummyKeysResp); + nock(mintUrl).get('/v1/keys/009a1f293253e41e').reply(200, dummyKeysResp); }); describe('test fees', () => { @@ -36,14 +42,14 @@ describe('test fees', () => { describe('receive', () => { const tokenInput = - 'eyJwcm9vZnMiOlt7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifV0sIm1pbnRzIjpbeyJ1cmwiOiJodHRwczovL2xlZ2VuZC5sbmJpdHMuY29tL2Nhc2h1L2FwaS92MS80Z3I5WGNtejNYRWtVTndpQmlRR29DIiwiaWRzIjpbIi91WUIvNndXbllrVSJdfV19'; + 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICI4ZTRlODU1NmZkODBkZGY1NDk4Y2JmOTY4ZjcxZGRkMDZiMTc2MDBkYTJmOWU1MWE4NTc1YmVjN2U3N2Q0YjgxIiwgIkMiOiAiMDJmZWM1ZGQzNzk3YmRhZTBiMzk3ZmFmZjkyOTAzYzEzZmQ2ZWVhZGQzN2NlNjJjZGJmODAwNGI1MTNjZDAzZmRmIn1dLCAibWludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzgifV19'; test('test receive', async () => { nock(mintUrl) .post('/v1/split') .reply(200, { signatures: [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' } @@ -51,96 +57,96 @@ describe('receive', () => { }); const wallet = new CashuWallet(mint); - const response = await wallet.receive(tokenInput); + const response: ReceiveResponse = await wallet.receive(tokenInput); + + expect(response.token.token).toHaveLength(1); + expect(response.token.token[0].proofs).toHaveLength(1); + expect(response.token.token[0]).toMatchObject({ + proofs: [{ amount: 1, id: '009a1f293253e41e' }], + mint: mintUrl + }); + expect(/[0-9a-f]{64}/.test(response.token.token[0].proofs[0].C)).toBe(true); + expect(/[0-9a-f]{64}/.test(response.token.token[0].proofs[0].secret)).toBe(true); + expect(response.tokensWithErrors).toBe(undefined); + }); + test('test receive custom split', async () => { + nock(mintUrl) + .post('/v1/split') + .reply(200, { + signatures: [ + { + id: '009a1f293253e41e', + amount: 1, + C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' + }, + { + id: '009a1f293253e41e', + amount: 1, + C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' + }, + { + id: '009a1f293253e41e', + amount: 1, + C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' + } + ] + }); + const wallet = new CashuWallet(mint); + const token3sat = 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0=' + const response: ReceiveResponse = await wallet.receive(token3sat, [{ amount: 1, count: 3 }]); - expect(response.token).toHaveLength(1); - expect(response.token[0].proofs).toHaveLength(1); - expect(response.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: 'z32vUtKgNCm1' }], - mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' + expect(response.token.token).toHaveLength(1); + expect(response.token.token[0].proofs).toHaveLength(3); + expect(response.token.token[0]).toMatchObject({ + proofs: [{ amount: 1, id: '009a1f293253e41e' }, { amount: 1, id: '009a1f293253e41e' }, { amount: 1, id: '009a1f293253e41e' }], }); - expect(/[0-9a-f]{64}/.test(response.token[0].proofs[0].C)).toBe(true); - // expect(/[A-Za-z0-9+/]{43}=/.test(response.token[0].proofs[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(response.token.token[0].proofs[0].C)).toBe(true); + expect(/[0-9a-f]{64}/.test(response.token.token[0].proofs[0].secret)).toBe(true); expect(response.tokensWithErrors).toBe(undefined); }); - // test('test receive custom split', async () => { - // nock(mintUrl) - // .post('/split') - // .reply(200, { - // promises: [ - // { - // id: 'z32vUtKgNCm1', - // amount: 1, - // C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - // }, - // { - // id: 'z32vUtKgNCm1', - // amount: 1, - // C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - // }, - // { - // id: 'z32vUtKgNCm1', - // amount: 1, - // C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' - // } - // ] - // }); - // const wallet = new CashuWallet(mint); - // const token3sat = 'eyJwcm9vZnMiOlt7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifSx7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifSx7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifV0sIm1pbnRzIjpbeyJ1cmwiOiJodHRwczovL2xlZ2VuZC5sbmJpdHMuY29tL2Nhc2h1L2FwaS92MS80Z3I5WGNtejNYRWtVTndpQmlRR29DIiwiaWRzIjpbIi91WUIvNndXbllrVSJdfV19' - // const { token: t, tokensWithErrors } = await wallet.receive(token3sat, [{ amount: 1, count: 3 }]); - - // expect(t.token).toHaveLength(1); - // expect(t.token[0].proofs).toHaveLength(3); - // expect(t.token[0]).toMatchObject({ - // proofs: [{ amount: 1, id: 'z32vUtKgNCm1' }, { amount: 1, id: 'z32vUtKgNCm1' }, { amount: 1, id: 'z32vUtKgNCm1' }], - // }); - // expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - // expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); - // expect(tokensWithErrors).toBe(undefined); - // }); - // test('test receive tokens already spent', async () => { - // const msg = 'tokens already spent. Secret: oEpEuViVHUV2vQH81INUbq++Yv2w3u5H0LhaqXJKeR0='; - // nock(mintUrl).post('/split').reply(200, { detail: msg }); - // const wallet = new CashuWallet(mint); - - // const { tokensWithErrors } = await wallet.receive(tokenInput); - // const t = tokensWithErrors!; - - // expect(tokensWithErrors).toBeDefined(); - // expect(t.token).toHaveLength(1); - // expect(t.token[0].proofs).toHaveLength(1); - // expect(t.token[0]).toMatchObject({ - // proofs: [{ amount: 1, id: '/uYB/6wWnYkU' }], - // mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' - // }); - // expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - // expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); - // }); - // test('test receive could not verify proofs', async () => { - // nock(mintUrl).post('/split').reply(200, { code: 0, error: 'could not verify proofs.' }); - // const wallet = new CashuWallet(mint); - - // const { tokensWithErrors } = await wallet.receive(tokenInput); - // const t = tokensWithErrors!; - - // expect(tokensWithErrors).toBeDefined(); - // expect(t.token).toHaveLength(1); - // expect(t.token[0].proofs).toHaveLength(1); - // expect(t.token[0]).toMatchObject({ - // proofs: [{ amount: 1, id: '/uYB/6wWnYkU' }], - // mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' - // }); - // expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - // expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); - // }); + test('test receive tokens already spent', async () => { + const msg = 'tokens already spent. Secret: asdasdasd'; + nock(mintUrl).post('/v1/split').reply(200, { detail: msg }); + const wallet = new CashuWallet(mint); + + const { tokensWithErrors } = await wallet.receive(tokenInput); + const t = tokensWithErrors!; + + expect(tokensWithErrors).toBeDefined(); + expect(t.token).toHaveLength(1); + expect(t.token[0].proofs).toHaveLength(1); + expect(t.token[0]).toMatchObject({ + proofs: [{ amount: 1, id: '009a1f293253e41e' }], + mint: 'http://localhost:3338' + }); + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); + }); + test('test receive could not verify proofs', async () => { + nock(mintUrl).post('/v1/split').reply(200, { code: 0, error: 'could not verify proofs.' }); + const wallet = new CashuWallet(mint); + + const { tokensWithErrors } = await wallet.receive(tokenInput); + const t = tokensWithErrors!; + + expect(tokensWithErrors).toBeDefined(); + expect(t.token).toHaveLength(1); + expect(t.token[0].proofs).toHaveLength(1); + expect(t.token[0]).toMatchObject({ + proofs: [{ amount: 1, id: '009a1f293253e41e' }], + mint: 'http://localhost:3338' + }); + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); + }); }); describe('checkProofsSpent', () => { const proofs = [ { - id: '0NI3TUAs1Sfy', + id: '009a1f293253e41e', amount: 1, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: 'e7c1b76d1b31e2bca2b229d160bdf6046f33bc4570222304b65110d926f7af89', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; @@ -159,15 +165,15 @@ describe('checkProofsSpent', () => { describe('payLnInvoice', () => { const proofs = [ { - id: '0NI3TUAs1Sfy', + id: '009a1f293253e41e', amount: 1, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: 'e7c1b76d1b31e2bca2b229d160bdf6046f33bc4570222304b65110d926f7af89', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; test('test payLnInvoice base case', async () => { - nock(mintUrl).post('/checkfees').reply(200, { fee: 0 }); - nock(mintUrl).post('/melt').reply(200, { paid: true, preimage: '' }); + nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, { quote: "quote_id", amount: 123, fee_reserve: 0 }); + nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, proof: '' }); const wallet = new CashuWallet(mint); const result = await wallet.payLnInvoice(invoice, proofs); @@ -176,19 +182,23 @@ describe('payLnInvoice', () => { }); test('test payLnInvoice change', async () => { nock.cleanAll(); - nock(mintUrl).get('/keys').reply(200, { - 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181', - 2: '03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5' + nock(mintUrl).get('/v1/keys').reply(200, { + keysets: [{ + id: '009a1f293253e41e', unit: 'sat', keys: { + 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181', + 2: '03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5' + } + },] }); - nock(mintUrl).post('/checkfees').reply(200, { fee: 2 }); + nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, { quote: "quote_id", amount: 123, fee_reserve: 2 }); nock(mintUrl) - .post('/melt') + .post('/v1/melt/bolt11') .reply(200, { paid: true, - preimage: '', + proof: 'asd', change: [ { - id: '+GmhrYs64zDj', + id: '009a1f293253e41e', amount: 2, C_: '0361a2725cfd88f60ded718378e8049a4a6cee32e214a9870b44c3ffea2dc9e625' } @@ -199,11 +209,11 @@ describe('payLnInvoice', () => { const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }]); expect(result.isPaid).toBe(true); - expect(result.preimage).toBe(''); + expect(result.preimage).toBe('asd'); expect(result.change).toHaveLength(1); }); test('test payLnInvoice bad resonse', async () => { - nock(mintUrl).post('/checkfees').reply(200, {}); + nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, {}); const wallet = new CashuWallet(mint); const result = await wallet.payLnInvoice(invoice, proofs).catch((e) => e); @@ -217,9 +227,9 @@ describe('requestTokens', () => { nock(mintUrl) .post('/v1/mint/bolt11') .reply(200, { - promises: [ + signatures: [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '0361a2725cfd88f60ded718378e8049a4a6cee32e214a9870b44c3ffea2dc9e625' } @@ -230,12 +240,12 @@ describe('requestTokens', () => { const { proofs } = await wallet.requestTokens(1, ''); expect(proofs).toHaveLength(1); - expect(proofs[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); + expect(proofs[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(proofs[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(proofs[0].secret)).toBe(true); }); test('test requestTokens bad resonse', async () => { - nock(mintUrl).post('/mint?hash=').reply(200, {}); + nock(mintUrl).post('/v1/mint/bolt11').reply(200, {}); const wallet = new CashuWallet(mint); const result = await wallet.requestTokens(1, '').catch((e) => e); @@ -247,9 +257,9 @@ describe('requestTokens', () => { describe('send', () => { const proofs = [ { - id: '0NI3TUAs1Sfy', + id: '009a1f293253e41e', amount: 1, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: 'e7c1b76d1b31e2bca2b229d160bdf6046f33bc4570222304b65110d926f7af89', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; @@ -257,9 +267,9 @@ describe('send', () => { nock(mintUrl) .post('/split') .reply(200, { - promises: [ + signatures: [ { - id: '0NI3TUAs1Sfy', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' } @@ -271,22 +281,22 @@ describe('send', () => { expect(result.returnChange).toHaveLength(0); expect(result.send).toHaveLength(1); - expect(result.send[0]).toMatchObject({ amount: 1, id: '0NI3TUAs1Sfy' }); + expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.send[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); }); test('test send over paying. Should return change', async () => { nock(mintUrl) .post('/split') .reply(200, { - promises: [ + signatures: [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' }, { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' } @@ -296,35 +306,35 @@ describe('send', () => { const result = await wallet.send(1, [ { - id: '0NI3TUAs1Sfy', + id: '009a1f293253e41e', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: 'e7c1b76d1b31e2bca2b229d160bdf6046f33bc4570222304b65110d926f7af89', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]); expect(result.send).toHaveLength(1); - expect(result.send[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); + expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.send[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); expect(result.returnChange).toHaveLength(1); - expect(result.returnChange[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); + expect(result.returnChange[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.returnChange[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.returnChange[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.returnChange[0].secret)).toBe(true); }); test('test send over paying2', async () => { nock(mintUrl) .post('/split') .reply(200, { - promises: [ + signatures: [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' }, { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' } @@ -334,45 +344,45 @@ describe('send', () => { const overpayProofs = [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: 'e7c1b76d1b31e2bca2b229d160bdf6046f33bc4570222304b65110d926f7af89', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; const result = await wallet.send(1, overpayProofs); expect(result.send).toHaveLength(1); - expect(result.send[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); + expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.send[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); expect(result.returnChange).toHaveLength(1); - expect(result.returnChange[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); + expect(result.returnChange[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.returnChange[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.returnChange[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.returnChange[0].secret)).toBe(true); }); test('test send preference', async () => { nock(mintUrl) .post('/split') .reply(200, { - promises: [ + signatures: [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' }, { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' }, { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' }, { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' } @@ -382,27 +392,27 @@ describe('send', () => { const overpayProofs = [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: 'e7c1b76d1b31e2bca2b229d160bdf6046f33bc4570222304b65110d926f7af89', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' }, { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: 'e7c1b76d1b31e2bca2b229d160bdf6046f33bc4570222304b65110d926f7af89', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; const result = await wallet.send(4, overpayProofs, [{ amount: 1, count: 4 }]); expect(result.send).toHaveLength(4); - expect(result.send[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); - expect(result.send[1]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); - expect(result.send[2]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); - expect(result.send[3]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); + expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); + expect(result.send[1]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); + expect(result.send[2]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); + expect(result.send[3]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.send[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); expect(result.returnChange).toHaveLength(0); }); @@ -410,24 +420,24 @@ describe('send', () => { nock(mintUrl) .post('/split') .reply(200, { - promises: [ + signatures: [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' }, { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' }, { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' }, { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' } @@ -437,37 +447,37 @@ describe('send', () => { const overpayProofs = [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: 'e7c1b76d1b31e2bca2b229d160bdf6046f33bc4570222304b65110d926f7af89', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' }, { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: 'e7c1b76d1b31e2bca2b229d160bdf6046f33bc4570222304b65110d926f7af89', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; const result = await wallet.send(4, overpayProofs, [{ amount: 1, count: 3 }]); expect(result.send).toHaveLength(3); - expect(result.send[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); - expect(result.send[1]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); - expect(result.send[2]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); + expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); + expect(result.send[1]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); + expect(result.send[2]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.send[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); expect(result.returnChange).toHaveLength(1); - expect(result.returnChange[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); + expect(result.returnChange[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); }); test('test send not enough funds', async () => { nock(mintUrl) .post('/split') .reply(200, { - promises: [ + signatures: [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 1, C_: '021179b095a67380ab3285424b563b7aab9818bd38068e1930641b3dceb364d422' } @@ -486,9 +496,9 @@ describe('send', () => { const result = await wallet .send(1, [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: 'e7c1b76d1b31e2bca2b229d160bdf6046f33bc4570222304b65110d926f7af89', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]) From a94e42c0317e602910c35be2627e2d345da88495 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:03:02 -0300 Subject: [PATCH 010/175] mostly working but something is wrong with crypto --- src/DHKE.ts | 7 +++---- test/crypto.scheme.test.ts | 2 +- test/dhke.test.ts | 24 ++++++++++++++++++++---- test/integration.test.ts | 15 ++++++++++++++- test/request.test.ts | 20 ++++++++++++++------ test/utils.test.ts | 2 +- test/wallet.test.ts | 12 ++++++------ 7 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/DHKE.ts b/src/DHKE.ts index 865177c8d..69555abb7 100644 --- a/src/DHKE.ts +++ b/src/DHKE.ts @@ -1,6 +1,5 @@ import { ProjPointType } from '@noble/curves/abstract/weierstrass'; import { secp256k1 } from '@noble/curves/secp256k1'; -import { encodeUint8toBase64 } from './base64.js'; import { MintKeys, Proof, SerializedBlindedSignature } from './model/types/index.js'; import { bytesToNumber } from './utils.js'; import { sha256 } from '@noble/hashes/sha256'; @@ -27,7 +26,8 @@ export function pointFromHex(hex: string) { return secp256k1.ProjectivePoint.fromAffine(h2c.toAffine()); } */ function blindMessage(secret: Uint8Array, r?: bigint): { B_: ProjPointType; r: bigint } { - const Y = hashToCurve(secret); + const secretMessage = new TextEncoder().encode(bytesToHex(secret)); + const Y = hashToCurve(secretMessage); if (!r) { r = bytesToNumber(secp256k1.utils.randomPrivateKey()); } @@ -55,8 +55,7 @@ function constructProofs( const C_ = pointFromHex(p.C_); const A = pointFromHex(keyset.keys[p.amount]); const C = unblindSignature(C_, rs[i], A); - // Encode Uint8Array byte array as hex string: - const secret = Buffer.from(secrets[i]).toString('hex'); + const secret = bytesToHex(secrets[i]) const proof = { id: p.id, amount: p.amount, diff --git a/test/crypto.scheme.test.ts b/test/crypto.scheme.test.ts index 16ca68a94..0dd1a745e 100644 --- a/test/crypto.scheme.test.ts +++ b/test/crypto.scheme.test.ts @@ -50,7 +50,7 @@ class Wallet { private rG: ProjPointType | undefined; private B_: ProjPointType | undefined; private secret = new Uint8Array(); - constructor() {} + constructor() { } async createBlindedMessage(message: string): Promise> { const enc = new TextEncoder(); diff --git a/test/dhke.test.ts b/test/dhke.test.ts index 30b2da162..d08cb4215 100644 --- a/test/dhke.test.ts +++ b/test/dhke.test.ts @@ -22,14 +22,30 @@ describe('testing hash to curve', () => { describe('test blinding message', () => { test('testing string 0000....01', async () => { - var enc = new TextEncoder(); - let secretUInt8 = enc.encode(SECRET_MESSAGE); + let secretUInt8 = new TextEncoder().encode(SECRET_MESSAGE); + expect(secretUInt8).toStrictEqual( + new Uint8Array([ + 116, + 101, + 115, + 116, + 95, + 109, + 101, + 115, + 115, + 97, + 103, + 101 + ]) + ); + const r = bytesToNumber(hexToBytes('0000000000000000000000000000000000000000000000000000000000000001')) let { B_ } = await dhke.blindMessage( secretUInt8, - bytesToNumber(hexToBytes('0000000000000000000000000000000000000000000000000000000000000001')) + r ); expect(B_.toHex(true)).toBe( - '03c509bbdd8aaa81d5e67468d07b4b7dffd5769ac596ff3964e151adcefc6b06d0' + '02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2' ); }); }); diff --git a/test/integration.test.ts b/test/integration.test.ts index ef47c61e7..95a3ddae7 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -118,7 +118,20 @@ describe('mint api', () => { expect(returnChangeSpent).toBeDefined(); expect(returnChangeSpent).toEqual([]); }); - test('test send tokens', async () => { + test('test send tokens exact without previous split', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + const request = await wallet.requestMint(64); + const tokens = await wallet.requestTokens(64, request.quote); + + const sendResponse = await wallet.send(64, tokens.proofs); + expect(sendResponse).toBeDefined(); + expect(sendResponse.send).toBeDefined(); + expect(sendResponse.returnChange).toBeDefined(); + expect(sendResponse.send.length).toBe(1); + expect(sendResponse.returnChange.length).toBe(0); + }); + test('test send tokens with change', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); const request = await wallet.requestMint(100); diff --git a/test/request.test.ts b/test/request.test.ts index 5c503b7b3..14ff1616a 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -21,14 +21,18 @@ describe('requests', () => { test('request with body contains the correct headers', async () => { const mint = new CashuMint(mintUrl); nock(mintUrl) - .post('/checkfees') + .post('/v1/melt/quote/bolt11') .reply(200, function () { request = this.req.headers; - return { fee: 20 }; + return { + quote: 'test_melt_quote_id', + amount: 2000, + fee_reserve: 20 + }; }); const wallet = new CashuWallet(mint); - await wallet.getFee(invoice); + await wallet.getMeltQuote(invoice); expect(request).toBeDefined(); expect(request!['content-type']).toContain('application/json'); @@ -37,15 +41,19 @@ describe('requests', () => { test('global custom headers can be set', async () => { const mint = new CashuMint(mintUrl); nock(mintUrl) - .post('/checkfees') + .post('/v1/melt/quote/bolt11') .reply(200, function () { request = this.req.headers; - return { fee: 20 }; + return { + quote: 'test_melt_quote_id', + amount: 2000, + fee_reserve: 20 + }; }); const wallet = new CashuWallet(mint); setGlobalRequestOptions({ headers: { 'x-cashu': 'xyz-123-abc' } }); - await wallet.getFee(invoice); + await wallet.getMeltQuote(invoice); expect(request).toBeDefined(); expect(request!['x-cashu']).toContain('xyz-123-abc'); diff --git a/test/utils.test.ts b/test/utils.test.ts index 3fe6f02f5..3795a91a0 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -93,7 +93,7 @@ describe('test decode token', () => { C: '02a42140d1bbd59ca4c5ba9eb73d020f31f68f4e3078fd3af1ee64ebece52b6eda' } ], - mint: 'https://localhost:3338' + mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' } ] }); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index b0aae5f1b..91e3fa5ef 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -287,7 +287,7 @@ describe('send', () => { }); test('test send over paying. Should return change', async () => { nock(mintUrl) - .post('/split') + .post('/v1/split') .reply(200, { signatures: [ { @@ -325,7 +325,7 @@ describe('send', () => { test('test send over paying2', async () => { nock(mintUrl) - .post('/split') + .post('/v1/split') .reply(200, { signatures: [ { @@ -363,7 +363,7 @@ describe('send', () => { }); test('test send preference', async () => { nock(mintUrl) - .post('/split') + .post('/v1/split') .reply(200, { signatures: [ { @@ -418,7 +418,7 @@ describe('send', () => { test('test send preference overpay', async () => { nock(mintUrl) - .post('/split') + .post('/v1/split') .reply(200, { signatures: [ { @@ -473,7 +473,7 @@ describe('send', () => { test('test send not enough funds', async () => { nock(mintUrl) - .post('/split') + .post('/v1/split') .reply(200, { signatures: [ { @@ -490,7 +490,7 @@ describe('send', () => { expect(result).toEqual(new Error('Not enough funds available')); }); test('test send bad response', async () => { - nock(mintUrl).post('/split').reply(200, {}); + nock(mintUrl).post('/v1/split').reply(200, {}); const wallet = new CashuWallet(mint); const result = await wallet From 83b1693dbb885f61d4280b837f19577be772ed0a Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:34:21 -0300 Subject: [PATCH 011/175] load keys before receive --- src/CashuWallet.ts | 19 +++++++------------ test/integration.test.ts | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 94f0b48d2..79ae9ddbb 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -215,6 +215,7 @@ class CashuWallet { const { payload, blindedMessages } = this.createSplitPayload( amount, tokenEntry.proofs, + keyset, preference ); const { signatures, error } = await CashuMint.split(tokenEntry.mint, payload); @@ -252,7 +253,7 @@ class CashuWallet { if (preference) { amount = preference?.reduce((acc, curr) => acc + curr.amount * curr.count, 0); } - + const keyset = await this.initKeys(); let amountAvailable = 0; const proofsToSend: Array = []; const proofsToKeep: Array = []; @@ -270,13 +271,13 @@ class CashuWallet { } if (amount < amountAvailable || preference) { const { amountKeep, amountSend } = this.splitReceive(amount, amountAvailable); - const { payload, blindedMessages } = this.createSplitPayload(amountSend, proofsToSend, preference); + const { payload, blindedMessages } = this.createSplitPayload(amountSend, proofsToSend, keyset, preference); const { signatures } = await this.mint.split(payload); const proofs = dhke.constructProofs( signatures, blindedMessages.rs, blindedMessages.secrets, - await this.getKeys(signatures) + keyset ); // sum up proofs until amount2 is reached const splitProofsToKeep: Array = []; @@ -322,7 +323,7 @@ class CashuWallet { }; const { signatures } = await this.mint.mint(postMintPayload); return { - proofs: dhke.constructProofs(signatures, rs, secrets, await this.getKeys(signatures)) + proofs: dhke.constructProofs(signatures, rs, secrets, keyset) }; } @@ -372,18 +373,12 @@ class CashuWallet { private createSplitPayload( amount: number, proofsToSend: Array, - preference?: Array, - keyset?: MintKeys + keyset: MintKeys, + preference?: Array ): { payload: SplitPayload; blindedMessages: BlindedTransaction; } { - if (!keyset) { - if (!this.keys) { - throw new Error('No keyset available'); - } - keyset = this.keys; - } const totalAmount = proofsToSend.reduce((total, curr) => total + curr.amount, 0); const keepBlindedMessages = this.createRandomBlindedMessages(totalAmount - amount, keyset); const sendBlindedMessages = this.createRandomBlindedMessages(amount, keyset, preference); diff --git a/test/integration.test.ts b/test/integration.test.ts index 95a3ddae7..73e42ae98 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -144,7 +144,7 @@ describe('mint api', () => { expect(sendResponse.send.length).toBe(2); expect(sendResponse.returnChange.length).toBe(4); }); - test('receive tokens', async () => { + test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); const request = await wallet.requestMint(100); @@ -159,4 +159,17 @@ describe('mint api', () => { expect(response.token).toBeDefined(); expect(response.tokensWithErrors).toBeUndefined(); }); + test('receive tokens with previous mint', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + const request = await wallet.requestMint(64); + const tokens = await wallet.requestTokens(64, request.quote); + const encoded = getEncodedToken({ + token: [{ mint: mintUrl, proofs: tokens.proofs }] + }); + const response = await wallet.receive(encoded); + expect(response).toBeDefined(); + expect(response.token).toBeDefined(); + expect(response.tokensWithErrors).toBeUndefined(); + }); }); From a84cc18aca6f65d2620d9e7b6c6f1306acd21fbd Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:36:59 -0300 Subject: [PATCH 012/175] generate 64 character hex string --- src/CashuWallet.ts | 5 +++-- src/DHKE.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 79ae9ddbb..533427ed3 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -30,6 +30,7 @@ import { getDefaultAmountPreference, splitAmount } from './utils.js'; +import { bytesToHex } from '@noble/curves/abstract/utils'; /** * Class that represents a Cashu wallet. @@ -424,7 +425,7 @@ class CashuWallet { const rs: Array = []; const amounts = splitAmount(amount, amountPreference); for (let i = 0; i < amounts.length; i++) { - const secret = randomBytes(32); + const secret = new TextEncoder().encode(bytesToHex(randomBytes(32))); secrets.push(secret); const { B_, r } = dhke.blindMessage(secret); rs.push(r); @@ -446,7 +447,7 @@ class CashuWallet { const rs: Array = []; const count = Math.ceil(Math.log2(feeReserve)) || 1; for (let i = 0; i < count; i++) { - const secret = randomBytes(32); + const secret = new TextEncoder().encode(bytesToHex(randomBytes(32))); secrets.push(secret); const { B_, r } = dhke.blindMessage(secret); rs.push(r); diff --git a/src/DHKE.ts b/src/DHKE.ts index 69555abb7..0be4b3024 100644 --- a/src/DHKE.ts +++ b/src/DHKE.ts @@ -26,7 +26,7 @@ export function pointFromHex(hex: string) { return secp256k1.ProjectivePoint.fromAffine(h2c.toAffine()); } */ function blindMessage(secret: Uint8Array, r?: bigint): { B_: ProjPointType; r: bigint } { - const secretMessage = new TextEncoder().encode(bytesToHex(secret)); + const secretMessage = new TextEncoder().encode(new TextDecoder().decode(secret)); const Y = hashToCurve(secretMessage); if (!r) { r = bytesToNumber(secp256k1.utils.randomPrivateKey()); @@ -55,7 +55,7 @@ function constructProofs( const C_ = pointFromHex(p.C_); const A = pointFromHex(keyset.keys[p.amount]); const C = unblindSignature(C_, rs[i], A); - const secret = bytesToHex(secrets[i]) + const secret = new TextDecoder().decode(secrets[i]) const proof = { id: p.id, amount: p.amount, From 100ae41d7443cf2cc21d2e92eb711da5206349ce Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:52:20 -0300 Subject: [PATCH 013/175] renamed requestTokens->mintTokens and payLnInvoice->meltTokens --- src/CashuMint.ts | 35 +------ src/CashuWallet.ts | 212 +++++++++++++++++++-------------------- src/model/types/index.ts | 2 +- test/integration.test.ts | 34 +++---- test/wallet.test.ts | 6 +- 5 files changed, 126 insertions(+), 163 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 6dde3b313..88609e74a 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -8,8 +8,6 @@ import { MintActiveKeys, MintAllKeysets, RequestMintResponse, - SerializedBlindedMessage, - SerializedBlindedSignature, SplitPayload, SplitResponse, RequestMintPayload, @@ -239,37 +237,6 @@ class CashuMint { async melt(meltPayload: MeltPayload): Promise { return CashuMint.melt(this._mintUrl, meltPayload); } - // /** - // * Estimate fees for a given LN invoice - // * @param mintUrl - // * @param checkfeesPayload Payload containing LN invoice that needs to get a fee estimate - // * @returns estimated Fee - // */ - // public static async checkFees( - // mintUrl: string, - // checkfeesPayload: { pr: string } - // ): Promise<{ fee: number }> { - // const data = await request<{ fee: number }>({ - // endpoint: joinUrls(mintUrl, 'checkfees'), - // method: 'POST', - // requestBody: checkfeesPayload - // }); - - // if (!isObj(data) || typeof data?.fee !== 'number') { - // throw new Error('bad response'); - // } - - // return data; - // } - // /** - // * Estimate fees for a given LN invoice - // * @param mintUrl - // * @param checkfeesPayload Payload containing LN invoice that needs to get a fee estimate - // * @returns estimated Fee - // */ - // async checkFees(checkfeesPayload: { pr: string }): Promise<{ fee: number }> { - // return CashuMint.checkFees(this._mintUrl, checkfeesPayload); - // } /** * Checks if specific proofs have already been redeemed * @param mintUrl @@ -281,7 +248,7 @@ class CashuMint { checkPayload: CheckSpendablePayload ): Promise { const data = await request({ - endpoint: joinUrls(mintUrl, 'check'), + endpoint: joinUrls(mintUrl, '/v1/check'), method: 'POST', requestBody: checkPayload }); diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 533427ed3..09acc18b0 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -10,7 +10,7 @@ import { MeltQuoteResponse, MintKeys, MintKeyset, - PayLnInvoiceResponse, + MeltTokensResponse, PaymentPayload, PostMintPayload, Proof, @@ -68,33 +68,82 @@ class CashuWallet { return this._keysetId; } /** - * returns proofs that are already spent (use for keeping wallet state clean) - * @param proofs (only the 'secret' field is required) - * @returns + * Initialize the wallet with the mints public keys */ - async checkProofsSpent(proofs: Array): Promise> { - const payload = { - //send only the secret - proofs: proofs.map((p) => ({ secret: p.secret })) - }; - const { spendable } = await this.mint.check(payload); - return proofs.filter((_, i) => !spendable[i]); + private async initKeys(): Promise { + if (!this.keysetId || !Object.keys(this.keys).length) { + this.keys = await this.mint.getKeys(); + // this._keysetId = deriveKeysetId(this.keys); + this._keysetId = this.keys.id; + } + return this.keys; } + + /** + * Get the mint's public keys for a given set of proofs + * @param arr array of proofs + * @param mint optional mint url + * @returns keys + */ + private async getKeys(arr: Array, mint?: string): Promise { + await this.initKeys(); + if (!arr?.length || !arr[0]?.id) { + return this.keys; + } + const keysetId = arr[0].id; + if (this.keysetId === keysetId) { + return this.keys; + } + + const keys = + !mint || mint === this.mint.mintUrl + ? await this.mint.getKeys(keysetId) + : await this.mint.getKeys(keysetId, mint); + + return keys; + } + /** - * Starts a minting process by requesting an invoice from the mint + * Requests a mint quote form the mint. Response returns a Lightning payment request for the requested given amount and unit. * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ - requestMint(amount: number) { + getMintQuote(amount: number) { const requestMintPayload: RequestMintPayload = { unit: this.unit, amount: amount } return this.mint.mintQuote(requestMintPayload); } + /** + * Mint tokens for a given mint quote + * @param amount amount to request + * @param quote ID of mint quote + * @returns proofs + */ + async mintTokens( + amount: number, + quote: string, + AmountPreference?: Array + ): Promise<{ proofs: Array }> { + const keyset = await this.initKeys(); + const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( + amount, + keyset, + AmountPreference + ); + const postMintPayload: PostMintPayload = { + outputs: blindedMessages, + quote: quote + }; + const { signatures } = await this.mint.mint(postMintPayload); + return { + proofs: dhke.constructProofs(signatures, rs, secrets, keyset) + }; + } /** - * Requests a quote for a LN payment. Response returns amount and fees for a given LN invoice. + * Requests a melt quote from the mint. Response returns amount and fees for a given unit in order to pay a Lightning invoice. * @param invoice LN invoice that needs to get a fee estimate * @returns estimated Fee */ @@ -103,33 +152,16 @@ class CashuWallet { return meltQuote; } /** - * Executes a payment of an invoice on the Lightning network. - * The combined amount of Proofs has to match the payment amount including fees. - * @param invoice - * @param proofsToSend the exact amount to send including fees - * @param meltQuote melt quote for the invoice + * Melt tokens for a melt quote. proofsToSend must be at least amount+fee_reserve form the melt quote. + * Returns payment proof and change proofs + * @param meltQuote ID of the melt quote + * @param proofsToSend proofs to melt + * @returns */ - async payLnInvoice( - invoice: string, - proofsToSend: Array, - meltQuote?: MeltQuoteResponse - ): Promise { - if (!meltQuote) { - meltQuote = await this.mint.meltQuote({ unit: this.unit, request: invoice }); - } - return await this.payMeltQuote(meltQuote, proofsToSend); - - } - /** - * Pays an LN quote. - * @param meltQuote - * @param proofsToSend the exact amount to send including fees - * @returns - */ - async payMeltQuote( + async meltTokens( meltQuote: MeltQuoteResponse, proofsToSend: Array - ): Promise { + ): Promise { const { blindedMessages, secrets, rs } = this.createBlankOutputs(meltQuote.fee_reserve); const meltPayload: MeltPayload = { quote: meltQuote.quote, @@ -146,23 +178,39 @@ class CashuWallet { : [] }; } + /** + * Helper function that pays a Lightning invoice directly without having to create a melt quote before + * The combined amount of Proofs must match the payment amount including fees. + * @param invoice + * @param proofsToSend the exact amount to send including fees + * @param meltQuote melt quote for the invoice + * @returns + */ + async payLnInvoice( + invoice: string, + proofsToSend: Array, + meltQuote?: MeltQuoteResponse + ): Promise { + if (!meltQuote) { + meltQuote = await this.mint.meltQuote({ unit: this.unit, request: invoice }); + } + return await this.meltTokens(meltQuote, proofsToSend); - + } /** - * NOTE: This method is a helper. Should be moved. - * - * Use a cashu token to pay an ln invoice + * Helper function to ingest a Cashu token and pay a Lightning invoice with it. * @param invoice Lightning invoice * @param token cashu token */ - payLnInvoiceWithToken(invoice: string, token: string): Promise { + payLnInvoiceWithToken(invoice: string, token: string): Promise { const decodedToken = getDecodedToken(token); const proofs = decodedToken.token .filter((x) => x.mint === this.mint.mintUrl) .flatMap((t) => t.proofs); return this.payLnInvoice(invoice, proofs); } + /** * Receive an encoded Cashu token * @param encodedToken Cashu token @@ -197,7 +245,6 @@ class CashuWallet { tokensWithErrors: tokenEntriesWithError.length ? { token: tokenEntriesWithError } : undefined, }; } - /** * Receive a single cashu token entry * @param tokenEntry a single entry of a cashu token @@ -300,70 +347,6 @@ class CashuWallet { } return { returnChange: proofsToKeep, send: proofsToSend }; } - - /** - * Request tokens from the mint - * @param amount amount to request - * @param hash hash to use to identify the request - * @returns proofs - */ - async requestTokens( - amount: number, - hash: string, - AmountPreference?: Array - ): Promise<{ proofs: Array }> { - const keyset = await this.initKeys(); - const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( - amount, - keyset, - AmountPreference - ); - const postMintPayload: PostMintPayload = { - outputs: blindedMessages, - quote: hash - }; - const { signatures } = await this.mint.mint(postMintPayload); - return { - proofs: dhke.constructProofs(signatures, rs, secrets, keyset) - }; - } - - /** - * Initialize the wallet with the mints public keys - */ - private async initKeys(): Promise { - if (!this.keysetId || !Object.keys(this.keys).length) { - this.keys = await this.mint.getKeys(); - // this._keysetId = deriveKeysetId(this.keys); - this._keysetId = this.keys.id; - } - return this.keys; - } - - /** - * Get the mint's public keys for a given set of proofs - * @param arr array of proofs - * @param mint optional mint url - * @returns keys - */ - private async getKeys(arr: Array, mint?: string): Promise { - await this.initKeys(); - if (!arr?.length || !arr[0]?.id) { - return this.keys; - } - const keysetId = arr[0].id; - if (this.keysetId === keysetId) { - return this.keys; - } - - const keys = - !mint || mint === this.mint.mintUrl - ? await this.mint.getKeys(keysetId) - : await this.mint.getKeys(keysetId, mint); - - return keys; - } - /** * Creates a split payload * @param amount1 amount to keep @@ -401,6 +384,19 @@ class CashuWallet { }; return { payload, blindedMessages }; } + /** + * returns proofs that are already spent (use for keeping wallet state clean) + * @param proofs (only the 'secret' field is required) + * @returns + */ + async checkProofsSpent(proofs: Array): Promise> { + const payload = { + //send only the secret + proofs: proofs.map((p) => ({ secret: p.secret })) + }; + const { spendable } = await this.mint.check(payload); + return proofs.filter((_, i) => !spendable[i]); + } private splitReceive( amount: number, amountAvailable: number diff --git a/src/model/types/index.ts b/src/model/types/index.ts index b5104b2b2..21aefb1cc 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -203,7 +203,7 @@ export type MeltResponse = { /** * Response after paying a Lightning invoice */ -export type PayLnInvoiceResponse = { +export type MeltTokensResponse = { /** * if false, the proofs have not been invalidated and the payment can be tried later again with the same proofs */ diff --git a/test/integration.test.ts b/test/integration.test.ts index 73e42ae98..205fce1e5 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -33,16 +33,16 @@ describe('mint api', () => { test('request mint', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const request = await wallet.requestMint(100); + const request = await wallet.getMintQuote(100); expect(request).toBeDefined(); }); test('mint tokens', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const request = await wallet.requestMint(1337); + const request = await wallet.getMintQuote(1337); expect(request).toBeDefined(); expect(request.request).toContain('lnbc1337'); - const tokens = await wallet.requestTokens(1337, request.quote); + const tokens = await wallet.mintTokens(1337, request.quote); expect(tokens).toBeDefined(); // expect that the sum of all tokens.proofs.amount is equal to the requested amount expect(tokens.proofs.reduce((a, b) => a + b.amount, 0)).toBe(1337); @@ -50,7 +50,7 @@ describe('mint api', () => { test('get fee for local invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const request = await wallet.requestMint(100); + const request = await wallet.getMintQuote(100); const fee = (await wallet.getMeltQuote(request.request)).fee_reserve; expect(fee).toBeDefined(); // because local invoice, fee should be 0 @@ -67,11 +67,11 @@ describe('mint api', () => { test('pay local invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const request = await wallet.requestMint(100); - const tokens = await wallet.requestTokens(100, request.quote); + const request = await wallet.getMintQuote(100); + const tokens = await wallet.mintTokens(100, request.quote); // expect no fee because local invoice - const requestToPay = await wallet.requestMint(10); + const requestToPay = await wallet.getMintQuote(10); const quote = await wallet.getMeltQuote(requestToPay.request); const fee = quote.fee_reserve expect(fee).toBe(0); @@ -95,8 +95,8 @@ describe('mint api', () => { test('pay external invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const request = await wallet.requestMint(3000); - const tokens = await wallet.requestTokens(3000, request.quote); + const request = await wallet.getMintQuote(3000); + const tokens = await wallet.mintTokens(3000, request.quote); const fee = (await wallet.getMeltQuote(externalInvoice)).fee_reserve; expect(fee).toBeGreaterThan(0); @@ -121,8 +121,8 @@ describe('mint api', () => { test('test send tokens exact without previous split', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const request = await wallet.requestMint(64); - const tokens = await wallet.requestTokens(64, request.quote); + const request = await wallet.getMintQuote(64); + const tokens = await wallet.mintTokens(64, request.quote); const sendResponse = await wallet.send(64, tokens.proofs); expect(sendResponse).toBeDefined(); @@ -134,8 +134,8 @@ describe('mint api', () => { test('test send tokens with change', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const request = await wallet.requestMint(100); - const tokens = await wallet.requestTokens(100, request.quote); + const request = await wallet.getMintQuote(100); + const tokens = await wallet.mintTokens(100, request.quote); const sendResponse = await wallet.send(10, tokens.proofs); expect(sendResponse).toBeDefined(); @@ -147,8 +147,8 @@ describe('mint api', () => { test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const request = await wallet.requestMint(100); - const tokens = await wallet.requestTokens(100, request.quote); + const request = await wallet.getMintQuote(100); + const tokens = await wallet.mintTokens(100, request.quote); const sendResponse = await wallet.send(10, tokens.proofs); const encoded = getEncodedToken({ @@ -162,8 +162,8 @@ describe('mint api', () => { test('receive tokens with previous mint', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - const request = await wallet.requestMint(64); - const tokens = await wallet.requestTokens(64, request.quote); + const request = await wallet.getMintQuote(64); + const tokens = await wallet.mintTokens(64, request.quote); const encoded = getEncodedToken({ token: [{ mint: mintUrl, proofs: tokens.proofs }] }); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 91e3fa5ef..70a19efb9 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -152,7 +152,7 @@ describe('checkProofsSpent', () => { ]; test('test checkProofsSpent - get proofs that are NOT spendable', async () => { nock(mintUrl) - .post('/check') + .post('/v1/check') .reply(200, { spendable: [true] }); const wallet = new CashuWallet(mint); @@ -237,7 +237,7 @@ describe('requestTokens', () => { }); const wallet = new CashuWallet(mint); - const { proofs } = await wallet.requestTokens(1, ''); + const { proofs } = await wallet.mintTokens(1, ''); expect(proofs).toHaveLength(1); expect(proofs[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); @@ -248,7 +248,7 @@ describe('requestTokens', () => { nock(mintUrl).post('/v1/mint/bolt11').reply(200, {}); const wallet = new CashuWallet(mint); - const result = await wallet.requestTokens(1, '').catch((e) => e); + const result = await wallet.mintTokens(1, '').catch((e) => e); expect(result).toEqual(new Error('bad response')); }); From f3a998a4cb5a03835e3e12916926e79203d0ecfc Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:28:54 -0300 Subject: [PATCH 014/175] remove unused imports --- .github/workflows/nutshell-integration.yml | 2 +- src/CashuWallet.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 8cdfc9d8c..c3c1a2374 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -22,7 +22,7 @@ jobs: env: MINT_LIGHTNING_BACKEND: FakeWallet run: | - docker-compose up -d + docker-compose up --build -d - name: Navigate to the parent directory run: cd .. diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 09acc18b0..03521e202 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -9,9 +9,7 @@ import { MeltPayload, MeltQuoteResponse, MintKeys, - MintKeyset, MeltTokensResponse, - PaymentPayload, PostMintPayload, Proof, ReceiveResponse, @@ -25,7 +23,6 @@ import { } from './model/types/index.js'; import { cleanToken, - deriveKeysetId, getDecodedToken, getDefaultAmountPreference, splitAmount From 0f46b99d3c7418e485ed24a55be2646b75759717 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:33:56 -0300 Subject: [PATCH 015/175] check docker --- .github/workflows/nutshell-integration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index c3c1a2374..255d34318 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -23,6 +23,8 @@ jobs: MINT_LIGHTNING_BACKEND: FakeWallet run: | docker-compose up --build -d + - name: Check if services are running + run: docker-compose ps - name: Navigate to the parent directory run: cd .. From dc507b67b3771e1b779f73635a5ebe9c5794d2c7 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:38:08 -0300 Subject: [PATCH 016/175] start mint? --- .github/workflows/nutshell-integration.yml | 5 ++++- package.json | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 255d34318..25b511278 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -21,6 +21,9 @@ jobs: - name: Build and start services env: MINT_LIGHTNING_BACKEND: FakeWallet + MINT_LISTEN_HOST: 0.0.0.0 + MINT_LISTEN_PORT: 3338 + MINT_PRIVATE_KEY: TEST_PRIVATE_KEY run: | docker-compose up --build -d - name: Check if services are running @@ -36,4 +39,4 @@ jobs: cache: 'npm' - run: npm ci - run: npm run compile - - run: npm test + - run: npm test-integration diff --git a/package.json b/package.json index 3af73c6f1..56f121763 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ }, "scripts": { "compile": "rm -rf dist/lib && tsc && tsc --build tsconfig.es5.json", - "test": "jest --coverage", + "test": "jest --coverage --testPathIgnorePatterns ./test/integration.test.ts", + "test-integration": "jest --coverage --testPathPattern ./test/integration.test.ts", "dev": "tsc --watch", "lint": "eslint --ext .js,.ts . --fix", "format": "prettier --write .", From 6b7887b1f28c8847c36fed24a61307c4db840c30 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:40:34 -0300 Subject: [PATCH 017/175] fix --- .github/workflows/nutshell-integration.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 25b511278..322e9b9bd 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -25,7 +25,7 @@ jobs: MINT_LISTEN_PORT: 3338 MINT_PRIVATE_KEY: TEST_PRIVATE_KEY run: | - docker-compose up --build -d + docker-compose up -d - name: Check if services are running run: docker-compose ps @@ -39,4 +39,4 @@ jobs: cache: 'npm' - run: npm ci - run: npm run compile - - run: npm test-integration + - run: npm run test-integration From a109881d80dc2d51ae60768edc8ab79b443b91fd Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:47:37 -0300 Subject: [PATCH 018/175] fix --- .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 322e9b9bd..405dcaac1 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -27,7 +27,7 @@ jobs: run: | docker-compose up -d - name: Check if services are running - run: docker-compose ps + run: netstat -lp - name: Navigate to the parent directory run: cd .. From 5945e3e0b59091612b1ffa957540e56e5522b691 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:49:25 -0300 Subject: [PATCH 019/175] add curl --- .github/workflows/nutshell-integration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 405dcaac1..be1c26ba8 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -28,6 +28,8 @@ jobs: docker-compose up -d - name: Check if services are running run: netstat -lp + - name: Curl on localhost:3338/keys + run: curl http://localhost:3338/keys - name: Navigate to the parent directory run: cd .. From 042c8d0e40e762ae79b5071bc6a337f1a677527d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:50:29 -0300 Subject: [PATCH 020/175] trigger CI --- .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 be1c26ba8..339d7cae4 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -25,7 +25,7 @@ jobs: MINT_LISTEN_PORT: 3338 MINT_PRIVATE_KEY: TEST_PRIVATE_KEY run: | - docker-compose up -d + docker-compose up - name: Check if services are running run: netstat -lp - name: Curl on localhost:3338/keys From 8215328ef7d96a7ffe587a1d9e4cdcce749961a5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:56:40 -0300 Subject: [PATCH 021/175] trigger CI --- .github/workflows/nutshell-integration.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 339d7cae4..a0d23c813 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -19,11 +19,6 @@ jobs: sudo chmod +x /usr/local/bin/docker-compose - name: Build and start services - env: - MINT_LIGHTNING_BACKEND: FakeWallet - MINT_LISTEN_HOST: 0.0.0.0 - MINT_LISTEN_PORT: 3338 - MINT_PRIVATE_KEY: TEST_PRIVATE_KEY run: | docker-compose up - name: Check if services are running From 6e6fdf00c1b611661e43d7244cd27a6525070000 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:58:07 -0300 Subject: [PATCH 022/175] trigger CI --- .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 a0d23c813..a3ae5cf4f 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -20,7 +20,7 @@ jobs: - name: Build and start services run: | - docker-compose up + docker-compose up -d - name: Check if services are running run: netstat -lp - name: Curl on localhost:3338/keys From d0b28d4014e2e973ae6866be45b10917d3c472ff Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:00:06 -0300 Subject: [PATCH 023/175] gotta try it --- .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 a3ae5cf4f..80c9468af 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -24,7 +24,7 @@ jobs: - name: Check if services are running run: netstat -lp - name: Curl on localhost:3338/keys - run: curl http://localhost:3338/keys + run: curl http://mint:3338/keys - name: Navigate to the parent directory run: cd .. From a891cc5145c61c35d5e76da255bee87557fa218b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:00:35 -0300 Subject: [PATCH 024/175] gotta try it --- .github/workflows/nutshell-integration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 80c9468af..a46b99de6 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -23,6 +23,8 @@ jobs: docker-compose up -d - name: Check if services are running run: netstat -lp + - name: Run ifconfig + run: ifconfig - name: Curl on localhost:3338/keys run: curl http://mint:3338/keys From a5615d65801ee977b98a524c62b4730612f4eb58 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:02:08 -0300 Subject: [PATCH 025/175] trigger CI --- .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 a46b99de6..a81ee0d65 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -26,7 +26,7 @@ jobs: - name: Run ifconfig run: ifconfig - name: Curl on localhost:3338/keys - run: curl http://mint:3338/keys + run: curl http://127.0.0.1:3338/keys - name: Navigate to the parent directory run: cd .. From 077697d43732743fbe29772550cbea186d002eb9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:03:31 -0300 Subject: [PATCH 026/175] trigger CI --- .github/workflows/nutshell-integration.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index a81ee0d65..a760dcc8b 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -25,6 +25,9 @@ jobs: run: netstat -lp - name: Run ifconfig run: ifconfig + - name: Curl on localhost:4448/keys + run: curl http://127.0.0.1:4448/ + - name: Curl on localhost:3338/keys run: curl http://127.0.0.1:3338/keys From 11b32d5653c50883ee6f480aa09799dad33a506c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:04:32 -0300 Subject: [PATCH 027/175] trigger CI --- .github/workflows/nutshell-integration.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index a760dcc8b..b469ac29a 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -14,9 +14,10 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Compose - run: | - sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - sudo chmod +x /usr/local/bin/docker-compose + uses: docker/compose-action@v1 + with: + compose-yaml: docker-compose.yml + compose-version: '1.27.4' - name: Build and start services run: | @@ -26,10 +27,10 @@ jobs: - name: Run ifconfig run: ifconfig - name: Curl on localhost:4448/keys - run: curl http://127.0.0.1:4448/ + run: curl http://localhost:4448/ - name: Curl on localhost:3338/keys - run: curl http://127.0.0.1:3338/keys + run: curl http://localhost:3338/keys - name: Navigate to the parent directory run: cd .. From 36b326cdd7817d05408bdda2c7c28fec813e5ab4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:05:55 -0300 Subject: [PATCH 028/175] trigger CI --- .github/workflows/nutshell-integration.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index b469ac29a..79df16ec5 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -14,10 +14,9 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Compose - uses: docker/compose-action@v1 + uses: adambirds/docker-compose-action@v1.3.0 with: - compose-yaml: docker-compose.yml - compose-version: '1.27.4' + compose-file: docker-compose.yml - name: Build and start services run: | From 0e2c9516563c1d9dddb533c7c50ebe1629726458 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:07:23 -0300 Subject: [PATCH 029/175] trigger CI --- .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 79df16ec5..a0a28e413 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Docker Compose uses: adambirds/docker-compose-action@v1.3.0 with: - compose-file: docker-compose.yml + compose-file: nutshell/docker-compose.yml - name: Build and start services run: | From 0d16b3f243cc54547e590da89dea433f7727ed7b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:08:32 -0300 Subject: [PATCH 030/175] trigger CI --- .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 a0a28e413..bc0d96f52 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Docker Compose uses: adambirds/docker-compose-action@v1.3.0 with: - compose-file: nutshell/docker-compose.yml + compose-file: ../nutshell/docker-compose.yml - name: Build and start services run: | From fbb83957ef6cc133bd4b126f7d0028d4a0ce3ff3 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:10:56 -0300 Subject: [PATCH 031/175] trigger CI --- .github/workflows/nutshell-integration.yml | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index bc0d96f52..66dde6083 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -7,30 +7,18 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout repository + - name: Set up Docker Compose + uses: adambirds/docker-compose-action@v1.3.0 + - name: Checkout Nutshell repository uses: actions/checkout@v2 with: repository: 'cashubtc/nutshell' token: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Compose - uses: adambirds/docker-compose-action@v1.3.0 - with: - compose-file: ../nutshell/docker-compose.yml - - - name: Build and start services + - name: Build and start mint run: | docker-compose up -d - - name: Check if services are running - run: netstat -lp - - name: Run ifconfig - run: ifconfig - - name: Curl on localhost:4448/keys - run: curl http://localhost:4448/ - - name: Curl on localhost:3338/keys run: curl http://localhost:3338/keys - - name: Navigate to the parent directory run: cd .. - uses: actions/checkout@v3 From 0093db0cd2150e92587a4f3571c16ea9ef2b6888 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:17:53 -0300 Subject: [PATCH 032/175] trigger CI --- .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 66dde6083..422a12504 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -5,7 +5,6 @@ on: [push, pull_request] jobs: build-and-test: runs-on: ubuntu-latest - steps: - name: Set up Docker Compose uses: adambirds/docker-compose-action@v1.3.0 @@ -13,6 +12,7 @@ jobs: uses: actions/checkout@v2 with: repository: 'cashubtc/nutshell' + ref: 'docker/network_mode_host' token: ${{ secrets.GITHUB_TOKEN }} - name: Build and start mint run: | From 5f34392c87404538a0b1ff720a5c2e43b7e6cdcc Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:20:37 -0300 Subject: [PATCH 033/175] trigger CI --- .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 422a12504..8f3f644e8 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -12,7 +12,7 @@ jobs: uses: actions/checkout@v2 with: repository: 'cashubtc/nutshell' - ref: 'docker/network_mode_host' + ref: 'docker-network-mode-host' token: ${{ secrets.GITHUB_TOKEN }} - name: Build and start mint run: | From fc38b0fc65a26d188e4b7fca4af899e2fc976a1e Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:22:29 -0300 Subject: [PATCH 034/175] trigger CI --- .github/workflows/nutshell-integration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 8f3f644e8..63aae2a97 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -14,6 +14,7 @@ jobs: repository: 'cashubtc/nutshell' ref: 'docker-network-mode-host' token: ${{ secrets.GITHUB_TOKEN }} + - name: Build and start mint run: | docker-compose up -d From 3508bbf5fda92ec11c51893b19c3808d24b6d635 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:24:16 -0300 Subject: [PATCH 035/175] trigger CI --- .github/workflows/nutshell-integration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 63aae2a97..c2c3aab15 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -18,6 +18,8 @@ jobs: - name: Build and start mint run: | docker-compose up -d + - name: Run ifconfig + run: ifconfig - name: Curl on localhost:3338/keys run: curl http://localhost:3338/keys - name: Navigate to the parent directory From 90c7a31b535e80370dc2b5afc8c4e5cc02b79599 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:25:50 -0300 Subject: [PATCH 036/175] trigger CI --- .github/workflows/nutshell-integration.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index c2c3aab15..4525d40b8 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -18,8 +18,8 @@ jobs: - name: Build and start mint run: | docker-compose up -d - - name: Run ifconfig - run: ifconfig + - name: Run netstat + run: netstat -lp - name: Curl on localhost:3338/keys run: curl http://localhost:3338/keys - name: Navigate to the parent directory From f1685ed17b9dc1ef2b89a3904b51e6f1bd9d6478 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:44:22 -0300 Subject: [PATCH 037/175] trigger CI --- .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 4525d40b8..301263c65 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -17,7 +17,7 @@ jobs: - name: Build and start mint run: | - docker-compose up -d + docker-compose up -d && netstat -lp - name: Run netstat run: netstat -lp - name: Curl on localhost:3338/keys From 22242bc8cea8b1889349e8a4ec26d50a324d7209 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:46:13 -0300 Subject: [PATCH 038/175] trigger CI --- .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 301263c65..35f05fc13 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -17,7 +17,7 @@ jobs: - name: Build and start mint run: | - docker-compose up -d && netstat -lp + docker-compose up - name: Run netstat run: netstat -lp - name: Curl on localhost:3338/keys From 84d6cfa42d5d05518821ccfa02a858762e41b8cc Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:13:00 -0300 Subject: [PATCH 039/175] try macos --- .github/workflows/nutshell-integration.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 35f05fc13..d8a8c837e 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: build-and-test: - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Set up Docker Compose uses: adambirds/docker-compose-action@v1.3.0 @@ -17,7 +17,7 @@ jobs: - name: Build and start mint run: | - docker-compose up + docker-compose up -d - name: Run netstat run: netstat -lp - name: Curl on localhost:3338/keys From 3f2e8a0f9f174aa3e99227e4cf6876f0a82cde0d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:17:08 -0300 Subject: [PATCH 040/175] try macos --- .github/workflows/nutshell-integration.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index d8a8c837e..4f97df541 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -7,12 +7,13 @@ jobs: runs-on: macos-latest steps: - name: Set up Docker Compose - uses: adambirds/docker-compose-action@v1.3.0 + run: | + sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose - name: Checkout Nutshell repository uses: actions/checkout@v2 with: repository: 'cashubtc/nutshell' - ref: 'docker-network-mode-host' token: ${{ secrets.GITHUB_TOKEN }} - name: Build and start mint From 66be50b5da58bf73d4126e565eda241f1e7d749c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:18:46 -0300 Subject: [PATCH 041/175] disable docker pipeline --- .github/workflows/nutshell-integration.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 4f97df541..2452c90cc 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -1,15 +1,17 @@ name: CI Test Workflow -on: [push, pull_request] +# on: [push, pull_request] +on: [] jobs: build-and-test: - runs-on: macos-latest + runs-on: ubuntu-latest steps: - name: Set up Docker Compose run: | sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose + - name: Checkout Nutshell repository uses: actions/checkout@v2 with: @@ -25,7 +27,8 @@ jobs: run: curl http://localhost:3338/keys - name: Navigate to the parent directory run: cd .. - - uses: actions/checkout@v3 + - name: Checkout cashu-ts repository + uses: actions/checkout@v3 - name: Run Tests uses: actions/setup-node@v3 From 848a0f57b19c8f087fa7b58d5fcc3c3e08b1d398 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:19:49 -0300 Subject: [PATCH 042/175] tests on push --- .github/workflows/node.js.yml | 8 ++++++-- src/utils.ts | 5 +++-- test/integration.test.ts | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 46581f2fa..f107fcd38 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -3,7 +3,11 @@ name: Node.js CI -on: +on: + # push for all branches + push: + branches: + - '*' pull_request: types: - ready_for_review @@ -27,4 +31,4 @@ jobs: cache: 'npm' - run: npm ci - run: npm run compile - - run: npm test \ No newline at end of file + - run: npm test diff --git a/src/utils.ts b/src/utils.ts index 541a94f64..b3c444f0d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -127,12 +127,13 @@ function handleTokens(token: string): Token { * @returns */ export function deriveKeysetId(keys: MintKeys) { - const pubkeysConcat = Object.entries(keys) + const pubkeysConcat = Object.entries(keys.keys) .sort((a, b) => +a[0] - +b[0]) .map(([, pubKey]) => pubKey) .join(''); const hash = sha256(new TextEncoder().encode(pubkeysConcat)); - return Buffer.from(hash).toString('base64').slice(0, 12); + const hashHex = bytesToHex(hash); + return "00" + hashHex.slice(0, 12); } /** * merge proofs from same mint, diff --git a/test/integration.test.ts b/test/integration.test.ts index 205fce1e5..f1c005806 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -2,7 +2,7 @@ import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; import dns from 'node:dns'; -import { getEncodedToken } from '../src/utils.js'; +import { deriveKeysetId, getEncodedToken } from '../src/utils.js'; dns.setDefaultResultOrder('ipv4first'); const externalInvoice = From 5ccf8badbf1e7d4944dba946640159de880c877e Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Fri, 16 Feb 2024 21:57:23 +0900 Subject: [PATCH 043/175] fix h2c --- src/CashuMint.ts | 5 +---- src/DHKE.ts | 23 ++++++++++++++--------- test/dhke.test.ts | 6 +++--- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index fc4f22797..1cfe339a3 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -23,10 +23,7 @@ class CashuMint { * @param _mintUrl requires mint URL to create this object * @param _customRequest if passed, use custom request implementation for network communication with the mint */ - constructor( - private _mintUrl: string, - private _customRequest?: typeof request - ) {} + constructor(private _mintUrl: string, private _customRequest?: typeof request) {} get mintUrl() { return this._mintUrl; diff --git a/src/DHKE.ts b/src/DHKE.ts index ca37559de..53003253d 100644 --- a/src/DHKE.ts +++ b/src/DHKE.ts @@ -4,22 +4,27 @@ import { encodeUint8toBase64 } from './base64.js'; import { MintKeys, Proof, SerializedBlindedSignature } from './model/types/index.js'; import { bytesToNumber } from './utils.js'; import { sha256 } from '@noble/hashes/sha256'; -import { bytesToHex } from '@noble/curves/abstract/utils'; +import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils'; +import { Buffer } from 'buffer/'; + +const DOMAIN_SEPARATOR = hexToBytes('536563703235366b315f48617368546f43757276655f43617368755f'); function hashToCurve(secret: Uint8Array): ProjPointType { - let point: ProjPointType | undefined; - while (!point) { - const hash = sha256(secret); - const hashHex = bytesToHex(hash); - const pointX = '02' + hashHex; + const msgToHash = sha256(Buffer.concat([DOMAIN_SEPARATOR, secret])); + const counter = new Uint32Array(1); + const maxIterations = 2 ** 16; + for (let i = 0; i < maxIterations; i++) { + const counterBytes = new Uint8Array(counter.buffer); + const hash = sha256(Buffer.concat([msgToHash, counterBytes])); try { - point = pointFromHex(pointX); + return pointFromHex(bytesToHex(Buffer.concat([new Uint8Array([0x02]), hash]))); } catch (error) { - secret = sha256(secret); + counter[0]++; } } - return point; + throw new Error('No valid point found'); } + export function pointFromHex(hex: string) { return secp256k1.ProjectivePoint.fromHex(hex); } diff --git a/test/dhke.test.ts b/test/dhke.test.ts index 30b2da162..a058515af 100644 --- a/test/dhke.test.ts +++ b/test/dhke.test.ts @@ -9,14 +9,14 @@ describe('testing hash to curve', () => { let secret = hexToBytes('0000000000000000000000000000000000000000000000000000000000000000'); let Y = dhke.hashToCurve(secret); let hexY = Y.toHex(true); - expect(hexY).toBe('0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925'); + expect(hexY).toBe('024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725'); }); test('testing string 0000....01', async () => { let secret = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001'); let Y = dhke.hashToCurve(secret); let hexY = Y.toHex(true); - expect(hexY).toBe('02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5'); + expect(hexY).toBe('022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf'); }); }); @@ -29,7 +29,7 @@ describe('test blinding message', () => { bytesToNumber(hexToBytes('0000000000000000000000000000000000000000000000000000000000000001')) ); expect(B_.toHex(true)).toBe( - '03c509bbdd8aaa81d5e67468d07b4b7dffd5769ac596ff3964e151adcefc6b06d0' + '030eded094dc2b0e3c78aae9fac76c1ad426a43e1f57fb32d2e64ca5dc82a85791' ); }); }); From e4b669121639acbb85e9525a484c69730fea4212 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 19 Feb 2024 17:48:52 +0900 Subject: [PATCH 044/175] add hashes dep and upgrade other deps --- package-lock.json | 87 ++++++++++++++++++++++++----------------------- package.json | 7 ++-- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79bf21e62..a57a47393 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,10 @@ "license": "MIT", "dependencies": { "@gandlaf21/bolt11-decode": "^3.0.6", - "@noble/curves": "^1.0.0", - "@scure/bip32": "^1.3.2", - "@scure/bip39": "^1.2.1", + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@scure/bip32": "^1.3.3", + "@scure/bip39": "^1.2.2", "buffer": "^6.0.3" }, "devDependencies": { @@ -1211,20 +1212,20 @@ } }, "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", "dependencies": { - "@noble/hashes": "1.3.2" + "@noble/hashes": "1.3.3" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", "engines": { "node": ">= 16" }, @@ -1268,33 +1269,33 @@ } }, "node_modules/@scure/base": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", - "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", + "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip32": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", - "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", "dependencies": { - "@noble/curves": "~1.2.0", + "@noble/curves": "~1.3.0", "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.2" + "@scure/base": "~1.1.4" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip39": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", - "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", + "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", "dependencies": { - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -7330,17 +7331,17 @@ } }, "@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", "requires": { - "@noble/hashes": "1.3.2" + "@noble/hashes": "1.3.3" } }, "@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==" }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -7369,27 +7370,27 @@ } }, "@scure/base": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", - "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==" + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", + "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==" }, "@scure/bip32": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", - "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", "requires": { - "@noble/curves": "~1.2.0", + "@noble/curves": "~1.3.0", "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.2" + "@scure/base": "~1.1.4" } }, "@scure/bip39": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", - "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", + "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", "requires": { - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" } }, "@sinclair/typebox": { diff --git a/package.json b/package.json index 78f8fc11e..5f93bcbc3 100644 --- a/package.json +++ b/package.json @@ -46,9 +46,10 @@ }, "dependencies": { "@gandlaf21/bolt11-decode": "^3.0.6", - "@noble/curves": "^1.0.0", - "@scure/bip32": "^1.3.2", - "@scure/bip39": "^1.2.1", + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@scure/bip32": "^1.3.3", + "@scure/bip39": "^1.2.2", "buffer": "^6.0.3" } } From b275740560b70bf4f10a20223f54e23b7e1ea7ed Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 19 Feb 2024 17:51:23 +0900 Subject: [PATCH 045/175] format --- src/CashuMint.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index fc4f22797..1cfe339a3 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -23,10 +23,7 @@ class CashuMint { * @param _mintUrl requires mint URL to create this object * @param _customRequest if passed, use custom request implementation for network communication with the mint */ - constructor( - private _mintUrl: string, - private _customRequest?: typeof request - ) {} + constructor(private _mintUrl: string, private _customRequest?: typeof request) {} get mintUrl() { return this._mintUrl; From cb1bfa2d6ed5880b4313e5e3a5bffc5f469a4da3 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 19 Feb 2024 20:35:06 +0900 Subject: [PATCH 046/175] switch to hex secrets --- src/CashuMint.ts | 5 +---- src/CashuWallet.ts | 4 +++- src/DHKE.ts | 7 ++---- test/dhke.test.ts | 4 ++-- test/secrets.test.ts | 40 +++++++++++++++++++++++++--------- test/wallet.test.ts | 52 ++++++++++++++++++++++---------------------- 6 files changed, 64 insertions(+), 48 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index fc4f22797..1cfe339a3 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -23,10 +23,7 @@ class CashuMint { * @param _mintUrl requires mint URL to create this object * @param _customRequest if passed, use custom request implementation for network communication with the mint */ - constructor( - private _mintUrl: string, - private _customRequest?: typeof request - ) {} + constructor(private _mintUrl: string, private _customRequest?: typeof request) {} get mintUrl() { return this._mintUrl; diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 8953ab8d1..a7d61ade0 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -1,4 +1,4 @@ -import { randomBytes } from '@noble/hashes/utils'; +import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils'; import { CashuMint } from './CashuMint.js'; import * as dhke from './DHKE.js'; import { BlindedMessage } from './model/BlindedMessage.js'; @@ -548,6 +548,8 @@ class CashuWallet { } else { secret = randomBytes(32); } + secret = bytesToHex(secret); + secret = new TextEncoder().encode(secret); secrets.push(secret); const { B_, r } = dhke.blindMessage(secret, deterministicR); rs.push(r); diff --git a/src/DHKE.ts b/src/DHKE.ts index ca37559de..c525522dc 100644 --- a/src/DHKE.ts +++ b/src/DHKE.ts @@ -1,6 +1,5 @@ import { ProjPointType } from '@noble/curves/abstract/weierstrass'; import { secp256k1 } from '@noble/curves/secp256k1'; -import { encodeUint8toBase64 } from './base64.js'; import { MintKeys, Proof, SerializedBlindedSignature } from './model/types/index.js'; import { bytesToNumber } from './utils.js'; import { sha256 } from '@noble/hashes/sha256'; @@ -27,9 +26,7 @@ export function pointFromHex(hex: string) { return secp256k1.ProjectivePoint.fromAffine(h2c.toAffine()); } */ function blindMessage(secret: Uint8Array, r?: bigint): { B_: ProjPointType; r: bigint } { - const secretMessageBase64 = encodeUint8toBase64(secret); - const secretMessage = new TextEncoder().encode(secretMessageBase64); - const Y = hashToCurve(secretMessage); + const Y = hashToCurve(secret); if (!r) { r = bytesToNumber(secp256k1.utils.randomPrivateKey()); } @@ -60,7 +57,7 @@ function constructProofs( const proof = { id: p.id, amount: p.amount, - secret: encodeUint8toBase64(secrets[i]), + secret: new TextDecoder().decode(secrets[i]), C: C.toHex(true) }; return proof; diff --git a/test/dhke.test.ts b/test/dhke.test.ts index 30b2da162..f97012bbc 100644 --- a/test/dhke.test.ts +++ b/test/dhke.test.ts @@ -23,13 +23,13 @@ describe('testing hash to curve', () => { describe('test blinding message', () => { test('testing string 0000....01', async () => { var enc = new TextEncoder(); - let secretUInt8 = enc.encode(SECRET_MESSAGE); + let secretUInt8 = enc.encode('test_message'); let { B_ } = await dhke.blindMessage( secretUInt8, bytesToNumber(hexToBytes('0000000000000000000000000000000000000000000000000000000000000001')) ); expect(B_.toHex(true)).toBe( - '03c509bbdd8aaa81d5e67468d07b4b7dffd5769ac596ff3964e151adcefc6b06d0' + '02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2' ); }); }); diff --git a/test/secrets.test.ts b/test/secrets.test.ts index 6a2cfd957..c561f2a8d 100644 --- a/test/secrets.test.ts +++ b/test/secrets.test.ts @@ -1,6 +1,7 @@ import { bytesToHex } from '@noble/curves/abstract/utils'; import { deriveSeedFromMnemonic } from '../src/secrets'; import { deriveBlindingFactor, deriveSecret } from '../src/secrets'; +import { blindMessage } from '../src/DHKE'; import { HDKey } from '@scure/bip32'; const mnemonic = 'half depart obvious quality work element tank gorilla view sugar picture humble'; @@ -26,18 +27,24 @@ describe('testing hdkey from seed', () => { describe('testing deterministic secrets', () => { const secrets = [ - '9d32fc57e6fa2942d05ee475d28ba6a56839b8cb8a3f174b05ed0ed9d3a420f6', - '1c0f2c32e7438e7cc992612049e9dfcdbffd454ea460901f24cc429921437802', - '327c606b761af03cbe26fa13c4b34a6183b868c52cda059fe57fdddcb4e1e1e7', - '53476919560398b56c0fdc5dd92cf8628b1e06de6f2652b0f7d6e8ac319de3b7', - 'b2f5d632229378a716be6752fc79ac8c2b43323b820859a7956f2dfe5432b7b4' + '485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae', + '8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270', + 'bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8', + '59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf', + '576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0' ]; test('derive Secret', async () => { - const secret1 = deriveSecret(seed, '1cCNIAZ2X/w1', 0); - const secret2 = deriveSecret(seed, '1cCNIAZ2X/w1', 1); - const secret3 = deriveSecret(seed, '1cCNIAZ2X/w1', 2); - const secret4 = deriveSecret(seed, '1cCNIAZ2X/w1', 3); - const secret5 = deriveSecret(seed, '1cCNIAZ2X/w1', 4); + const secret1 = deriveSecret(seed, '009a1f293253e41e', 0); + const secret2 = deriveSecret(seed, '009a1f293253e41e', 1); + const secret3 = deriveSecret(seed, '009a1f293253e41e', 2); + const secret4 = deriveSecret(seed, '009a1f293253e41e', 3); + const secret5 = deriveSecret(seed, '009a1f293253e41e', 4); + + const bf1 = deriveBlindingFactor(seed, '009a1f293253e41e', 0); + const bf2 = deriveBlindingFactor(seed, '009a1f293253e41e', 1); + const bf3 = deriveBlindingFactor(seed, '009a1f293253e41e', 2); + const bf4 = deriveBlindingFactor(seed, '009a1f293253e41e', 3); + const bf5 = deriveBlindingFactor(seed, '009a1f293253e41e', 4); expect(bytesToHex(secret1)).toBe(secrets[0]); expect(bytesToHex(secret2)).toBe(secrets[1]); @@ -47,6 +54,19 @@ describe('testing deterministic secrets', () => { }); }); +describe('testing deterministic blindedMessage', () => { + const secrets = ['485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae']; + test('derive Secret', async () => { + const secret1 = deriveSecret(seed, '009a1f293253e41e', 0); + + const bf1 = deriveBlindingFactor(seed, '009a1f293253e41e', 0); + + expect(bytesToHex(secret1)).toBe(secrets[0]); + + // blindMessage() + }); +}); + describe('test private key derivation from derivation path', () => { const seed = 'dd44ee516b0647e80b488e8dcc56d736a148f15276bef588b37057476d4b2b25780d3688a32b37353d6995997842c0fd8b412475c891c16310471fbc86dcbda8'; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index b4f05a923..cb256d294 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -35,7 +35,7 @@ describe('test fees', () => { describe('receive', () => { const tokenInput = - 'eyJwcm9vZnMiOlt7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiJBZmtRYlJYQUc1UU1tT3ArbG9vRzQ2OXBZWTdiaStqbEcxRXRDT2tIa2hZPSIsIkMiOiIwMmY4NWRkODRiMGY4NDE4NDM2NmNiNjkxNDYxMDZhZjdjMGYyNmYyZWUwYWQyODdhM2U1ZmE4NTI1MjhiYjI5ZGYifV0sIm1pbnRzIjpbeyJ1cmwiOiJodHRwczovL2xlZ2VuZC5sbmJpdHMuY29tL2Nhc2h1L2FwaS92MS80Z3I5WGNtejNYRWtVTndpQmlRR29DIiwiaWRzIjpbIi91WUIvNndXbllrVSJdfV19'; + 'eyJwcm9vZnMiOlt7ImlkIjoiL3VZQi82d1duWWtVIiwiYW1vdW50IjoxLCJzZWNyZXQiOiIwMWY5MTA2ZDE1YzAxYjk0MGM5OGVhN2U5NjhhMDZlM2FmNjk2MThlZGI4YmU4ZTUxYjUxMmQwOGU5MDc5MjE2IiwiQyI6IjAyZjg1ZGQ4NGIwZjg0MTg0MzY2Y2I2OTE0NjEwNmFmN2MwZjI2ZjJlZTBhZDI4N2EzZTVmYTg1MjUyOGJiMjlkZiJ9XSwibWludHMiOlt7InVybCI6Imh0dHBzOi8vbGVnZW5kLmxuYml0cy5jb20vY2FzaHUvYXBpL3YxLzRncjlYY216M1hFa1VOd2lCaVFHb0MiLCJpZHMiOlsiL3VZQi82d1duWWtVIl19XX0='; test('test receive encoded token', async () => { nock(mintUrl) .post('/split') @@ -59,7 +59,7 @@ describe('receive', () => { mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' }); expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); expect(tokensWithErrors).toBe(undefined); }); @@ -88,7 +88,7 @@ describe('receive', () => { mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' }); expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); expect(tokensWithErrors).toBe(undefined); }); test('test receive custom split', async () => { @@ -130,7 +130,7 @@ describe('receive', () => { ] }); expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); expect(tokensWithErrors).toBe(undefined); }); test('test receive tokens already spent', async () => { @@ -149,7 +149,7 @@ describe('receive', () => { mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' }); expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); }); test('test receive could not verify proofs', async () => { nock(mintUrl).post('/split').reply(500, { code: 0, error: 'could not verify proofs.' }); @@ -166,7 +166,7 @@ describe('receive', () => { mint: 'https://legend.lnbits.com/cashu/api/v1/4gr9Xcmz3XEkUNwiBiQGoC' }); expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(t.token[0].proofs[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); }); test('test receive keys changed', async () => { nock(mintUrl) @@ -197,7 +197,7 @@ describe('receive', () => { expect(token[0].proofs).toHaveLength(1); expect(token[0].proofs[0]).toMatchObject({ amount: 1, id: 'test' }); expect(/[0-9a-f]{64}/.test(token[0].proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(token[0].proofs[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(token[0].proofs[0].secret)).toBe(true); expect(tokensWithErrors).toBe(undefined); expect(newKeys).toStrictEqual({ 1: '0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad' @@ -210,7 +210,7 @@ describe('checkProofsSpent', () => { { id: '0NI3TUAs1Sfy', amount: 1, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; @@ -231,7 +231,7 @@ describe('payLnInvoice', () => { { id: '0NI3TUAs1Sfy', amount: 1, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; @@ -334,7 +334,7 @@ describe('requestTokens', () => { expect(proofs).toHaveLength(1); expect(proofs[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); expect(/[0-9a-f]{64}/.test(proofs[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(proofs[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(proofs[0].secret)).toBe(true); }); test('test requestTokens bad resonse', async () => { nock(mintUrl).post('/mint?hash=').reply(200, {}); @@ -351,7 +351,7 @@ describe('send', () => { { id: '0NI3TUAs1Sfy', amount: 1, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; @@ -375,7 +375,7 @@ describe('send', () => { expect(result.send).toHaveLength(1); expect(result.send[0]).toMatchObject({ amount: 1, id: '0NI3TUAs1Sfy' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.send[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); }); test('test send over paying. Should return change', async () => { nock(mintUrl) @@ -400,7 +400,7 @@ describe('send', () => { { id: '0NI3TUAs1Sfy', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]); @@ -408,11 +408,11 @@ describe('send', () => { expect(result.send).toHaveLength(1); expect(result.send[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.send[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); expect(result.returnChange).toHaveLength(1); expect(result.returnChange[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); expect(/[0-9a-f]{64}/.test(result.returnChange[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.returnChange[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.returnChange[0].secret)).toBe(true); }); test('test send over paying2', async () => { @@ -438,7 +438,7 @@ describe('send', () => { { id: 'z32vUtKgNCm1', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; @@ -447,11 +447,11 @@ describe('send', () => { expect(result.send).toHaveLength(1); expect(result.send[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.send[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); expect(result.returnChange).toHaveLength(1); expect(result.returnChange[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); expect(/[0-9a-f]{64}/.test(result.returnChange[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.returnChange[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.returnChange[0].secret)).toBe(true); }); test('test send preference', async () => { nock(mintUrl) @@ -486,13 +486,13 @@ describe('send', () => { { id: 'z32vUtKgNCm1', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' }, { id: 'z32vUtKgNCm1', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; @@ -504,7 +504,7 @@ describe('send', () => { expect(result.send[2]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); expect(result.send[3]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.send[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); expect(result.returnChange).toHaveLength(0); }); @@ -541,13 +541,13 @@ describe('send', () => { { id: 'z32vUtKgNCm1', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' }, { id: 'z32vUtKgNCm1', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; @@ -558,7 +558,7 @@ describe('send', () => { expect(result.send[1]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); expect(result.send[2]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); - expect(/[A-Za-z0-9+/]{43}=/.test(result.send[0].secret)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); expect(result.returnChange).toHaveLength(1); expect(result.returnChange[0]).toMatchObject({ amount: 1, id: 'z32vUtKgNCm1' }); }); @@ -590,7 +590,7 @@ describe('send', () => { { id: 'z32vUtKgNCm1', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]) @@ -610,7 +610,7 @@ describe('deterministic', () => { { id: 'z32vUtKgNCm1', amount: 2, - secret: 'H5jmg3pDRkTJQRgl18bW4Tl0uTH48GUiF86ikBBnShM=', + secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ], From 0ae8726a4aadcdc04711ff674d9a7f3846eabdee Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 20 Feb 2024 11:02:01 +0900 Subject: [PATCH 047/175] fix variables as per review --- src/CashuWallet.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index a7d61ade0..7bb5cc433 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -539,17 +539,17 @@ class CashuWallet { const rs: Array = []; for (let i = 0; i < amounts.length; i++) { let deterministicR = undefined; - let secret = undefined; + let secretBytes = undefined; if (this._seed && counter != undefined) { - secret = deriveSecret(this._seed, keysetId ?? this.keysetId, counter + i); + secretBytes = deriveSecret(this._seed, keysetId ?? this.keysetId, counter + i); deterministicR = bytesToNumber( deriveBlindingFactor(this._seed, keysetId ?? this.keysetId, counter + i) ); } else { - secret = randomBytes(32); + secretBytes = randomBytes(32); } - secret = bytesToHex(secret); - secret = new TextEncoder().encode(secret); + const secretHex = bytesToHex(secretBytes); + const secret = new TextEncoder().encode(secretHex); secrets.push(secret); const { B_, r } = dhke.blindMessage(secret, deterministicR); rs.push(r); From f8c9fe5352c37cd67aa0873cc983f892d266667e Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 20 Feb 2024 12:45:00 +0900 Subject: [PATCH 048/175] pipeline for npm release --- .github/workflows/version.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/version.yml diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 000000000..725840a86 --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,17 @@ +name: Publish Package to npmjs +on: + release: + types: [published] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + - run: npm ci + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From b76e01ee6631445eeadfd50ec0e1dc99931d9d2e Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 21 Feb 2024 17:02:13 +0900 Subject: [PATCH 049/175] update test vector --- test/dhke.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dhke.test.ts b/test/dhke.test.ts index 492445f85..b839b6db4 100644 --- a/test/dhke.test.ts +++ b/test/dhke.test.ts @@ -23,13 +23,13 @@ describe('testing hash to curve', () => { describe('test blinding message', () => { test('testing string 0000....01', async () => { var enc = new TextEncoder(); - let secretUInt8 = enc.encode('test_message'); + let secretUInt8 = enc.encode(SECRET_MESSAGE); let { B_ } = await dhke.blindMessage( secretUInt8, bytesToNumber(hexToBytes('0000000000000000000000000000000000000000000000000000000000000001')) ); expect(B_.toHex(true)).toBe( - '02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2' + '025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b' ); }); }); From a1f3e31f36b903af0325745b94bbd270cf627db5 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 24 Feb 2024 14:26:06 +0900 Subject: [PATCH 050/175] add output dirs to prettierignore --- .prettierignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 0841ed31d..3f9a94978 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,5 @@ dist node_modules -package-lock.json \ No newline at end of file +package-lock.json +coverage +docs \ No newline at end of file From e13a9ab857b9adb033fb024a68a561abfe7a1048 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 24 Feb 2024 14:49:05 +0900 Subject: [PATCH 051/175] add provenance to workflow --- .github/workflows/version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 725840a86..aa3558163 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -12,6 +12,6 @@ jobs: node-version: '20.x' registry-url: 'https://registry.npmjs.org' - run: npm ci - - run: npm publish + - run: npm publish --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 60083093b6d020597f2382b36b33e1ec9c1d5e1d Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 24 Feb 2024 16:22:35 +0900 Subject: [PATCH 052/175] fix version workflow --- .github/workflows/version.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index aa3558163..eae242f56 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -1,4 +1,7 @@ name: Publish Package to npmjs +permissions: + contents: read + id-token: write on: release: types: [published] @@ -9,9 +12,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: '20.x' + node-version: 20 registry-url: 'https://registry.npmjs.org' - - run: npm ci + - run: npm i + - run: npm run compile - run: npm publish --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 91a66cbe6d524ec2755be2b0b819afd3af906757 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 24 Feb 2024 15:04:36 +0100 Subject: [PATCH 053/175] merge staging --- src/CashuWallet.ts | 11 ++++++----- src/DHKE.ts | 7 ++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 834ecc9f9..54d916e99 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -1,4 +1,4 @@ -import { randomBytes } from '@noble/hashes/utils'; +import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils'; import { CashuMint } from './CashuMint.js'; import * as dhke from './DHKE.js'; import { BlindedMessage } from './model/BlindedMessage.js'; @@ -30,7 +30,6 @@ import { getDefaultAmountPreference, splitAmount } from './utils.js'; -import { bytesToHex } from '@noble/curves/abstract/utils'; import { deriveBlindingFactor, deriveSecret, deriveSeedFromMnemonic } from './secrets.js'; import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; @@ -557,15 +556,17 @@ class CashuWallet { const rs: Array = []; for (let i = 0; i < amounts.length; i++) { let deterministicR = undefined; - let secret = undefined; + let secretBytes = undefined; if (this._seed && counter != undefined) { - secret = deriveSecret(this._seed, keysetId ?? this.keysetId, counter + i); + secretBytes = deriveSecret(this._seed, keysetId ?? this.keysetId, counter + i); deterministicR = bytesToNumber( deriveBlindingFactor(this._seed, keysetId ?? this.keysetId, counter + i) ); } else { - secret = randomBytes(32); + secretBytes = randomBytes(32); } + const secretHex = bytesToHex(secretBytes); + const secret = new TextEncoder().encode(secretHex); secrets.push(secret); const { B_, r } = dhke.blindMessage(secret, deterministicR); rs.push(r); diff --git a/src/DHKE.ts b/src/DHKE.ts index 0be4b3024..a26f7cb7e 100644 --- a/src/DHKE.ts +++ b/src/DHKE.ts @@ -1,5 +1,6 @@ import { ProjPointType } from '@noble/curves/abstract/weierstrass'; import { secp256k1 } from '@noble/curves/secp256k1'; +import { encodeUint8toBase64 } from './base64.js'; import { MintKeys, Proof, SerializedBlindedSignature } from './model/types/index.js'; import { bytesToNumber } from './utils.js'; import { sha256 } from '@noble/hashes/sha256'; @@ -26,7 +27,8 @@ export function pointFromHex(hex: string) { return secp256k1.ProjectivePoint.fromAffine(h2c.toAffine()); } */ function blindMessage(secret: Uint8Array, r?: bigint): { B_: ProjPointType; r: bigint } { - const secretMessage = new TextEncoder().encode(new TextDecoder().decode(secret)); + const secretMessageBase64 = encodeUint8toBase64(secret); + const secretMessage = new TextEncoder().encode(secretMessageBase64); const Y = hashToCurve(secretMessage); if (!r) { r = bytesToNumber(secp256k1.utils.randomPrivateKey()); @@ -55,11 +57,10 @@ function constructProofs( const C_ = pointFromHex(p.C_); const A = pointFromHex(keyset.keys[p.amount]); const C = unblindSignature(C_, rs[i], A); - const secret = new TextDecoder().decode(secrets[i]) const proof = { id: p.id, amount: p.amount, - secret: secret, + secret: encodeUint8toBase64(secrets[i]), C: C.toHex(true) }; return proof; From 43fea0523e3494a5cd41ff00f16c437d42f2034c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 24 Feb 2024 19:53:40 +0100 Subject: [PATCH 054/175] reenable integration test --- .github/workflows/nutshell-integration.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 2452c90cc..8421599c7 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -1,7 +1,7 @@ name: CI Test Workflow -# on: [push, pull_request] -on: [] +on: [push, pull_request] +# on: [] jobs: build-and-test: From 77702da895082354d97c887ef65c240825622c26 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 24 Feb 2024 19:54:14 +0100 Subject: [PATCH 055/175] Node JS CI for all pushes --- .github/workflows/node.js.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index f107fcd38..b79346fa3 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -8,10 +8,10 @@ on: push: branches: - '*' - pull_request: - types: - - ready_for_review - - opened + # pull_request: + # types: + # - ready_for_review + # - opened jobs: build: From 0a7a5463b5c8ebee6ffe1a3d6ba65c812021d4d2 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 24 Feb 2024 20:45:36 +0100 Subject: [PATCH 056/175] try integration test without docker compose --- .github/workflows/nutshell-integration.yml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 8421599c7..1a56759cb 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -1,16 +1,15 @@ name: CI Test Workflow on: [push, pull_request] -# on: [] jobs: build-and-test: runs-on: ubuntu-latest steps: - - name: Set up Docker Compose + - name: Set up Docker run: | - sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - sudo chmod +x /usr/local/bin/docker-compose + sudo apt-get update + sudo apt-get install -y docker.io - name: Checkout Nutshell repository uses: actions/checkout@v2 @@ -18,22 +17,27 @@ jobs: repository: 'cashubtc/nutshell' token: ${{ secrets.GITHUB_TOKEN }} - - name: Build and start mint + - name: Pull and start mint run: | - docker-compose up -d - - name: Run netstat - run: netstat -lp + sudo docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.0 poetry run mint + + - name: Check running containers + run: sudo docker ps + - name: Curl on localhost:3338/keys run: curl http://localhost:3338/keys + - name: Navigate to the parent directory run: cd .. + - name: Checkout cashu-ts repository uses: actions/checkout@v3 - - name: Run Tests + - name: Set up Node.js uses: actions/setup-node@v3 with: cache: 'npm' + - run: npm ci - run: npm run compile - run: npm run test-integration From a39c058fe2492709c52627c180eb7017ec3d2c98 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 24 Feb 2024 20:49:19 +0100 Subject: [PATCH 057/175] try system docker --- .github/workflows/nutshell-integration.yml | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 1a56759cb..1145a5d5a 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -6,29 +6,15 @@ jobs: build-and-test: runs-on: ubuntu-latest steps: - - name: Set up Docker - run: | - sudo apt-get update - sudo apt-get install -y docker.io - - - name: Checkout Nutshell repository - uses: actions/checkout@v2 - with: - repository: 'cashubtc/nutshell' - token: ${{ secrets.GITHUB_TOKEN }} - - name: Pull and start mint run: | - sudo docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.0 poetry run mint + docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.0 poetry run mint - name: Check running containers - run: sudo docker ps - - - name: Curl on localhost:3338/keys - run: curl http://localhost:3338/keys + run: docker ps - - name: Navigate to the parent directory - run: cd .. + - name: Curl on localhost:3338/v1/keys + run: curl http://localhost:3338/v1/keys - name: Checkout cashu-ts repository uses: actions/checkout@v3 From fa6b14966e10d08c734bf6f512242d07a2ed5627 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 24 Feb 2024 20:50:21 +0100 Subject: [PATCH 058/175] add sleep --- .github/workflows/nutshell-integration.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 1145a5d5a..53be11429 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -13,8 +13,10 @@ jobs: - name: Check running containers run: docker ps - - name: Curl on localhost:3338/v1/keys - run: curl http://localhost:3338/v1/keys + - name: Sleep 5 seconds and curl on localhost:3338/v1/keys + run: | + sleep 5 + curl localhost:3338/v1/keys - name: Checkout cashu-ts repository uses: actions/checkout@v3 From 40b5f81a94856cdc382bd23747319d8f2377e9b5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 24 Feb 2024 20:53:17 +0100 Subject: [PATCH 059/175] CI --- .github/workflows/node.js.yml | 10 +--------- .github/workflows/nutshell-integration.yml | 10 +++++----- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index b79346fa3..9a85d7288 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -3,15 +3,7 @@ name: Node.js CI -on: - # push for all branches - push: - branches: - - '*' - # pull_request: - # types: - # - ready_for_review - # - opened +on: [push, pull_request] jobs: build: diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 53be11429..c9ed06dd3 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -1,4 +1,4 @@ -name: CI Test Workflow +name: Nutshell end-to-end tests on: [push, pull_request] @@ -13,10 +13,10 @@ jobs: - name: Check running containers run: docker ps - - name: Sleep 5 seconds and curl on localhost:3338/v1/keys - run: | - sleep 5 - curl localhost:3338/v1/keys + # - name: Sleep 5 seconds and curl on localhost:3338/v1/info + # run: | + # sleep 5 + # curl localhost:3338/v1/info - name: Checkout cashu-ts repository uses: actions/checkout@v3 From 2f4d58c2b360e11fbff3a24eb7bd0fc475b7c968 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 24 Feb 2024 22:32:23 +0100 Subject: [PATCH 060/175] fix all API changes --- .github/workflows/node.js.yml | 2 +- .github/workflows/nutshell-integration.yml | 2 +- package.json | 4 +-- src/CashuMint.ts | 20 +++++++-------- src/CashuWallet.ts | 16 +++++++----- src/model/types/index.ts | 30 +++++++++++++++++----- test/wallet.test.ts | 30 +++++++++++----------- 7 files changed, 63 insertions(+), 41 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 9a85d7288..6b14853a4 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -6,7 +6,7 @@ name: Node.js CI on: [push, pull_request] jobs: - build: + tests: runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index c9ed06dd3..f8222c08a 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -3,7 +3,7 @@ name: Nutshell end-to-end tests on: [push, pull_request] jobs: - build-and-test: + integration: runs-on: ubuntu-latest steps: - name: Pull and start mint diff --git a/package.json b/package.json index f09c292bb..1d4e32913 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ }, "scripts": { "compile": "rm -rf dist/lib && tsc && tsc --build tsconfig.es5.json", - "test": "jest --coverage --testPathIgnorePatterns ./test/integration.test.ts", - "test-integration": "jest --coverage --testPathPattern ./test/integration.test.ts", + "test": "jest --coverage --testPathIgnorePatterns ./test/integration.test.ts", + "test-integration": "jest --coverage --testPathPattern ./test/integration.test.ts", "dev": "tsc --watch", "lint": "eslint --ext .js,.ts . --fix", "format": "prettier --write .", diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 5bb22bd31..c1a27f33a 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -1,6 +1,6 @@ import { - CheckSpendablePayload, - CheckSpendableResponse, + CheckStatePayload, + CheckStateResponse, GetInfoResponse, MeltPayload, MeltResponse, @@ -180,7 +180,7 @@ class CashuMint { public static async split(mintUrl: string, splitPayload: SplitPayload, customRequest?: typeof request): Promise { const requestInstance = customRequest || request; const data = await requestInstance({ - endpoint: joinUrls(mintUrl, '/v1/split'), + endpoint: joinUrls(mintUrl, '/v1/swap'), method: 'POST', requestBody: splitPayload }); @@ -244,7 +244,7 @@ class CashuMint { if ( !isObj(data) || typeof data?.paid !== 'boolean' || - (data?.proof !== null && typeof data?.proof !== 'string') + (data?.payment_preimage !== null && typeof data?.payment_preimage !== 'string') ) { throw new Error('bad response'); } @@ -268,17 +268,17 @@ class CashuMint { */ public static async check( mintUrl: string, - checkPayload: CheckSpendablePayload, + checkPayload: CheckStatePayload, customRequest?: typeof request - ): Promise { + ): Promise { const requestInstance = customRequest || request; - const data = await requestInstance({ - endpoint: joinUrls(mintUrl, '/v1/check'), + const data = await requestInstance({ + endpoint: joinUrls(mintUrl, '/v1/checkstate'), method: 'POST', requestBody: checkPayload }); - if (!isObj(data) || !Array.isArray(data?.spendable)) { + if (!isObj(data) || !Array.isArray(data?.states)) { throw new Error('bad response'); } @@ -289,7 +289,7 @@ class CashuMint { * @param checkPayload * @returns redeemed and unredeemed ordered list of booleans */ - async check(checkPayload: CheckSpendablePayload): Promise { + async check(checkPayload: CheckStatePayload): Promise { return CashuMint.check(this._mintUrl, checkPayload, this._customRequest); } diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index ebb4071f9..892c985de 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -19,7 +19,7 @@ import { SerializedBlindedMessage, SerializedBlindedSignature, SplitPayload, - MeltQuotePayload, + CheckStateEnum, Token, TokenEntry } from './model/types/index.js'; @@ -385,7 +385,7 @@ class CashuWallet { return { isPaid: meltResponse.paid ?? false, - preimage: meltResponse.proof, + preimage: meltResponse.payment_preimage, change: meltResponse?.change ? dhke.constructProofs( meltResponse.change, @@ -501,11 +501,15 @@ class CashuWallet { */ async checkProofsSpent(proofs: Array): Promise> { const payload = { - //send only the secret - proofs: proofs.map((p) => ({ secret: p.secret })) + // array of secrets of proofs to check + secrets: proofs.map((p) => p.secret) }; - const { spendable } = await this.mint.check(payload); - return proofs.filter((_, i) => !spendable[i]); + const { states } = await this.mint.check(payload); + + return proofs.filter((proof) => { + const state = states.find((state) => state.secret === proof.secret); + return state && state.state === CheckStateEnum.SPENT; + }); } private splitReceive( amount: number, diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 0c96302d2..8410af54c 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -193,7 +193,7 @@ export type MeltResponse = { /** * preimage of the paid invoice. can be null, depending on which LN-backend the mint uses */ - proof: string | null; + payment_preimage: string | null; /** * Return/Change from overpaid fees. This happens due to Lighting fee estimation being inaccurate */ @@ -304,22 +304,40 @@ export type PostMintResponse = { /** * Payload that needs to be sent to the mint when checking for spendable proofs */ -export type CheckSpendablePayload = { +export type CheckStatePayload = { /** * array of proofs. Only the secret is strictly needed. * If the whole object is passed, it will be stripped of other objects before sending it to the mint. */ - proofs: Array<{ secret: string }>; + secrets: Array; }; +/** + * Enum for the state of a proof + */ +export enum CheckStateEnum { + UNSPENT = "UNSPENT", + PENDING = "PENDING", + SPENT = "SPENT", +} + +/** + * Entries of CheckStateResponse with state of the proof + */ +export type CheckStateEntry = { + secret: string; + state: CheckStateEnum; + witness: string | null; +} + /** * Response when checking proofs if they are spendable. Should not rely on this for receiving, since it can be easily cheated. */ -export type CheckSpendableResponse = { +export type CheckStateResponse = { /** - * Ordered list for checked proofs. True if the secret has not been redeemed at the mint before + * */ - spendable: Array; + states: Array; } & ApiError; /** * blinded message for sending to the mint diff --git a/test/wallet.test.ts b/test/wallet.test.ts index e33f41dc2..f2821e5a1 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -48,7 +48,7 @@ describe('receive', () => { 'cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6IjAwOWExZjI5MzI1M2U0MWUiLCJhbW91bnQiOjEsInNlY3JldCI6IjAxZjkxMDZkMTVjMDFiOTQwYzk4ZWE3ZTk2OGEwNmUzYWY2OTYxOGVkYjhiZThlNTFiNTEyZDA4ZTkwNzkyMTYiLCJDIjoiMDJmODVkZDg0YjBmODQxODQzNjZjYjY5MTQ2MTA2YWY3YzBmMjZmMmVlMGFkMjg3YTNlNWZhODUyNTI4YmIyOWRmIn1dLCJtaW50IjoiaHR0cDovL2xvY2FsaG9zdDozMzM4In1dfQ='; test('test receive encoded token', async () => { nock(mintUrl) - .post('/v1/split') + .post('/v1/swap') .reply(200, { signatures: [ { @@ -77,7 +77,7 @@ describe('receive', () => { const decodedInput = cleanToken(getDecodedToken(tokenInput)); nock(mintUrl) - .post('/v1/split') + .post('/v1/swap') .reply(200, { signatures: [ { @@ -103,7 +103,7 @@ describe('receive', () => { }); test('test receive custom split', async () => { nock(mintUrl) - .post('/v1/split') + .post('/v1/swap') .reply(200, { signatures: [ { @@ -138,7 +138,7 @@ describe('receive', () => { }); test('test receive tokens already spent', async () => { const msg = 'tokens already spent. Secret: asdasdasd'; - nock(mintUrl).post('/v1/split').reply(200, { detail: msg }); + nock(mintUrl).post('/v1/swap').reply(200, { detail: msg }); const wallet = new CashuWallet(mint); const { tokensWithErrors } = await wallet.receive(tokenInput); @@ -155,7 +155,7 @@ describe('receive', () => { expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); }); test('test receive could not verify proofs', async () => { - nock(mintUrl).post('/v1/split').reply(200, { code: 0, error: 'could not verify proofs.' }); + nock(mintUrl).post('/v1/swap').reply(200, { code: 0, error: 'could not verify proofs.' }); const wallet = new CashuWallet(mint); const { tokensWithErrors } = await wallet.receive(tokenInput); @@ -184,8 +184,8 @@ describe('checkProofsSpent', () => { ]; test('test checkProofsSpent - get proofs that are NOT spendable', async () => { nock(mintUrl) - .post('/v1/check') - .reply(200, { spendable: [true] }); + .post('/v1/checkstate') + .reply(200, { states: [{ secret: "asd", state: "UNSPENT", witness: "witness-asd" }] }); const wallet = new CashuWallet(mint); const result = await wallet.checkProofsSpent(proofs); @@ -205,7 +205,7 @@ describe('payLnInvoice', () => { ]; test('test payLnInvoice base case', async () => { nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, { quote: "quote_id", amount: 123, fee_reserve: 0 }); - nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, proof: '' }); + nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, payment_preimage: '' }); const wallet = new CashuWallet(mint); const result = await wallet.payLnInvoice(invoice, proofs); @@ -227,7 +227,7 @@ describe('payLnInvoice', () => { .post('/v1/melt/bolt11') .reply(200, { paid: true, - proof: 'asd', + payment_preimage: 'asd', change: [ { id: '009a1f293253e41e', @@ -319,7 +319,7 @@ describe('send', () => { }); test('test send over paying. Should return change', async () => { nock(mintUrl) - .post('/v1/split') + .post('/v1/swap') .reply(200, { signatures: [ { @@ -357,7 +357,7 @@ describe('send', () => { test('test send over paying2', async () => { nock(mintUrl) - .post('/v1/split') + .post('/v1/swap') .reply(200, { signatures: [ { @@ -395,7 +395,7 @@ describe('send', () => { }); test('test send preference', async () => { nock(mintUrl) - .post('/v1/split') + .post('/v1/swap') .reply(200, { signatures: [ { @@ -450,7 +450,7 @@ describe('send', () => { test('test send preference overpay', async () => { nock(mintUrl) - .post('/v1/split') + .post('/v1/swap') .reply(200, { signatures: [ { @@ -505,7 +505,7 @@ describe('send', () => { test('test send not enough funds', async () => { nock(mintUrl) - .post('/v1/split') + .post('/v1/swap') .reply(200, { signatures: [ { @@ -522,7 +522,7 @@ describe('send', () => { expect(result).toEqual(new Error('Not enough funds available')); }); test('test send bad response', async () => { - nock(mintUrl).post('/v1/split').reply(200, {}); + nock(mintUrl).post('/v1/swap').reply(200, {}); const wallet = new CashuWallet(mint); const result = await wallet From f57757f318fd060f3c5d868c204cfcc0879f6a58 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 27 Feb 2024 17:26:49 +0900 Subject: [PATCH 061/175] format --- package.json | 4 +-- src/CashuMint.ts | 59 ++++++++++++++++++++++++++++++-------- src/CashuWallet.ts | 10 +++---- src/model/types/index.ts | 24 +++++++--------- src/utils.ts | 2 +- test/crypto.scheme.test.ts | 2 +- test/dhke.test.ts | 22 +++----------- test/integration.test.ts | 10 +++---- test/wallet.test.ts | 51 +++++++++++++++++++++----------- 9 files changed, 111 insertions(+), 73 deletions(-) diff --git a/package.json b/package.json index 1d4e32913..f09c292bb 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ }, "scripts": { "compile": "rm -rf dist/lib && tsc && tsc --build tsconfig.es5.json", - "test": "jest --coverage --testPathIgnorePatterns ./test/integration.test.ts", - "test-integration": "jest --coverage --testPathPattern ./test/integration.test.ts", + "test": "jest --coverage --testPathIgnorePatterns ./test/integration.test.ts", + "test-integration": "jest --coverage --testPathPattern ./test/integration.test.ts", "dev": "tsc --watch", "lint": "eslint --ext .js,.ts . --fix", "format": "prettier --write .", diff --git a/src/CashuMint.ts b/src/CashuMint.ts index c1a27f33a..084b286e3 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -30,7 +30,7 @@ class CashuMint { * @param _mintUrl requires mint URL to create this object * @param _customRequest if passed, use custom request implementation for network communication with the mint */ - constructor(private _mintUrl: string, private _customRequest?: typeof request) { } + constructor(private _mintUrl: string, private _customRequest?: typeof request) {} get mintUrl() { return this._mintUrl; @@ -61,7 +61,11 @@ class CashuMint { * @param customRequest * @returns the mint will create and return a Lightning invoice for the specified amount */ - public static async mintQuote(mintUrl: string, requestMintPayload: RequestMintPayload, customRequest?: typeof request): Promise { + public static async mintQuote( + mintUrl: string, + requestMintPayload: RequestMintPayload, + customRequest?: typeof request + ): Promise { const requestInstance = customRequest || request; return requestInstance({ endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11'), @@ -75,7 +79,10 @@ class CashuMint { * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ - async mintQuote(requestMintPayload: RequestMintPayload, customRequest?: typeof request): Promise { + async mintQuote( + requestMintPayload: RequestMintPayload, + customRequest?: typeof request + ): Promise { return CashuMint.mintQuote(this._mintUrl, requestMintPayload, customRequest); } /** @@ -120,7 +127,11 @@ class CashuMint { * @param customRequest * @returns */ - public static async getKeys(mintUrl: string, keysetId?: string, customRequest?: typeof request): Promise { + public static async getKeys( + mintUrl: string, + keysetId?: string, + customRequest?: typeof request + ): Promise { // backwards compatibility for base64 encoded keyset ids if (keysetId) { // make the keysetId url safe @@ -142,14 +153,19 @@ class CashuMint { * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from the active keyset are fetched * @returns the mints public keys */ - async getKeys(keysetId?: string, mintUrl?: string, unit?: string, customRequest?: typeof request): Promise { + async getKeys( + keysetId?: string, + mintUrl?: string, + unit?: string, + customRequest?: typeof request + ): Promise { const allKeys = await CashuMint.getKeys(mintUrl || this._mintUrl, keysetId, customRequest); // find keyset with unit 'sat' - const satKeys = (allKeys.keysets).find((keys) => keys.unit === unit ? unit : 'sat'); + const satKeys = allKeys.keysets.find((keys) => (keys.unit === unit ? unit : 'sat')); if (!satKeys) { throw new Error('No keyset with unit "sat" found'); } - return satKeys + return satKeys; } /** * Get the mints keysets in no specific order @@ -157,7 +173,10 @@ class CashuMint { * @param customRequest * @returns all the mints past and current keysets. */ - public static async getKeySets(mintUrl: string, customRequest?: typeof request): Promise { + public static async getKeySets( + mintUrl: string, + customRequest?: typeof request + ): Promise { const requestInstance = customRequest || request; return requestInstance({ endpoint: joinUrls(mintUrl, '/v1/keysets') }); } @@ -177,7 +196,11 @@ class CashuMint { * @param customRequest * @returns split tokens */ - public static async split(mintUrl: string, splitPayload: SplitPayload, customRequest?: typeof request): Promise { + public static async split( + mintUrl: string, + splitPayload: SplitPayload, + customRequest?: typeof request + ): Promise { const requestInstance = customRequest || request; const data = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/swap'), @@ -205,14 +228,22 @@ class CashuMint { * @param MeltQuotePayload * @returns */ - public static async meltQuote(mintUrl: string, meltQuotePayload: MeltQuotePayload): Promise { + public static async meltQuote( + mintUrl: string, + meltQuotePayload: MeltQuotePayload + ): Promise { const data = await request({ endpoint: joinUrls(mintUrl, '/v1/melt/quote/bolt11'), method: 'POST', requestBody: meltQuotePayload }); - if (!isObj(data) || typeof data?.amount !== 'number' || typeof data?.fee_reserve !== 'number' || typeof data?.quote !== 'string') { + if ( + !isObj(data) || + typeof data?.amount !== 'number' || + typeof data?.fee_reserve !== 'number' || + typeof data?.quote !== 'string' + ) { throw new Error('bad response'); } @@ -233,7 +264,11 @@ class CashuMint { * @param customRequest * @returns */ - public static async melt(mintUrl: string, meltPayload: MeltPayload, customRequest?: typeof request): Promise { + public static async melt( + mintUrl: string, + meltPayload: MeltPayload, + customRequest?: typeof request + ): Promise { const requestInstance = customRequest || request; const data = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/melt/bolt11'), diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 892c985de..1d036c983 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -388,11 +388,11 @@ class CashuWallet { preimage: meltResponse.payment_preimage, change: meltResponse?.change ? dhke.constructProofs( - meltResponse.change, - rs, - secrets, - await this.getKeys(meltResponse.change) - ) + meltResponse.change, + rs, + secrets, + await this.getKeys(meltResponse.change) + ) : [] }; } diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 8410af54c..0445f0d8a 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -46,7 +46,6 @@ export type MintKeys = { * Public keys are a dictionary of number and string. The number represents the amount that the key signs for. */ keys: { [amount: number]: string }; - }; /** @@ -132,11 +131,11 @@ export type PaymentPayload = { proofs: Array; }; -/** +/** * Payload that needs to be send to the mint to request a melt quote */ export type MeltQuotePayload = { - /** + /** * Unit to be melted */ unit: string; @@ -227,7 +226,7 @@ export type SplitPayload = { */ inputs: Array; /** - * Outputs (blinded messages) to be signed by the mint + * Outputs (blinded messages) to be signed by the mint */ outputs: Array; }; @@ -291,7 +290,7 @@ export type PostMintPayload = { /** * Outputs (blinded messages) to be signed by the mint. */ - outputs: Array + outputs: Array; }; /** * Response from the mint after requesting a mint @@ -300,7 +299,6 @@ export type PostMintResponse = { signatures: Array; } & ApiError; - /** * Payload that needs to be sent to the mint when checking for spendable proofs */ @@ -316,9 +314,9 @@ export type CheckStatePayload = { * Enum for the state of a proof */ export enum CheckStateEnum { - UNSPENT = "UNSPENT", - PENDING = "PENDING", - SPENT = "SPENT", + UNSPENT = 'UNSPENT', + PENDING = 'PENDING', + SPENT = 'SPENT' } /** @@ -328,14 +326,14 @@ export type CheckStateEntry = { secret: string; state: CheckStateEnum; witness: string | null; -} +}; /** * Response when checking proofs if they are spendable. Should not rely on this for receiving, since it can be easily cheated. */ export type CheckStateResponse = { /** - * + * */ states: Array; } & ApiError; @@ -351,7 +349,7 @@ export type SerializedBlindedMessage = { * Blinded message */ B_: string; - /** + /** * Keyset id */ id: string; @@ -470,7 +468,7 @@ export type GetInfoResponse = { export type PostRestorePayload = { outputs: Array; -} +}; /** * Response from mint at /v1/restore endpoint diff --git a/src/utils.ts b/src/utils.ts index e9d3198af..e568e33b1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -135,7 +135,7 @@ export function deriveKeysetId(keys: MintKeys) { .join(''); const hash = sha256(new TextEncoder().encode(pubkeysConcat)); const hashHex = bytesToHex(hash); - return "00" + hashHex.slice(0, 12); + return '00' + hashHex.slice(0, 12); } /** * merge proofs from same mint, diff --git a/test/crypto.scheme.test.ts b/test/crypto.scheme.test.ts index 0dd1a745e..16ca68a94 100644 --- a/test/crypto.scheme.test.ts +++ b/test/crypto.scheme.test.ts @@ -50,7 +50,7 @@ class Wallet { private rG: ProjPointType | undefined; private B_: ProjPointType | undefined; private secret = new Uint8Array(); - constructor() { } + constructor() {} async createBlindedMessage(message: string): Promise> { const enc = new TextEncoder(); diff --git a/test/dhke.test.ts b/test/dhke.test.ts index ff1bf43f5..d85e7d627 100644 --- a/test/dhke.test.ts +++ b/test/dhke.test.ts @@ -24,26 +24,12 @@ describe('test blinding message', () => { test('testing string 0000....01', async () => { let secretUInt8 = new TextEncoder().encode(SECRET_MESSAGE); expect(secretUInt8).toStrictEqual( - new Uint8Array([ - 116, - 101, - 115, - 116, - 95, - 109, - 101, - 115, - 115, - 97, - 103, - 101 - ]) + new Uint8Array([116, 101, 115, 116, 95, 109, 101, 115, 115, 97, 103, 101]) ); - const r = bytesToNumber(hexToBytes('0000000000000000000000000000000000000000000000000000000000000001')) - let { B_ } = await dhke.blindMessage( - secretUInt8, - r + const r = bytesToNumber( + hexToBytes('0000000000000000000000000000000000000000000000000000000000000001') ); + let { B_ } = await dhke.blindMessage(secretUInt8, r); expect(B_.toHex(true)).toBe( '025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b' ); diff --git a/test/integration.test.ts b/test/integration.test.ts index f1c005806..a24a51872 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -73,7 +73,7 @@ describe('mint api', () => { // expect no fee because local invoice const requestToPay = await wallet.getMintQuote(10); const quote = await wallet.getMeltQuote(requestToPay.request); - const fee = quote.fee_reserve + const fee = quote.fee_reserve; expect(fee).toBe(0); const sendResponse = await wallet.send(10, tokens.proofs); @@ -83,12 +83,12 @@ describe('mint api', () => { expect(response.change.reduce((a, b) => a + b.amount, 0)).toBe(fee); // check states of spent and kept proofs after payment - const sentProofsSpent = await wallet.checkProofsSpent(sendResponse.send) + const sentProofsSpent = await wallet.checkProofsSpent(sendResponse.send); expect(sentProofsSpent).toBeDefined(); // expect that all proofs are spent, i.e. sendProofsSpent == sendResponse.send expect(sentProofsSpent).toEqual(sendResponse.send); // expect none of the sendResponse.returnChange to be spent - const returnChangeSpent = await wallet.checkProofsSpent(sendResponse.returnChange) + const returnChangeSpent = await wallet.checkProofsSpent(sendResponse.returnChange); expect(returnChangeSpent).toBeDefined(); expect(returnChangeSpent).toEqual([]); }); @@ -109,12 +109,12 @@ describe('mint api', () => { expect(response.change.reduce((a, b) => a + b.amount, 0)).toBe(fee); // check states of spent and kept proofs after payment - const sentProofsSpent = await wallet.checkProofsSpent(sendResponse.send) + const sentProofsSpent = await wallet.checkProofsSpent(sendResponse.send); expect(sentProofsSpent).toBeDefined(); // expect that all proofs are spent, i.e. sendProofsSpent == sendResponse.send expect(sentProofsSpent).toEqual(sendResponse.send); // expect none of the sendResponse.returnChange to be spent - const returnChangeSpent = await wallet.checkProofsSpent(sendResponse.returnChange) + const returnChangeSpent = await wallet.checkProofsSpent(sendResponse.returnChange); expect(returnChangeSpent).toBeDefined(); expect(returnChangeSpent).toEqual([]); }); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index f2821e5a1..61ce98b31 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -6,9 +6,13 @@ import { ReceiveResponse } from '../src/model/types/index.js'; import { cleanToken, getDecodedToken } from '../src/utils.js'; const dummyKeysResp = { - keysets: [{ - id: '009a1f293253e41e', unit: 'sat', keys: { 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181' } - },] + keysets: [ + { + id: '009a1f293253e41e', + unit: 'sat', + keys: { 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181' } + } + ] }; const mintUrl = 'http://localhost:3338'; const mint = new CashuMint(mintUrl); @@ -124,13 +128,18 @@ describe('receive', () => { ] }); const wallet = new CashuWallet(mint); - const token3sat = 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0=' + const token3sat = + 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0='; const response: ReceiveResponse = await wallet.receive(token3sat, [{ amount: 1, count: 3 }]); expect(response.token.token).toHaveLength(1); expect(response.token.token[0].proofs).toHaveLength(3); expect(response.token.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: '009a1f293253e41e' }, { amount: 1, id: '009a1f293253e41e' }, { amount: 1, id: '009a1f293253e41e' }], + proofs: [ + { amount: 1, id: '009a1f293253e41e' }, + { amount: 1, id: '009a1f293253e41e' }, + { amount: 1, id: '009a1f293253e41e' } + ] }); expect(/[0-9a-f]{64}/.test(response.token.token[0].proofs[0].C)).toBe(true); expect(/[0-9a-f]{64}/.test(response.token.token[0].proofs[0].secret)).toBe(true); @@ -185,7 +194,7 @@ describe('checkProofsSpent', () => { test('test checkProofsSpent - get proofs that are NOT spendable', async () => { nock(mintUrl) .post('/v1/checkstate') - .reply(200, { states: [{ secret: "asd", state: "UNSPENT", witness: "witness-asd" }] }); + .reply(200, { states: [{ secret: 'asd', state: 'UNSPENT', witness: 'witness-asd' }] }); const wallet = new CashuWallet(mint); const result = await wallet.checkProofsSpent(proofs); @@ -204,7 +213,9 @@ describe('payLnInvoice', () => { } ]; test('test payLnInvoice base case', async () => { - nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, { quote: "quote_id", amount: 123, fee_reserve: 0 }); + nock(mintUrl) + .post('/v1/melt/quote/bolt11') + .reply(200, { quote: 'quote_id', amount: 123, fee_reserve: 0 }); nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, payment_preimage: '' }); const wallet = new CashuWallet(mint); @@ -214,15 +225,23 @@ describe('payLnInvoice', () => { }); test('test payLnInvoice change', async () => { nock.cleanAll(); - nock(mintUrl).get('/v1/keys').reply(200, { - keysets: [{ - id: '009a1f293253e41e', unit: 'sat', keys: { - 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181', - 2: '03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5' - } - },] - }); - nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, { quote: "quote_id", amount: 123, fee_reserve: 2 }); + nock(mintUrl) + .get('/v1/keys') + .reply(200, { + keysets: [ + { + id: '009a1f293253e41e', + unit: 'sat', + keys: { + 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181', + 2: '03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5' + } + } + ] + }); + nock(mintUrl) + .post('/v1/melt/quote/bolt11') + .reply(200, { quote: 'quote_id', amount: 123, fee_reserve: 2 }); nock(mintUrl) .post('/v1/melt/bolt11') .reply(200, { From 6bee3b3c979e72a95618e1cb065a65c4e3d8e92a Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 27 Feb 2024 17:32:57 +0900 Subject: [PATCH 062/175] fix custom requests --- src/CashuMint.ts | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 084b286e3..8dfbee500 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -79,11 +79,8 @@ class CashuMint { * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ - async mintQuote( - requestMintPayload: RequestMintPayload, - customRequest?: typeof request - ): Promise { - return CashuMint.mintQuote(this._mintUrl, requestMintPayload, customRequest); + async mintQuote(requestMintPayload: RequestMintPayload): Promise { + return CashuMint.mintQuote(this._mintUrl, requestMintPayload, this._customRequest); } /** * Requests the mint to perform token minting after the LN invoice has been paid @@ -117,8 +114,8 @@ class CashuMint { * @param hash hash (id) used for by the mint to keep track of wether the invoice has been paid yet * @returns serialized blinded signatures */ - async mint(mintPayload: PostMintPayload, customRequest?: typeof request) { - return CashuMint.mint(this._mintUrl, mintPayload, customRequest); + async mint(mintPayload: PostMintPayload) { + return CashuMint.mint(this._mintUrl, mintPayload, this._customRequest); } /** * Get the mints public keys @@ -153,13 +150,12 @@ class CashuMint { * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from the active keyset are fetched * @returns the mints public keys */ - async getKeys( - keysetId?: string, - mintUrl?: string, - unit?: string, - customRequest?: typeof request - ): Promise { - const allKeys = await CashuMint.getKeys(mintUrl || this._mintUrl, keysetId, customRequest); + async getKeys(keysetId?: string, mintUrl?: string, unit?: string): Promise { + const allKeys = await CashuMint.getKeys( + mintUrl || this._mintUrl, + keysetId, + this._customRequest + ); // find keyset with unit 'sat' const satKeys = allKeys.keysets.find((keys) => (keys.unit === unit ? unit : 'sat')); if (!satKeys) { @@ -186,7 +182,7 @@ class CashuMint { * @returns all the mints past and current keysets. */ async getKeySets(): Promise { - return CashuMint.getKeySets(this._mintUrl); + return CashuMint.getKeySets(this._mintUrl, this._customRequest); } /** @@ -230,9 +226,11 @@ class CashuMint { */ public static async meltQuote( mintUrl: string, - meltQuotePayload: MeltQuotePayload + meltQuotePayload: MeltQuotePayload, + customRequest?: typeof request ): Promise { - const data = await request({ + const requestInstance = customRequest || request; + const data = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/melt/quote/bolt11'), method: 'POST', requestBody: meltQuotePayload @@ -255,7 +253,7 @@ class CashuMint { * @returns */ async meltQuote(meltQuotePayload: MeltQuotePayload): Promise { - return CashuMint.meltQuote(this._mintUrl, meltQuotePayload); + return CashuMint.meltQuote(this._mintUrl, meltQuotePayload, this._customRequest); } /** * Ask mint to perform a melt operation. This pays a lightning invoice and destroys tokens matching its amount + fees From 60ddb733e29d49a722bc0d48a18fde77a5fc5969 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 27 Feb 2024 17:53:52 +0900 Subject: [PATCH 063/175] add keyset test and type --- src/model/types/index.ts | 7 ++++- src/utils.ts | 5 +-- test/consts.ts | 66 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 test/consts.ts diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 0445f0d8a..4ef934041 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -20,6 +20,11 @@ export type Proof = { C: string; }; +/** + * Public keys are a dictionary of number and string. The number represents the amount that the key signs for. + */ +export type Keys = { [amount: number]: string }; + /** * An array of mint keysets */ @@ -45,7 +50,7 @@ export type MintKeys = { /** * Public keys are a dictionary of number and string. The number represents the amount that the key signs for. */ - keys: { [amount: number]: string }; + keys: Keys; }; /** diff --git a/src/utils.ts b/src/utils.ts index e568e33b1..dff290e21 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,6 +3,7 @@ import { encodeBase64ToJson, encodeJsonToBase64 } from './base64.js'; import { AmountPreference, InvoiceData, + Keys, MintKeys, Proof, Token, @@ -128,8 +129,8 @@ function handleTokens(token: string): Token { * @param keys keys object to derive keyset id from * @returns */ -export function deriveKeysetId(keys: MintKeys) { - const pubkeysConcat = Object.entries(keys.keys) +export function deriveKeysetId(keys: Keys) { + const pubkeysConcat = Object.entries(keys) .sort((a, b) => +a[0] - +b[0]) .map(([, pubKey]) => pubKey) .join(''); diff --git a/test/consts.ts b/test/consts.ts new file mode 100644 index 000000000..a4dc2e611 --- /dev/null +++ b/test/consts.ts @@ -0,0 +1,66 @@ +export const PUBKEYS = { + '1': '0352d74be2366c43a3e2e88798b8e2d03c569ef09d22e02bb365f69a5210a1760b', + '2': '033b88748953acc5e6e3bd08c1c706eb7cb2ea91afd24a2a7f58ce1a5ea662f528', + '4': '0337290d84d9754e86b16e6908649548f54225038112d30d5dc5cdd03edca59b42', + '8': '030ffb1c6541e36c0ed7d64d2317f125a04126de38a9fddeabda860f9823a315b8', + '16': '035aaf89544bef31d420eba46b4f24bd1a26018883cbfafeec79ce7e61637bc593', + '32': '0219e43d08e6ce640c6cee70a5cb5946a967ec3b32207c8c7f465875356c0b3e7a', + '64': '039eca547db9d9709631038edbb287b06f7c591acb3a7a863cc027c5d0c5296524', + '128': '036d0d2fb209a5b43527a816ab7475151e2ec8b0b9535543c0aa2a9fab43c0e7c1', + '256': '0209d950bf0a545c7cd33cd3b76100ed008c1b62f49eb1627564aaedd1a05942c5', + '512': '02393187e1c1df39f08610d0af930d971c8d2474beb5a08e86a3de3359d3ab9320', + '1024': '02464f570d0413e918aebcb8d5734debd9368548bef4f36eb62637dd602e6253ea', + '2048': '02b3c7e26a17e20952e4ac8f58534217831fd9f4e3d3b1ee1c80908d4f1e1ee705', + '4096': '023f60d762c187210a9cd8de2dccd1d4e74594d4f489c194ea05c588cda978eacf', + '8192': '029df2cd59413e63b545be9c56f4b0dc4bbf663489bc3ab67eadde2772fbfd370c', + '16384': '034543746c6fce7ad3db341796356a71b4d3b7f6cb2908dea1f8c001b64761dd9b', + '32768': '035e642822cd59400058d402058c6c887f620ef7a9ba09ef815f26ba3f59118a8e', + '65536': '03d0fc713531149cf8b47b96ecec54f853e19ff47417d550cd7a1dbb654edff98a', + '131072': '023db71e398e641540202a2b429354f6d62cb25b0db2a8e0247ff8cdbb152b66c4', + '262144': '02f3edf578a999fd471d0651c95202efabbf9b8096d3fa81975e155979418197f3', + '524288': '03db113ce4b8c01a282ad6d89e594ca4e67cb0f2f9ceb218938aa9420f31085092', + '1048576': '02da6b3a16443b131a2fdc764106de83f50f6af5e0d648d49f808eb5913e841093', + '2097152': '02d6c7b6ba9c2d0ea5fa888de1616f2fc1a34c5e21df15bdf5a71c75aa510c07c9', + '4194304': '021d66e4ef4cbb0860483ac640723e231a55e63dad77bdc9ed3156dcada33f6cf3', + '8388608': '035064a5c497fdf8398bc3790304ac1efd86594f88ab65e04500168d5100291213', + '16777216': '03f4f7f2afe38bde4e59f50478473fbb0dfb6d869cf2e28400e91728301b774955', + '33554432': '02cb68adad06865f8a774ac73a83d20c6cf649b5ca766e41f6376bc391625d35f5', + '67108864': '028302c0995a8e1129b1a27152e3683112ee38902cbc6b04c5edbf804811f7898a', + '134217728': '02e67e852e6d0d9709a9e4ba164a812adef870f42d4bcc36d541a277da4acdfdb6', + '268435456': '0210276a3d3c6615bfa10f430b20fb51ff61591f7c4da6906f455b7f096673e689', + '536870912': '02abffa5e1f0036aabcca596d527720b62d4921a5df6ffccef52c801f8a4b1e0ab', + '1073741824': '03e07c92aa7ceab7e2c01adb7af77def101b7caa502795b9b8e30a8b53b39d7dad', + '2147483648': '0203786ad188ba2368fd4f44d24ec1cdc9736d757557946b9d4b2403ec7b116d29', + '4294967296': '02caa6c148adbbe3e6ae71d074770085c43624acd8107a40dc5e7929ef24b5bfaa', + '8589934592': '02a3879c0a6e4297654c109096559326b80ff70432fedcaa3673a2ae8742e4cb33', + '17179869184': '0212fd5125cb773ab2b8dbb304c1e5b9359fc75f0889e3073d5a411886f54f6d38', + '34359738368': '03797b244dee73204127223ca4d341a92e3a87d3f17e1c80fedcc5a336ff616a1e', + '68719476736': '03086982375f4097b2f4d00e1924b89d696352b874fec8bc76504347f51328671d', + '137438953472': '02b61f17e50dc9d0887d7b6953f97a520ea0796870e0bba51e05b7e2f495d9b806', + '274877906944': '02290f140f84512e0ba941e6f958833390d4495f161544b8bcf3241a0ead89de18', + '549755813888': '0310ee60086163731d7839a7bf2062e655ab2563871488429c8a67433f2373876d', + '1099511627776': '0212b293c4e0f9c2f1dac473077f2daa280cd6600ac3fb8789c1bccddc272054a1', + '2199023255552': '03e590752f1379fd09986b07ef4a14dfadf8bec5aacf4ec83f56990f2a07ec6656', + '4398046511104': '02ec77ebaa46fdaf0eca996865dcb8121f71c2a4d35e70135933f21c7f632e2f38', + '8796093022208': '038c5f43fa83c4dd84cadd22ec12df56de435eff713629d0706eae6f42741ce00e', + '17592186044416': '036ba077c8350e7dcfcb972a4087252e59cf1e74ea8cffbe0405143037f295f58e', + '35184372088832': '0279771f0d2971c35e93d0b22b3b42ce6b859141f1f72b215b51c7feff96e87cef', + '70368744177664': '03864a3ae9e3e3817c14d0635d2cd88b1130c3440e2473815c02121d26140b5b57', + '140737488355328': '029e7296b502ed49b32c0981790a159bbfb0c9a827fb1178295067810cf6e31921', + '281474976710656': '030024a03f98bd27743ccecb7d6e3204646415bed7cb7c1098aaebdeabb72d63b4', + '562949953421312': '0384fb48aa0b4e4a68f645c963ffaaf3de4a5561edeb486cba74daf1b93a02a04a', + '1125899906842624': '02ef5cf515cb8b7459b6a0d6f631013fbef0885718dc30f272397beb3643f5bfa3', + '2251799813685248': '02f944f0fef655d8c89f042460e15fd3c6d452a1e8cbac503b826893477656f9b2', + '4503599627370496': '0378b14720f73c27ecfe9051f07ae09051d69e077ac8849a66bd77a7965f2cc760', + '9007199254740992': '02a0c981d62cf945144724de11c9ca595aebbda8b6afb39a9faa7f4abcec3e6da8', + '18014398509481984': '0225ce53547e4f322a80fab97e5028ab78282b6f63f9528ca83108b47d8007cc72', + '36028797018963968': '0315a18f669aee0c983ca7fcd23d929b71cf6b6983d760a96e950d17a061d43a06', + '72057594037927936': '03d1877405fb8ba7f7767fd646d305afd566088e5742d47f45b7c38753ce4d1167', + '144115188075855872': '029bbf9e5c76d11096a2f5bd176917e21ca941e294081866895183a131ba4358e6', + '288230376151711744': '023cf13b9c1a5a242dbb15c63d5e397bc595a672f90e2046b59e036f786713d796', + '576460752303423488': '03a34759634b1615d33ce7da47ef18df738559f9b47d96cbda897810de86aa0a9c', + '1152921504606846976': '03d1d210b1d41cf860876260ad4761fc5daedfefcf1874d2a87386cfccfe689d65', + '2305843009213693952': '03d42abc9c05b8ae2ac926de932030e7aa6ec845eacd5ddcab589df5c779ffb3e4', + '4611686018427387904': '022e2acccc241b9936cedcd6447308c344ee4903798507bbb604ef81468fabf277', + '9223372036854775808': '027fd1b5d36ddf18de228b5f3bdf61bff8d94b5a2f7ddc44b117372d7436b178e3' +}; From 77c8b5f4a6d819ce44ace4fadadd78c8db9eb62e Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 27 Feb 2024 18:10:15 +0900 Subject: [PATCH 064/175] change param to keysetId for consistencey --- src/CashuWallet.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 1d036c983..2513f5c23 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -336,7 +336,7 @@ class CashuWallet { const keyset = await this.initKeys(); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, - keyset, + keyset.id, AmountPreference ); const postMintPayload: PostMintPayload = { @@ -463,7 +463,7 @@ class CashuWallet { const totalAmount = proofsToSend.reduce((total, curr) => total + curr.amount, 0); const keepBlindedMessages = this.createRandomBlindedMessages( totalAmount - amount, - keyset, + keyset.id, undefined, counter ); @@ -472,7 +472,7 @@ class CashuWallet { } const sendBlindedMessages = this.createRandomBlindedMessages( amount, - keyset, + keyset.id, preference, counter ); @@ -529,12 +529,12 @@ class CashuWallet { */ private createRandomBlindedMessages( amount: number, - keyset: MintKeys, + keysetId: string, amountPreference?: Array, counter?: number ): BlindedMessageData & { amounts: Array } { const amounts = splitAmount(amount, amountPreference); - return this.createBlindedMessages(amounts, keyset.id, counter); + return this.createBlindedMessages(amounts, keysetId, counter); } /** From 598ec08fd2a1e0ee43486113d528fab32333e762 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:25:59 +0100 Subject: [PATCH 065/175] Token state check with Y, https://github.com/cashubtc/nuts/pull/93 --- src/CashuWallet.ts | 5 +++-- src/model/types/index.ts | 5 ++--- test/wallet.test.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 892c985de..52cc4d328 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -500,9 +500,10 @@ class CashuWallet { * @returns */ async checkProofsSpent(proofs: Array): Promise> { + const enc = new TextEncoder(); const payload = { - // array of secrets of proofs to check - secrets: proofs.map((p) => p.secret) + // array of Ys of proofs to check + Ys: proofs.map((p) => dhke.hashToCurve(enc.encode(p.secret)).toHex(true)) }; const { states } = await this.mint.check(payload); diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 8410af54c..9ab38ec51 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -306,10 +306,9 @@ export type PostMintResponse = { */ export type CheckStatePayload = { /** - * array of proofs. Only the secret is strictly needed. - * If the whole object is passed, it will be stripped of other objects before sending it to the mint. + * The Y = hash_to_curve(secret) of the proofs to be checked. */ - secrets: Array; + Ys: Array; }; /** diff --git a/test/wallet.test.ts b/test/wallet.test.ts index f2821e5a1..2fda33c65 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -185,7 +185,7 @@ describe('checkProofsSpent', () => { test('test checkProofsSpent - get proofs that are NOT spendable', async () => { nock(mintUrl) .post('/v1/checkstate') - .reply(200, { states: [{ secret: "asd", state: "UNSPENT", witness: "witness-asd" }] }); + .reply(200, { states: [{ Y: "asd", state: "UNSPENT", witness: "witness-asd" }] }); const wallet = new CashuWallet(mint); const result = await wallet.checkProofsSpent(proofs); From a2c3954983fa605edfbe5037a12c089c99572323 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:26:30 +0100 Subject: [PATCH 066/175] rename integration test --- .github/workflows/nutshell-integration.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index f8222c08a..d0248ffe5 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -1,9 +1,9 @@ -name: Nutshell end-to-end tests +name: Nutshell integration on: [push, pull_request] jobs: - integration: + integration-tests: runs-on: ubuntu-latest steps: - name: Pull and start mint From f46001babb0ff12335dfac325476e1c025b4275d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:46:13 +0100 Subject: [PATCH 067/175] fix Y response --- src/CashuWallet.ts | 17 +++++++++-------- src/model/types/index.ts | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 0f4003f26..ab4fad397 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -388,11 +388,11 @@ class CashuWallet { preimage: meltResponse.payment_preimage, change: meltResponse?.change ? dhke.constructProofs( - meltResponse.change, - rs, - secrets, - await this.getKeys(meltResponse.change) - ) + meltResponse.change, + rs, + secrets, + await this.getKeys(meltResponse.change) + ) : [] }; } @@ -501,14 +501,15 @@ class CashuWallet { */ async checkProofsSpent(proofs: Array): Promise> { const enc = new TextEncoder(); + const Ys = proofs.map((p) => dhke.hashToCurve(enc.encode(p.secret)).toHex(true)); const payload = { // array of Ys of proofs to check - Ys: proofs.map((p) => dhke.hashToCurve(enc.encode(p.secret)).toHex(true)) + Ys: Ys }; const { states } = await this.mint.check(payload); - return proofs.filter((proof) => { - const state = states.find((state) => state.secret === proof.secret); + return proofs.filter((_, i) => { + const state = states.find((state) => state.Y === Ys[i]); return state && state.state === CheckStateEnum.SPENT; }); } diff --git a/src/model/types/index.ts b/src/model/types/index.ts index d27bef087..781a8724c 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -327,7 +327,7 @@ export enum CheckStateEnum { * Entries of CheckStateResponse with state of the proof */ export type CheckStateEntry = { - secret: string; + Y: string; state: CheckStateEnum; witness: string | null; }; From 086f89690fc53ad364365b184e291bad74b8384c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 17 Mar 2024 13:49:23 +0100 Subject: [PATCH 068/175] use nutshell 0.15.1 --- .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 d0248ffe5..cf17cd906 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_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.0 poetry run mint + docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.1 poetry run mint - name: Check running containers run: docker ps From c8054c3ad7384e7f660acb7ec57f472eb8ac698f Mon Sep 17 00:00:00 2001 From: starbackr-dev Date: Tue, 19 Mar 2024 22:44:32 -0400 Subject: [PATCH 069/175] Added unit field to CashuWallet unit field is required for v1 API to quote, mint and melt tokens based on unit=usd or others. Updated CashuWallet and test scripts related to that. --- src/CashuWallet.ts | 15 +++++++++------ test/integration.test.ts | 21 +++++++++++---------- test/request.test.ts | 4 ++-- test/wallet.test.ts | 37 +++++++++++++++++++------------------ 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 03521e202..e870eb038 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -36,20 +36,23 @@ import { bytesToHex } from '@noble/curves/abstract/utils'; class CashuWallet { private _keys = {} as MintKeys; private _keysetId = ''; + private _unit = 'sat'; mint: CashuMint; - unit = 'sat'; + /** * @param keys public keys from the mint * @param mint Cashu mint instance is used to make api calls */ - constructor(mint: CashuMint, keys?: MintKeys) { + constructor(mint: CashuMint, unit?:string, keys?: MintKeys) { this.mint = mint; + if (unit) this._unit = unit; if (keys) { this._keys = keys; // this._keysetId = deriveKeysetId(this._keys); this._keysetId = keys.id; + } } @@ -107,7 +110,7 @@ class CashuWallet { */ getMintQuote(amount: number) { const requestMintPayload: RequestMintPayload = { - unit: this.unit, + unit: this._unit, amount: amount } return this.mint.mintQuote(requestMintPayload); @@ -145,7 +148,7 @@ class CashuWallet { * @returns estimated Fee */ async getMeltQuote(invoice: string): Promise { - const meltQuote = await this.mint.meltQuote({ unit: this.unit, request: invoice }); + const meltQuote = await this.mint.meltQuote({ unit: this._unit, request: invoice }); return meltQuote; } /** @@ -189,7 +192,7 @@ class CashuWallet { meltQuote?: MeltQuoteResponse ): Promise { if (!meltQuote) { - meltQuote = await this.mint.meltQuote({ unit: this.unit, request: invoice }); + meltQuote = await this.mint.meltQuote({ unit: this._unit, request: invoice }); } return await this.meltTokens(meltQuote, proofsToSend); @@ -263,7 +266,7 @@ class CashuWallet { keyset, preference ); - const { signatures, error } = await CashuMint.split(tokenEntry.mint, payload); + const { signatures} = await CashuMint.split(tokenEntry.mint, payload); const newProofs = dhke.constructProofs( signatures, blindedMessages.rs, diff --git a/test/integration.test.ts b/test/integration.test.ts index f1c005806..faef690b1 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -10,6 +10,7 @@ const externalInvoice = let request: Record | undefined; const mintUrl = 'http://localhost:3338'; +const unit = 'sats' describe('mint api', () => { test('get keys', async () => { @@ -32,13 +33,13 @@ describe('mint api', () => { }); test('request mint', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const request = await wallet.getMintQuote(100); expect(request).toBeDefined(); }); test('mint tokens', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const request = await wallet.getMintQuote(1337); expect(request).toBeDefined(); expect(request.request).toContain('lnbc1337'); @@ -49,7 +50,7 @@ describe('mint api', () => { }); test('get fee for local invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const request = await wallet.getMintQuote(100); const fee = (await wallet.getMeltQuote(request.request)).fee_reserve; expect(fee).toBeDefined(); @@ -58,7 +59,7 @@ describe('mint api', () => { }); test('get fee for external invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const fee = (await wallet.getMeltQuote(externalInvoice)).fee_reserve; expect(fee).toBeDefined(); // because external invoice, fee should be > 0 @@ -66,7 +67,7 @@ describe('mint api', () => { }); test('pay local invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const request = await wallet.getMintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); @@ -94,7 +95,7 @@ describe('mint api', () => { }); test('pay external invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const request = await wallet.getMintQuote(3000); const tokens = await wallet.mintTokens(3000, request.quote); @@ -120,7 +121,7 @@ describe('mint api', () => { }); test('test send tokens exact without previous split', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const request = await wallet.getMintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); @@ -133,7 +134,7 @@ describe('mint api', () => { }); test('test send tokens with change', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const request = await wallet.getMintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); @@ -146,7 +147,7 @@ describe('mint api', () => { }); test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const request = await wallet.getMintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); @@ -161,7 +162,7 @@ describe('mint api', () => { }); test('receive tokens with previous mint', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const request = await wallet.getMintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); const encoded = getEncodedToken({ diff --git a/test/request.test.ts b/test/request.test.ts index 14ff1616a..38e77e030 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -31,7 +31,7 @@ describe('requests', () => { }; }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); await wallet.getMeltQuote(invoice); expect(request).toBeDefined(); @@ -51,7 +51,7 @@ describe('requests', () => { }; }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); setGlobalRequestOptions({ headers: { 'x-cashu': 'xyz-123-abc' } }); await wallet.getMeltQuote(invoice); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 70a19efb9..6e4059362 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -11,6 +11,7 @@ const dummyKeysResp = { }; const mintUrl = 'http://localhost:3338'; const mint = new CashuMint(mintUrl); +const unit = 'sats'; const invoice = 'lnbc20u1p3u27nppp5pm074ffk6m42lvae8c6847z7xuvhyknwgkk7pzdce47grf2ksqwsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzpgxqyz5vqsp5sw6n7cztudpl5m5jv3z6dtqpt2zhd3q6dwgftey9qxv09w82rgjq9qyyssqhtfl8wv7scwp5flqvmgjjh20nf6utvv5daw5h43h69yqfwjch7wnra3cn94qkscgewa33wvfh7guz76rzsfg9pwlk8mqd27wavf2udsq3yeuju'; @@ -31,7 +32,7 @@ describe('test fees', () => { amount: 2000, fee_reserve: 20 }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const fee = await wallet.getMeltQuote(invoice); const amount = decode(invoice).sections[2].value / 1000; @@ -55,7 +56,7 @@ describe('receive', () => { } ] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const response: ReceiveResponse = await wallet.receive(tokenInput); @@ -91,7 +92,7 @@ describe('receive', () => { } ] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const token3sat = 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0=' const response: ReceiveResponse = await wallet.receive(token3sat, [{ amount: 1, count: 3 }]); @@ -107,7 +108,7 @@ describe('receive', () => { test('test receive tokens already spent', async () => { const msg = 'tokens already spent. Secret: asdasdasd'; nock(mintUrl).post('/v1/split').reply(200, { detail: msg }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const { tokensWithErrors } = await wallet.receive(tokenInput); const t = tokensWithErrors!; @@ -124,7 +125,7 @@ describe('receive', () => { }); test('test receive could not verify proofs', async () => { nock(mintUrl).post('/v1/split').reply(200, { code: 0, error: 'could not verify proofs.' }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const { tokensWithErrors } = await wallet.receive(tokenInput); const t = tokensWithErrors!; @@ -154,7 +155,7 @@ describe('checkProofsSpent', () => { nock(mintUrl) .post('/v1/check') .reply(200, { spendable: [true] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const result = await wallet.checkProofsSpent(proofs); @@ -174,7 +175,7 @@ describe('payLnInvoice', () => { test('test payLnInvoice base case', async () => { nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, { quote: "quote_id", amount: 123, fee_reserve: 0 }); nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, proof: '' }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const result = await wallet.payLnInvoice(invoice, proofs); @@ -204,7 +205,7 @@ describe('payLnInvoice', () => { } ] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }]); @@ -214,7 +215,7 @@ describe('payLnInvoice', () => { }); test('test payLnInvoice bad resonse', async () => { nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, {}); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const result = await wallet.payLnInvoice(invoice, proofs).catch((e) => e); @@ -235,7 +236,7 @@ describe('requestTokens', () => { } ] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const { proofs } = await wallet.mintTokens(1, ''); @@ -246,7 +247,7 @@ describe('requestTokens', () => { }); test('test requestTokens bad resonse', async () => { nock(mintUrl).post('/v1/mint/bolt11').reply(200, {}); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const result = await wallet.mintTokens(1, '').catch((e) => e); @@ -275,7 +276,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const result = await wallet.send(1, proofs); @@ -302,7 +303,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const result = await wallet.send(1, [ { @@ -340,7 +341,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const overpayProofs = [ { @@ -388,7 +389,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const overpayProofs = [ { @@ -443,7 +444,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const overpayProofs = [ { @@ -483,7 +484,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const result = await wallet.send(2, proofs).catch((e) => e); @@ -491,7 +492,7 @@ describe('send', () => { }); test('test send bad response', async () => { nock(mintUrl).post('/v1/split').reply(200, {}); - const wallet = new CashuWallet(mint); + const wallet = new CashuWallet(mint, unit); const result = await wallet .send(1, [ From 6a2544bafdb0885a47dad1d8f72368dadc909180 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 21 Mar 2024 15:27:16 +0900 Subject: [PATCH 070/175] fixes for keyset --- src/CashuMint.ts | 12 ++--- src/CashuWallet.ts | 113 +++++++++++++++++++-------------------------- src/utils.ts | 3 +- test/utils.test.ts | 10 ++++ 4 files changed, 65 insertions(+), 73 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 8dfbee500..6a35f1a7d 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -150,18 +150,13 @@ class CashuMint { * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from the active keyset are fetched * @returns the mints public keys */ - async getKeys(keysetId?: string, mintUrl?: string, unit?: string): Promise { + async getKeys(keysetId?: string, mintUrl?: string): Promise { const allKeys = await CashuMint.getKeys( mintUrl || this._mintUrl, keysetId, this._customRequest ); - // find keyset with unit 'sat' - const satKeys = allKeys.keysets.find((keys) => (keys.unit === unit ? unit : 'sat')); - if (!satKeys) { - throw new Error('No keyset with unit "sat" found'); - } - return satKeys; + return allKeys; } /** * Get the mints keysets in no specific order @@ -332,6 +327,9 @@ class CashuMint { customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; + //TODO remove after fix + //@ts-expect-error temp fix + restorePayload.quote = ''; const data = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/restore'), method: 'POST', diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index ab4fad397..f14d49a3b 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -39,8 +39,7 @@ import { wordlist } from '@scure/bip39/wordlists/english'; * This class should act as the entry point for this library */ class CashuWallet { - private _keys = {} as MintKeys; - private _keysetId = ''; + private _keys: MintKeys | undefined; private _seed: Uint8Array | undefined; mint: CashuMint; unit = 'sat'; @@ -52,12 +51,9 @@ class CashuWallet { * This can lead to poor performance, in which case the seed should be directly provided */ constructor(mint: CashuMint, keys?: MintKeys, mnemonicOrSeed?: string | Uint8Array) { - this._keys = keys || ({} as MintKeys); this.mint = mint; if (keys) { this._keys = keys; - // this._keysetId = deriveKeysetId(this._keys); - this._keysetId = keys.id; } if (!mnemonicOrSeed) { return; @@ -73,15 +69,13 @@ class CashuWallet { } get keys(): MintKeys { + if (!this._keys) { + throw new Error('Keys are not set'); + } return this._keys; } set keys(keys: MintKeys) { this._keys = keys; - // this._keysetId = deriveKeysetId(this._keys); - this._keysetId = keys.id; - } - get keysetId(): string { - return this._keysetId; } /** @@ -149,11 +143,11 @@ class CashuWallet { if (!preference) { preference = getDefaultAmountPreference(amount); } - const keyset = await this.initKeys(); + const keys = await this.getKeys(); const { payload, blindedMessages } = this.createSplitPayload( amount, tokenEntry.proofs, - keyset, + keys, preference, counter ); @@ -162,7 +156,7 @@ class CashuWallet { signatures, blindedMessages.rs, blindedMessages.secrets, - keyset + keys ); proofs.push(...newProofs); } catch (error) { @@ -194,7 +188,7 @@ class CashuWallet { if (preference) { amount = preference?.reduce((acc, curr) => acc + curr.amount * curr.count, 0); } - const keyset = await this.initKeys(); + const keyset = await this.getKeys(); let amountAvailable = 0; const proofsToSend: Array = []; const proofsToKeep: Array = []; @@ -252,13 +246,18 @@ class CashuWallet { * @param count set number of blinded messages that should be generated * @returns proofs */ - async restore(start: number, count: number, keysetId: string): Promise<{ proofs: Array }> { + async restore( + start: number, + count: number, + keysetId?: string + ): Promise<{ proofs: Array }> { + const keys = await this.getKeys(keysetId); if (!this._seed) { throw new Error('CashuWallet must be initialized with mnemonic to use restore'); } // create blank amounts for unknown restore amounts const amounts = Array(count).fill(0); - const { blindedMessages, rs, secrets } = this.createBlindedMessages(amounts, keysetId, start); + const { blindedMessages, rs, secrets } = this.createBlindedMessages(amounts, keys.id, start); const { outputs, promises } = await this.mint.restore({ outputs: blindedMessages }); @@ -269,44 +268,30 @@ class CashuWallet { ); return { - proofs: dhke.constructProofs(promises, validRs, validSecrets, await this.getKeys(promises)) + proofs: dhke.constructProofs(promises, validRs, validSecrets, keys) }; } /** * Initialize the wallet with the mints public keys */ - private async initKeys(): Promise { - if (!this.keysetId || !Object.keys(this.keys).length) { - this.keys = await this.mint.getKeys(); - // this._keysetId = deriveKeysetId(this.keys); - this._keysetId = this.keys.id; - } - return this.keys; - } - - /** - * Get the mint's public keys for a given set of proofs - * @param arr array of proofs - * @param mint optional mint url - * @returns keys - */ - private async getKeys(arr: Array, mint?: string): Promise { - await this.initKeys(); - if (!arr?.length || !arr[0]?.id) { - return this.keys; - } - const keysetId = arr[0].id; - if (this.keysetId === keysetId) { - return this.keys; + private async getKeys(keysetId?: string, unit?: string): Promise { + if (!this._keys) { + const allKeys = await this.mint.getKeys(keysetId); + let keys; + if (keysetId) { + keys = allKeys.keysets.find((k) => k.id === keysetId); + } else { + keys = allKeys.keysets.find((k) => (unit ? k.unit === unit : k.unit === 'sat')); + } + if (!keys) { + throw new Error( + `could not initialize keys. No keyset with unit '${unit ? unit : 'sat'}' found` + ); + } + this._keys = keys; } - - const keys = - !mint || mint === this.mint.mintUrl - ? await this.mint.getKeys(keysetId) - : await this.mint.getKeys(keysetId, mint); - - return keys; + return this._keys; } /** @@ -314,12 +299,12 @@ class CashuWallet { * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ - getMintQuote(amount: number) { + async getMintQuote(amount: number) { const requestMintPayload: RequestMintPayload = { unit: this.unit, amount: amount }; - return this.mint.mintQuote(requestMintPayload); + return await this.mint.mintQuote(requestMintPayload); } /** @@ -331,13 +316,16 @@ class CashuWallet { async mintTokens( amount: number, quote: string, - AmountPreference?: Array + keysetId?: string, + AmountPreference?: Array, + counter?: number ): Promise<{ proofs: Array }> { - const keyset = await this.initKeys(); + const keyset = await this.getKeys(keysetId); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, - keyset.id, - AmountPreference + keysetId ?? keyset.id, + AmountPreference, + counter ); const postMintPayload: PostMintPayload = { outputs: blindedMessages, @@ -371,9 +359,11 @@ class CashuWallet { keysetId?: string, counter?: number ): Promise { + const keys = await this.getKeys(keysetId); + const { blindedMessages, secrets, rs } = this.createBlankOutputs( meltQuote.fee_reserve, - keysetId ?? this._keysetId, + keys.id, counter ); const meltPayload: MeltPayload = { @@ -387,12 +377,7 @@ class CashuWallet { isPaid: meltResponse.paid ?? false, preimage: meltResponse.payment_preimage, change: meltResponse?.change - ? dhke.constructProofs( - meltResponse.change, - rs, - secrets, - await this.getKeys(meltResponse.change) - ) + ? dhke.constructProofs(meltResponse.change, rs, secrets, keys) : [] }; } @@ -564,10 +549,8 @@ class CashuWallet { let deterministicR = undefined; let secretBytes = undefined; if (this._seed && counter != undefined) { - secretBytes = deriveSecret(this._seed, keysetId ?? this.keysetId, counter + i); - deterministicR = bytesToNumber( - deriveBlindingFactor(this._seed, keysetId ?? this.keysetId, counter + i) - ); + secretBytes = deriveSecret(this._seed, keysetId, counter + i); + deterministicR = bytesToNumber(deriveBlindingFactor(this._seed, keysetId, counter + i)); } else { secretBytes = randomBytes(32); } @@ -576,7 +559,7 @@ class CashuWallet { secrets.push(secret); const { B_, r } = dhke.blindMessage(secret, deterministicR); rs.push(r); - const blindedMessage = new BlindedMessage(amounts[i], B_, keysetId ?? this.keysetId); + const blindedMessage = new BlindedMessage(amounts[i], B_, keysetId); blindedMessages.push(blindedMessage.getSerializedBlindedMessage()); } return { blindedMessages, secrets, rs, amounts }; diff --git a/src/utils.ts b/src/utils.ts index dff290e21..c15fda477 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -136,8 +136,9 @@ export function deriveKeysetId(keys: Keys) { .join(''); const hash = sha256(new TextEncoder().encode(pubkeysConcat)); const hashHex = bytesToHex(hash); - return '00' + hashHex.slice(0, 12); + return '00' + hashHex.slice(0, 14); } + /** * merge proofs from same mint, * removes TokenEntrys with no proofs or no mint field diff --git a/test/utils.test.ts b/test/utils.test.ts index 0ec54f9f8..ade88d3bf 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,5 +1,6 @@ import { AmountPreference } from '../src/model/types/index.js'; import * as utils from '../src/utils.js'; +import { PUBKEYS } from './consts.js'; describe('test split amounts ', () => { test('testing amount 2561', async () => { @@ -314,3 +315,12 @@ describe('test decodeInvoice', () => { expect(invoiceData.memo).toStrictEqual('bolt11.org'); }); }); + +describe('test keyset derivation', () => { + test('derive', () => { + const keys = PUBKEYS; + const keysetId = utils.deriveKeysetId(keys); + console.log(keysetId); + expect(keysetId).toBe('00a627821fbe96e4'); + }); +}); From b2fe1d2f1d6541bf799bfcff63785773bb301a53 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 21 Mar 2024 15:44:24 +0900 Subject: [PATCH 071/175] remove some unused imports --- src/CashuMint.ts | 3 +-- src/CashuWallet.ts | 3 +-- src/secrets.ts | 2 +- src/utils.ts | 2 -- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 6a35f1a7d..32bbe16b8 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -1,10 +1,9 @@ -import { +import type { CheckStatePayload, CheckStateResponse, GetInfoResponse, MeltPayload, MeltResponse, - MintKeys, MintActiveKeys, MintAllKeysets, PostRestoreResponse, diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index f14d49a3b..2cbd22fe5 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -1,4 +1,4 @@ -import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils'; +import { bytesToHex, randomBytes } from '@noble/hashes/utils'; import { CashuMint } from './CashuMint.js'; import * as dhke from './DHKE.js'; import { BlindedMessage } from './model/BlindedMessage.js'; @@ -17,7 +17,6 @@ import { RequestMintPayload, SendResponse, SerializedBlindedMessage, - SerializedBlindedSignature, SplitPayload, CheckStateEnum, Token, diff --git a/src/secrets.ts b/src/secrets.ts index 0db290af7..48e4fbb69 100644 --- a/src/secrets.ts +++ b/src/secrets.ts @@ -1,5 +1,5 @@ import { HDKey } from '@scure/bip32'; -import { generateMnemonic, validateMnemonic, mnemonicToSeedSync } from '@scure/bip39'; +import { generateMnemonic, mnemonicToSeedSync } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; import { encodeBase64toUint8 } from './base64'; import { bytesToNumber } from './utils'; diff --git a/src/utils.ts b/src/utils.ts index c15fda477..d9d28b5fa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,7 +4,6 @@ import { AmountPreference, InvoiceData, Keys, - MintKeys, Proof, Token, TokenEntry, @@ -13,7 +12,6 @@ import { import { TOKEN_PREFIX, TOKEN_VERSION } from './utils/Constants.js'; import { bytesToHex } from '@noble/curves/abstract/utils'; import { sha256 } from '@noble/hashes/sha256'; -import { Buffer } from 'buffer/'; function splitAmount(value: number, amountPreference?: Array): Array { const chunks: Array = []; From 8d92c512d6b01052ccb59da1491c318916cb0795 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 23 Mar 2024 09:28:01 +0900 Subject: [PATCH 072/175] fix info response type --- src/model/types/index.ts | 42 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 781a8724c..8e5885daf 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -460,12 +460,48 @@ export type GetInfoResponse = { version: string; description?: string; description_long?: string; - contact: Array>; - nuts: Array; + contact: Array<[string, string]>; + nuts: { + "4": { + methods: Array + disabled: boolean + } + "5": { + methods: Array + disabled: boolean + } + "7"?: { + supported: boolean + }, + "8"?: { + supported: boolean + }, + "9"?: { + supported: boolean + }, + "10"?: { + supported: boolean + }, + "11"?: { + supported: boolean + }, + "12"?: { + supported: boolean + } + "13"?: { + supported: boolean + } + } motd?: string; - parameter: { peg_out_only: boolean }; }; +export type SwapMethod = { + method: string, + unit: string, + min_amount: number, + max_amount: number +} + /** * Request to mint at /v1/restore endpoint */ From 322a7f02a9cab8d69663b491bebf73212da2324b Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 23 Mar 2024 09:30:20 +0900 Subject: [PATCH 073/175] add doc to new type --- src/model/types/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 8e5885daf..2b8366784 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -495,6 +495,9 @@ export type GetInfoResponse = { motd?: string; }; +/** + * Ecash to other MoE swap method, displayed in @type {GetInfoResponse} + */ export type SwapMethod = { method: string, unit: string, From 152ade8c09a236dc53ed076d64d0830fa4f491ef Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 23 Mar 2024 09:30:48 +0900 Subject: [PATCH 074/175] format --- src/model/types/index.ts | 70 ++++++++++++++++++++-------------------- test/wallet.test.ts | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 2b8366784..ed69683fa 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -462,36 +462,36 @@ export type GetInfoResponse = { description_long?: string; contact: Array<[string, string]>; nuts: { - "4": { - methods: Array - disabled: boolean - } - "5": { - methods: Array - disabled: boolean - } - "7"?: { - supported: boolean - }, - "8"?: { - supported: boolean - }, - "9"?: { - supported: boolean - }, - "10"?: { - supported: boolean - }, - "11"?: { - supported: boolean - }, - "12"?: { - supported: boolean - } - "13"?: { - supported: boolean - } - } + '4': { + methods: Array; + disabled: boolean; + }; + '5': { + methods: Array; + disabled: boolean; + }; + '7'?: { + supported: boolean; + }; + '8'?: { + supported: boolean; + }; + '9'?: { + supported: boolean; + }; + '10'?: { + supported: boolean; + }; + '11'?: { + supported: boolean; + }; + '12'?: { + supported: boolean; + }; + '13'?: { + supported: boolean; + }; + }; motd?: string; }; @@ -499,11 +499,11 @@ export type GetInfoResponse = { * Ecash to other MoE swap method, displayed in @type {GetInfoResponse} */ export type SwapMethod = { - method: string, - unit: string, - min_amount: number, - max_amount: number -} + method: string; + unit: string; + min_amount: number; + max_amount: number; +}; /** * Request to mint at /v1/restore endpoint diff --git a/test/wallet.test.ts b/test/wallet.test.ts index f19593e4d..0f80b8592 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -194,7 +194,7 @@ describe('checkProofsSpent', () => { test('test checkProofsSpent - get proofs that are NOT spendable', async () => { nock(mintUrl) .post('/v1/checkstate') - .reply(200, { states: [{ Y: "asd", state: "UNSPENT", witness: "witness-asd" }] }); + .reply(200, { states: [{ Y: 'asd', state: 'UNSPENT', witness: 'witness-asd' }] }); const wallet = new CashuWallet(mint); const result = await wallet.checkProofsSpent(proofs); From c69d358006685c21b065ee057d13d86c3d19d890 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 25 Mar 2024 10:36:32 +0900 Subject: [PATCH 075/175] get keys from mint if getKeys is called with different keyset id --- src/CashuWallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 2cbd22fe5..d5fd633d0 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -275,7 +275,7 @@ class CashuWallet { * Initialize the wallet with the mints public keys */ private async getKeys(keysetId?: string, unit?: string): Promise { - if (!this._keys) { + if (!this._keys || this._keys.id !== keysetId) { const allKeys = await this.mint.getKeys(keysetId); let keys; if (keysetId) { From 559365fc5ded4928374971e4ee7b980810da6f16 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 25 Mar 2024 11:08:20 +0900 Subject: [PATCH 076/175] update readme --- README.md | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c7a52e813..675f4661e 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Supported token formats: ## Usage -Go to the [docs](https://cashubtc.github.io/cashu-ts/docs) for detailed usage. +Go to the [docs](https://cashubtc.github.io/cashu-ts/docs) for detailed usage, or have a look at the [integration tests](./test/integration.test.ts) for examples on how to implement a wallet. ### Install @@ -52,26 +52,16 @@ Go to the [docs](https://cashubtc.github.io/cashu-ts/docs) for detailed usage. npm i @cashu/cashu-ts ``` -### Import +### Example ```typescript import { CashuMint, CashuWallet, getEncodedToken } from '@cashu/cashu-ts'; -const wallet = new CashuWallet(new CashuMint('{MINT_URL}')); +const mint = new CashuMint(mintUrl); +const wallet = new CashuWallet(mint); +const request = await wallet.getMintQuote(64); +const tokens = await wallet.mintTokens(64, request.quote); -const { pr, hash } = await wallet.requestMint(200); - -//pay this LN invoice -console.log({ pr }, { hash }); - -async function invoiceHasBeenPaid() { - const { proofs } = await wallet.requestTokens(200, hash); - //Encoded proofs can be spent at the mint - const encoded = getEncodedToken({ - token: [{ mint: '{MINT_URL}', proofs }] - }); - console.log(encoded); -} ``` ## Contribute From 499c449435419b3cf1ef84e75b6564ec7c34a0d0 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 25 Mar 2024 13:23:18 +0900 Subject: [PATCH 077/175] remove decode lnInvoice lib --- package-lock.json | 41 ----------------------------------------- package.json | 1 - src/index.ts | 10 +--------- src/utils.ts | 28 ---------------------------- test/utils.test.ts | 16 ---------------- test/wallet.test.ts | 3 +-- 6 files changed, 2 insertions(+), 97 deletions(-) diff --git a/package-lock.json b/package-lock.json index a57a47393..6d366d677 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.9.0", "license": "MIT", "dependencies": { - "@gandlaf21/bolt11-decode": "^3.0.6", "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", @@ -818,16 +817,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@gandlaf21/bolt11-decode": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@gandlaf21/bolt11-decode/-/bolt11-decode-3.0.6.tgz", - "integrity": "sha512-KUcAK2b9or8J47hzNTM2A+xdU0jCGIL4oC4TDyUlRYMfS5dBVOh4ywg9r3TZD8C/eVx7r14Hp4F79CSDjyCWTQ==", - "dependencies": { - "bech32": "^1.1.2", - "bn.js": "^4.11.8", - "buffer": "^6.0.3" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -2086,16 +2075,6 @@ } ] }, - "node_modules/bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7020,16 +6999,6 @@ "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true }, - "@gandlaf21/bolt11-decode": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@gandlaf21/bolt11-decode/-/bolt11-decode-3.0.6.tgz", - "integrity": "sha512-KUcAK2b9or8J47hzNTM2A+xdU0jCGIL4oC4TDyUlRYMfS5dBVOh4ywg9r3TZD8C/eVx7r14Hp4F79CSDjyCWTQ==", - "requires": { - "bech32": "^1.1.2", - "bn.js": "^4.11.8", - "buffer": "^6.0.3" - } - }, "@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -7979,16 +7948,6 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, - "bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/package.json b/package.json index f09c292bb..dcf6d73f1 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "typescript": "^5.0.4" }, "dependencies": { - "@gandlaf21/bolt11-decode": "^3.0.6", "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", diff --git a/src/index.ts b/src/index.ts index a321c0407..f1c49f0b0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,25 +2,17 @@ import { CashuMint } from './CashuMint.js'; import { CashuWallet } from './CashuWallet.js'; import { setGlobalRequestOptions } from './request.js'; import { generateNewMnemonic, deriveSeedFromMnemonic } from './secrets.js'; -import { getEncodedToken, getDecodedToken, deriveKeysetId, decodeInvoice } from './utils.js'; -import { decode } from '@gandlaf21/bolt11-decode'; +import { getEncodedToken, getDecodedToken, deriveKeysetId } from './utils.js'; export * from './model/types/index.js'; -/** - * @deprecated use decodeInvoice instead - */ -const getDecodedLnInvoice = decode; - export { CashuMint, CashuWallet, getDecodedToken, getEncodedToken, deriveKeysetId, - getDecodedLnInvoice, generateNewMnemonic, deriveSeedFromMnemonic, - decodeInvoice, setGlobalRequestOptions }; diff --git a/src/utils.ts b/src/utils.ts index d9d28b5fa..2cef21dcb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,6 @@ -import { decode } from '@gandlaf21/bolt11-decode'; import { encodeBase64ToJson, encodeJsonToBase64 } from './base64.js'; import { AmountPreference, - InvoiceData, Keys, Proof, Token, @@ -191,32 +189,6 @@ export function joinUrls(...parts: Array): string { return parts.map((part) => part.replace(/(^\/+|\/+$)/g, '')).join('/'); } -export function decodeInvoice(bolt11Invoice: string): InvoiceData { - const invoiceData: InvoiceData = {} as InvoiceData; - const decodeResult = decode(bolt11Invoice); - invoiceData.paymentRequest = decodeResult.paymentRequest; - for (let i = 0; i < decodeResult.sections.length; i++) { - const decodedSection = decodeResult.sections[i]; - if (decodedSection.name === 'amount') { - invoiceData.amountInSats = Number(decodedSection.value) / 1000; - invoiceData.amountInMSats = Number(decodedSection.value); - } - if (decodedSection.name === 'timestamp') { - invoiceData.timestamp = decodedSection.value; - } - if (decodedSection.name === 'description') { - invoiceData.memo = decodedSection.value; - } - if (decodedSection.name === 'expiry') { - invoiceData.expiry = decodedSection.value; - } - if (decodedSection.name === 'payment_hash') { - invoiceData.paymentHash = decodedSection.value.toString('hex'); - } - } - return invoiceData; -} - export { bigIntStringify, bytesToNumber, diff --git a/test/utils.test.ts b/test/utils.test.ts index ade88d3bf..ed7448b6e 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -300,22 +300,6 @@ describe('test cleanToken', () => { }); }); -describe('test decodeInvoice', () => { - test('decoding a lightning invoice', async () => { - const invoice = - 'lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs'; - const invoiceData = utils.decodeInvoice(invoice); - expect(invoiceData.timestamp).toStrictEqual(1651105770); - expect(invoiceData.amountInMSats).toStrictEqual(1500000); - expect(invoiceData.amountInSats).toStrictEqual(1500); - expect(invoiceData.paymentHash).toStrictEqual( - '90570c8d3688ad5012aa5ff982606971ae46b3f9df0a100cb15f05f61718f223' - ); - expect(invoiceData.expiry).toStrictEqual(600); - expect(invoiceData.memo).toStrictEqual('bolt11.org'); - }); -}); - describe('test keyset derivation', () => { test('derive', () => { const keys = PUBKEYS; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 0f80b8592..e68ac5be0 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -1,4 +1,3 @@ -import { decode } from '@gandlaf21/bolt11-decode'; import nock from 'nock'; import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; @@ -41,7 +40,7 @@ describe('test fees', () => { const wallet = new CashuWallet(mint); const fee = await wallet.getMeltQuote(invoice); - const amount = decode(invoice).sections[2].value / 1000; + const amount = 2000; expect(fee.fee_reserve + amount).toEqual(2020); }); From d11117dae13194324e4b86cf3a536ee4178395e7 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 25 Mar 2024 14:36:52 +0900 Subject: [PATCH 078/175] migration guide --- migration-1.0.0.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 migration-1.0.0.md diff --git a/migration-1.0.0.md b/migration-1.0.0.md new file mode 100644 index 000000000..e4a1ae4fa --- /dev/null +++ b/migration-1.0.0.md @@ -0,0 +1,93 @@ +# Version 1.0.0 Migration guide + +⚠️ Upgrading to version 1.0.0 will come with breaking changes! Please follow the migration guide for a smooth transition to the new version. + +## Context + +In Version 1.0.0 the api version of mints has been upgraded to `v1`. Please read the `v1` [v1 cleanup PR](https://github.com/cashubtc/nuts/pull/55) to understand more about how the API has changed. + +## Breaking changes + +### ⚠️ Important! When upgrading to this version, the `hash2curve` function and the `default secret format` have changed. This means deterministic secret derivation will produce NOT THE SAME SECRETS as before. When upgrading to this version, wallets that have been using deterministic secrets (seed phrase) must reset counters and then `self spend`/`refresh` all proofs, so that the backups continue working. + +--- + +### Decoding LN invoices + +**Removed LN invoice decode:** +Decoding LN invoices is no longer used inside the lib. + +**How to fix:** If you need to decode LN invoices, you can use +> npm i [@gandlaf21/bolt11-decode](https://www.npmjs.com/package/@gandlaf21/bolt11-decode) + +--- + +### `CashuWallet` interface changes + +**`requestMint(amount: number)` --> `getMintQuote(amount: number)`** +Now returns the following: + +```typescript +type MintQuoteResponse = { + request: string; + quote: string; +} +``` + +where `request` is the invoice to be paid, and `quote` is the identifier used to pass to `mintTokens()`. + +--- + +**`getMeltQuote(invoice: string)`** is now used to get fee estimation and conversion quotes instead of `getFee()` and returns: + +```typescript +type MeltQuoteResponse = { + quote: string; + amount: number; + fee_reserve: number; +} +``` +where `quote` is the identifier to pass to `meltTokens()` + +--- + +### Model changes + +**`MintKeys`--> `Keys`**: +`MintKeys` now include the `keys`, `id` and `unit` + +```typescript +type MintKeys = { + id: string; + unit: string; + keys: Keys; +}; + +type Keys = { [amount: number]: string }; + +``` +--- +**`MintKeyset`**: +Used to be a string array, but now contains the additional fields `active` and `unit` + +```typescript +type MintKeyset = { + id: string; + unit: string; + active: boolean; +}; +``` +--- +**`BlindedMessages`:** now include the field `id`, corresponding with the mints `keysetId` + +```typescript +type BlindedMessage { + amount: number; + B_: ProjPointType; + id: string; +} +``` +--- +### Pattern changes + +**removed `newKeys` from returns**: Functions no longer return `newKeys`. Wallets now specify the keyset they use in the BlindedMessage via the `id` field. \ No newline at end of file From 7e311ed924db0ef66481bffa2a634ce266ce00ef Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 25 Mar 2024 14:45:28 +0900 Subject: [PATCH 079/175] 1.0.0-rc.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d366d677..3348abb6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "0.9.0", + "version": "1.0.0-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "0.9.0", + "version": "1.0.0-rc.0", "license": "MIT", "dependencies": { "@noble/curves": "^1.3.0", diff --git a/package.json b/package.json index dcf6d73f1..d2ecb4de8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "0.9.0", + "version": "1.0.0-rc.0", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From 05175adc849eb92137261378b01788b0f22f3c6b Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 25 Mar 2024 14:46:34 +0900 Subject: [PATCH 080/175] 1.0.0-rc.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3348abb6d..975ed1c76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.0", + "version": "1.0.0-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.0", + "version": "1.0.0-rc.1", "license": "MIT", "dependencies": { "@noble/curves": "^1.3.0", diff --git a/package.json b/package.json index d2ecb4de8..5cab1bc7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.0", + "version": "1.0.0-rc.1", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From 1b9a10f991dc9da2cb04e053c685e174693883ea Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 27 Mar 2024 09:38:13 +0900 Subject: [PATCH 081/175] fix unused restore payload --- src/CashuMint.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 32bbe16b8..198aa1fe3 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -326,9 +326,6 @@ class CashuMint { customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; - //TODO remove after fix - //@ts-expect-error temp fix - restorePayload.quote = ''; const data = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/restore'), method: 'POST', From aa0323553ea66b95ff1c004eee1f2efbc54f5938 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 27 Mar 2024 09:40:49 +0900 Subject: [PATCH 082/175] update mint image in int test pipeline --- .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 cf17cd906..8dafd40be 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_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.1 poetry run mint + docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.2 poetry run mint - name: Check running containers run: docker ps From c8f711c1e638308dc504c639d8c3f3adde76ba74 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 27 Mar 2024 09:58:13 +0900 Subject: [PATCH 083/175] fix pay external invoice integration test --- test/integration.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index a24a51872..0c75747ba 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -105,8 +105,8 @@ describe('mint api', () => { const response = await wallet.payLnInvoice(externalInvoice, sendResponse.send); expect(response).toBeDefined(); - // expect that we have received the fee back, since it was internal - expect(response.change.reduce((a, b) => a + b.amount, 0)).toBe(fee); + // expect that we have not received the fee back, since it was external + expect(response.change.reduce((a, b) => a + b.amount, 0)).toBeLessThan(fee); // check states of spent and kept proofs after payment const sentProofsSpent = await wallet.checkProofsSpent(sendResponse.send); From 03464ec059e7fe0f9cd5ab5521d7a20558a80507 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 28 Mar 2024 08:25:50 +0900 Subject: [PATCH 084/175] remove duplicate test ci runs, remove deprecated node version run --- .github/workflows/node.js.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 6b14853a4..c47fd4ade 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: - node-version: [20.x, 16.x, 18.x] + node-version: [20.x, 18.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: From 3cb236d47694b881056a2fb1d9e7542e246b5b3b Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 28 Mar 2024 08:27:09 +0900 Subject: [PATCH 085/175] only run tests once --- .github/workflows/node.js.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index c47fd4ade..741a896b1 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -3,7 +3,7 @@ name: Node.js CI -on: [push, pull_request] +on: [push] jobs: tests: @@ -11,7 +11,7 @@ jobs: strategy: matrix: - node-version: [20.x, 18.x] + node-version: [20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: From b07e864e9bf3fd113f3adcc4f9c08f6f05d67714 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 28 Mar 2024 09:23:43 +0900 Subject: [PATCH 086/175] fix keyset derivation --- src/utils.ts | 21 +++++++++----- test/consts.ts | 68 ++-------------------------------------------- test/utils.test.ts | 2 +- 3 files changed, 18 insertions(+), 73 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 2cef21dcb..dd33d9751 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,7 +8,7 @@ import { TokenV2 } from './model/types/index.js'; import { TOKEN_PREFIX, TOKEN_VERSION } from './utils/Constants.js'; -import { bytesToHex } from '@noble/curves/abstract/utils'; +import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils'; import { sha256 } from '@noble/hashes/sha256'; function splitAmount(value: number, amountPreference?: Array): Array { @@ -128,12 +128,19 @@ function handleTokens(token: string): Token { export function deriveKeysetId(keys: Keys) { const pubkeysConcat = Object.entries(keys) .sort((a, b) => +a[0] - +b[0]) - .map(([, pubKey]) => pubKey) - .join(''); - const hash = sha256(new TextEncoder().encode(pubkeysConcat)); - const hashHex = bytesToHex(hash); - return '00' + hashHex.slice(0, 14); -} + .map(([, pubKey]) => hexToBytes(pubKey)).reduce((prev,curr)=>mergeUInt8Arrays(prev,curr),new Uint8Array()) + const hash = sha256(pubkeysConcat); + const hashHex = Buffer.from(hash).toString('hex').slice(0, 14) + return '00' + hashHex +} + +function mergeUInt8Arrays(a1: Uint8Array, a2: Uint8Array): Uint8Array { + // sum of individual array lengths + const mergedArray = new Uint8Array(a1.length + a2.length); + mergedArray.set(a1); + mergedArray.set(a2, a1.length); + return mergedArray; + } /** * merge proofs from same mint, diff --git a/test/consts.ts b/test/consts.ts index a4dc2e611..f79a0652a 100644 --- a/test/consts.ts +++ b/test/consts.ts @@ -1,66 +1,4 @@ export const PUBKEYS = { - '1': '0352d74be2366c43a3e2e88798b8e2d03c569ef09d22e02bb365f69a5210a1760b', - '2': '033b88748953acc5e6e3bd08c1c706eb7cb2ea91afd24a2a7f58ce1a5ea662f528', - '4': '0337290d84d9754e86b16e6908649548f54225038112d30d5dc5cdd03edca59b42', - '8': '030ffb1c6541e36c0ed7d64d2317f125a04126de38a9fddeabda860f9823a315b8', - '16': '035aaf89544bef31d420eba46b4f24bd1a26018883cbfafeec79ce7e61637bc593', - '32': '0219e43d08e6ce640c6cee70a5cb5946a967ec3b32207c8c7f465875356c0b3e7a', - '64': '039eca547db9d9709631038edbb287b06f7c591acb3a7a863cc027c5d0c5296524', - '128': '036d0d2fb209a5b43527a816ab7475151e2ec8b0b9535543c0aa2a9fab43c0e7c1', - '256': '0209d950bf0a545c7cd33cd3b76100ed008c1b62f49eb1627564aaedd1a05942c5', - '512': '02393187e1c1df39f08610d0af930d971c8d2474beb5a08e86a3de3359d3ab9320', - '1024': '02464f570d0413e918aebcb8d5734debd9368548bef4f36eb62637dd602e6253ea', - '2048': '02b3c7e26a17e20952e4ac8f58534217831fd9f4e3d3b1ee1c80908d4f1e1ee705', - '4096': '023f60d762c187210a9cd8de2dccd1d4e74594d4f489c194ea05c588cda978eacf', - '8192': '029df2cd59413e63b545be9c56f4b0dc4bbf663489bc3ab67eadde2772fbfd370c', - '16384': '034543746c6fce7ad3db341796356a71b4d3b7f6cb2908dea1f8c001b64761dd9b', - '32768': '035e642822cd59400058d402058c6c887f620ef7a9ba09ef815f26ba3f59118a8e', - '65536': '03d0fc713531149cf8b47b96ecec54f853e19ff47417d550cd7a1dbb654edff98a', - '131072': '023db71e398e641540202a2b429354f6d62cb25b0db2a8e0247ff8cdbb152b66c4', - '262144': '02f3edf578a999fd471d0651c95202efabbf9b8096d3fa81975e155979418197f3', - '524288': '03db113ce4b8c01a282ad6d89e594ca4e67cb0f2f9ceb218938aa9420f31085092', - '1048576': '02da6b3a16443b131a2fdc764106de83f50f6af5e0d648d49f808eb5913e841093', - '2097152': '02d6c7b6ba9c2d0ea5fa888de1616f2fc1a34c5e21df15bdf5a71c75aa510c07c9', - '4194304': '021d66e4ef4cbb0860483ac640723e231a55e63dad77bdc9ed3156dcada33f6cf3', - '8388608': '035064a5c497fdf8398bc3790304ac1efd86594f88ab65e04500168d5100291213', - '16777216': '03f4f7f2afe38bde4e59f50478473fbb0dfb6d869cf2e28400e91728301b774955', - '33554432': '02cb68adad06865f8a774ac73a83d20c6cf649b5ca766e41f6376bc391625d35f5', - '67108864': '028302c0995a8e1129b1a27152e3683112ee38902cbc6b04c5edbf804811f7898a', - '134217728': '02e67e852e6d0d9709a9e4ba164a812adef870f42d4bcc36d541a277da4acdfdb6', - '268435456': '0210276a3d3c6615bfa10f430b20fb51ff61591f7c4da6906f455b7f096673e689', - '536870912': '02abffa5e1f0036aabcca596d527720b62d4921a5df6ffccef52c801f8a4b1e0ab', - '1073741824': '03e07c92aa7ceab7e2c01adb7af77def101b7caa502795b9b8e30a8b53b39d7dad', - '2147483648': '0203786ad188ba2368fd4f44d24ec1cdc9736d757557946b9d4b2403ec7b116d29', - '4294967296': '02caa6c148adbbe3e6ae71d074770085c43624acd8107a40dc5e7929ef24b5bfaa', - '8589934592': '02a3879c0a6e4297654c109096559326b80ff70432fedcaa3673a2ae8742e4cb33', - '17179869184': '0212fd5125cb773ab2b8dbb304c1e5b9359fc75f0889e3073d5a411886f54f6d38', - '34359738368': '03797b244dee73204127223ca4d341a92e3a87d3f17e1c80fedcc5a336ff616a1e', - '68719476736': '03086982375f4097b2f4d00e1924b89d696352b874fec8bc76504347f51328671d', - '137438953472': '02b61f17e50dc9d0887d7b6953f97a520ea0796870e0bba51e05b7e2f495d9b806', - '274877906944': '02290f140f84512e0ba941e6f958833390d4495f161544b8bcf3241a0ead89de18', - '549755813888': '0310ee60086163731d7839a7bf2062e655ab2563871488429c8a67433f2373876d', - '1099511627776': '0212b293c4e0f9c2f1dac473077f2daa280cd6600ac3fb8789c1bccddc272054a1', - '2199023255552': '03e590752f1379fd09986b07ef4a14dfadf8bec5aacf4ec83f56990f2a07ec6656', - '4398046511104': '02ec77ebaa46fdaf0eca996865dcb8121f71c2a4d35e70135933f21c7f632e2f38', - '8796093022208': '038c5f43fa83c4dd84cadd22ec12df56de435eff713629d0706eae6f42741ce00e', - '17592186044416': '036ba077c8350e7dcfcb972a4087252e59cf1e74ea8cffbe0405143037f295f58e', - '35184372088832': '0279771f0d2971c35e93d0b22b3b42ce6b859141f1f72b215b51c7feff96e87cef', - '70368744177664': '03864a3ae9e3e3817c14d0635d2cd88b1130c3440e2473815c02121d26140b5b57', - '140737488355328': '029e7296b502ed49b32c0981790a159bbfb0c9a827fb1178295067810cf6e31921', - '281474976710656': '030024a03f98bd27743ccecb7d6e3204646415bed7cb7c1098aaebdeabb72d63b4', - '562949953421312': '0384fb48aa0b4e4a68f645c963ffaaf3de4a5561edeb486cba74daf1b93a02a04a', - '1125899906842624': '02ef5cf515cb8b7459b6a0d6f631013fbef0885718dc30f272397beb3643f5bfa3', - '2251799813685248': '02f944f0fef655d8c89f042460e15fd3c6d452a1e8cbac503b826893477656f9b2', - '4503599627370496': '0378b14720f73c27ecfe9051f07ae09051d69e077ac8849a66bd77a7965f2cc760', - '9007199254740992': '02a0c981d62cf945144724de11c9ca595aebbda8b6afb39a9faa7f4abcec3e6da8', - '18014398509481984': '0225ce53547e4f322a80fab97e5028ab78282b6f63f9528ca83108b47d8007cc72', - '36028797018963968': '0315a18f669aee0c983ca7fcd23d929b71cf6b6983d760a96e950d17a061d43a06', - '72057594037927936': '03d1877405fb8ba7f7767fd646d305afd566088e5742d47f45b7c38753ce4d1167', - '144115188075855872': '029bbf9e5c76d11096a2f5bd176917e21ca941e294081866895183a131ba4358e6', - '288230376151711744': '023cf13b9c1a5a242dbb15c63d5e397bc595a672f90e2046b59e036f786713d796', - '576460752303423488': '03a34759634b1615d33ce7da47ef18df738559f9b47d96cbda897810de86aa0a9c', - '1152921504606846976': '03d1d210b1d41cf860876260ad4761fc5daedfefcf1874d2a87386cfccfe689d65', - '2305843009213693952': '03d42abc9c05b8ae2ac926de932030e7aa6ec845eacd5ddcab589df5c779ffb3e4', - '4611686018427387904': '022e2acccc241b9936cedcd6447308c344ee4903798507bbb604ef81468fabf277', - '9223372036854775808': '027fd1b5d36ddf18de228b5f3bdf61bff8d94b5a2f7ddc44b117372d7436b178e3' -}; + "1": "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104", "2": "03b0f36d6d47ce14df8a7be9137712c42bcdd960b19dd02f1d4a9703b1f31d7513", "4": "0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09", "8": "0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d", "16": "0217ff15bc0e183ae1745911bd84c761945a446fa27284eeb4a18220abc3a39eae", "32": "03c65d28721a9fe81e91e596fe1ab667e5ca0f8a9872d5376e34fd4f29501910a9", "64": "02cedd62d7518736ff0c172fdfc3079845affc13eed9427cb81988e6f184ca5530", "128": "03c03e0922d1efec034c06074553f87453322cb36a6d789e61cfd8786961d7b5bc", "256": "0296d2894191b73993621848544053d47df0b43e7a34b8bfd12f2deb5258cebe57", "512": "03405f14f5c507eedba8658ab6b79179a31722c4226680ba03c6508fef2d0cad46", "1024": "031c6cc53a5a19fdf6bcd311871209a1645afb9ff52ae00128e46d75073b42a376", "2048": "03a5e61e7aa6b0d6c314d577ec44d58a5f7e4149f762b05cdba519949b957adae6", "4096": "027e0d8bab3525843d3b7d6193ae88d211d505e097bf8810b1fa6a205f912efccf", "8192": "022c0f3a5274acc129ad567f64542e01e538818689bdc21f4bbaf88be729295771", "16384": "03ef1e94c645e56fa09f9cc9bc07469077c209552a0ce4dbdb4bf44981a7b9fa85", "32768": "0208b013c5a52987dd0323aef4cf0d48c941a4a9d2d76bb974cc59e4b41d26d47b", "65536": "02f881a2534ca06d0da485b4be5050883022235ea89e6ea81f43115ed6cdefb190", "131072": "03154dab4b35831536dd85d9bdf892c9839bf6958617619b31f4ca5748bf0dbf2a", "262144": "02edaaee84d297212ad0819ec923c40e958c208bd96d2abf76e4004455cfd4e365", "524288": "030a08fc4c9f4ca0125c73b62d036845e43f7bcffe38cb5f09cfe35a8e59e135e6", "1048576": "030288d91b21db418027f2902b39fd1712c5afd22b9a734f6021dc6829363dc7e9", "2097152": "0231a3cd012a916db6ee899dfcc0f31da6dacc4c3497b74dd937e0b85a72081913", "4194304": "03467db9a45f138bcb7a762d64cd6029cbdf4ed740b1851831ce380efe3bef913c", "8388608": "0234104b610749c0276105c84da30c2ff82d831768a7edc2c4131b02ebc3eb3ae2", "16777216": "02599c06e1f52f16eb7593886c56a43bc53a5451a728cdf246c188720d1d138173", "33554432": "035d7ecd096a6a703e052de84133fac77c1c33c8b37e72ccc3d8a9d770a163da5d", "67108864": "028c06896499c7b16719ae249f7e826cdabfb77cbc9ccc80c668830b1071f9f5f6", "134217728": "02c8ac5f30cf34e011bac12414d6fce384f5cf2cbd0aa6b21340f68c119c835241", "268435456": "0258094c7f26006eaaf00fc1fcb9950e591c578d2900108fa46e6b4961285fe914", "536870912": "03be658104ed6f3c3109e4ca33ce5e3d63e6f537a70d47084e83097f0906e50b32", "1073741824": "03de09651ec6ca86e505c8e800fdd3fcea5c80246702189f2457678273e8cea462", "2147483648": "020f2ed905d971c57893c5ca8af869de33987f11e14d8eedc475f33cc9b6387b9f", "4294967296": "03d9b0de71d3618d746c5366839da7a2f84b6c75d59849f54a19b076eb1b9c54ef", "8589934592": "036097d00a195fdd2899d1687c414b39f21bd7c112bddb9239c4f0d9af4ff27ab7", "17179869184": "03d8ffa01c9e4e4802b0899a2694612dafd6390b24f19a8bd19fc2abae5679f9cf", "34359738368": "031b8a21a0acd754137b2c6d780693b1ef4528da02a8a29d4bae04c20df69084f5", "68719476736": "026c81e4498ec4693f4030c40bb8d48db38a436d6a3aa0ea1263f92f3a40f9239a", "137438953472": "03a95f4b5736d6fba5678ae67716f874be26ae2c10044d84204f6e51e41aef97dd", "274877906944": "025792fc31361ca5202a1d1313512eee9ce83c86356fa5f7b9642d5416c53f2571", "549755813888": "03555f71d7e951e6b9c3113afe1f228855331db24b9805fc274b1b5e5d9e8d0f7c", "1099511627776": "029b1b433c98ed693d07f53157093386c7a63d54d752a4da00fc252b84afdb89e7", "2199023255552": "03b03bd1f346ebc1c84b315f0cd7c9ae7bb577617a447b085d5f896419f6214651", "4398046511104": "0248a3f5162755d65a79b2fa35e51aa50d51b00040c127b12badaf97d3de12ef04", "8796093022208": "02bf233b579a6b402886b706ff2fef5deed2bf53b3bd370844091f4b1cdaf688dc", "17592186044416": "03c1eb5422c58b8e3522355c3cc470ccb6b0add8e4b0d5aadd0412dadbaddb0096", "35184372088832": "026c9450a3afae0103766d7767193ac5a49669cbc85bdbefab04b9fa182e42d911", "70368744177664": "034688f63ed273e524f08a155ac00c54c5f85d941e45394695e7c345cc00bcfaa4", "140737488355328": "0331df7f9c1876cf57db9f0a29b5c50c6c2cff20fef5d8b4c17158332bb10e72fd", "281474976710656": "0230dd1dba84778a2302b0c50db29e9c4846bbad96a7c847d92ce084bffb45305e", "562949953421312": "02fd0386d1998cd76e45fc17d07ffe82015ad918f5d8e516406956e78fe38e3511", "1125899906842624": "03d3fbf9c2dc0ca0fa5bfc5ccad526a1305331b4a14637cc2ebe64d324a3fedc9e", "2251799813685248": "03d2e21a411422f04acf9fd7ce307f410d6642714232103f52e61edef99172188f", "4503599627370496": "022163656cb4a2369d3a7b1fab1599fef924150c3030ffed3fbc40fec6f984613a", "9007199254740992": "03d25547594eeaa261ed48ebd6794389706c6c47cccd167c44a322721f0d07b509", "18014398509481984": "027ed195fe4f4565d4cbcc8755de0aa6ccd5878efa13da9607191ac854c806390f", "36028797018963968": "0231d6bfb9f2414c5992e96615904beaa5af844d2214e9e14d24202059639ee0df", "72057594037927936": "03860470440f4f5a2488ee64bacd4e4a9eb0d50d2a1d6215167acb247025b566bb", "144115188075855872": "031055a11ecc825257788f065c247da1a84493c1438aa95c6f2a1dffc886eccd1a", "288230376151711744": "03bb3f9f774a1f2651a2cbe9f21954c637aec565807a61f5551d9109a7d1d17b6f", "576460752303423488": "032701bb051cfa46676da504c6967b5b7d3a70eb6499a9feaa71ffe629b0edac9a", "1152921504606846976": "0342f67cf3e82cde49ced155b7117f8d583ae8766bce4fd7aa170179ee43a0608e", "2305843009213693952": "0366c51883c60f37f3a74d05e725791efd98773eb143a98aa8808915b0ffe23c6d", "4611686018427387904": "020d25d8c67e59395983569cf5a7b86f13cccf95a7b4d374bc40ffac04c6437b86", "9223372036854775808": "023c84c0895cc0e827b348ea0a62951ca489a5e436f3ea7545f3c1d5f1bea1c866" +} + diff --git a/test/utils.test.ts b/test/utils.test.ts index ed7448b6e..ccc341607 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -305,6 +305,6 @@ describe('test keyset derivation', () => { const keys = PUBKEYS; const keysetId = utils.deriveKeysetId(keys); console.log(keysetId); - expect(keysetId).toBe('00a627821fbe96e4'); + expect(keysetId).toBe('009a1f293253e41e'); }); }); From c36dd0189ee51fa1f176f8147f764b7e99525ff9 Mon Sep 17 00:00:00 2001 From: starbackr-dev Date: Thu, 28 Mar 2024 10:54:27 -0400 Subject: [PATCH 087/175] fixed issue with merge --- src/CashuWallet.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index b73432da9..15e23c6ab 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -40,7 +40,7 @@ import { wordlist } from '@scure/bip39/wordlists/english'; class CashuWallet { private _keys: MintKeys | undefined; private _seed: Uint8Array | undefined; - private _unit = 'sat'; + private _unit = 'sat'; mint: CashuMint; @@ -52,6 +52,7 @@ class CashuWallet { */ constructor(mint: CashuMint, unit?:string, keys?: MintKeys, mnemonicOrSeed?: string | Uint8Array) { this.mint = mint; + if (unit) this._unit = unit; if (keys) { this._keys = keys; } @@ -151,7 +152,7 @@ class CashuWallet { preference, counter ); - const { signatures, error } = await CashuMint.split(tokenEntry.mint, payload); + const { signatures } = await CashuMint.split(tokenEntry.mint, payload); const newProofs = dhke.constructProofs( signatures, blindedMessages.rs, From 45318302b95366c06a5b124c12c2c93e7fee4855 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 30 Mar 2024 09:23:41 +0900 Subject: [PATCH 088/175] format --- README.md | 1 - migration-1.0.0.md | 31 +++++++++++++-------- src/utils.ts | 18 ++++-------- test/consts.ts | 68 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 90 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 675f4661e..62ad97404 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,6 @@ const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); const request = await wallet.getMintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); - ``` ## Contribute diff --git a/migration-1.0.0.md b/migration-1.0.0.md index e4a1ae4fa..af164f606 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -14,10 +14,11 @@ In Version 1.0.0 the api version of mints has been upgraded to `v1`. Please read ### Decoding LN invoices -**Removed LN invoice decode:** -Decoding LN invoices is no longer used inside the lib. +**Removed LN invoice decode:** +Decoding LN invoices is no longer used inside the lib. + +**How to fix:** If you need to decode LN invoices, you can use -**How to fix:** If you need to decode LN invoices, you can use > npm i [@gandlaf21/bolt11-decode](https://www.npmjs.com/package/@gandlaf21/bolt11-decode) --- @@ -25,13 +26,13 @@ Decoding LN invoices is no longer used inside the lib. ### `CashuWallet` interface changes **`requestMint(amount: number)` --> `getMintQuote(amount: number)`** -Now returns the following: +Now returns the following: ```typescript type MintQuoteResponse = { - request: string; - quote: string; -} + request: string; + quote: string; +}; ``` where `request` is the invoice to be paid, and `quote` is the identifier used to pass to `mintTokens()`. @@ -45,8 +46,9 @@ type MeltQuoteResponse = { quote: string; amount: number; fee_reserve: number; -} +}; ``` + where `quote` is the identifier to pass to `meltTokens()` --- @@ -64,11 +66,12 @@ type MintKeys = { }; type Keys = { [amount: number]: string }; - ``` + --- + **`MintKeyset`**: -Used to be a string array, but now contains the additional fields `active` and `unit` +Used to be a string array, but now contains the additional fields `active` and `unit` ```typescript type MintKeyset = { @@ -77,17 +80,21 @@ type MintKeyset = { active: boolean; }; ``` + --- + **`BlindedMessages`:** now include the field `id`, corresponding with the mints `keysetId` -```typescript +```typescript type BlindedMessage { amount: number; B_: ProjPointType; id: string; } ``` + --- + ### Pattern changes -**removed `newKeys` from returns**: Functions no longer return `newKeys`. Wallets now specify the keyset they use in the BlindedMessage via the `id` field. \ No newline at end of file +**removed `newKeys` from returns**: Functions no longer return `newKeys`. Wallets now specify the keyset they use in the BlindedMessage via the `id` field. diff --git a/src/utils.ts b/src/utils.ts index dd33d9751..1729b995f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,12 +1,5 @@ import { encodeBase64ToJson, encodeJsonToBase64 } from './base64.js'; -import { - AmountPreference, - Keys, - Proof, - Token, - TokenEntry, - TokenV2 -} from './model/types/index.js'; +import { AmountPreference, Keys, Proof, Token, TokenEntry, TokenV2 } from './model/types/index.js'; import { TOKEN_PREFIX, TOKEN_VERSION } from './utils/Constants.js'; import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils'; import { sha256 } from '@noble/hashes/sha256'; @@ -128,10 +121,11 @@ function handleTokens(token: string): Token { export function deriveKeysetId(keys: Keys) { const pubkeysConcat = Object.entries(keys) .sort((a, b) => +a[0] - +b[0]) - .map(([, pubKey]) => hexToBytes(pubKey)).reduce((prev,curr)=>mergeUInt8Arrays(prev,curr),new Uint8Array()) + .map(([, pubKey]) => hexToBytes(pubKey)) + .reduce((prev, curr) => mergeUInt8Arrays(prev, curr), new Uint8Array()); const hash = sha256(pubkeysConcat); - const hashHex = Buffer.from(hash).toString('hex').slice(0, 14) - return '00' + hashHex + const hashHex = Buffer.from(hash).toString('hex').slice(0, 14); + return '00' + hashHex; } function mergeUInt8Arrays(a1: Uint8Array, a2: Uint8Array): Uint8Array { @@ -140,7 +134,7 @@ function mergeUInt8Arrays(a1: Uint8Array, a2: Uint8Array): Uint8Array { mergedArray.set(a1); mergedArray.set(a2, a1.length); return mergedArray; - } +} /** * merge proofs from same mint, diff --git a/test/consts.ts b/test/consts.ts index f79a0652a..5d840a8ce 100644 --- a/test/consts.ts +++ b/test/consts.ts @@ -1,4 +1,66 @@ export const PUBKEYS = { - "1": "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104", "2": "03b0f36d6d47ce14df8a7be9137712c42bcdd960b19dd02f1d4a9703b1f31d7513", "4": "0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09", "8": "0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d", "16": "0217ff15bc0e183ae1745911bd84c761945a446fa27284eeb4a18220abc3a39eae", "32": "03c65d28721a9fe81e91e596fe1ab667e5ca0f8a9872d5376e34fd4f29501910a9", "64": "02cedd62d7518736ff0c172fdfc3079845affc13eed9427cb81988e6f184ca5530", "128": "03c03e0922d1efec034c06074553f87453322cb36a6d789e61cfd8786961d7b5bc", "256": "0296d2894191b73993621848544053d47df0b43e7a34b8bfd12f2deb5258cebe57", "512": "03405f14f5c507eedba8658ab6b79179a31722c4226680ba03c6508fef2d0cad46", "1024": "031c6cc53a5a19fdf6bcd311871209a1645afb9ff52ae00128e46d75073b42a376", "2048": "03a5e61e7aa6b0d6c314d577ec44d58a5f7e4149f762b05cdba519949b957adae6", "4096": "027e0d8bab3525843d3b7d6193ae88d211d505e097bf8810b1fa6a205f912efccf", "8192": "022c0f3a5274acc129ad567f64542e01e538818689bdc21f4bbaf88be729295771", "16384": "03ef1e94c645e56fa09f9cc9bc07469077c209552a0ce4dbdb4bf44981a7b9fa85", "32768": "0208b013c5a52987dd0323aef4cf0d48c941a4a9d2d76bb974cc59e4b41d26d47b", "65536": "02f881a2534ca06d0da485b4be5050883022235ea89e6ea81f43115ed6cdefb190", "131072": "03154dab4b35831536dd85d9bdf892c9839bf6958617619b31f4ca5748bf0dbf2a", "262144": "02edaaee84d297212ad0819ec923c40e958c208bd96d2abf76e4004455cfd4e365", "524288": "030a08fc4c9f4ca0125c73b62d036845e43f7bcffe38cb5f09cfe35a8e59e135e6", "1048576": "030288d91b21db418027f2902b39fd1712c5afd22b9a734f6021dc6829363dc7e9", "2097152": "0231a3cd012a916db6ee899dfcc0f31da6dacc4c3497b74dd937e0b85a72081913", "4194304": "03467db9a45f138bcb7a762d64cd6029cbdf4ed740b1851831ce380efe3bef913c", "8388608": "0234104b610749c0276105c84da30c2ff82d831768a7edc2c4131b02ebc3eb3ae2", "16777216": "02599c06e1f52f16eb7593886c56a43bc53a5451a728cdf246c188720d1d138173", "33554432": "035d7ecd096a6a703e052de84133fac77c1c33c8b37e72ccc3d8a9d770a163da5d", "67108864": "028c06896499c7b16719ae249f7e826cdabfb77cbc9ccc80c668830b1071f9f5f6", "134217728": "02c8ac5f30cf34e011bac12414d6fce384f5cf2cbd0aa6b21340f68c119c835241", "268435456": "0258094c7f26006eaaf00fc1fcb9950e591c578d2900108fa46e6b4961285fe914", "536870912": "03be658104ed6f3c3109e4ca33ce5e3d63e6f537a70d47084e83097f0906e50b32", "1073741824": "03de09651ec6ca86e505c8e800fdd3fcea5c80246702189f2457678273e8cea462", "2147483648": "020f2ed905d971c57893c5ca8af869de33987f11e14d8eedc475f33cc9b6387b9f", "4294967296": "03d9b0de71d3618d746c5366839da7a2f84b6c75d59849f54a19b076eb1b9c54ef", "8589934592": "036097d00a195fdd2899d1687c414b39f21bd7c112bddb9239c4f0d9af4ff27ab7", "17179869184": "03d8ffa01c9e4e4802b0899a2694612dafd6390b24f19a8bd19fc2abae5679f9cf", "34359738368": "031b8a21a0acd754137b2c6d780693b1ef4528da02a8a29d4bae04c20df69084f5", "68719476736": "026c81e4498ec4693f4030c40bb8d48db38a436d6a3aa0ea1263f92f3a40f9239a", "137438953472": "03a95f4b5736d6fba5678ae67716f874be26ae2c10044d84204f6e51e41aef97dd", "274877906944": "025792fc31361ca5202a1d1313512eee9ce83c86356fa5f7b9642d5416c53f2571", "549755813888": "03555f71d7e951e6b9c3113afe1f228855331db24b9805fc274b1b5e5d9e8d0f7c", "1099511627776": "029b1b433c98ed693d07f53157093386c7a63d54d752a4da00fc252b84afdb89e7", "2199023255552": "03b03bd1f346ebc1c84b315f0cd7c9ae7bb577617a447b085d5f896419f6214651", "4398046511104": "0248a3f5162755d65a79b2fa35e51aa50d51b00040c127b12badaf97d3de12ef04", "8796093022208": "02bf233b579a6b402886b706ff2fef5deed2bf53b3bd370844091f4b1cdaf688dc", "17592186044416": "03c1eb5422c58b8e3522355c3cc470ccb6b0add8e4b0d5aadd0412dadbaddb0096", "35184372088832": "026c9450a3afae0103766d7767193ac5a49669cbc85bdbefab04b9fa182e42d911", "70368744177664": "034688f63ed273e524f08a155ac00c54c5f85d941e45394695e7c345cc00bcfaa4", "140737488355328": "0331df7f9c1876cf57db9f0a29b5c50c6c2cff20fef5d8b4c17158332bb10e72fd", "281474976710656": "0230dd1dba84778a2302b0c50db29e9c4846bbad96a7c847d92ce084bffb45305e", "562949953421312": "02fd0386d1998cd76e45fc17d07ffe82015ad918f5d8e516406956e78fe38e3511", "1125899906842624": "03d3fbf9c2dc0ca0fa5bfc5ccad526a1305331b4a14637cc2ebe64d324a3fedc9e", "2251799813685248": "03d2e21a411422f04acf9fd7ce307f410d6642714232103f52e61edef99172188f", "4503599627370496": "022163656cb4a2369d3a7b1fab1599fef924150c3030ffed3fbc40fec6f984613a", "9007199254740992": "03d25547594eeaa261ed48ebd6794389706c6c47cccd167c44a322721f0d07b509", "18014398509481984": "027ed195fe4f4565d4cbcc8755de0aa6ccd5878efa13da9607191ac854c806390f", "36028797018963968": "0231d6bfb9f2414c5992e96615904beaa5af844d2214e9e14d24202059639ee0df", "72057594037927936": "03860470440f4f5a2488ee64bacd4e4a9eb0d50d2a1d6215167acb247025b566bb", "144115188075855872": "031055a11ecc825257788f065c247da1a84493c1438aa95c6f2a1dffc886eccd1a", "288230376151711744": "03bb3f9f774a1f2651a2cbe9f21954c637aec565807a61f5551d9109a7d1d17b6f", "576460752303423488": "032701bb051cfa46676da504c6967b5b7d3a70eb6499a9feaa71ffe629b0edac9a", "1152921504606846976": "0342f67cf3e82cde49ced155b7117f8d583ae8766bce4fd7aa170179ee43a0608e", "2305843009213693952": "0366c51883c60f37f3a74d05e725791efd98773eb143a98aa8808915b0ffe23c6d", "4611686018427387904": "020d25d8c67e59395983569cf5a7b86f13cccf95a7b4d374bc40ffac04c6437b86", "9223372036854775808": "023c84c0895cc0e827b348ea0a62951ca489a5e436f3ea7545f3c1d5f1bea1c866" -} - + '1': '02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104', + '2': '03b0f36d6d47ce14df8a7be9137712c42bcdd960b19dd02f1d4a9703b1f31d7513', + '4': '0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09', + '8': '0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d', + '16': '0217ff15bc0e183ae1745911bd84c761945a446fa27284eeb4a18220abc3a39eae', + '32': '03c65d28721a9fe81e91e596fe1ab667e5ca0f8a9872d5376e34fd4f29501910a9', + '64': '02cedd62d7518736ff0c172fdfc3079845affc13eed9427cb81988e6f184ca5530', + '128': '03c03e0922d1efec034c06074553f87453322cb36a6d789e61cfd8786961d7b5bc', + '256': '0296d2894191b73993621848544053d47df0b43e7a34b8bfd12f2deb5258cebe57', + '512': '03405f14f5c507eedba8658ab6b79179a31722c4226680ba03c6508fef2d0cad46', + '1024': '031c6cc53a5a19fdf6bcd311871209a1645afb9ff52ae00128e46d75073b42a376', + '2048': '03a5e61e7aa6b0d6c314d577ec44d58a5f7e4149f762b05cdba519949b957adae6', + '4096': '027e0d8bab3525843d3b7d6193ae88d211d505e097bf8810b1fa6a205f912efccf', + '8192': '022c0f3a5274acc129ad567f64542e01e538818689bdc21f4bbaf88be729295771', + '16384': '03ef1e94c645e56fa09f9cc9bc07469077c209552a0ce4dbdb4bf44981a7b9fa85', + '32768': '0208b013c5a52987dd0323aef4cf0d48c941a4a9d2d76bb974cc59e4b41d26d47b', + '65536': '02f881a2534ca06d0da485b4be5050883022235ea89e6ea81f43115ed6cdefb190', + '131072': '03154dab4b35831536dd85d9bdf892c9839bf6958617619b31f4ca5748bf0dbf2a', + '262144': '02edaaee84d297212ad0819ec923c40e958c208bd96d2abf76e4004455cfd4e365', + '524288': '030a08fc4c9f4ca0125c73b62d036845e43f7bcffe38cb5f09cfe35a8e59e135e6', + '1048576': '030288d91b21db418027f2902b39fd1712c5afd22b9a734f6021dc6829363dc7e9', + '2097152': '0231a3cd012a916db6ee899dfcc0f31da6dacc4c3497b74dd937e0b85a72081913', + '4194304': '03467db9a45f138bcb7a762d64cd6029cbdf4ed740b1851831ce380efe3bef913c', + '8388608': '0234104b610749c0276105c84da30c2ff82d831768a7edc2c4131b02ebc3eb3ae2', + '16777216': '02599c06e1f52f16eb7593886c56a43bc53a5451a728cdf246c188720d1d138173', + '33554432': '035d7ecd096a6a703e052de84133fac77c1c33c8b37e72ccc3d8a9d770a163da5d', + '67108864': '028c06896499c7b16719ae249f7e826cdabfb77cbc9ccc80c668830b1071f9f5f6', + '134217728': '02c8ac5f30cf34e011bac12414d6fce384f5cf2cbd0aa6b21340f68c119c835241', + '268435456': '0258094c7f26006eaaf00fc1fcb9950e591c578d2900108fa46e6b4961285fe914', + '536870912': '03be658104ed6f3c3109e4ca33ce5e3d63e6f537a70d47084e83097f0906e50b32', + '1073741824': '03de09651ec6ca86e505c8e800fdd3fcea5c80246702189f2457678273e8cea462', + '2147483648': '020f2ed905d971c57893c5ca8af869de33987f11e14d8eedc475f33cc9b6387b9f', + '4294967296': '03d9b0de71d3618d746c5366839da7a2f84b6c75d59849f54a19b076eb1b9c54ef', + '8589934592': '036097d00a195fdd2899d1687c414b39f21bd7c112bddb9239c4f0d9af4ff27ab7', + '17179869184': '03d8ffa01c9e4e4802b0899a2694612dafd6390b24f19a8bd19fc2abae5679f9cf', + '34359738368': '031b8a21a0acd754137b2c6d780693b1ef4528da02a8a29d4bae04c20df69084f5', + '68719476736': '026c81e4498ec4693f4030c40bb8d48db38a436d6a3aa0ea1263f92f3a40f9239a', + '137438953472': '03a95f4b5736d6fba5678ae67716f874be26ae2c10044d84204f6e51e41aef97dd', + '274877906944': '025792fc31361ca5202a1d1313512eee9ce83c86356fa5f7b9642d5416c53f2571', + '549755813888': '03555f71d7e951e6b9c3113afe1f228855331db24b9805fc274b1b5e5d9e8d0f7c', + '1099511627776': '029b1b433c98ed693d07f53157093386c7a63d54d752a4da00fc252b84afdb89e7', + '2199023255552': '03b03bd1f346ebc1c84b315f0cd7c9ae7bb577617a447b085d5f896419f6214651', + '4398046511104': '0248a3f5162755d65a79b2fa35e51aa50d51b00040c127b12badaf97d3de12ef04', + '8796093022208': '02bf233b579a6b402886b706ff2fef5deed2bf53b3bd370844091f4b1cdaf688dc', + '17592186044416': '03c1eb5422c58b8e3522355c3cc470ccb6b0add8e4b0d5aadd0412dadbaddb0096', + '35184372088832': '026c9450a3afae0103766d7767193ac5a49669cbc85bdbefab04b9fa182e42d911', + '70368744177664': '034688f63ed273e524f08a155ac00c54c5f85d941e45394695e7c345cc00bcfaa4', + '140737488355328': '0331df7f9c1876cf57db9f0a29b5c50c6c2cff20fef5d8b4c17158332bb10e72fd', + '281474976710656': '0230dd1dba84778a2302b0c50db29e9c4846bbad96a7c847d92ce084bffb45305e', + '562949953421312': '02fd0386d1998cd76e45fc17d07ffe82015ad918f5d8e516406956e78fe38e3511', + '1125899906842624': '03d3fbf9c2dc0ca0fa5bfc5ccad526a1305331b4a14637cc2ebe64d324a3fedc9e', + '2251799813685248': '03d2e21a411422f04acf9fd7ce307f410d6642714232103f52e61edef99172188f', + '4503599627370496': '022163656cb4a2369d3a7b1fab1599fef924150c3030ffed3fbc40fec6f984613a', + '9007199254740992': '03d25547594eeaa261ed48ebd6794389706c6c47cccd167c44a322721f0d07b509', + '18014398509481984': '027ed195fe4f4565d4cbcc8755de0aa6ccd5878efa13da9607191ac854c806390f', + '36028797018963968': '0231d6bfb9f2414c5992e96615904beaa5af844d2214e9e14d24202059639ee0df', + '72057594037927936': '03860470440f4f5a2488ee64bacd4e4a9eb0d50d2a1d6215167acb247025b566bb', + '144115188075855872': '031055a11ecc825257788f065c247da1a84493c1438aa95c6f2a1dffc886eccd1a', + '288230376151711744': '03bb3f9f774a1f2651a2cbe9f21954c637aec565807a61f5551d9109a7d1d17b6f', + '576460752303423488': '032701bb051cfa46676da504c6967b5b7d3a70eb6499a9feaa71ffe629b0edac9a', + '1152921504606846976': '0342f67cf3e82cde49ced155b7117f8d583ae8766bce4fd7aa170179ee43a0608e', + '2305843009213693952': '0366c51883c60f37f3a74d05e725791efd98773eb143a98aa8808915b0ffe23c6d', + '4611686018427387904': '020d25d8c67e59395983569cf5a7b86f13cccf95a7b4d374bc40ffac04c6437b86', + '9223372036854775808': '023c84c0895cc0e827b348ea0a62951ca489a5e436f3ea7545f3c1d5f1bea1c866' +}; From e9a679e2cd38915661e54ee4b3f16266cbbb25ca Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Fri, 29 Mar 2024 15:58:23 +0900 Subject: [PATCH 089/175] pay to pubkey --- package-lock.json | 38 ++++++++- package.json | 4 +- src/CashuWallet.ts | 171 +++++++++++++++++++++++++++------------ test/base64.test.ts | 23 ++++++ test/integration.test.ts | 5 +- test/wallet.test.ts | 24 +++--- 6 files changed, 196 insertions(+), 69 deletions(-) diff --git a/package-lock.json b/package-lock.json index 975ed1c76..5a0252a18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,13 @@ "version": "1.0.0-rc.1", "license": "MIT", "dependencies": { + "@gandlaf21/cashu-crypto": "^0.2.5", "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", "@scure/bip39": "^1.2.2", - "buffer": "^6.0.3" + "buffer": "^6.0.3", + "js-base64": "^3.7.7" }, "devDependencies": { "@types/jest": "^29.5.1", @@ -817,6 +819,18 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@gandlaf21/cashu-crypto": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@gandlaf21/cashu-crypto/-/cashu-crypto-0.2.5.tgz", + "integrity": "sha512-MvXHAKvNDREzTsHzK4M9LOsK3S/BwAtbyZ+myFkNFS68z9IhQVfUKP+lQgLXdiQdf5i64LQRkzLV+OpOXg6dHg==", + "dependencies": { + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@scure/bip32": "^1.3.3", + "@scure/bip39": "^1.2.2", + "buffer": "^6.0.3" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -4692,6 +4706,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + }, "node_modules/js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -6999,6 +7018,18 @@ "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true }, + "@gandlaf21/cashu-crypto": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@gandlaf21/cashu-crypto/-/cashu-crypto-0.2.5.tgz", + "integrity": "sha512-MvXHAKvNDREzTsHzK4M9LOsK3S/BwAtbyZ+myFkNFS68z9IhQVfUKP+lQgLXdiQdf5i64LQRkzLV+OpOXg6dHg==", + "requires": { + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@scure/bip32": "^1.3.3", + "@scure/bip39": "^1.2.2", + "buffer": "^6.0.3" + } + }, "@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -9847,6 +9878,11 @@ } } }, + "js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + }, "js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", diff --git a/package.json b/package.json index 5cab1bc7e..845c4e253 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,12 @@ "typescript": "^5.0.4" }, "dependencies": { + "@gandlaf21/cashu-crypto": "^0.2.5", "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", "@scure/bip39": "^1.2.2", - "buffer": "^6.0.3" + "buffer": "^6.0.3", + "js-base64": "^3.7.7" } } diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index d5fd633d0..4b3d83541 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -32,6 +32,9 @@ import { import { deriveBlindingFactor, deriveSecret, deriveSeedFromMnemonic } from './secrets.js'; import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; +import { createP2PKsecret, getSignedProofs } from "@gandlaf21/cashu-crypto/modules/client/NUT11"; +import { serializeProof } from "@gandlaf21/cashu-crypto/modules/client"; +import { pointFromHex } from './DHKE'; /** * Class that represents a Cashu wallet. @@ -82,13 +85,19 @@ class CashuWallet { * @param {(string|Token)} token - Cashu token * @param preference optional preference for splitting proofs into specific amounts * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! + * @param privkey? will create a signature on the @param token secrets if set * @returns New token with newly created proofs, token entries that had errors */ async receive( token: string | Token, - preference?: Array, - counter?: number - ): Promise { + options?: { + preference?: Array, + counter?: number, + pubkey?: string, + privkey?: string + } + ): Promise { let decodedToken: Array; if (typeof token === 'string') { decodedToken = cleanToken(getDecodedToken(token)).token; @@ -104,8 +113,12 @@ class CashuWallet { try { const { proofs, proofsWithError } = await this.receiveTokenEntry( tokenEntry, - preference, - counter + { + preference: options?.preference, + counter: options?.counter, + pubkey: options?.pubkey, + privkey: options?.privkey + } ); if (proofsWithError?.length) { tokenEntriesWithError.push(tokenEntry); @@ -128,17 +141,24 @@ class CashuWallet { * @param tokenEntry a single entry of a cashu token * @param preference optional preference for splitting proofs into specific amounts. * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! + * @param privkey? will create a signature on the @param tokenEntry secrets if set * @returns New token entry with newly created proofs, proofs that had errors */ async receiveTokenEntry( tokenEntry: TokenEntry, - preference?: Array, - counter?: number + options?:{ + preference?: Array, + counter?: number, + pubkey?: string, + privkey?: string + } ): Promise { const proofsWithError: Array = []; const proofs: Array = []; try { const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); + let preference = options?.preference if (!preference) { preference = getDefaultAmountPreference(amount); } @@ -148,7 +168,9 @@ class CashuWallet { tokenEntry.proofs, keys, preference, - counter + options?.counter, + options?.pubkey, + options?.privkey ); const { signatures, error } = await CashuMint.split(tokenEntry.mint, payload); const newProofs = dhke.constructProofs( @@ -176,16 +198,22 @@ class CashuWallet { * @param proofs proofs matching that amount * @param preference optional preference for splitting proofs into specific amounts. overrides amount param * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! + * @param privkey? will create a signature on the @param proofs secrets if set * @returns promise of the change- and send-proofs */ async send( amount: number, proofs: Array, - preference?: Array, - counter?: number + options?:{ + preference?: Array, + counter?: number, + pubkey?: string, + privkey?: string + } ): Promise { - if (preference) { - amount = preference?.reduce((acc, curr) => acc + curr.amount * curr.count, 0); + if (options?.preference) { + amount = options?.preference?.reduce((acc, curr) => acc + curr.amount * curr.count, 0); } const keyset = await this.getKeys(); let amountAvailable = 0; @@ -203,14 +231,16 @@ class CashuWallet { if (amount > amountAvailable) { throw new Error('Not enough funds available'); } - if (amount < amountAvailable || preference) { + if (amount < amountAvailable || options?.preference || options?.pubkey) { const { amountKeep, amountSend } = this.splitReceive(amount, amountAvailable); const { payload, blindedMessages } = this.createSplitPayload( amountSend, proofsToSend, keyset, - preference, - counter + options?.preference, + options?.counter, + options?.pubkey, + options?.privkey ); const { signatures } = await this.mint.split(payload); const proofs = dhke.constructProofs( @@ -248,9 +278,11 @@ class CashuWallet { async restore( start: number, count: number, - keysetId?: string + options?: { + keysetId?: string + } ): Promise<{ proofs: Array }> { - const keys = await this.getKeys(keysetId); + const keys = await this.getKeys(options?.keysetId); if (!this._seed) { throw new Error('CashuWallet must be initialized with mnemonic to use restore'); } @@ -288,7 +320,9 @@ class CashuWallet { `could not initialize keys. No keyset with unit '${unit ? unit : 'sat'}' found` ); } - this._keys = keys; + if (!this._keys) { + this._keys = keys; + } } return this._keys; } @@ -315,16 +349,20 @@ class CashuWallet { async mintTokens( amount: number, quote: string, - keysetId?: string, - AmountPreference?: Array, - counter?: number + options?: { + keysetId?: string, + AmountPreference?: Array, + counter?: number, + pubkey?: string + } ): Promise<{ proofs: Array }> { - const keyset = await this.getKeys(keysetId); + const keyset = await this.getKeys(options?.keysetId); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, - keysetId ?? keyset.id, - AmountPreference, - counter + options?.keysetId ?? keyset.id, + options?.AmountPreference, + options?.counter, + options?.pubkey ); const postMintPayload: PostMintPayload = { outputs: blindedMessages, @@ -350,20 +388,24 @@ class CashuWallet { * Returns payment proof and change proofs * @param meltQuote ID of the melt quote * @param proofsToSend proofs to melt + * @param options.keysetId? optionally set keysetId for blank outputs for returned change. + * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect * @returns */ async meltTokens( meltQuote: MeltQuoteResponse, proofsToSend: Array, - keysetId?: string, - counter?: number + options?: { + keysetId?: string, + counter?: number, + } ): Promise { - const keys = await this.getKeys(keysetId); + const keys = await this.getKeys(options?.keysetId); const { blindedMessages, secrets, rs } = this.createBlankOutputs( meltQuote.fee_reserve, keys.id, - counter + options?.counter ); const meltPayload: MeltPayload = { quote: meltQuote.quote, @@ -387,21 +429,23 @@ class CashuWallet { * @param invoice * @param proofsToSend the exact amount to send including fees * @param meltQuote melt quote for the invoice - * @param keysetId? optionally set keysetId for blank outputs for returned change. - * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param options.keysetId? optionally set keysetId for blank outputs for returned change. + * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect * @returns */ async payLnInvoice( invoice: string, proofsToSend: Array, - meltQuote?: MeltQuoteResponse, - keysetId?: string, - counter?: number + meltQuote: MeltQuoteResponse, + options?:{ + keysetId?: string, + counter?: number + } ): Promise { if (!meltQuote) { meltQuote = await this.mint.meltQuote({ unit: this.unit, request: invoice }); } - return await this.meltTokens(meltQuote, proofsToSend, keysetId, counter); + return await this.meltTokens(meltQuote, proofsToSend, {keysetId:options?.keysetId, counter: options?.counter}); } /** @@ -409,21 +453,23 @@ class CashuWallet { * @param invoice Lightning invoice * @param token cashu token * @param meltQuote melt quote for the invoice - * @param keysetId? optionally set keysetId for blank outputs for returned change. - * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param options.keysetId? optionally set keysetId for blank outputs for returned change. + * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect */ - payLnInvoiceWithToken( + async payLnInvoiceWithToken( invoice: string, token: string, - meltQuote?: MeltQuoteResponse, - keysetId?: string, - counter?: number + meltQuote: MeltQuoteResponse, + options?: { + keysetId?: string, + counter?: number + } ): Promise { const decodedToken = getDecodedToken(token); const proofs = decodedToken.token .filter((x) => x.mint === this.mint.mintUrl) .flatMap((t) => t.proofs); - return this.payLnInvoice(invoice, proofs, meltQuote, keysetId, counter); + return this.payLnInvoice(invoice, proofs, meltQuote, {keysetId: options?.keysetId, counter: options?.counter}); } /** @@ -432,6 +478,8 @@ class CashuWallet { * @param proofsToSend proofs to split* * @param preference optional preference for splitting proofs into specific amounts. overrides amount param * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! + * @param privkey? will create a signature on the @param proofsToSend secrets if set * @returns */ private createSplitPayload( @@ -439,7 +487,9 @@ class CashuWallet { proofsToSend: Array, keyset: MintKeys, preference?: Array, - counter?: number + counter?: number, + pubkey?: string, + privkey?: string ): { payload: SplitPayload; blindedMessages: BlindedTransaction; @@ -458,8 +508,12 @@ class CashuWallet { amount, keyset.id, preference, - counter + counter, + pubkey ); + if (privkey) { + proofsToSend = getSignedProofs(proofsToSend.map((p)=>{return {amount:p.amount, C: pointFromHex(p.C), id: p.id, secret: new TextEncoder().encode(p.secret)}}), privkey).map(p=> serializeProof(p)) + } // join keepBlindedMessages and sendBlindedMessages const blindedMessages: BlindedTransaction = { @@ -480,7 +534,7 @@ class CashuWallet { } /** * returns proofs that are already spent (use for keeping wallet state clean) - * @param proofs (only the 'secret' field is required) + * @param proofs (only the 'Y' field is required) * @returns */ async checkProofsSpent(proofs: Array): Promise> { @@ -510,17 +564,20 @@ class CashuWallet { * Creates blinded messages for a given amount * @param amount amount to create blinded messages for * @param amountPreference optional preference for splitting proofs into specific amounts. overrides amount param + * @param keyksetId? override the keysetId derived from the current mintKeys with a custom one. This should be a keyset that was fetched from the `/keysets` endpoint * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! * @returns blinded messages, secrets, rs, and amounts */ private createRandomBlindedMessages( amount: number, keysetId: string, amountPreference?: Array, - counter?: number + counter?: number, + pubkey?: string ): BlindedMessageData & { amounts: Array } { const amounts = splitAmount(amount, amountPreference); - return this.createBlindedMessages(amounts, keysetId, counter); + return this.createBlindedMessages(amounts, keysetId, counter, pubkey); } /** @@ -528,12 +585,14 @@ class CashuWallet { * @param amount array of amounts to create blinded messages for * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect * @param keyksetId? override the keysetId derived from the current mintKeys with a custom one. This should be a keyset that was fetched from the `/keysets` endpoint + * @param pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! * @returns blinded messages, secrets, rs, and amounts */ private createBlindedMessages( amounts: Array, keysetId: string, - counter?: number + counter?: number, + pubkey?: string ): BlindedMessageData & { amounts: Array } { // if we atempt to create deterministic messages without a _seed, abort. if (counter != undefined && !this._seed) { @@ -547,16 +606,21 @@ class CashuWallet { for (let i = 0; i < amounts.length; i++) { let deterministicR = undefined; let secretBytes = undefined; - if (this._seed && counter != undefined) { + if (pubkey) { + secretBytes = createP2PKsecret(pubkey) + } + else if (this._seed && counter != undefined) { secretBytes = deriveSecret(this._seed, keysetId, counter + i); deterministicR = bytesToNumber(deriveBlindingFactor(this._seed, keysetId, counter + i)); } else { secretBytes = randomBytes(32); } - const secretHex = bytesToHex(secretBytes); - const secret = new TextEncoder().encode(secretHex); - secrets.push(secret); - const { B_, r } = dhke.blindMessage(secret, deterministicR); + if (!pubkey) { + const secretHex = bytesToHex(secretBytes); + secretBytes = new TextEncoder().encode(secretHex); + } + secrets.push(secretBytes); + const { B_, r } = dhke.blindMessage(secretBytes, deterministicR); rs.push(r); const blindedMessage = new BlindedMessage(amounts[i], B_, keysetId); blindedMessages.push(blindedMessage.getSerializedBlindedMessage()); @@ -568,6 +632,7 @@ class CashuWallet { * Creates NUT-08 blank outputs (fee returns) for a given fee reserve * See: https://github.com/cashubtc/nuts/blob/main/08.md * @param feeReserve amount to cover with blank outputs + * @param keysetId mint keysetId * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect * @returns blinded messages, secrets, and rs */ diff --git a/test/base64.test.ts b/test/base64.test.ts index 88ac080a6..94540d58d 100644 --- a/test/base64.test.ts +++ b/test/base64.test.ts @@ -72,4 +72,27 @@ describe('testing uint8 encoding', () => { expect(encodeBase64ToJson(base64url)).toStrictEqual(obj); expect(encodeJsonToBase64(obj)).toStrictEqual(base64url); }); + test('test script secret to from base64', () => { + const base64url = 'eyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6IjAwOWExZjI5MzI1M2U0MWUiLCJhbW91bnQiOjEsInNlY3JldCI6IltcIlAyUEtcIix7XCJub25jZVwiOlwiZDU2YWM4MzljMzdiZWRiNGM1MGIxODcxOTY1MDI2N2E2MWIzMTBlZjdhY2Q5ZWFjMzgwZmIxZmRmNmM1ZjkxNlwiLFwiZGF0YVwiOlwiYjM4Y2FjMmY0N2QzZWNjYjY0NmUxYmFiZDBiNDFlMzZhMTc5MmRlZjlhODU5ODRlNWZiZmVkZTU1ZjQ4Yjc4OVwifV0iLCJDIjoiMDM4YTcyZWRmNWRmN2M3ZmNiMTRhMDhjYjhiZDljODVlOTVkZmM0MzY4ZTU5YTk3OTRkZmI5OTAxZWEyZDIxNzI5In1dLCJtaW50IjoiaHR0cHM6Ly90ZXN0bnV0LmNhc2h1LnNwYWNlIn1dfQ'; + // const base64 = 'eyJ0ZXN0RGF0YSI6IvCfj7PvuI/wn4+z77iPIn0=' + const obj = { + "token": [ + { + "proofs": [ + { + "id": "009a1f293253e41e", + "amount": 1, + "secret": "[\"P2PK\",{\"nonce\":\"d56ac839c37bedb4c50b18719650267a61b310ef7acd9eac380fb1fdf6c5f916\",\"data\":\"b38cac2f47d3eccb646e1babd0b41e36a1792def9a85984e5fbfede55f48b789\"}]", + "C": "038a72edf5df7c7fcb14a08cb8bd9c85e95dfc4368e59a9794dfb9901ea2d21729" + } + ], + "mint": "https://testnut.cashu.space" + } + ] + }; + + expect(encodeBase64ToJson(base64url)).toStrictEqual(obj); + expect(encodeJsonToBase64(obj)).toStrictEqual(base64url); + }); + }); diff --git a/test/integration.test.ts b/test/integration.test.ts index 0c75747ba..1fbee165a 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -98,11 +98,12 @@ describe('mint api', () => { const request = await wallet.getMintQuote(3000); const tokens = await wallet.mintTokens(3000, request.quote); - const fee = (await wallet.getMeltQuote(externalInvoice)).fee_reserve; + const meltQuote = (await wallet.getMeltQuote(externalInvoice)); + const fee = meltQuote.fee_reserve expect(fee).toBeGreaterThan(0); const sendResponse = await wallet.send(2000 + fee, tokens.proofs); - const response = await wallet.payLnInvoice(externalInvoice, sendResponse.send); + const response = await wallet.payLnInvoice(externalInvoice, sendResponse.send, meltQuote); expect(response).toBeDefined(); // expect that we have not received the fee back, since it was external diff --git a/test/wallet.test.ts b/test/wallet.test.ts index e68ac5be0..f2e1ec929 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -1,8 +1,9 @@ import nock from 'nock'; import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; -import { ReceiveResponse } from '../src/model/types/index.js'; +import { MeltQuoteResponse, ReceiveResponse } from '../src/model/types/index.js'; import { cleanToken, getDecodedToken } from '../src/utils.js'; +import { AmountPreference } from '../src/model/types/index'; const dummyKeysResp = { keysets: [ @@ -129,7 +130,7 @@ describe('receive', () => { const wallet = new CashuWallet(mint); const token3sat = 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0='; - const response: ReceiveResponse = await wallet.receive(token3sat, [{ amount: 1, count: 3 }]); + const response: ReceiveResponse = await wallet.receive(token3sat, {preference:[{ amount: 1, count: 3 }]}); expect(response.token.token).toHaveLength(1); expect(response.token.token[0].proofs).toHaveLength(3); @@ -217,8 +218,9 @@ describe('payLnInvoice', () => { .reply(200, { quote: 'quote_id', amount: 123, fee_reserve: 0 }); nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, payment_preimage: '' }); const wallet = new CashuWallet(mint); + const meltQuote = await wallet.getMeltQuote("lnbcabbc") - const result = await wallet.payLnInvoice(invoice, proofs); + const result = await wallet.payLnInvoice(invoice, proofs,meltQuote); expect(result).toEqual({ isPaid: true, preimage: '', change: [] }); }); @@ -255,18 +257,17 @@ describe('payLnInvoice', () => { ] }); const wallet = new CashuWallet(mint); - - const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }]); + const meltQuote = await wallet.getMeltQuote("lnbcabbc") + const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }],meltQuote); expect(result.isPaid).toBe(true); expect(result.preimage).toBe('asd'); expect(result.change).toHaveLength(1); }); test('test payLnInvoice bad resonse', async () => { - nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, {}); + nock(mintUrl).post('/v1/melt/bolt11').reply(200, {}); const wallet = new CashuWallet(mint); - - const result = await wallet.payLnInvoice(invoice, proofs).catch((e) => e); + const result = await wallet.payLnInvoice(invoice, proofs, {} as MeltQuoteResponse).catch((e) => e); expect(result).toEqual(new Error('bad response')); }); @@ -454,7 +455,7 @@ describe('send', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; - const result = await wallet.send(4, overpayProofs, [{ amount: 1, count: 4 }]); + const result = await wallet.send(4, overpayProofs, {preference:[{ amount: 1, count: 4 }]}); expect(result.send).toHaveLength(4); expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); @@ -509,7 +510,7 @@ describe('send', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; - const result = await wallet.send(4, overpayProofs, [{ amount: 1, count: 3 }]); + const result = await wallet.send(4, overpayProofs, {preference:[{ amount: 1, count: 3 }]}); expect(result.send).toHaveLength(3); expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); @@ -572,8 +573,7 @@ describe('deterministic', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ], - undefined, - 1 + {counter: 1} ) .catch((e) => e); expect(result).toEqual( From 428b776ea8bbf619b81fdcef4c23f06806ebc57d Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 30 Mar 2024 09:20:53 +0900 Subject: [PATCH 090/175] update deps --- package-lock.json | 27 ++++++++------------------- package.json | 5 ++--- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a0252a18..7acb928d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,12 @@ "version": "1.0.0-rc.1", "license": "MIT", "dependencies": { - "@gandlaf21/cashu-crypto": "^0.2.5", + "@gandlaf21/cashu-crypto": "^0.2.6", "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", "@scure/bip39": "^1.2.2", - "buffer": "^6.0.3", - "js-base64": "^3.7.7" + "buffer": "^6.0.3" }, "devDependencies": { "@types/jest": "^29.5.1", @@ -820,9 +819,9 @@ } }, "node_modules/@gandlaf21/cashu-crypto": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@gandlaf21/cashu-crypto/-/cashu-crypto-0.2.5.tgz", - "integrity": "sha512-MvXHAKvNDREzTsHzK4M9LOsK3S/BwAtbyZ+myFkNFS68z9IhQVfUKP+lQgLXdiQdf5i64LQRkzLV+OpOXg6dHg==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@gandlaf21/cashu-crypto/-/cashu-crypto-0.2.6.tgz", + "integrity": "sha512-BwiaSAhk98XSgy+BXAeGNRLfjOh1CsGNQGi957wF7u+dfPH5EkOqnd7tnynmWi8yx0xdk0eF9EFwdKryjzEQrA==", "dependencies": { "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", @@ -4706,11 +4705,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-base64": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", - "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" - }, "node_modules/js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -7019,9 +7013,9 @@ "dev": true }, "@gandlaf21/cashu-crypto": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@gandlaf21/cashu-crypto/-/cashu-crypto-0.2.5.tgz", - "integrity": "sha512-MvXHAKvNDREzTsHzK4M9LOsK3S/BwAtbyZ+myFkNFS68z9IhQVfUKP+lQgLXdiQdf5i64LQRkzLV+OpOXg6dHg==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@gandlaf21/cashu-crypto/-/cashu-crypto-0.2.6.tgz", + "integrity": "sha512-BwiaSAhk98XSgy+BXAeGNRLfjOh1CsGNQGi957wF7u+dfPH5EkOqnd7tnynmWi8yx0xdk0eF9EFwdKryjzEQrA==", "requires": { "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", @@ -9878,11 +9872,6 @@ } } }, - "js-base64": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", - "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" - }, "js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", diff --git a/package.json b/package.json index 845c4e253..c02c5baf9 100644 --- a/package.json +++ b/package.json @@ -46,12 +46,11 @@ "typescript": "^5.0.4" }, "dependencies": { - "@gandlaf21/cashu-crypto": "^0.2.5", + "@gandlaf21/cashu-crypto": "^0.2.6", "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", "@scure/bip39": "^1.2.2", - "buffer": "^6.0.3", - "js-base64": "^3.7.7" + "buffer": "^6.0.3" } } From 31248c93c33ed3dc7c209b56280b35f729592112 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 30 Mar 2024 09:22:02 +0900 Subject: [PATCH 091/175] format --- src/CashuWallet.ts | 102 ++++++++++++++++++++++----------------- test/base64.test.ts | 31 ++++++------ test/integration.test.ts | 4 +- test/wallet.test.ts | 22 +++++---- 4 files changed, 88 insertions(+), 71 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 4b3d83541..75cb17707 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -32,8 +32,8 @@ import { import { deriveBlindingFactor, deriveSecret, deriveSeedFromMnemonic } from './secrets.js'; import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; -import { createP2PKsecret, getSignedProofs } from "@gandlaf21/cashu-crypto/modules/client/NUT11"; -import { serializeProof } from "@gandlaf21/cashu-crypto/modules/client"; +import { createP2PKsecret, getSignedProofs } from '@gandlaf21/cashu-crypto/modules/client/NUT11'; +import { serializeProof } from '@gandlaf21/cashu-crypto/modules/client'; import { pointFromHex } from './DHKE'; /** @@ -92,12 +92,12 @@ class CashuWallet { async receive( token: string | Token, options?: { - preference?: Array, - counter?: number, - pubkey?: string, - privkey?: string + preference?: Array; + counter?: number; + pubkey?: string; + privkey?: string; } - ): Promise { + ): Promise { let decodedToken: Array; if (typeof token === 'string') { decodedToken = cleanToken(getDecodedToken(token)).token; @@ -111,15 +111,12 @@ class CashuWallet { continue; } try { - const { proofs, proofsWithError } = await this.receiveTokenEntry( - tokenEntry, - { - preference: options?.preference, - counter: options?.counter, - pubkey: options?.pubkey, - privkey: options?.privkey - } - ); + const { proofs, proofsWithError } = await this.receiveTokenEntry(tokenEntry, { + preference: options?.preference, + counter: options?.counter, + pubkey: options?.pubkey, + privkey: options?.privkey + }); if (proofsWithError?.length) { tokenEntriesWithError.push(tokenEntry); continue; @@ -147,18 +144,18 @@ class CashuWallet { */ async receiveTokenEntry( tokenEntry: TokenEntry, - options?:{ - preference?: Array, - counter?: number, - pubkey?: string, - privkey?: string + options?: { + preference?: Array; + counter?: number; + pubkey?: string; + privkey?: string; } ): Promise { const proofsWithError: Array = []; const proofs: Array = []; try { const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); - let preference = options?.preference + let preference = options?.preference; if (!preference) { preference = getDefaultAmountPreference(amount); } @@ -205,11 +202,11 @@ class CashuWallet { async send( amount: number, proofs: Array, - options?:{ - preference?: Array, - counter?: number, - pubkey?: string, - privkey?: string + options?: { + preference?: Array; + counter?: number; + pubkey?: string; + privkey?: string; } ): Promise { if (options?.preference) { @@ -279,7 +276,7 @@ class CashuWallet { start: number, count: number, options?: { - keysetId?: string + keysetId?: string; } ): Promise<{ proofs: Array }> { const keys = await this.getKeys(options?.keysetId); @@ -350,10 +347,10 @@ class CashuWallet { amount: number, quote: string, options?: { - keysetId?: string, - AmountPreference?: Array, - counter?: number, - pubkey?: string + keysetId?: string; + AmountPreference?: Array; + counter?: number; + pubkey?: string; } ): Promise<{ proofs: Array }> { const keyset = await this.getKeys(options?.keysetId); @@ -396,8 +393,8 @@ class CashuWallet { meltQuote: MeltQuoteResponse, proofsToSend: Array, options?: { - keysetId?: string, - counter?: number, + keysetId?: string; + counter?: number; } ): Promise { const keys = await this.getKeys(options?.keysetId); @@ -437,15 +434,18 @@ class CashuWallet { invoice: string, proofsToSend: Array, meltQuote: MeltQuoteResponse, - options?:{ - keysetId?: string, - counter?: number + options?: { + keysetId?: string; + counter?: number; } ): Promise { if (!meltQuote) { meltQuote = await this.mint.meltQuote({ unit: this.unit, request: invoice }); } - return await this.meltTokens(meltQuote, proofsToSend, {keysetId:options?.keysetId, counter: options?.counter}); + return await this.meltTokens(meltQuote, proofsToSend, { + keysetId: options?.keysetId, + counter: options?.counter + }); } /** @@ -461,15 +461,18 @@ class CashuWallet { token: string, meltQuote: MeltQuoteResponse, options?: { - keysetId?: string, - counter?: number + keysetId?: string; + counter?: number; } ): Promise { const decodedToken = getDecodedToken(token); const proofs = decodedToken.token .filter((x) => x.mint === this.mint.mintUrl) .flatMap((t) => t.proofs); - return this.payLnInvoice(invoice, proofs, meltQuote, {keysetId: options?.keysetId, counter: options?.counter}); + return this.payLnInvoice(invoice, proofs, meltQuote, { + keysetId: options?.keysetId, + counter: options?.counter + }); } /** @@ -512,7 +515,17 @@ class CashuWallet { pubkey ); if (privkey) { - proofsToSend = getSignedProofs(proofsToSend.map((p)=>{return {amount:p.amount, C: pointFromHex(p.C), id: p.id, secret: new TextEncoder().encode(p.secret)}}), privkey).map(p=> serializeProof(p)) + proofsToSend = getSignedProofs( + proofsToSend.map((p) => { + return { + amount: p.amount, + C: pointFromHex(p.C), + id: p.id, + secret: new TextEncoder().encode(p.secret) + }; + }), + privkey + ).map((p) => serializeProof(p)); } // join keepBlindedMessages and sendBlindedMessages @@ -607,9 +620,8 @@ class CashuWallet { let deterministicR = undefined; let secretBytes = undefined; if (pubkey) { - secretBytes = createP2PKsecret(pubkey) - } - else if (this._seed && counter != undefined) { + secretBytes = createP2PKsecret(pubkey); + } else if (this._seed && counter != undefined) { secretBytes = deriveSecret(this._seed, keysetId, counter + i); deterministicR = bytesToNumber(deriveBlindingFactor(this._seed, keysetId, counter + i)); } else { diff --git a/test/base64.test.ts b/test/base64.test.ts index 94540d58d..45a9b50be 100644 --- a/test/base64.test.ts +++ b/test/base64.test.ts @@ -73,26 +73,27 @@ describe('testing uint8 encoding', () => { expect(encodeJsonToBase64(obj)).toStrictEqual(base64url); }); test('test script secret to from base64', () => { - const base64url = 'eyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6IjAwOWExZjI5MzI1M2U0MWUiLCJhbW91bnQiOjEsInNlY3JldCI6IltcIlAyUEtcIix7XCJub25jZVwiOlwiZDU2YWM4MzljMzdiZWRiNGM1MGIxODcxOTY1MDI2N2E2MWIzMTBlZjdhY2Q5ZWFjMzgwZmIxZmRmNmM1ZjkxNlwiLFwiZGF0YVwiOlwiYjM4Y2FjMmY0N2QzZWNjYjY0NmUxYmFiZDBiNDFlMzZhMTc5MmRlZjlhODU5ODRlNWZiZmVkZTU1ZjQ4Yjc4OVwifV0iLCJDIjoiMDM4YTcyZWRmNWRmN2M3ZmNiMTRhMDhjYjhiZDljODVlOTVkZmM0MzY4ZTU5YTk3OTRkZmI5OTAxZWEyZDIxNzI5In1dLCJtaW50IjoiaHR0cHM6Ly90ZXN0bnV0LmNhc2h1LnNwYWNlIn1dfQ'; + const base64url = + 'eyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6IjAwOWExZjI5MzI1M2U0MWUiLCJhbW91bnQiOjEsInNlY3JldCI6IltcIlAyUEtcIix7XCJub25jZVwiOlwiZDU2YWM4MzljMzdiZWRiNGM1MGIxODcxOTY1MDI2N2E2MWIzMTBlZjdhY2Q5ZWFjMzgwZmIxZmRmNmM1ZjkxNlwiLFwiZGF0YVwiOlwiYjM4Y2FjMmY0N2QzZWNjYjY0NmUxYmFiZDBiNDFlMzZhMTc5MmRlZjlhODU5ODRlNWZiZmVkZTU1ZjQ4Yjc4OVwifV0iLCJDIjoiMDM4YTcyZWRmNWRmN2M3ZmNiMTRhMDhjYjhiZDljODVlOTVkZmM0MzY4ZTU5YTk3OTRkZmI5OTAxZWEyZDIxNzI5In1dLCJtaW50IjoiaHR0cHM6Ly90ZXN0bnV0LmNhc2h1LnNwYWNlIn1dfQ'; // const base64 = 'eyJ0ZXN0RGF0YSI6IvCfj7PvuI/wn4+z77iPIn0=' const obj = { - "token": [ - { - "proofs": [ - { - "id": "009a1f293253e41e", - "amount": 1, - "secret": "[\"P2PK\",{\"nonce\":\"d56ac839c37bedb4c50b18719650267a61b310ef7acd9eac380fb1fdf6c5f916\",\"data\":\"b38cac2f47d3eccb646e1babd0b41e36a1792def9a85984e5fbfede55f48b789\"}]", - "C": "038a72edf5df7c7fcb14a08cb8bd9c85e95dfc4368e59a9794dfb9901ea2d21729" - } - ], - "mint": "https://testnut.cashu.space" - } + token: [ + { + proofs: [ + { + id: '009a1f293253e41e', + amount: 1, + secret: + '["P2PK",{"nonce":"d56ac839c37bedb4c50b18719650267a61b310ef7acd9eac380fb1fdf6c5f916","data":"b38cac2f47d3eccb646e1babd0b41e36a1792def9a85984e5fbfede55f48b789"}]', + C: '038a72edf5df7c7fcb14a08cb8bd9c85e95dfc4368e59a9794dfb9901ea2d21729' + } + ], + mint: 'https://testnut.cashu.space' + } ] - }; + }; expect(encodeBase64ToJson(base64url)).toStrictEqual(obj); expect(encodeJsonToBase64(obj)).toStrictEqual(base64url); }); - }); diff --git a/test/integration.test.ts b/test/integration.test.ts index 1fbee165a..cffd71d74 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -98,8 +98,8 @@ describe('mint api', () => { const request = await wallet.getMintQuote(3000); const tokens = await wallet.mintTokens(3000, request.quote); - const meltQuote = (await wallet.getMeltQuote(externalInvoice)); - const fee = meltQuote.fee_reserve + const meltQuote = await wallet.getMeltQuote(externalInvoice); + const fee = meltQuote.fee_reserve; expect(fee).toBeGreaterThan(0); const sendResponse = await wallet.send(2000 + fee, tokens.proofs); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index f2e1ec929..881c5c216 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -130,7 +130,9 @@ describe('receive', () => { const wallet = new CashuWallet(mint); const token3sat = 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0='; - const response: ReceiveResponse = await wallet.receive(token3sat, {preference:[{ amount: 1, count: 3 }]}); + const response: ReceiveResponse = await wallet.receive(token3sat, { + preference: [{ amount: 1, count: 3 }] + }); expect(response.token.token).toHaveLength(1); expect(response.token.token[0].proofs).toHaveLength(3); @@ -218,9 +220,9 @@ describe('payLnInvoice', () => { .reply(200, { quote: 'quote_id', amount: 123, fee_reserve: 0 }); nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, payment_preimage: '' }); const wallet = new CashuWallet(mint); - const meltQuote = await wallet.getMeltQuote("lnbcabbc") + const meltQuote = await wallet.getMeltQuote('lnbcabbc'); - const result = await wallet.payLnInvoice(invoice, proofs,meltQuote); + const result = await wallet.payLnInvoice(invoice, proofs, meltQuote); expect(result).toEqual({ isPaid: true, preimage: '', change: [] }); }); @@ -257,8 +259,8 @@ describe('payLnInvoice', () => { ] }); const wallet = new CashuWallet(mint); - const meltQuote = await wallet.getMeltQuote("lnbcabbc") - const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }],meltQuote); + const meltQuote = await wallet.getMeltQuote('lnbcabbc'); + const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }], meltQuote); expect(result.isPaid).toBe(true); expect(result.preimage).toBe('asd'); @@ -267,7 +269,9 @@ describe('payLnInvoice', () => { test('test payLnInvoice bad resonse', async () => { nock(mintUrl).post('/v1/melt/bolt11').reply(200, {}); const wallet = new CashuWallet(mint); - const result = await wallet.payLnInvoice(invoice, proofs, {} as MeltQuoteResponse).catch((e) => e); + const result = await wallet + .payLnInvoice(invoice, proofs, {} as MeltQuoteResponse) + .catch((e) => e); expect(result).toEqual(new Error('bad response')); }); @@ -455,7 +459,7 @@ describe('send', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; - const result = await wallet.send(4, overpayProofs, {preference:[{ amount: 1, count: 4 }]}); + const result = await wallet.send(4, overpayProofs, { preference: [{ amount: 1, count: 4 }] }); expect(result.send).toHaveLength(4); expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); @@ -510,7 +514,7 @@ describe('send', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; - const result = await wallet.send(4, overpayProofs, {preference:[{ amount: 1, count: 3 }]}); + const result = await wallet.send(4, overpayProofs, { preference: [{ amount: 1, count: 3 }] }); expect(result.send).toHaveLength(3); expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); @@ -573,7 +577,7 @@ describe('deterministic', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ], - {counter: 1} + { counter: 1 } ) .catch((e) => e); expect(result).toEqual( From 871d9777a8b1cc69bd9062b4702a27ed18ac65be Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 30 Mar 2024 13:20:34 +0900 Subject: [PATCH 092/175] edit migrations guide --- migration-1.0.0.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/migration-1.0.0.md b/migration-1.0.0.md index af164f606..fcc2d70f8 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -25,6 +25,10 @@ Decoding LN invoices is no longer used inside the lib. ### `CashuWallet` interface changes +**optional parameters are now in an onpional `options?` Object** + +Utility functions now have an `options` object for optional parameters, instead of passing them directly + **`requestMint(amount: number)` --> `getMintQuote(amount: number)`** Now returns the following: From 18a1e1768767d5fe4e1a2fc57944b9e488de9f3a Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sun, 31 Mar 2024 17:34:41 +0900 Subject: [PATCH 093/175] add p2pk integration test --- test/integration.test.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/integration.test.ts b/test/integration.test.ts index cffd71d74..ae24b0410 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -3,6 +3,8 @@ import { CashuWallet } from '../src/CashuWallet.js'; import dns from 'node:dns'; import { deriveKeysetId, getEncodedToken } from '../src/utils.js'; +import { secp256k1 } from '@noble/curves/secp256k1'; +import { bytesToHex } from '@noble/curves/abstract/utils'; dns.setDefaultResultOrder('ipv4first'); const externalInvoice = @@ -173,4 +175,31 @@ describe('mint api', () => { expect(response.token).toBeDefined(); expect(response.tokensWithErrors).toBeUndefined(); }); + test('send and receive p2pk', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + + const privKeyAlice = secp256k1.utils.randomPrivateKey() + const pubKeyAlice = secp256k1.getPublicKey(privKeyAlice) + + const privKeyBob = secp256k1.utils.randomPrivateKey() + const pubKeyBob = secp256k1.getPublicKey(privKeyBob) + + const request = await wallet.getMintQuote(64); + const tokens = await wallet.mintTokens(64, request.quote); + + const {send} = await wallet.send(64, tokens.proofs, {pubkey: bytesToHex(pubKeyBob)}) + const encoded = getEncodedToken({ + token: [{ mint: mintUrl, proofs: send }] + }); + + const res = await wallet.receive(encoded,{privkey:bytesToHex(privKeyAlice)}).catch() + expect(res.token.token).toEqual([]) + expect(res.tokensWithErrors?.token.length).toBe(1) + + const { token } = await wallet.receive(encoded,{privkey:bytesToHex(privKeyBob)}) + + expect(token.token.map(t=>t.proofs).flat().reduce((curr,acc)=>{return curr+acc.amount},0)).toBe(64) + + }); }); From 4b0056d08bb9ab1457a8ad13e60daa2db8b07687 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 3 Apr 2024 21:08:09 +0900 Subject: [PATCH 094/175] format --- migration-1.0.0.md | 2 +- test/integration.test.ts | 36 +++++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/migration-1.0.0.md b/migration-1.0.0.md index fcc2d70f8..f93966e84 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -27,7 +27,7 @@ Decoding LN invoices is no longer used inside the lib. **optional parameters are now in an onpional `options?` Object** -Utility functions now have an `options` object for optional parameters, instead of passing them directly +Utility functions now have an `options` object for optional parameters, instead of passing them directly **`requestMint(amount: number)` --> `getMintQuote(amount: number)`** Now returns the following: diff --git a/test/integration.test.ts b/test/integration.test.ts index ae24b0410..2a1b4688a 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -178,28 +178,34 @@ describe('mint api', () => { test('send and receive p2pk', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); - - const privKeyAlice = secp256k1.utils.randomPrivateKey() - const pubKeyAlice = secp256k1.getPublicKey(privKeyAlice) - - const privKeyBob = secp256k1.utils.randomPrivateKey() - const pubKeyBob = secp256k1.getPublicKey(privKeyBob) - + + const privKeyAlice = secp256k1.utils.randomPrivateKey(); + const pubKeyAlice = secp256k1.getPublicKey(privKeyAlice); + + const privKeyBob = secp256k1.utils.randomPrivateKey(); + const pubKeyBob = secp256k1.getPublicKey(privKeyBob); + const request = await wallet.getMintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); - - const {send} = await wallet.send(64, tokens.proofs, {pubkey: bytesToHex(pubKeyBob)}) + + const { send } = await wallet.send(64, tokens.proofs, { pubkey: bytesToHex(pubKeyBob) }); const encoded = getEncodedToken({ token: [{ mint: mintUrl, proofs: send }] }); - const res = await wallet.receive(encoded,{privkey:bytesToHex(privKeyAlice)}).catch() - expect(res.token.token).toEqual([]) - expect(res.tokensWithErrors?.token.length).toBe(1) + const res = await wallet.receive(encoded, { privkey: bytesToHex(privKeyAlice) }).catch(); + expect(res.token.token).toEqual([]); + expect(res.tokensWithErrors?.token.length).toBe(1); - const { token } = await wallet.receive(encoded,{privkey:bytesToHex(privKeyBob)}) + const { token } = await wallet.receive(encoded, { privkey: bytesToHex(privKeyBob) }); - expect(token.token.map(t=>t.proofs).flat().reduce((curr,acc)=>{return curr+acc.amount},0)).toBe(64) - + expect( + token.token + .map((t) => t.proofs) + .flat() + .reduce((curr, acc) => { + return curr + acc.amount; + }, 0) + ).toBe(64); }); }); From e7aafe1dd78d300cc1b217de067c8813d7c4480d Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 3 Apr 2024 21:21:47 +0900 Subject: [PATCH 095/175] 1.0.0-rc.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7acb928d4..6dce2f4b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.1", + "version": "1.0.0-rc.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.1", + "version": "1.0.0-rc.2", "license": "MIT", "dependencies": { "@gandlaf21/cashu-crypto": "^0.2.6", diff --git a/package.json b/package.json index c02c5baf9..ea6c69462 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.1", + "version": "1.0.0-rc.2", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From fb2d112cd10a51b87b89d1c9c6b674a18a32d2e0 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 3 Apr 2024 22:06:15 +0900 Subject: [PATCH 096/175] move use lib from @cashu namespace --- package.json | 2 +- src/CashuWallet.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c02c5baf9..9632472b0 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "typescript": "^5.0.4" }, "dependencies": { - "@gandlaf21/cashu-crypto": "^0.2.6", + "@cashu/crypto": "^0.2.6", "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 75cb17707..4e4a8c561 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -32,8 +32,8 @@ import { import { deriveBlindingFactor, deriveSecret, deriveSeedFromMnemonic } from './secrets.js'; import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; -import { createP2PKsecret, getSignedProofs } from '@gandlaf21/cashu-crypto/modules/client/NUT11'; -import { serializeProof } from '@gandlaf21/cashu-crypto/modules/client'; +import { createP2PKsecret, getSignedProofs } from '@cashu/crypto/modules/client/NUT11'; +import { serializeProof } from '@cashu/crypto/modules/client'; import { pointFromHex } from './DHKE'; /** From 220bd72e3138910b0f9e506df652070e25cbc809 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 3 Apr 2024 22:09:05 +0900 Subject: [PATCH 097/175] update lockfile --- package-lock.json | 50 +++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7acb928d4..e09b4d253 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0-rc.1", "license": "MIT", "dependencies": { - "@gandlaf21/cashu-crypto": "^0.2.6", + "@cashu/crypto": "^0.2.6", "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", @@ -695,6 +695,18 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@cashu/crypto": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@cashu/crypto/-/crypto-0.2.6.tgz", + "integrity": "sha512-qjytcY26MRntG6nJc9U2tSeDw+BApKQaIch58POjEiTuc7MbIxgR/l/xU5NzXa/nGrSLdNZQwl/o5RQDhc2otw==", + "dependencies": { + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@scure/bip32": "^1.3.3", + "@scure/bip39": "^1.2.2", + "buffer": "^6.0.3" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -818,18 +830,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@gandlaf21/cashu-crypto": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@gandlaf21/cashu-crypto/-/cashu-crypto-0.2.6.tgz", - "integrity": "sha512-BwiaSAhk98XSgy+BXAeGNRLfjOh1CsGNQGi957wF7u+dfPH5EkOqnd7tnynmWi8yx0xdk0eF9EFwdKryjzEQrA==", - "dependencies": { - "@noble/curves": "^1.3.0", - "@noble/hashes": "^1.3.3", - "@scure/bip32": "^1.3.3", - "@scure/bip39": "^1.2.2", - "buffer": "^6.0.3" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -6921,6 +6921,18 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@cashu/crypto": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@cashu/crypto/-/crypto-0.2.6.tgz", + "integrity": "sha512-qjytcY26MRntG6nJc9U2tSeDw+BApKQaIch58POjEiTuc7MbIxgR/l/xU5NzXa/nGrSLdNZQwl/o5RQDhc2otw==", + "requires": { + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@scure/bip32": "^1.3.3", + "@scure/bip39": "^1.2.2", + "buffer": "^6.0.3" + } + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -7012,18 +7024,6 @@ "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true }, - "@gandlaf21/cashu-crypto": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@gandlaf21/cashu-crypto/-/cashu-crypto-0.2.6.tgz", - "integrity": "sha512-BwiaSAhk98XSgy+BXAeGNRLfjOh1CsGNQGi957wF7u+dfPH5EkOqnd7tnynmWi8yx0xdk0eF9EFwdKryjzEQrA==", - "requires": { - "@noble/curves": "^1.3.0", - "@noble/hashes": "^1.3.3", - "@scure/bip32": "^1.3.3", - "@scure/bip39": "^1.2.2", - "buffer": "^6.0.3" - } - }, "@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", From b810839158db3c0d0b3efdba3e984ef08c175a0a Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 3 Apr 2024 22:45:34 +0900 Subject: [PATCH 098/175] fix param typo --- src/CashuWallet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 4e4a8c561..2de5a367f 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -348,7 +348,7 @@ class CashuWallet { quote: string, options?: { keysetId?: string; - AmountPreference?: Array; + amountPreference?: Array; counter?: number; pubkey?: string; } @@ -357,7 +357,7 @@ class CashuWallet { const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, options?.keysetId ?? keyset.id, - options?.AmountPreference, + options?.amountPreference, options?.counter, options?.pubkey ); From 5eed1a1e331d5af7fcbba4cee3ac764252275c4c Mon Sep 17 00:00:00 2001 From: gandlafbtc <123852829+gandlafbtc@users.noreply.github.com> Date: Thu, 4 Apr 2024 19:07:28 +0900 Subject: [PATCH 099/175] Update migration-1.0.0.md add missing function rename --- migration-1.0.0.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/migration-1.0.0.md b/migration-1.0.0.md index f93966e84..8c93f009a 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -41,6 +41,8 @@ type MintQuoteResponse = { where `request` is the invoice to be paid, and `quote` is the identifier used to pass to `mintTokens()`. +**`requestTokens()` --> `mintTokens()`** + --- **`getMeltQuote(invoice: string)`** is now used to get fee estimation and conversion quotes instead of `getFee()` and returns: From 149d52c54203654e8e76bd4d37ebd623bbd22e80 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 3 Apr 2024 21:21:47 +0900 Subject: [PATCH 100/175] 1.0.0-rc.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e09b4d253..ff509ecbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.1", + "version": "1.0.0-rc.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.1", + "version": "1.0.0-rc.2", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.6", diff --git a/package.json b/package.json index 9632472b0..ddf2c6d59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.1", + "version": "1.0.0-rc.2", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From 2b334eae06652f97fd1a9749eacd92e94462ebed Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 5 Apr 2024 19:44:45 +0200 Subject: [PATCH 101/175] clarify comment --- src/CashuMint.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 198aa1fe3..c70299007 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -29,7 +29,7 @@ class CashuMint { * @param _mintUrl requires mint URL to create this object * @param _customRequest if passed, use custom request implementation for network communication with the mint */ - constructor(private _mintUrl: string, private _customRequest?: typeof request) {} + constructor(private _mintUrl: string, private _customRequest?: typeof request) { } get mintUrl() { return this._mintUrl; @@ -119,7 +119,7 @@ class CashuMint { /** * Get the mints public keys * @param mintUrl - * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from the active keyset are fetched + * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from all active keysets are fetched * @param customRequest * @returns */ @@ -146,7 +146,7 @@ class CashuMint { } /** * Get the mints public keys - * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from the active keyset are fetched + * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from all active keysets are fetched * @returns the mints public keys */ async getKeys(keysetId?: string, mintUrl?: string): Promise { From fa903c98db80a1de5d99658644c21111ce269ef7 Mon Sep 17 00:00:00 2001 From: starbackr-dev Date: Sat, 6 Apr 2024 10:00:14 -0400 Subject: [PATCH 102/175] updated test scripts to fix fail --- test/request.test.ts | 1 + test/wallet.test.ts | 13 ++----------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/test/request.test.ts b/test/request.test.ts index 38e77e030..e931f6d15 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -5,6 +5,7 @@ import { setGlobalRequestOptions } from '../src/request.js'; let request: Record | undefined; const mintUrl = 'https://localhost:3338'; +const unit = 'sats'; const invoice = 'lnbc20u1p3u27nppp5pm074ffk6m42lvae8c6847z7xuvhyknwgkk7pzdce47grf2ksqwsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzpgxqyz5vqsp5sw6n7cztudpl5m5jv3z6dtqpt2zhd3q6dwgftey9qxv09w82rgjq9qyyssqhtfl8wv7scwp5flqvmgjjh20nf6utvv5daw5h43h69yqfwjch7wnra3cn94qkscgewa33wvfh7guz76rzsfg9pwlk8mqd27wavf2udsq3yeuju'; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 41e11e646..865aad0dc 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -129,7 +129,7 @@ describe('receive', () => { ] }); - const wallet = new CashuWallet(mint, mint); + const wallet = new CashuWallet(mint, unit); const token3sat = 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0='; @@ -154,7 +154,7 @@ describe('receive', () => { const msg = 'tokens already spent. Secret: asdasdasd'; nock(mintUrl).post('/v1/swap').reply(200, { detail: msg }); - const wallet = new CashuWallet(mint, mint); + const wallet = new CashuWallet(mint, unit); const { tokensWithErrors } = await wallet.receive(tokenInput); @@ -175,10 +175,6 @@ describe('receive', () => { nock(mintUrl).post('/v1/split').reply(200, { code: 0, error: 'could not verify proofs.' }); const wallet = new CashuWallet(mint, unit); - nock(mintUrl).post('/v1/swap').reply(200, { code: 0, error: 'could not verify proofs.' }); - const wallet = new CashuWallet(mint, unit); - - const { tokensWithErrors } = await wallet.receive(tokenInput); const t = tokensWithErrors!; @@ -210,11 +206,6 @@ describe('checkProofsSpent', () => { .reply(200, { spendable: [true] }); const wallet = new CashuWallet(mint, unit); - .post('/v1/checkstate') - .reply(200, { states: [{ Y: 'asd', state: 'UNSPENT', witness: 'witness-asd' }] }); - const wallet = new CashuWallet(mint, unit); - - const result = await wallet.checkProofsSpent(proofs); expect(result).toStrictEqual([]); From 3012d26b466c5f82fae4364164a35914b14538d8 Mon Sep 17 00:00:00 2001 From: starbackr-dev Date: Sat, 6 Apr 2024 12:00:43 -0400 Subject: [PATCH 103/175] fixed wallet.test that got changed when merging. --- test/wallet.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 865aad0dc..5ca056ab7 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -201,9 +201,8 @@ describe('checkProofsSpent', () => { ]; test('test checkProofsSpent - get proofs that are NOT spendable', async () => { nock(mintUrl) - - .post('/v1/check') - .reply(200, { spendable: [true] }); + .post('/v1/checkstate') + .reply(200, { states: [{ Y: 'asd', state: 'UNSPENT', witness: 'witness-asd' }] }); const wallet = new CashuWallet(mint, unit); const result = await wallet.checkProofsSpent(proofs); @@ -212,6 +211,7 @@ describe('checkProofsSpent', () => { }); }); + describe('payLnInvoice', () => { const proofs = [ { From da910e17e0a88b7cc5333da1af4cbc2da844d3ac Mon Sep 17 00:00:00 2001 From: starbackr-dev Date: Sat, 6 Apr 2024 12:16:44 -0400 Subject: [PATCH 104/175] changed the unit field default to 'sat' instead of 'sats' --- test/integration.test.ts | 2 +- test/wallet.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index 7a8d2d9a4..e01dd2666 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -12,7 +12,7 @@ const externalInvoice = let request: Record | undefined; const mintUrl = 'http://localhost:3338'; -const unit = 'sats' +const unit = 'sat' describe('mint api', () => { test('get keys', async () => { diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 5ca056ab7..685170296 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -16,7 +16,7 @@ const dummyKeysResp = { }; const mintUrl = 'http://localhost:3338'; const mint = new CashuMint(mintUrl); -const unit = 'sats'; +const unit = 'sat'; const invoice = 'lnbc20u1p3u27nppp5pm074ffk6m42lvae8c6847z7xuvhyknwgkk7pzdce47grf2ksqwsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzpgxqyz5vqsp5sw6n7cztudpl5m5jv3z6dtqpt2zhd3q6dwgftey9qxv09w82rgjq9qyyssqhtfl8wv7scwp5flqvmgjjh20nf6utvv5daw5h43h69yqfwjch7wnra3cn94qkscgewa33wvfh7guz76rzsfg9pwlk8mqd27wavf2udsq3yeuju'; From b816dd3794cbf36c00048aca132472c68e07a1fa Mon Sep 17 00:00:00 2001 From: starbuilder <101296187+starbackr-dev@users.noreply.github.com> Date: Sat, 6 Apr 2024 12:37:34 -0400 Subject: [PATCH 105/175] Update node.js.yml - missing 'run' --- .github/workflows/node.js.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 741a896b1..acf317e6f 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -23,4 +23,4 @@ jobs: cache: 'npm' - run: npm ci - run: npm run compile - - run: npm test + - run: npm run test From c6588bac7a2545318ef3f3e5e1ea6b35f8c34b42 Mon Sep 17 00:00:00 2001 From: starbuilder <101296187+starbackr-dev@users.noreply.github.com> Date: Sat, 6 Apr 2024 12:45:43 -0400 Subject: [PATCH 106/175] Update node.js.yml - match to main branch --- .github/workflows/node.js.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index acf317e6f..285e3196b 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -6,12 +6,12 @@ name: Node.js CI on: [push] jobs: - tests: + build: runs-on: ubuntu-latest strategy: matrix: - node-version: [20.x] + node-version: [20.x, 16.x, 18.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: @@ -23,4 +23,4 @@ jobs: cache: 'npm' - run: npm ci - run: npm run compile - - run: npm run test + - run: npm test From a8b069df45df6ba36c0b3dc5e9f0cf9ccf6fceae Mon Sep 17 00:00:00 2001 From: starbuilder <101296187+starbackr-dev@users.noreply.github.com> Date: Sat, 6 Apr 2024 12:48:07 -0400 Subject: [PATCH 107/175] Update node.js.yml - switching node.js to 18.x --- .github/workflows/node.js.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 285e3196b..8cdb2edf0 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -6,12 +6,12 @@ name: Node.js CI on: [push] jobs: - build: + tests: runs-on: ubuntu-latest strategy: matrix: - node-version: [20.x, 16.x, 18.x] + node-version: [18.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: @@ -24,3 +24,4 @@ jobs: - run: npm ci - run: npm run compile - run: npm test + From cc3f59d43a3144554f6dfbda7fd1326dea842155 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 5 Apr 2024 21:48:26 +0200 Subject: [PATCH 108/175] equalize mint and melt payload and request object names --- src/CashuMint.ts | 26 +++++++++++++------------- src/CashuWallet.ts | 12 ++++++------ src/model/types/index.ts | 8 ++++---- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index c70299007..7cbca2d4b 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -7,13 +7,13 @@ import type { MintActiveKeys, MintAllKeysets, PostRestoreResponse, - RequestMintResponse, + MintQuoteResponse, SerializedBlindedMessage, SplitPayload, SplitResponse, - RequestMintPayload, - PostMintPayload, - PostMintResponse, + MintQuotePayload, + MintPayload, + MintResponse, PostRestorePayload, MeltQuotePayload, MeltQuoteResponse @@ -62,14 +62,14 @@ class CashuMint { */ public static async mintQuote( mintUrl: string, - requestMintPayload: RequestMintPayload, + MintQuotePayload: MintQuotePayload, customRequest?: typeof request - ): Promise { + ): Promise { const requestInstance = customRequest || request; - return requestInstance({ + return requestInstance({ endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11'), method: 'POST', - requestBody: requestMintPayload + requestBody: MintQuotePayload }); } @@ -78,8 +78,8 @@ class CashuMint { * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ - async mintQuote(requestMintPayload: RequestMintPayload): Promise { - return CashuMint.mintQuote(this._mintUrl, requestMintPayload, this._customRequest); + async mintQuote(MintQuotePayload: MintQuotePayload): Promise { + return CashuMint.mintQuote(this._mintUrl, MintQuotePayload, this._customRequest); } /** * Requests the mint to perform token minting after the LN invoice has been paid @@ -91,11 +91,11 @@ class CashuMint { */ public static async mint( mintUrl: string, - mintPayload: PostMintPayload, + mintPayload: MintPayload, customRequest?: typeof request ) { const requestInstance = customRequest || request; - const data = await requestInstance({ + const data = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/mint/bolt11'), method: 'POST', requestBody: mintPayload @@ -113,7 +113,7 @@ class CashuMint { * @param hash hash (id) used for by the mint to keep track of wether the invoice has been paid yet * @returns serialized blinded signatures */ - async mint(mintPayload: PostMintPayload) { + async mint(mintPayload: MintPayload) { return CashuMint.mint(this._mintUrl, mintPayload, this._customRequest); } /** diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 70f34a6d9..19aea8403 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -10,11 +10,11 @@ import { MeltQuoteResponse, MintKeys, MeltTokensResponse, - PostMintPayload, + MintPayload, Proof, ReceiveResponse, ReceiveTokenEntryResponse, - RequestMintPayload, + MintQuotePayload, SendResponse, SerializedBlindedMessage, SplitPayload, @@ -332,11 +332,11 @@ class CashuWallet { * @returns the mint will create and return a Lightning invoice for the specified amount */ async getMintQuote(amount: number) { - const requestMintPayload: RequestMintPayload = { + const MintQuotePayload: MintQuotePayload = { unit: this._unit, amount: amount }; - return await this.mint.mintQuote(requestMintPayload); + return await this.mint.mintQuote(MintQuotePayload); } /** @@ -363,11 +363,11 @@ class CashuWallet { options?.counter, options?.pubkey ); - const postMintPayload: PostMintPayload = { + const MintPayload: MintPayload = { outputs: blindedMessages, quote: quote }; - const { signatures } = await this.mint.mint(postMintPayload); + const { signatures } = await this.mint.mint(MintPayload); return { proofs: dhke.constructProofs(signatures, rs, secrets, keyset) }; diff --git a/src/model/types/index.ts b/src/model/types/index.ts index ed69683fa..95f7d4e3d 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -266,7 +266,7 @@ export type ApiError = { /** * Payload that needs to be sent to the mint when requesting a mint */ -export type RequestMintPayload = { +export type MintQuotePayload = { /** * Unit to be minted */ @@ -279,7 +279,7 @@ export type RequestMintPayload = { /** * Response from the mint after requesting a mint */ -export type RequestMintResponse = { +export type MintQuoteResponse = { request: string; quote: string; } & ApiError; @@ -287,7 +287,7 @@ export type RequestMintResponse = { /** * Payload that needs to be sent to the mint when requesting a mint */ -export type PostMintPayload = { +export type MintPayload = { /** * Quote ID received from the mint. */ @@ -300,7 +300,7 @@ export type PostMintPayload = { /** * Response from the mint after requesting a mint */ -export type PostMintResponse = { +export type MintResponse = { signatures: Array; } & ApiError; From 641e27e6910eec313b27507a8f747324a69cb8eb Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 5 Apr 2024 21:49:26 +0200 Subject: [PATCH 109/175] npm run format --- src/CashuMint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 7cbca2d4b..8da6e62fd 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -29,7 +29,7 @@ class CashuMint { * @param _mintUrl requires mint URL to create this object * @param _customRequest if passed, use custom request implementation for network communication with the mint */ - constructor(private _mintUrl: string, private _customRequest?: typeof request) { } + constructor(private _mintUrl: string, private _customRequest?: typeof request) {} get mintUrl() { return this._mintUrl; From 2b1a99fe9fe8d3f91ec62ef3702fe0f1b41e816d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 5 Apr 2024 21:52:46 +0200 Subject: [PATCH 110/175] fix replace with capitalized objects --- src/CashuMint.ts | 10 +++++----- src/CashuWallet.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 8da6e62fd..af19a0e38 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -29,7 +29,7 @@ class CashuMint { * @param _mintUrl requires mint URL to create this object * @param _customRequest if passed, use custom request implementation for network communication with the mint */ - constructor(private _mintUrl: string, private _customRequest?: typeof request) {} + constructor(private _mintUrl: string, private _customRequest?: typeof request) { } get mintUrl() { return this._mintUrl; @@ -62,14 +62,14 @@ class CashuMint { */ public static async mintQuote( mintUrl: string, - MintQuotePayload: MintQuotePayload, + mintQuotePayload: MintQuotePayload, customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; return requestInstance({ endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11'), method: 'POST', - requestBody: MintQuotePayload + requestBody: mintQuotePayload }); } @@ -78,8 +78,8 @@ class CashuMint { * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ - async mintQuote(MintQuotePayload: MintQuotePayload): Promise { - return CashuMint.mintQuote(this._mintUrl, MintQuotePayload, this._customRequest); + async mintQuote(mintQuotePayload: MintQuotePayload): Promise { + return CashuMint.mintQuote(this._mintUrl, mintQuotePayload, this._customRequest); } /** * Requests the mint to perform token minting after the LN invoice has been paid diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 19aea8403..56916918a 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -332,11 +332,11 @@ class CashuWallet { * @returns the mint will create and return a Lightning invoice for the specified amount */ async getMintQuote(amount: number) { - const MintQuotePayload: MintQuotePayload = { + const mintQuotePayload: MintQuotePayload = { unit: this._unit, amount: amount }; - return await this.mint.mintQuote(MintQuotePayload); + return await this.mint.mintQuote(mintQuotePayload); } /** @@ -363,7 +363,7 @@ class CashuWallet { options?.counter, options?.pubkey ); - const MintPayload: MintPayload = { + const mintPayload: MintPayload = { outputs: blindedMessages, quote: quote }; From 33bac22c0f86fa2a8de6eb71f10235d6f35d9310 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 6 Apr 2024 11:26:29 +0900 Subject: [PATCH 111/175] import and typo fixes --- src/CashuWallet.ts | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 56916918a..2931a95ce 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -3,24 +3,24 @@ import { CashuMint } from './CashuMint.js'; import * as dhke from './DHKE.js'; import { BlindedMessage } from './model/BlindedMessage.js'; import { - AmountPreference, - BlindedMessageData, - BlindedTransaction, - MeltPayload, - MeltQuoteResponse, - MintKeys, - MeltTokensResponse, - MintPayload, - Proof, - ReceiveResponse, - ReceiveTokenEntryResponse, - MintQuotePayload, - SendResponse, - SerializedBlindedMessage, - SplitPayload, - CheckStateEnum, - Token, - TokenEntry + type AmountPreference, + type BlindedMessageData, + type BlindedTransaction, + type MeltPayload, + type MeltQuoteResponse, + type MintKeys, + type MeltTokensResponse, + type MintPayload, + type Proof, + type ReceiveResponse, + type ReceiveTokenEntryResponse, + type MintQuotePayload, + type SendResponse, + type SerializedBlindedMessage, + type SplitPayload, + type Token, + type TokenEntry, + CheckStateEnum } from './model/types/index.js'; import { bytesToNumber, @@ -367,7 +367,7 @@ class CashuWallet { outputs: blindedMessages, quote: quote }; - const { signatures } = await this.mint.mint(MintPayload); + const { signatures } = await this.mint.mint(mintPayload); return { proofs: dhke.constructProofs(signatures, rs, secrets, keyset) }; From c4ecba4106531dcb933a10f2afd2290f28586bf9 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 9 Apr 2024 09:55:27 +0900 Subject: [PATCH 112/175] format --- .github/workflows/node.js.yml | 1 - src/CashuMint.ts | 2 +- src/CashuWallet.ts | 12 ++++++++---- test/integration.test.ts | 2 +- test/request.test.ts | 2 +- test/wallet.test.ts | 15 +++------------ 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 8cdb2edf0..8242752f7 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -24,4 +24,3 @@ jobs: - run: npm ci - run: npm run compile - run: npm test - diff --git a/src/CashuMint.ts b/src/CashuMint.ts index af19a0e38..14919344a 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -29,7 +29,7 @@ class CashuMint { * @param _mintUrl requires mint URL to create this object * @param _customRequest if passed, use custom request implementation for network communication with the mint */ - constructor(private _mintUrl: string, private _customRequest?: typeof request) { } + constructor(private _mintUrl: string, private _customRequest?: typeof request) {} get mintUrl() { return this._mintUrl; diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 2931a95ce..f78a1bce1 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -45,7 +45,6 @@ class CashuWallet { private _seed: Uint8Array | undefined; private _unit = 'sat'; mint: CashuMint; - /** * @param keys public keys from the mint @@ -53,9 +52,14 @@ class CashuWallet { * @param mnemonicOrSeed mnemonic phrase or Seed to initial derivation key for this wallets deterministic secrets. When the mnemonic is provided, the seed will be derived from it. * This can lead to poor performance, in which case the seed should be directly provided */ - constructor(mint: CashuMint, unit?:string, keys?: MintKeys, mnemonicOrSeed?: string | Uint8Array) { + constructor( + mint: CashuMint, + unit?: string, + keys?: MintKeys, + mnemonicOrSeed?: string | Uint8Array + ) { this.mint = mint; - if (unit) this._unit = unit; + if (unit) this._unit = unit; if (keys) { this._keys = keys; } @@ -332,7 +336,7 @@ class CashuWallet { * @returns the mint will create and return a Lightning invoice for the specified amount */ async getMintQuote(amount: number) { - const mintQuotePayload: MintQuotePayload = { + const mintQuotePayload: MintQuotePayload = { unit: this._unit, amount: amount }; diff --git a/test/integration.test.ts b/test/integration.test.ts index e01dd2666..e8005121c 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -12,7 +12,7 @@ const externalInvoice = let request: Record | undefined; const mintUrl = 'http://localhost:3338'; -const unit = 'sat' +const unit = 'sat'; describe('mint api', () => { test('get keys', async () => { diff --git a/test/request.test.ts b/test/request.test.ts index e931f6d15..f9692a42b 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -5,7 +5,7 @@ import { setGlobalRequestOptions } from '../src/request.js'; let request: Record | undefined; const mintUrl = 'https://localhost:3338'; -const unit = 'sats'; +const unit = 'sats'; const invoice = 'lnbc20u1p3u27nppp5pm074ffk6m42lvae8c6847z7xuvhyknwgkk7pzdce47grf2ksqwsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzpgxqyz5vqsp5sw6n7cztudpl5m5jv3z6dtqpt2zhd3q6dwgftey9qxv09w82rgjq9qyyssqhtfl8wv7scwp5flqvmgjjh20nf6utvv5daw5h43h69yqfwjch7wnra3cn94qkscgewa33wvfh7guz76rzsfg9pwlk8mqd27wavf2udsq3yeuju'; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 685170296..f6218b47c 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -132,8 +132,8 @@ describe('receive', () => { const wallet = new CashuWallet(mint, unit); const token3sat = 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0='; - - const response: ReceiveResponse = await wallet.receive(token3sat, { + + const response: ReceiveResponse = await wallet.receive(token3sat, { preference: [{ amount: 1, count: 3 }] }); @@ -156,7 +156,6 @@ describe('receive', () => { nock(mintUrl).post('/v1/swap').reply(200, { detail: msg }); const wallet = new CashuWallet(mint, unit); - const { tokensWithErrors } = await wallet.receive(tokenInput); const t = tokensWithErrors!; @@ -171,7 +170,6 @@ describe('receive', () => { expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); }); test('test receive could not verify proofs', async () => { - nock(mintUrl).post('/v1/split').reply(200, { code: 0, error: 'could not verify proofs.' }); const wallet = new CashuWallet(mint, unit); @@ -211,7 +209,6 @@ describe('checkProofsSpent', () => { }); }); - describe('payLnInvoice', () => { const proofs = [ { @@ -222,16 +219,13 @@ describe('payLnInvoice', () => { } ]; test('test payLnInvoice base case', async () => { - - nock(mintUrl) .post('/v1/melt/quote/bolt11') .reply(200, { quote: 'quote_id', amount: 123, fee_reserve: 0 }); nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, payment_preimage: '' }); const wallet = new CashuWallet(mint, unit); - const meltQuote = await wallet.getMeltQuote('lnbcabbc'); - + const meltQuote = await wallet.getMeltQuote('lnbcabbc'); const result = await wallet.payLnInvoice(invoice, proofs, meltQuote); @@ -274,13 +268,11 @@ describe('payLnInvoice', () => { const meltQuote = await wallet.getMeltQuote('lnbcabbc'); const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }], meltQuote); - expect(result.isPaid).toBe(true); expect(result.preimage).toBe('asd'); expect(result.change).toHaveLength(1); }); test('test payLnInvoice bad resonse', async () => { - nock(mintUrl).post('/v1/melt/bolt11').reply(200, {}); const wallet = new CashuWallet(mint, unit); const result = await wallet @@ -559,7 +551,6 @@ describe('send', () => { expect(result).toEqual(new Error('Not enough funds available')); }); test('test send bad response', async () => { - nock(mintUrl).post('/v1/swap').reply(200, {}); const wallet = new CashuWallet(mint, unit); From 3c1ff1c67005388f986aae3a3e9ee8644b1e5bd8 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 9 Apr 2024 10:09:59 +0900 Subject: [PATCH 113/175] constructor options and set unit from keyset --- src/CashuWallet.ts | 28 ++++++++++++++++------------ test/integration.test.ts | 20 ++++++++++---------- test/request.test.ts | 4 ++-- test/wallet.test.ts | 36 ++++++++++++++++++------------------ 4 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index f78a1bce1..110860eb0 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -47,33 +47,37 @@ class CashuWallet { mint: CashuMint; /** - * @param keys public keys from the mint + * @param unit optionally set unit + * @param keys public keys from the mint. If set, it will override the unit with the keysets unit * @param mint Cashu mint instance is used to make api calls * @param mnemonicOrSeed mnemonic phrase or Seed to initial derivation key for this wallets deterministic secrets. When the mnemonic is provided, the seed will be derived from it. * This can lead to poor performance, in which case the seed should be directly provided */ constructor( mint: CashuMint, - unit?: string, - keys?: MintKeys, - mnemonicOrSeed?: string | Uint8Array + options?: { + unit?: string; + keys?: MintKeys; + mnemonicOrSeed?: string | Uint8Array; + } ) { this.mint = mint; - if (unit) this._unit = unit; - if (keys) { - this._keys = keys; + if (options?.unit) this._unit = options?.unit; + if (options?.keys) { + this._keys = options.keys; + this._unit = options.keys.unit; } - if (!mnemonicOrSeed) { + if (!options?.mnemonicOrSeed) { return; } - if (mnemonicOrSeed instanceof Uint8Array) { - this._seed = mnemonicOrSeed; + if (options?.mnemonicOrSeed instanceof Uint8Array) { + this._seed = options.mnemonicOrSeed; return; } - if (!validateMnemonic(mnemonicOrSeed, wordlist)) { + if (!validateMnemonic(options.mnemonicOrSeed, wordlist)) { throw new Error('Tried to instantiate with mnemonic, but mnemonic was invalid'); } - this._seed = deriveSeedFromMnemonic(mnemonicOrSeed); + this._seed = deriveSeedFromMnemonic(options.mnemonicOrSeed); } get keys(): MintKeys { diff --git a/test/integration.test.ts b/test/integration.test.ts index e8005121c..6c96263f4 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -35,13 +35,13 @@ describe('mint api', () => { }); test('request mint', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.getMintQuote(100); expect(request).toBeDefined(); }); test('mint tokens', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.getMintQuote(1337); expect(request).toBeDefined(); expect(request.request).toContain('lnbc1337'); @@ -52,7 +52,7 @@ describe('mint api', () => { }); test('get fee for local invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.getMintQuote(100); const fee = (await wallet.getMeltQuote(request.request)).fee_reserve; expect(fee).toBeDefined(); @@ -61,7 +61,7 @@ describe('mint api', () => { }); test('get fee for external invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const fee = (await wallet.getMeltQuote(externalInvoice)).fee_reserve; expect(fee).toBeDefined(); // because external invoice, fee should be > 0 @@ -69,7 +69,7 @@ describe('mint api', () => { }); test('pay local invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.getMintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); @@ -97,7 +97,7 @@ describe('mint api', () => { }); test('pay external invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.getMintQuote(3000); const tokens = await wallet.mintTokens(3000, request.quote); @@ -124,7 +124,7 @@ describe('mint api', () => { }); test('test send tokens exact without previous split', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.getMintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); @@ -137,7 +137,7 @@ describe('mint api', () => { }); test('test send tokens with change', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.getMintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); @@ -150,7 +150,7 @@ describe('mint api', () => { }); test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.getMintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); @@ -165,7 +165,7 @@ describe('mint api', () => { }); test('receive tokens with previous mint', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.getMintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); const encoded = getEncodedToken({ diff --git a/test/request.test.ts b/test/request.test.ts index f9692a42b..40c272dc1 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -32,7 +32,7 @@ describe('requests', () => { }; }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); await wallet.getMeltQuote(invoice); expect(request).toBeDefined(); @@ -52,7 +52,7 @@ describe('requests', () => { }; }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); setGlobalRequestOptions({ headers: { 'x-cashu': 'xyz-123-abc' } }); await wallet.getMeltQuote(invoice); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index f6218b47c..27fcb7224 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -39,7 +39,7 @@ describe('test fees', () => { amount: 2000, fee_reserve: 20 }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const fee = await wallet.getMeltQuote(invoice); const amount = 2000; @@ -63,7 +63,7 @@ describe('receive', () => { } ] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const response: ReceiveResponse = await wallet.receive(tokenInput); @@ -129,7 +129,7 @@ describe('receive', () => { ] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const token3sat = 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0='; @@ -154,7 +154,7 @@ describe('receive', () => { const msg = 'tokens already spent. Secret: asdasdasd'; nock(mintUrl).post('/v1/swap').reply(200, { detail: msg }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const { tokensWithErrors } = await wallet.receive(tokenInput); const t = tokensWithErrors!; @@ -171,7 +171,7 @@ describe('receive', () => { }); test('test receive could not verify proofs', async () => { nock(mintUrl).post('/v1/split').reply(200, { code: 0, error: 'could not verify proofs.' }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const { tokensWithErrors } = await wallet.receive(tokenInput); const t = tokensWithErrors!; @@ -201,7 +201,7 @@ describe('checkProofsSpent', () => { nock(mintUrl) .post('/v1/checkstate') .reply(200, { states: [{ Y: 'asd', state: 'UNSPENT', witness: 'witness-asd' }] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const result = await wallet.checkProofsSpent(proofs); @@ -224,7 +224,7 @@ describe('payLnInvoice', () => { .reply(200, { quote: 'quote_id', amount: 123, fee_reserve: 0 }); nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, payment_preimage: '' }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const meltQuote = await wallet.getMeltQuote('lnbcabbc'); const result = await wallet.payLnInvoice(invoice, proofs, meltQuote); @@ -264,7 +264,7 @@ describe('payLnInvoice', () => { ] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const meltQuote = await wallet.getMeltQuote('lnbcabbc'); const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }], meltQuote); @@ -274,7 +274,7 @@ describe('payLnInvoice', () => { }); test('test payLnInvoice bad resonse', async () => { nock(mintUrl).post('/v1/melt/bolt11').reply(200, {}); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const result = await wallet .payLnInvoice(invoice, proofs, {} as MeltQuoteResponse) .catch((e) => e); @@ -296,7 +296,7 @@ describe('requestTokens', () => { } ] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const { proofs } = await wallet.mintTokens(1, ''); @@ -307,7 +307,7 @@ describe('requestTokens', () => { }); test('test requestTokens bad resonse', async () => { nock(mintUrl).post('/v1/mint/bolt11').reply(200, {}); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const result = await wallet.mintTokens(1, '').catch((e) => e); @@ -336,7 +336,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const result = await wallet.send(1, proofs); @@ -363,7 +363,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const result = await wallet.send(1, [ { @@ -401,7 +401,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const overpayProofs = [ { @@ -449,7 +449,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const overpayProofs = [ { @@ -504,7 +504,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const overpayProofs = [ { @@ -544,7 +544,7 @@ describe('send', () => { } ] }); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const result = await wallet.send(2, proofs).catch((e) => e); @@ -552,7 +552,7 @@ describe('send', () => { }); test('test send bad response', async () => { nock(mintUrl).post('/v1/swap').reply(200, {}); - const wallet = new CashuWallet(mint, unit); + const wallet = new CashuWallet(mint, { unit }); const result = await wallet .send(1, [ From 227444d7d70fe917a90b57f929724fcc1f72d463 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 9 Apr 2024 10:12:07 +0900 Subject: [PATCH 114/175] update migration guide --- migration-1.0.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migration-1.0.0.md b/migration-1.0.0.md index 8c93f009a..cbc8344b4 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -25,7 +25,7 @@ Decoding LN invoices is no longer used inside the lib. ### `CashuWallet` interface changes -**optional parameters are now in an onpional `options?` Object** +**optional function AND constructor parameters are now in an onpional `options?` Object** Utility functions now have an `options` object for optional parameters, instead of passing them directly From 565badde2c68c34385e04458fc4974a6532cf220 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 9 Apr 2024 10:14:22 +0900 Subject: [PATCH 115/175] unit getter --- src/CashuWallet.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 110860eb0..6d3a0b4e2 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -80,6 +80,10 @@ class CashuWallet { this._seed = deriveSeedFromMnemonic(options.mnemonicOrSeed); } + get unit(): string { + return this._unit; + } + get keys(): MintKeys { if (!this._keys) { throw new Error('Keys are not set'); From 3d708fdebc366b6474516a42fb5e809beee94ee9 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 9 Apr 2024 10:17:36 +0900 Subject: [PATCH 116/175] 1.0.0-rc.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff509ecbe..75b02d00a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.2", + "version": "1.0.0-rc.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.2", + "version": "1.0.0-rc.3", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.6", diff --git a/package.json b/package.json index ddf2c6d59..f2809bdb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.2", + "version": "1.0.0-rc.3", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From e462896e37ee9d907367e60d766bb1fb1e281be2 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Tue, 9 Apr 2024 10:23:46 +0900 Subject: [PATCH 117/175] add set unit to set keys --- src/CashuWallet.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 6d3a0b4e2..d19685c2e 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -92,6 +92,7 @@ class CashuWallet { } set keys(keys: MintKeys) { this._keys = keys; + this._unit = keys.unit; } /** From eeb5708a2cee54bc1f242547747b61addfe3bd8f Mon Sep 17 00:00:00 2001 From: Kelbie Date: Tue, 9 Apr 2024 23:00:25 +0100 Subject: [PATCH 118/175] Update CashuWallet.ts --- src/CashuWallet.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index d19685c2e..ca07c885a 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -107,6 +107,7 @@ class CashuWallet { async receive( token: string | Token, options?: { + keysetId?: string; preference?: Array; counter?: number; pubkey?: string; @@ -127,6 +128,7 @@ class CashuWallet { } try { const { proofs, proofsWithError } = await this.receiveTokenEntry(tokenEntry, { + keysetId: options?.keysetId, preference: options?.preference, counter: options?.counter, pubkey: options?.pubkey, @@ -160,6 +162,7 @@ class CashuWallet { async receiveTokenEntry( tokenEntry: TokenEntry, options?: { + keysetId?: string; preference?: Array; counter?: number; pubkey?: string; @@ -174,7 +177,7 @@ class CashuWallet { if (!preference) { preference = getDefaultAmountPreference(amount); } - const keys = await this.getKeys(); + const keys = await this.getKeys(options?.keysetId); const { payload, blindedMessages } = this.createSplitPayload( amount, tokenEntry.proofs, @@ -218,6 +221,7 @@ class CashuWallet { amount: number, proofs: Array, options?: { + keysetId?: string; preference?: Array; counter?: number; pubkey?: string; @@ -227,7 +231,7 @@ class CashuWallet { if (options?.preference) { amount = options?.preference?.reduce((acc, curr) => acc + curr.amount * curr.count, 0); } - const keyset = await this.getKeys(); + const keyset = await this.getKeys(options?.keysetId); let amountAvailable = 0; const proofsToSend: Array = []; const proofsToKeep: Array = []; From 78099d0a9dd0b03530c3374214b97882ccf86a5f Mon Sep 17 00:00:00 2001 From: Kelbie Date: Tue, 9 Apr 2024 23:05:35 +0100 Subject: [PATCH 119/175] Update CashuWallet.ts --- src/CashuWallet.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index ca07c885a..a8bbf8a3c 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -107,7 +107,7 @@ class CashuWallet { async receive( token: string | Token, options?: { - keysetId?: string; + keysetId?: string; preference?: Array; counter?: number; pubkey?: string; @@ -128,7 +128,7 @@ class CashuWallet { } try { const { proofs, proofsWithError } = await this.receiveTokenEntry(tokenEntry, { - keysetId: options?.keysetId, + keysetId: options?.keysetId, preference: options?.preference, counter: options?.counter, pubkey: options?.pubkey, @@ -162,7 +162,7 @@ class CashuWallet { async receiveTokenEntry( tokenEntry: TokenEntry, options?: { - keysetId?: string; + keysetId?: string; preference?: Array; counter?: number; pubkey?: string; @@ -221,7 +221,7 @@ class CashuWallet { amount: number, proofs: Array, options?: { - keysetId?: string; + keysetId?: string; preference?: Array; counter?: number; pubkey?: string; @@ -231,7 +231,7 @@ class CashuWallet { if (options?.preference) { amount = options?.preference?.reduce((acc, curr) => acc + curr.amount * curr.count, 0); } - const keyset = await this.getKeys(options?.keysetId); + const keyset = await this.getKeys(options?.keysetId); let amountAvailable = 0; const proofsToSend: Array = []; const proofsToKeep: Array = []; From a2c23320074ac1472c287203bb9d07f653184ebe Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:00:32 +0200 Subject: [PATCH 120/175] add unit to Token --- src/model/types/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 95f7d4e3d..64de13567 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -388,6 +388,10 @@ export type Token = { * a message to send along with the token */ memo?: string; + /** + * the unit of the token + */ + unit?: string; }; /** * TokenEntry that stores proofs and mints From 935272cbf257ab812b99703dfae4797376d2d914 Mon Sep 17 00:00:00 2001 From: gudnuf Date: Fri, 19 Apr 2024 11:54:17 -0700 Subject: [PATCH 121/175] pass keysetId to `CashuWallet.send` + getKeys bug --- src/CashuWallet.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index d19685c2e..fe6d5225f 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -222,12 +222,13 @@ class CashuWallet { counter?: number; pubkey?: string; privkey?: string; + keysetId?: string; } ): Promise { if (options?.preference) { amount = options?.preference?.reduce((acc, curr) => acc + curr.amount * curr.count, 0); } - const keyset = await this.getKeys(); + const keyset = await this.getKeys(options?.keysetId); let amountAvailable = 0; const proofsToSend: Array = []; const proofsToKeep: Array = []; @@ -319,7 +320,7 @@ class CashuWallet { * Initialize the wallet with the mints public keys */ private async getKeys(keysetId?: string, unit?: string): Promise { - if (!this._keys || this._keys.id !== keysetId) { + if (!this._keys || (keysetId !== undefined && this._keys.id !== keysetId)) { const allKeys = await this.mint.getKeys(keysetId); let keys; if (keysetId) { From 90d692b555188c83c5f83508d521c9c1cdc03890 Mon Sep 17 00:00:00 2001 From: gudnuf Date: Fri, 19 Apr 2024 11:56:00 -0700 Subject: [PATCH 122/175] `meltQuote` optional in `CashuWallet.payLnInvoice` --- src/CashuWallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index fe6d5225f..fa0397b59 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -449,7 +449,7 @@ class CashuWallet { async payLnInvoice( invoice: string, proofsToSend: Array, - meltQuote: MeltQuoteResponse, + meltQuote?: MeltQuoteResponse, options?: { keysetId?: string; counter?: number; From 7af17248b8dae9758f0e27792f8f4c8cb890b54d Mon Sep 17 00:00:00 2001 From: dayvvo Date: Thu, 25 Apr 2024 02:43:28 +0100 Subject: [PATCH 123/175] fix: handle trailing slash for mint url in CashuMint class --- src/CashuMint.ts | 4 ++-- src/utils.ts | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 32bbe16b8..ca93666b6 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -19,7 +19,7 @@ import type { MeltQuoteResponse } from './model/types/index.js'; import request from './request.js'; -import { isObj, joinUrls } from './utils.js'; +import { isObj, joinUrls, sanitizeUrl } from './utils.js'; /** * Class represents Cashu Mint API. This class contains Lower level functions that are implemented by CashuWallet. @@ -32,7 +32,7 @@ class CashuMint { constructor(private _mintUrl: string, private _customRequest?: typeof request) {} get mintUrl() { - return this._mintUrl; + return sanitizeUrl(this._mintUrl); } /** diff --git a/src/utils.ts b/src/utils.ts index d9d28b5fa..2ad744ca7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -189,7 +189,11 @@ export function checkResponse(data: { error?: string; detail?: string }) { export function joinUrls(...parts: Array): string { return parts.map((part) => part.replace(/(^\/+|\/+$)/g, '')).join('/'); -} +}; + +export function sanitizeUrl(url:string): string{ + return url.replace(/\/$/, ''); +}; export function decodeInvoice(bolt11Invoice: string): InvoiceData { const invoiceData: InvoiceData = {} as InvoiceData; From d5dd172dcb35fe7dba871c81006f55b345b47945 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 2 May 2024 09:47:29 +0200 Subject: [PATCH 124/175] rename amountPreference to preference --- src/CashuWallet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 6d3a0b4e2..e7649f0ca 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -362,7 +362,7 @@ class CashuWallet { quote: string, options?: { keysetId?: string; - amountPreference?: Array; + preference?: Array; counter?: number; pubkey?: string; } @@ -371,7 +371,7 @@ class CashuWallet { const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, options?.keysetId ?? keyset.id, - options?.amountPreference, + options?.preference, options?.counter, options?.pubkey ); From 0d59735cd659c45662d01a6ac374cdf9c38e7633 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 18 May 2024 19:12:28 +0900 Subject: [PATCH 125/175] 1.0.0-rc.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 75b02d00a..89a642c84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.6", diff --git a/package.json b/package.json index f2809bdb8..8669695ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From b60254ed2cef6281fd64102948b79adf21071619 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 20 May 2024 11:51:06 +0900 Subject: [PATCH 126/175] format --- src/model/types/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 64de13567..da3ac7224 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -389,8 +389,8 @@ export type Token = { */ memo?: string; /** - * the unit of the token - */ + * the unit of the token + */ unit?: string; }; /** From 0fd4266e1b27d6b07a33dbd327f0dbe74a043ae6 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 20 May 2024 12:10:11 +0900 Subject: [PATCH 127/175] format and sanitize url in constructor --- src/CashuMint.ts | 7 +++++-- src/utils.ts | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index ca93666b6..f2700d0f2 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -29,10 +29,13 @@ class CashuMint { * @param _mintUrl requires mint URL to create this object * @param _customRequest if passed, use custom request implementation for network communication with the mint */ - constructor(private _mintUrl: string, private _customRequest?: typeof request) {} + constructor(private _mintUrl: string, private _customRequest?: typeof request) { + this._mintUrl = sanitizeUrl(_mintUrl); + this._customRequest = _customRequest; + } get mintUrl() { - return sanitizeUrl(this._mintUrl); + return this._mintUrl; } /** diff --git a/src/utils.ts b/src/utils.ts index 2ad744ca7..dca0ee639 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -189,11 +189,11 @@ export function checkResponse(data: { error?: string; detail?: string }) { export function joinUrls(...parts: Array): string { return parts.map((part) => part.replace(/(^\/+|\/+$)/g, '')).join('/'); -}; +} -export function sanitizeUrl(url:string): string{ +export function sanitizeUrl(url: string): string { return url.replace(/\/$/, ''); -}; +} export function decodeInvoice(bolt11Invoice: string): InvoiceData { const invoiceData: InvoiceData = {} as InvoiceData; From eadbaf29881a0a09524525781bc0d482722ca6ea Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 20 May 2024 12:31:19 +0900 Subject: [PATCH 128/175] type and remove duplicate variable --- src/CashuWallet.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index a2fd31561..10c8d8233 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -221,7 +221,6 @@ class CashuWallet { amount: number, proofs: Array, options?: { - keysetId?: string; preference?: Array; counter?: number; pubkey?: string; @@ -545,7 +544,7 @@ class CashuWallet { }; }), privkey - ).map((p) => serializeProof(p)); + ).map((p: Proof) => serializeProof(p)); } // join keepBlindedMessages and sendBlindedMessages From 3d8343e1dac37f1214cd2ffc2cd76ff8a52d1cad Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 20 May 2024 12:36:10 +0900 Subject: [PATCH 129/175] add type ref for complex proofs --- src/CashuWallet.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 10c8d8233..bc994ea22 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -33,6 +33,7 @@ import { deriveBlindingFactor, deriveSecret, deriveSeedFromMnemonic } from './se import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; import { createP2PKsecret, getSignedProofs } from '@cashu/crypto/modules/client/NUT11'; +import { type Proof as NUT11Proof } from '@cashu/crypto/modules/common/index'; import { serializeProof } from '@cashu/crypto/modules/client'; import { pointFromHex } from './DHKE'; @@ -544,7 +545,7 @@ class CashuWallet { }; }), privkey - ).map((p: Proof) => serializeProof(p)); + ).map((p: NUT11Proof) => serializeProof(p)); } // join keepBlindedMessages and sendBlindedMessages From 078341fe100da12e4ac7dcbf0d0d12e0657cb95e Mon Sep 17 00:00:00 2001 From: capitalist42 Date: Mon, 20 May 2024 09:13:03 +0800 Subject: [PATCH 130/175] Remove DHKE module and test file --- src/DHKE.ts | 72 ----------------------------------------------- test/dhke.test.ts | 53 ---------------------------------- 2 files changed, 125 deletions(-) delete mode 100644 src/DHKE.ts delete mode 100644 test/dhke.test.ts diff --git a/src/DHKE.ts b/src/DHKE.ts deleted file mode 100644 index da1dfe6b1..000000000 --- a/src/DHKE.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { ProjPointType } from '@noble/curves/abstract/weierstrass'; -import { secp256k1 } from '@noble/curves/secp256k1'; -import { MintKeys, Proof, SerializedBlindedSignature } from './model/types/index.js'; -import { bytesToNumber } from './utils.js'; -import { sha256 } from '@noble/hashes/sha256'; -import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils'; -import { Buffer } from 'buffer/'; - -const DOMAIN_SEPARATOR = hexToBytes('536563703235366b315f48617368546f43757276655f43617368755f'); - -function hashToCurve(secret: Uint8Array): ProjPointType { - const msgToHash = sha256(Buffer.concat([DOMAIN_SEPARATOR, secret])); - const counter = new Uint32Array(1); - const maxIterations = 2 ** 16; - for (let i = 0; i < maxIterations; i++) { - const counterBytes = new Uint8Array(counter.buffer); - const hash = sha256(Buffer.concat([msgToHash, counterBytes])); - try { - return pointFromHex(bytesToHex(Buffer.concat([new Uint8Array([0x02]), hash]))); - } catch (error) { - counter[0]++; - } - } - throw new Error('No valid point found'); -} - -export function pointFromHex(hex: string) { - return secp256k1.ProjectivePoint.fromHex(hex); -} -/* export function h2cToPoint(h2c: H2CPoint): ProjPointType { - return secp256k1.ProjectivePoint.fromAffine(h2c.toAffine()); -} */ -function blindMessage(secret: Uint8Array, r?: bigint): { B_: ProjPointType; r: bigint } { - const Y = hashToCurve(secret); - if (!r) { - r = bytesToNumber(secp256k1.utils.randomPrivateKey()); - } - const rG = secp256k1.ProjectivePoint.BASE.multiply(r); - const B_ = Y.add(rG); - return { B_, r }; -} - -function unblindSignature( - C_: ProjPointType, - r: bigint, - A: ProjPointType -): ProjPointType { - const C = C_.subtract(A.multiply(r)); - return C; -} - -function constructProofs( - promises: Array, - rs: Array, - secrets: Array, - keyset: MintKeys -): Array { - return promises.map((p: SerializedBlindedSignature, i: number) => { - const C_ = pointFromHex(p.C_); - const A = pointFromHex(keyset.keys[p.amount]); - const C = unblindSignature(C_, rs[i], A); - const proof = { - id: p.id, - amount: p.amount, - secret: new TextDecoder().decode(secrets[i]), - C: C.toHex(true) - }; - return proof; - }); -} - -export { hashToCurve, blindMessage, unblindSignature, constructProofs }; diff --git a/test/dhke.test.ts b/test/dhke.test.ts deleted file mode 100644 index d85e7d627..000000000 --- a/test/dhke.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { hexToBytes } from '@noble/curves/abstract/utils'; -import * as dhke from '../src/DHKE.js'; -import { bytesToNumber } from '../src/utils.js'; - -const SECRET_MESSAGE = 'test_message'; - -describe('testing hash to curve', () => { - test('testing string 0000....00', async () => { - let secret = hexToBytes('0000000000000000000000000000000000000000000000000000000000000000'); - let Y = dhke.hashToCurve(secret); - let hexY = Y.toHex(true); - expect(hexY).toBe('024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725'); - }); - - test('testing string 0000....01', async () => { - let secret = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001'); - let Y = dhke.hashToCurve(secret); - let hexY = Y.toHex(true); - expect(hexY).toBe('022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf'); - }); -}); - -describe('test blinding message', () => { - test('testing string 0000....01', async () => { - let secretUInt8 = new TextEncoder().encode(SECRET_MESSAGE); - expect(secretUInt8).toStrictEqual( - new Uint8Array([116, 101, 115, 116, 95, 109, 101, 115, 115, 97, 103, 101]) - ); - const r = bytesToNumber( - hexToBytes('0000000000000000000000000000000000000000000000000000000000000001') - ); - let { B_ } = await dhke.blindMessage(secretUInt8, r); - expect(B_.toHex(true)).toBe( - '025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b' - ); - }); -}); - -describe('test unblinding signature', () => { - test('testing string 0000....01', async () => { - let C_ = dhke.pointFromHex( - '02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2' - ); - let r = bytesToNumber( - hexToBytes('0000000000000000000000000000000000000000000000000000000000000001') - ); - let A = dhke.pointFromHex('020000000000000000000000000000000000000000000000000000000000000001'); - let C = dhke.unblindSignature(C_, r, A); - expect(C.toHex(true)).toBe( - '03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd' - ); - }); -}); From c5dae618dceb7c9c0f02b6649abd5cd41afa2b67 Mon Sep 17 00:00:00 2001 From: capitalist42 Date: Mon, 20 May 2024 09:14:25 +0800 Subject: [PATCH 131/175] remove crypto scheme test --- test/crypto.scheme.test.ts | 72 -------------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 test/crypto.scheme.test.ts diff --git a/test/crypto.scheme.test.ts b/test/crypto.scheme.test.ts deleted file mode 100644 index 16ca68a94..000000000 --- a/test/crypto.scheme.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { secp256k1 } from '@noble/curves/secp256k1'; -import { hashToCurve } from '../src/DHKE.js'; -import { bytesToNumber } from '../src/utils.js'; -import { ProjPointType } from '@noble/curves/abstract/weierstrass'; - -describe('test crypto bdhke', () => { - test('bdhke', async () => { - //Mint(Alice) - const mint: Mint = new Mint(); - - //Wallet(Bob) - const wallet: Wallet = new Wallet(); - const B_ = await wallet.createBlindedMessage('secret'); - - //Mint - const C_ = mint.createBlindSignature(B_); - - //Wallet - const { C, secret } = wallet.unblindSignature(C_, mint.publicKey); - - //Mint - const aY = await mint.calculateCVerify(secret); - expect(aY.equals(C)).toBe(true); - }); -}); - -class Mint { - private privateKey: Uint8Array; - publicKey: ProjPointType; - constructor() { - this.privateKey = secp256k1.utils.randomPrivateKey(); - this.publicKey = secp256k1.ProjectivePoint.BASE.multiply(bytesToNumber(this.privateKey)); - } - - createBlindSignature(B_: ProjPointType): ProjPointType { - const C_ = B_.multiply(bytesToNumber(this.privateKey)); - return C_; - } - - async calculateCVerify(secret: Uint8Array): Promise> { - const Y = hashToCurve(secret); - const aY = Y.multiply(bytesToNumber(this.privateKey)); - return aY; - } -} - -class Wallet { - private Y: ProjPointType | undefined; - private r = BigInt(0); - private rG: ProjPointType | undefined; - private B_: ProjPointType | undefined; - private secret = new Uint8Array(); - constructor() {} - - async createBlindedMessage(message: string): Promise> { - const enc = new TextEncoder(); - this.secret = enc.encode(message); - this.Y = hashToCurve(this.secret); - this.r = bytesToNumber(secp256k1.utils.randomPrivateKey()); - this.rG = secp256k1.ProjectivePoint.BASE.multiply(this.r); - this.B_ = this.Y.add(this.rG); - return this.B_; - } - - unblindSignature( - C_: ProjPointType, - mintPubK: ProjPointType - ): { C: ProjPointType; secret: Uint8Array } { - const C = C_.subtract(mintPubK.multiply(this.r)); - return { C, secret: this.secret }; - } -} From bf976a070fd60af8b0edf0503bc183d8390d79ff Mon Sep 17 00:00:00 2001 From: capitalist42 Date: Mon, 20 May 2024 09:17:05 +0800 Subject: [PATCH 132/175] remove console.log from test --- test/utils.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/utils.test.ts b/test/utils.test.ts index ccc341607..898ae22bb 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -304,7 +304,6 @@ describe('test keyset derivation', () => { test('derive', () => { const keys = PUBKEYS; const keysetId = utils.deriveKeysetId(keys); - console.log(keysetId); expect(keysetId).toBe('009a1f293253e41e'); }); }); From 39ded4664e18677963bf90683f74364d49a00e23 Mon Sep 17 00:00:00 2001 From: capitalist42 Date: Mon, 20 May 2024 11:00:59 +0800 Subject: [PATCH 133/175] import and use @cash/crypto library; remove dhke import; add private function constructProofs; --- src/CashuWallet.ts | 51 ++++++++++++++++++++++++++++++++++---------- test/secrets.test.ts | 1 - 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index bc994ea22..b8174f11b 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -1,6 +1,5 @@ import { bytesToHex, randomBytes } from '@noble/hashes/utils'; import { CashuMint } from './CashuMint.js'; -import * as dhke from './DHKE.js'; import { BlindedMessage } from './model/BlindedMessage.js'; import { type AmountPreference, @@ -20,7 +19,8 @@ import { type SplitPayload, type Token, type TokenEntry, - CheckStateEnum + CheckStateEnum, + SerializedBlindedSignature } from './model/types/index.js'; import { bytesToNumber, @@ -32,10 +32,14 @@ import { import { deriveBlindingFactor, deriveSecret, deriveSeedFromMnemonic } from './secrets.js'; import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; +import { hashToCurve, pointFromHex } from '@cashu/crypto/modules/common'; +import { + blindMessage, + constructProofFromPromise, + serializeProof +} from '@cashu/crypto/modules/client'; import { createP2PKsecret, getSignedProofs } from '@cashu/crypto/modules/client/NUT11'; import { type Proof as NUT11Proof } from '@cashu/crypto/modules/common/index'; -import { serializeProof } from '@cashu/crypto/modules/client'; -import { pointFromHex } from './DHKE'; /** * Class that represents a Cashu wallet. @@ -189,7 +193,7 @@ class CashuWallet { options?.privkey ); const { signatures } = await CashuMint.split(tokenEntry.mint, payload); - const newProofs = dhke.constructProofs( + const newProofs = this.constructProofs( signatures, blindedMessages.rs, blindedMessages.secrets, @@ -260,7 +264,7 @@ class CashuWallet { options?.privkey ); const { signatures } = await this.mint.split(payload); - const proofs = dhke.constructProofs( + const proofs = this.constructProofs( signatures, blindedMessages.rs, blindedMessages.secrets, @@ -316,7 +320,7 @@ class CashuWallet { ); return { - proofs: dhke.constructProofs(promises, validRs, validSecrets, keys) + proofs: this.constructProofs(promises, validRs, validSecrets, keys) }; } @@ -387,7 +391,7 @@ class CashuWallet { }; const { signatures } = await this.mint.mint(mintPayload); return { - proofs: dhke.constructProofs(signatures, rs, secrets, keyset) + proofs: this.constructProofs(signatures, rs, secrets, keyset) }; } @@ -435,7 +439,7 @@ class CashuWallet { isPaid: meltResponse.paid ?? false, preimage: meltResponse.payment_preimage, change: meltResponse?.change - ? dhke.constructProofs(meltResponse.change, rs, secrets, keys) + ? this.constructProofs(meltResponse.change, rs, secrets, keys) : [] }; } @@ -572,7 +576,7 @@ class CashuWallet { */ async checkProofsSpent(proofs: Array): Promise> { const enc = new TextEncoder(); - const Ys = proofs.map((p) => dhke.hashToCurve(enc.encode(p.secret)).toHex(true)); + const Ys = proofs.map((p) => hashToCurve(enc.encode(p.secret)).toHex(true)); const payload = { // array of Ys of proofs to check Ys: Ys @@ -652,7 +656,7 @@ class CashuWallet { secretBytes = new TextEncoder().encode(secretHex); } secrets.push(secretBytes); - const { B_, r } = dhke.blindMessage(secretBytes, deterministicR); + const { B_, r } = blindMessage(secretBytes, deterministicR); rs.push(r); const blindedMessage = new BlindedMessage(amounts[i], B_, keysetId); blindedMessages.push(blindedMessage.getSerializedBlindedMessage()); @@ -682,6 +686,31 @@ class CashuWallet { const { blindedMessages, rs, secrets } = this.createBlindedMessages(amounts, keysetId, counter); return { blindedMessages, secrets, rs }; } + + /** + * construct proofs from @params promises, @params rs, @params secrets, and @params keyset + * @param promises array of serialized blinded signatures + * @param rs arrays of binding factors + * @param secrets array of secrets + * @param keyset mint keyset + * @returns array of serialized proofs + */ + private constructProofs( + promises: Array, + rs: Array, + secrets: Array, + keyset: MintKeys + ): Array { + return promises + .map((p: SerializedBlindedSignature, i: number) => { + const blindSignature = { id: p.id, amount: p.amount, C_: pointFromHex(p.C_) }; + const r = rs[i]; + const secret = secrets[i]; + const A = pointFromHex(keyset.keys[p.amount]); + return constructProofFromPromise(blindSignature, r, secret, A); + }) + .map((p) => serializeProof(p) as Proof); + } } export { CashuWallet }; diff --git a/test/secrets.test.ts b/test/secrets.test.ts index c561f2a8d..34b4f1a1c 100644 --- a/test/secrets.test.ts +++ b/test/secrets.test.ts @@ -1,7 +1,6 @@ import { bytesToHex } from '@noble/curves/abstract/utils'; import { deriveSeedFromMnemonic } from '../src/secrets'; import { deriveBlindingFactor, deriveSecret } from '../src/secrets'; -import { blindMessage } from '../src/DHKE'; import { HDKey } from '@scure/bip32'; const mnemonic = 'half depart obvious quality work element tank gorilla view sugar picture humble'; From 00c2b085f036b3f84dddb243360143b39ac9475d Mon Sep 17 00:00:00 2001 From: capitalist42 Date: Mon, 20 May 2024 11:17:19 +0800 Subject: [PATCH 134/175] import functions from "@cashu/crypto"; remove secrets module and test; --- src/CashuWallet.ts | 6 +++- src/index.ts | 2 +- src/secrets.ts | 62 --------------------------------- test/secrets.test.ts | 82 -------------------------------------------- 4 files changed, 6 insertions(+), 146 deletions(-) delete mode 100644 src/secrets.ts delete mode 100644 test/secrets.test.ts diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index b8174f11b..fe57fa3c9 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -29,7 +29,6 @@ import { getDefaultAmountPreference, splitAmount } from './utils.js'; -import { deriveBlindingFactor, deriveSecret, deriveSeedFromMnemonic } from './secrets.js'; import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; import { hashToCurve, pointFromHex } from '@cashu/crypto/modules/common'; @@ -38,6 +37,11 @@ import { constructProofFromPromise, serializeProof } from '@cashu/crypto/modules/client'; +import { + deriveBlindingFactor, + deriveSecret, + deriveSeedFromMnemonic +} 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'; diff --git a/src/index.ts b/src/index.ts index f1c49f0b0..efa9c23f3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { CashuMint } from './CashuMint.js'; import { CashuWallet } from './CashuWallet.js'; import { setGlobalRequestOptions } from './request.js'; -import { generateNewMnemonic, deriveSeedFromMnemonic } from './secrets.js'; +import { generateNewMnemonic, deriveSeedFromMnemonic } from '@cashu/crypto/modules/client/NUT09'; import { getEncodedToken, getDecodedToken, deriveKeysetId } from './utils.js'; export * from './model/types/index.js'; diff --git a/src/secrets.ts b/src/secrets.ts deleted file mode 100644 index 48e4fbb69..000000000 --- a/src/secrets.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { HDKey } from '@scure/bip32'; -import { generateMnemonic, mnemonicToSeedSync } from '@scure/bip39'; -import { wordlist } from '@scure/bip39/wordlists/english'; -import { encodeBase64toUint8 } from './base64'; -import { bytesToNumber } from './utils'; -import { hexToNumber } from '@noble/curves/abstract/utils'; - -const STANDARD_DERIVATION_PATH = `m/129372'/0'`; - -enum DerivationType { - SECRET = 0, - BLINDING_FACTOR = 1 -} - -export const generateNewMnemonic = (): string => { - const mnemonic = generateMnemonic(wordlist, 128); - return mnemonic; -}; - -export const deriveSeedFromMnemonic = (mnemonic: string): Uint8Array => { - const seed = mnemonicToSeedSync(mnemonic); - return seed; -}; - -export const deriveSecret = (seed: Uint8Array, keysetId: string, counter: number): Uint8Array => { - return derive(seed, keysetId, counter, DerivationType.SECRET); -}; - -export const deriveBlindingFactor = ( - seed: Uint8Array, - keysetId: string, - counter: number -): Uint8Array => { - return derive(seed, keysetId, counter, DerivationType.BLINDING_FACTOR); -}; - -const derive = ( - seed: Uint8Array, - keysetId: string, - counter: number, - secretOrBlinding: DerivationType -): Uint8Array => { - const hdkey = HDKey.fromMasterSeed(seed); - const keysetIdInt = getKeysetIdInt(keysetId); - const derivationPath = `${STANDARD_DERIVATION_PATH}/${keysetIdInt}'/${counter}'/${secretOrBlinding}`; - const derived = hdkey.derive(derivationPath); - if (derived.privateKey === null) { - throw new Error('Could not derive private key'); - } - return derived.privateKey; -}; - -const getKeysetIdInt = (keysetId: string): bigint => { - let keysetIdInt: bigint; - if (/^[a-fA-F0-9]+$/.test(keysetId)) { - keysetIdInt = hexToNumber(keysetId) % BigInt(2 ** 31 - 1); - } else { - //legacy keyset compatibility - keysetIdInt = bytesToNumber(encodeBase64toUint8(keysetId)) % BigInt(2 ** 31 - 1); - } - return keysetIdInt; -}; diff --git a/test/secrets.test.ts b/test/secrets.test.ts deleted file mode 100644 index 34b4f1a1c..000000000 --- a/test/secrets.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { bytesToHex } from '@noble/curves/abstract/utils'; -import { deriveSeedFromMnemonic } from '../src/secrets'; -import { deriveBlindingFactor, deriveSecret } from '../src/secrets'; -import { HDKey } from '@scure/bip32'; - -const mnemonic = 'half depart obvious quality work element tank gorilla view sugar picture humble'; -const seed = deriveSeedFromMnemonic(mnemonic); - -describe('testing hdkey from seed', () => { - test('hdkey from seed', async () => { - const hdkey = HDKey.fromMasterSeed(seed); - expect(hdkey).not.toBeNull(); - }); - - test('hdkey to uint8array', async () => { - const hdkey = HDKey.fromMasterSeed(seed); - const privateKey = hdkey.privateKey; - expect(privateKey).not.toBeNull(); - - const seed_expected = - 'dd44ee516b0647e80b488e8dcc56d736a148f15276bef588b37057476d4b2b25780d3688a32b37353d6995997842c0fd8b412475c891c16310471fbc86dcbda8'; - const seed_uint8_array_expected = Uint8Array.from(Buffer.from(seed_expected, 'hex')); - expect(seed).toEqual(seed_uint8_array_expected); - }); -}); - -describe('testing deterministic secrets', () => { - const secrets = [ - '485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae', - '8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270', - 'bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8', - '59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf', - '576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0' - ]; - test('derive Secret', async () => { - const secret1 = deriveSecret(seed, '009a1f293253e41e', 0); - const secret2 = deriveSecret(seed, '009a1f293253e41e', 1); - const secret3 = deriveSecret(seed, '009a1f293253e41e', 2); - const secret4 = deriveSecret(seed, '009a1f293253e41e', 3); - const secret5 = deriveSecret(seed, '009a1f293253e41e', 4); - - const bf1 = deriveBlindingFactor(seed, '009a1f293253e41e', 0); - const bf2 = deriveBlindingFactor(seed, '009a1f293253e41e', 1); - const bf3 = deriveBlindingFactor(seed, '009a1f293253e41e', 2); - const bf4 = deriveBlindingFactor(seed, '009a1f293253e41e', 3); - const bf5 = deriveBlindingFactor(seed, '009a1f293253e41e', 4); - - expect(bytesToHex(secret1)).toBe(secrets[0]); - expect(bytesToHex(secret2)).toBe(secrets[1]); - expect(bytesToHex(secret3)).toBe(secrets[2]); - expect(bytesToHex(secret4)).toBe(secrets[3]); - expect(bytesToHex(secret5)).toBe(secrets[4]); - }); -}); - -describe('testing deterministic blindedMessage', () => { - const secrets = ['485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae']; - test('derive Secret', async () => { - const secret1 = deriveSecret(seed, '009a1f293253e41e', 0); - - const bf1 = deriveBlindingFactor(seed, '009a1f293253e41e', 0); - - expect(bytesToHex(secret1)).toBe(secrets[0]); - - // blindMessage() - }); -}); - -describe('test private key derivation from derivation path', () => { - const seed = - 'dd44ee516b0647e80b488e8dcc56d736a148f15276bef588b37057476d4b2b25780d3688a32b37353d6995997842c0fd8b412475c891c16310471fbc86dcbda8'; - const seed_uint8_array = Uint8Array.from(Buffer.from(seed, 'hex')); - const hdkey = HDKey.fromMasterSeed(seed_uint8_array); - const expected_privatekey = '9d32fc57e6fa2942d05ee475d28ba6a56839b8cb8a3f174b05ed0ed9d3a420f6'; - const derivation_path = "m/129372'/0'/2004500376'/0'/0"; - const derived = hdkey.derive(derivation_path); - test('derive Secret', async () => { - expect(derived.privateKey).not.toBeNull(); - const privateKey = derived.privateKey || new Uint8Array(); - expect(bytesToHex(privateKey)).toBe(expected_privatekey); - }); -}); From 4535e0f23c5c519528063f03d7cdaf8093e3b997 Mon Sep 17 00:00:00 2001 From: capitalist42 Date: Mon, 20 May 2024 15:17:37 +0800 Subject: [PATCH 135/175] fix no match request mock --- test/wallet.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 27fcb7224..e32b300d3 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -170,7 +170,7 @@ describe('receive', () => { expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); }); test('test receive could not verify proofs', async () => { - nock(mintUrl).post('/v1/split').reply(200, { code: 0, error: 'could not verify proofs.' }); + nock(mintUrl).post('/v1/swap').reply(200, { code: 0, error: 'could not verify proofs.' }); const wallet = new CashuWallet(mint, { unit }); const { tokensWithErrors } = await wallet.receive(tokenInput); From cbbf29dc85c36422b62f88151618f54b16e40186 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 12 May 2024 11:46:48 +0200 Subject: [PATCH 136/175] Mint api to get existing quotes + rename split to swap --- src/CashuMint.ts | 274 ++++++++++++++++++++++++--------------- src/CashuWallet.ts | 6 +- src/model/types/index.ts | 4 +- 3 files changed, 175 insertions(+), 109 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index b96385ad2..e7f6e831f 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -9,8 +9,8 @@ import type { PostRestoreResponse, MintQuoteResponse, SerializedBlindedMessage, - SplitPayload, - SplitResponse, + SwapPayload, + SwapResponse, MintQuotePayload, MintPayload, MintResponse, @@ -56,167 +56,132 @@ class CashuMint { async getInfo(): Promise { return CashuMint.getInfo(this._mintUrl, this._customRequest); } - /** - * Starts a minting process by requesting an invoice from the mint - * @param mintUrl - * @param amount Amount requesting for mint. - * @param customRequest - * @returns the mint will create and return a Lightning invoice for the specified amount - */ - public static async mintQuote( - mintUrl: string, - mintQuotePayload: MintQuotePayload, - customRequest?: typeof request - ): Promise { - const requestInstance = customRequest || request; - return requestInstance({ - endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11'), - method: 'POST', - requestBody: mintQuotePayload - }); - } /** - * Starts a minting process by requesting an invoice from the mint - * @param amount Amount requesting for mint. - * @returns the mint will create and return a Lightning invoice for the specified amount - */ - async mintQuote(mintQuotePayload: MintQuotePayload): Promise { - return CashuMint.mintQuote(this._mintUrl, mintQuotePayload, this._customRequest); - } - /** - * Requests the mint to perform token minting after the LN invoice has been paid + * Performs a swap operation with ecash inputs and outputs. * @param mintUrl - * @param payloads outputs (Blinded messages) that can be written - * @param hash hash (id) used for by the mint to keep track of wether the invoice has been paid yet + * @param swapPayload payload containing inputs and outputs * @param customRequest - * @returns serialized blinded signatures + * @returns signed outputs */ - public static async mint( + public static async split( mintUrl: string, - mintPayload: MintPayload, + swapPayload: SwapPayload, customRequest?: typeof request - ) { + ): Promise { const requestInstance = customRequest || request; - const data = await requestInstance({ - endpoint: joinUrls(mintUrl, '/v1/mint/bolt11'), + const data = await requestInstance({ + endpoint: joinUrls(mintUrl, '/v1/swap'), method: 'POST', - requestBody: mintPayload + requestBody: swapPayload }); if (!isObj(data) || !Array.isArray(data?.signatures)) { - throw new Error('bad response'); + throw new Error(data.detail ?? 'bad response'); } return data; } /** - * Requests the mint to perform token minting after the LN invoice has been paid - * @param payloads outputs (Blinded messages) that can be written - * @param hash hash (id) used for by the mint to keep track of wether the invoice has been paid yet - * @returns serialized blinded signatures + * Performs a swap operation with ecash inputs and outputs. + * @param swapPayload payload containing inputs and outputs + * @returns signed outputs */ - async mint(mintPayload: MintPayload) { - return CashuMint.mint(this._mintUrl, mintPayload, this._customRequest); + async split(swapPayload: SwapPayload): Promise { + return CashuMint.split(this._mintUrl, swapPayload, this._customRequest); } + /** - * Get the mints public keys + * Requests a new mint quote from the mint. * @param mintUrl - * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from all active keysets are fetched + * @param mintQuotePayload Payload for creating a new mint quote * @param customRequest - * @returns + * @returns the mint will create and return a new mint quote containing a payment request for the specified amount and unit */ - public static async getKeys( + public static async mintQuote( mintUrl: string, - keysetId?: string, + mintQuotePayload: MintQuotePayload, customRequest?: typeof request - ): Promise { - // backwards compatibility for base64 encoded keyset ids - if (keysetId) { - // make the keysetId url safe - keysetId = keysetId.replace(/\//g, '_').replace(/\+/g, '-'); - } + ): Promise { const requestInstance = customRequest || request; - const data = await requestInstance({ - endpoint: keysetId ? joinUrls(mintUrl, '/v1/keys', keysetId) : joinUrls(mintUrl, '/v1/keys') + return requestInstance({ + endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11'), + method: 'POST', + requestBody: mintQuotePayload }); - - if (!isObj(data) || !Array.isArray(data.keysets)) { - throw new Error('bad response'); - } - - return data; } /** - * Get the mints public keys - * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from all active keysets are fetched - * @returns the mints public keys + * Requests a new mint quote from the mint. + * @param mintQuotePayload Payload for creating a new mint quote + * @returns the mint will create and return a new mint quote containing a payment request for the specified amount and unit */ - async getKeys(keysetId?: string, mintUrl?: string): Promise { - const allKeys = await CashuMint.getKeys( - mintUrl || this._mintUrl, - keysetId, - this._customRequest - ); - return allKeys; + async mintQuote(mintQuotePayload: MintQuotePayload): Promise { + return CashuMint.mintQuote(this._mintUrl, mintQuotePayload, this._customRequest); } + /** - * Get the mints keysets in no specific order + * Gets an existing mint quote from the mint. * @param mintUrl + * @param quoteId Quote ID * @param customRequest - * @returns all the mints past and current keysets. + * @returns the mint will create and return a Lightning invoice for the specified amount */ - public static async getKeySets( + public static async getMintQuote( mintUrl: string, + quoteId: string, customRequest?: typeof request - ): Promise { + ): Promise { const requestInstance = customRequest || request; - return requestInstance({ endpoint: joinUrls(mintUrl, '/v1/keysets') }); + return requestInstance({ + endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11', quoteId), + method: 'GET', + }); } - /** - * Get the mints keysets in no specific order - * @returns all the mints past and current keysets. + * Gets an existing mint quote from the mint. + * @param quoteId Quote ID + * @returns the mint will create and return a Lightning invoice for the specified amount */ - async getKeySets(): Promise { - return CashuMint.getKeySets(this._mintUrl, this._customRequest); + async getMintQuote(quoteId: string): Promise { + return CashuMint.getMintQuote(this._mintUrl, quoteId, this._customRequest); } /** - * Ask mint to perform a split operation + * Mints new tokens by requesting blind signatures on the provided outputs. * @param mintUrl - * @param splitPayload data needed for performing a token split + * @param mintPayload Payload containing the outputs to get blind signatures on * @param customRequest - * @returns split tokens + * @returns serialized blinded signatures */ - public static async split( + public static async mint( mintUrl: string, - splitPayload: SplitPayload, + mintPayload: MintPayload, customRequest?: typeof request - ): Promise { + ) { const requestInstance = customRequest || request; - const data = await requestInstance({ - endpoint: joinUrls(mintUrl, '/v1/swap'), + const data = await requestInstance({ + endpoint: joinUrls(mintUrl, '/v1/mint/bolt11'), method: 'POST', - requestBody: splitPayload + requestBody: mintPayload }); if (!isObj(data) || !Array.isArray(data?.signatures)) { - throw new Error(data.detail ?? 'bad response'); + throw new Error('bad response'); } return data; } /** - * Ask mint to perform a split operation - * @param splitPayload data needed for performing a token split - * @returns split tokens + * Mints new tokens by requesting blind signatures on the provided outputs. + * @param mintPayload Payload containing the outputs to get blind signatures on + * @returns serialized blinded signatures */ - async split(splitPayload: SplitPayload): Promise { - return CashuMint.split(this._mintUrl, splitPayload, this._customRequest); + async mint(mintPayload: MintPayload) { + return CashuMint.mint(this._mintUrl, mintPayload, this._customRequest); } + /** - * Asks the mint for a melt quote + * Requests a new melt quote from the mint. * @param mintUrl * @param MeltQuotePayload * @returns @@ -241,19 +206,56 @@ class CashuMint { ) { throw new Error('bad response'); } - return data; } /** - * Asks the mint for a melt quote + * Requests a new melt quote from the mint. * @param MeltQuotePayload * @returns */ async meltQuote(meltQuotePayload: MeltQuotePayload): Promise { return CashuMint.meltQuote(this._mintUrl, meltQuotePayload, this._customRequest); } + /** - * Ask mint to perform a melt operation. This pays a lightning invoice and destroys tokens matching its amount + fees + * Gets an existing melt quote. + * @param mintUrl + * @param quoteId Quote ID + * @returns + */ + public static async getMeltQuote( + mintUrl: string, + quoteId: string, + customRequest?: typeof request + ): Promise { + const requestInstance = customRequest || request; + const data = await requestInstance({ + endpoint: joinUrls(mintUrl, '/v1/melt/quote/bolt11', quoteId), + method: 'GET', + }); + + if ( + !isObj(data) || + typeof data?.amount !== 'number' || + typeof data?.fee_reserve !== 'number' || + typeof data?.quote !== 'string' + ) { + throw new Error('bad response'); + } + + return data; + } + /** + * Gets an existing melt quote. + * @param quoteId Quote ID + * @returns + */ + async getMeltQuote(quoteId: string): Promise { + return CashuMint.getMeltQuote(this._mintUrl, quoteId, this._customRequest); + } + + /** + * Requests the mint to pay for a Bolt11 payment request by providing ecash as inputs to be spent. The inputs contain the amount and the fee_reserves for a Lightning payment. The payload can also contain blank outputs in order to receive back overpaid Lightning fees. * @param mintUrl * @param meltPayload * @param customRequest @@ -314,6 +316,70 @@ class CashuMint { return data; } + + /** + * Get the mints public keys + * @param mintUrl + * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from all active keysets are fetched + * @param customRequest + * @returns + */ + public static async getKeys( + mintUrl: string, + keysetId?: string, + customRequest?: typeof request + ): Promise { + // backwards compatibility for base64 encoded keyset ids + if (keysetId) { + // make the keysetId url safe + keysetId = keysetId.replace(/\//g, '_').replace(/\+/g, '-'); + } + const requestInstance = customRequest || request; + const data = await requestInstance({ + endpoint: keysetId ? joinUrls(mintUrl, '/v1/keys', keysetId) : joinUrls(mintUrl, '/v1/keys') + }); + + if (!isObj(data) || !Array.isArray(data.keysets)) { + throw new Error('bad response'); + } + + return data; + } + /** + * Get the mints public keys + * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from all active keysets are fetched + * @returns the mints public keys + */ + async getKeys(keysetId?: string, mintUrl?: string): Promise { + const allKeys = await CashuMint.getKeys( + mintUrl || this._mintUrl, + keysetId, + this._customRequest + ); + return allKeys; + } + /** + * Get the mints keysets in no specific order + * @param mintUrl + * @param customRequest + * @returns all the mints past and current keysets. + */ + public static async getKeySets( + mintUrl: string, + customRequest?: typeof request + ): Promise { + const requestInstance = customRequest || request; + return requestInstance({ endpoint: joinUrls(mintUrl, '/v1/keysets') }); + } + + /** + * Get the mints keysets in no specific order + * @returns all the mints past and current keysets. + */ + async getKeySets(): Promise { + return CashuMint.getKeySets(this._mintUrl, this._customRequest); + } + /** * Checks if specific proofs have already been redeemed * @param checkPayload diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index fe57fa3c9..d81054602 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -16,11 +16,11 @@ import { type MintQuotePayload, type SendResponse, type SerializedBlindedMessage, - type SplitPayload, type Token, type TokenEntry, CheckStateEnum, - SerializedBlindedSignature + SerializedBlindedSignature, + SwapPayload } from './model/types/index.js'; import { bytesToNumber, @@ -522,7 +522,7 @@ class CashuWallet { pubkey?: string, privkey?: string ): { - payload: SplitPayload; + payload: SwapPayload; blindedMessages: BlindedTransaction; } { const totalAmount = proofsToSend.reduce((total, curr) => total + curr.amount, 0); diff --git a/src/model/types/index.ts b/src/model/types/index.ts index da3ac7224..d94599d81 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -225,7 +225,7 @@ export type MeltTokensResponse = { /** * Payload that needs to be sent to the mint when performing a split action */ -export type SplitPayload = { +export type SwapPayload = { /** * Inputs to the split operation */ @@ -238,7 +238,7 @@ export type SplitPayload = { /** * Response from the mint after performing a split action */ -export type SplitResponse = { +export type SwapResponse = { /** * represents the outputs after the split */ From fd6a03de29febe4b57d2ccada165635713b8fe54 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 12 May 2024 17:11:05 +0200 Subject: [PATCH 137/175] split to swap --- src/CashuWallet.ts | 10 +++++++--- src/model/types/index.ts | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index d81054602..72427095f 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -16,6 +16,10 @@ import { type MintQuotePayload, type SendResponse, type SerializedBlindedMessage, +<<<<<<< HEAD +======= + type SwapPayload, +>>>>>>> split to swap type Token, type TokenEntry, CheckStateEnum, @@ -187,7 +191,7 @@ class CashuWallet { preference = getDefaultAmountPreference(amount); } const keys = await this.getKeys(options?.keysetId); - const { payload, blindedMessages } = this.createSplitPayload( + const { payload, blindedMessages } = this.createSwapPayload( amount, tokenEntry.proofs, keys, @@ -258,7 +262,7 @@ class CashuWallet { } if (amount < amountAvailable || options?.preference || options?.pubkey) { const { amountKeep, amountSend } = this.splitReceive(amount, amountAvailable); - const { payload, blindedMessages } = this.createSplitPayload( + const { payload, blindedMessages } = this.createSwapPayload( amountSend, proofsToSend, keyset, @@ -513,7 +517,7 @@ class CashuWallet { * @param privkey? will create a signature on the @param proofsToSend secrets if set * @returns */ - private createSplitPayload( + private createSwapPayload( amount: number, proofsToSend: Array, keyset: MintKeys, diff --git a/src/model/types/index.ts b/src/model/types/index.ts index d94599d81..66626112b 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -166,6 +166,15 @@ export type MeltQuoteResponse = { * Fee reserve to be added to the amount */ fee_reserve: number; + /** + * Whether the quote has been paid. + */ + paid: boolean; + /** + * Timestamp of when the quote expires + */ + expiry: number; + } & ApiError; /** @@ -280,8 +289,22 @@ export type MintQuotePayload = { * Response from the mint after requesting a mint */ export type MintQuoteResponse = { + /** + * Payment request + */ request: string; + /** + * Quote ID + */ quote: string; + /** + * Whether the quote has been paid. + */ + paid: boolean; + /** + * Timestamp of when the quote expires + */ + expiry: number; } & ApiError; /** From 5bac6a2160f144f7a0291604699c51a905afc27d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 12 May 2024 17:43:28 +0200 Subject: [PATCH 138/175] test get methods for quotes and rename all test functions from getMeltQuote to meltQuote and getMintQuote to mintQuote --- README.md | 5 +++-- migration-1.0.0.md | 6 +++++- src/CashuMint.ts | 24 +++++++++++------------ src/CashuWallet.ts | 36 +++++++++++++++++++++++++++++----- test/integration.test.ts | 42 +++++++++++++++++++++++++--------------- 5 files changed, 77 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 62ad97404..e3e219247 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Implemented [NUTs](https://github.com/cashubtc/nuts/): - [x] [NUT-07](https://github.com/cashubtc/nuts/blob/main/07.md) - [x] [NUT-08](https://github.com/cashubtc/nuts/blob/main/08.md) - [x] [NUT-09](https://github.com/cashubtc/nuts/blob/main/09.md) +- [x] [NUT-11](https://github.com/cashubtc/nuts/blob/main/11.md) Supported token formats: @@ -59,8 +60,8 @@ import { CashuMint, CashuWallet, getEncodedToken } from '@cashu/cashu-ts'; const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); -const request = await wallet.getMintQuote(64); -const tokens = await wallet.mintTokens(64, request.quote); +const mintQuote = await wallet.mintQuote(64); +const tokens = await wallet.mintTokens(64, mintQuote.quote); ``` ## Contribute diff --git a/migration-1.0.0.md b/migration-1.0.0.md index cbc8344b4..2e4b0d1ac 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -29,13 +29,15 @@ Decoding LN invoices is no longer used inside the lib. Utility functions now have an `options` object for optional parameters, instead of passing them directly -**`requestMint(amount: number)` --> `getMintQuote(amount: number)`** +**`requestMint(amount: number)` --> `mintQuote(amount: number)`** Now returns the following: ```typescript type MintQuoteResponse = { request: string; quote: string; + paid: boolean; + expiry: number; }; ``` @@ -52,6 +54,8 @@ type MeltQuoteResponse = { quote: string; amount: number; fee_reserve: number; + paid: boolean; + expiry: number; }; ``` diff --git a/src/CashuMint.ts b/src/CashuMint.ts index e7f6e831f..3cbead7cc 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -122,28 +122,28 @@ class CashuMint { /** * Gets an existing mint quote from the mint. * @param mintUrl - * @param quoteId Quote ID + * @param quote Quote ID * @param customRequest * @returns the mint will create and return a Lightning invoice for the specified amount */ public static async getMintQuote( mintUrl: string, - quoteId: string, + quote: string, customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; return requestInstance({ - endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11', quoteId), + endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11', quote), method: 'GET', }); } /** * Gets an existing mint quote from the mint. - * @param quoteId Quote ID + * @param quote Quote ID * @returns the mint will create and return a Lightning invoice for the specified amount */ - async getMintQuote(quoteId: string): Promise { - return CashuMint.getMintQuote(this._mintUrl, quoteId, this._customRequest); + async getMintQuote(quote: string): Promise { + return CashuMint.getMintQuote(this._mintUrl, quote, this._customRequest); } /** @@ -220,17 +220,17 @@ class CashuMint { /** * Gets an existing melt quote. * @param mintUrl - * @param quoteId Quote ID + * @param quote Quote ID * @returns */ public static async getMeltQuote( mintUrl: string, - quoteId: string, + quote: string, customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; const data = await requestInstance({ - endpoint: joinUrls(mintUrl, '/v1/melt/quote/bolt11', quoteId), + endpoint: joinUrls(mintUrl, '/v1/melt/quote/bolt11', quote), method: 'GET', }); @@ -247,11 +247,11 @@ class CashuMint { } /** * Gets an existing melt quote. - * @param quoteId Quote ID + * @param quote Quote ID * @returns */ - async getMeltQuote(quoteId: string): Promise { - return CashuMint.getMeltQuote(this._mintUrl, quoteId, this._customRequest); + async getMeltQuote(quote: string): Promise { + return CashuMint.getMeltQuote(this._mintUrl, quote, this._customRequest); } /** diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 72427095f..c84f260d0 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -14,6 +14,7 @@ import { type ReceiveResponse, type ReceiveTokenEntryResponse, type MintQuotePayload, + type MeltQuotePayload, type SendResponse, type SerializedBlindedMessage, <<<<<<< HEAD @@ -359,9 +360,9 @@ class CashuWallet { /** * Requests a mint quote form the mint. Response returns a Lightning payment request for the requested given amount and unit. * @param amount Amount requesting for mint. - * @returns the mint will create and return a Lightning invoice for the specified amount + * @returns the mint will return a mint quote with a Lightning invoice for minting tokens of the specified amount and unit */ - async getMintQuote(amount: number) { + async mintQuote(amount: number) { const mintQuotePayload: MintQuotePayload = { unit: this._unit, amount: amount @@ -369,6 +370,16 @@ class CashuWallet { return await this.mint.mintQuote(mintQuotePayload); } + /** + * Gets an existing mint quote from the mint. + * @param quote Quote ID + * @returns the mint will create and return a Lightning invoice for the specified amount + */ + async getMintQuote(quote: string) { + return await this.mint.getMintQuote(quote); + } + + /** * Mint tokens for a given mint quote * @param amount amount to request @@ -406,12 +417,27 @@ class CashuWallet { /** * Requests a melt quote from the mint. Response returns amount and fees for a given unit in order to pay a Lightning invoice. * @param invoice LN invoice that needs to get a fee estimate - * @returns estimated Fee + * @returns the mint will create and return a melt quote for the invoice with an amount and fee reserve */ - async getMeltQuote(invoice: string): Promise { - const meltQuote = await this.mint.meltQuote({ unit: this._unit, request: invoice }); + async meltQuote(invoice: string): Promise { + const meltQuotePayload: MeltQuotePayload = { + unit: this._unit, + request: invoice + }; + const meltQuote = await this.mint.meltQuote(meltQuotePayload); return meltQuote; } + + /** + * Return an existing melt quote from the mint. + * @param quote ID of the melt quote + * @returns the mint will return an existing melt quote + */ + async getMeltQuote(quote: string): Promise { + const meltQuote = await this.mint.getMeltQuote(quote); + return meltQuote; + } + /** * Melt tokens for a melt quote. proofsToSend must be at least amount+fee_reserve form the melt quote. * Returns payment proof and change proofs diff --git a/test/integration.test.ts b/test/integration.test.ts index 6c96263f4..c1826ec5f 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -36,13 +36,15 @@ describe('mint api', () => { test('request mint', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.getMintQuote(100); + const request = await wallet.mintQuote(100); expect(request).toBeDefined(); + const mintQuote = await wallet.getMeltQuote(request.quote); + expect(mintQuote).toBeDefined(); }); test('mint tokens', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.getMintQuote(1337); + const request = await wallet.mintQuote(1337); expect(request).toBeDefined(); expect(request.request).toContain('lnbc1337'); const tokens = await wallet.mintTokens(1337, request.quote); @@ -53,8 +55,8 @@ describe('mint api', () => { test('get fee for local invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.getMintQuote(100); - const fee = (await wallet.getMeltQuote(request.request)).fee_reserve; + const request = await wallet.mintQuote(100); + const fee = (await wallet.meltQuote(request.request)).fee_reserve; expect(fee).toBeDefined(); // because local invoice, fee should be 0 expect(fee).toBe(0); @@ -62,7 +64,7 @@ describe('mint api', () => { test('get fee for external invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const fee = (await wallet.getMeltQuote(externalInvoice)).fee_reserve; + const fee = (await wallet.meltQuote(externalInvoice)).fee_reserve; expect(fee).toBeDefined(); // because external invoice, fee should be > 0 expect(fee).toBeGreaterThan(0); @@ -70,17 +72,21 @@ describe('mint api', () => { test('pay local invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.getMintQuote(100); + const request = await wallet.mintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); // expect no fee because local invoice - const requestToPay = await wallet.getMintQuote(10); - const quote = await wallet.getMeltQuote(requestToPay.request); + const meltQuote = await wallet.mintQuote(10); + const quote = await wallet.meltQuote(meltQuote.request); const fee = quote.fee_reserve; expect(fee).toBe(0); + // get the quote from the mint + const quote_ = await wallet.getMeltQuote(meltQuote.quote); + expect(quote_).toBeDefined(); + const sendResponse = await wallet.send(10, tokens.proofs); - const response = await wallet.payLnInvoice(requestToPay.request, sendResponse.send, quote); + const response = await wallet.payLnInvoice(meltQuote.request, sendResponse.send, quote); expect(response).toBeDefined(); // expect that we have received the fee back, since it was internal expect(response.change.reduce((a, b) => a + b.amount, 0)).toBe(fee); @@ -98,13 +104,17 @@ describe('mint api', () => { test('pay external invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.getMintQuote(3000); + const request = await wallet.mintQuote(3000); const tokens = await wallet.mintTokens(3000, request.quote); - const meltQuote = await wallet.getMeltQuote(externalInvoice); + const meltQuote = await wallet.meltQuote(externalInvoice); const fee = meltQuote.fee_reserve; expect(fee).toBeGreaterThan(0); + // get the quote from the mint + const quote_ = await wallet.getMeltQuote(meltQuote.quote); + expect(quote_).toBeDefined(); + const sendResponse = await wallet.send(2000 + fee, tokens.proofs); const response = await wallet.payLnInvoice(externalInvoice, sendResponse.send, meltQuote); @@ -125,7 +135,7 @@ describe('mint api', () => { test('test send tokens exact without previous split', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.getMintQuote(64); + const request = await wallet.mintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); const sendResponse = await wallet.send(64, tokens.proofs); @@ -138,7 +148,7 @@ describe('mint api', () => { test('test send tokens with change', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.getMintQuote(100); + const request = await wallet.mintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); const sendResponse = await wallet.send(10, tokens.proofs); @@ -151,7 +161,7 @@ describe('mint api', () => { test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.getMintQuote(100); + const request = await wallet.mintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); const sendResponse = await wallet.send(10, tokens.proofs); @@ -166,7 +176,7 @@ describe('mint api', () => { test('receive tokens with previous mint', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.getMintQuote(64); + const request = await wallet.mintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); const encoded = getEncodedToken({ token: [{ mint: mintUrl, proofs: tokens.proofs }] @@ -186,7 +196,7 @@ describe('mint api', () => { const privKeyBob = secp256k1.utils.randomPrivateKey(); const pubKeyBob = secp256k1.getPublicKey(privKeyBob); - const request = await wallet.getMintQuote(64); + const request = await wallet.mintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); const { send } = await wallet.send(64, tokens.proofs, { pubkey: bytesToHex(pubKeyBob) }); From 492cc208f637870e199dcff4346758ed37e21ec2 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 5 Jun 2024 16:13:53 +0900 Subject: [PATCH 139/175] fix tests --- src/CashuWallet.ts | 4 ---- test/request.test.ts | 14 ++++++++------ test/wallet.test.ts | 12 ++++++------ 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index c84f260d0..519912324 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -17,15 +17,11 @@ import { type MeltQuotePayload, type SendResponse, type SerializedBlindedMessage, -<<<<<<< HEAD -======= type SwapPayload, ->>>>>>> split to swap type Token, type TokenEntry, CheckStateEnum, SerializedBlindedSignature, - SwapPayload } from './model/types/index.js'; import { bytesToNumber, diff --git a/test/request.test.ts b/test/request.test.ts index 40c272dc1..32bd9cff1 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -22,9 +22,10 @@ describe('requests', () => { test('request with body contains the correct headers', async () => { const mint = new CashuMint(mintUrl); nock(mintUrl) - .post('/v1/melt/quote/bolt11') + .get('/v1/melt/quote/bolt11/test') .reply(200, function () { - request = this.req.headers; + request = this.req.headers + console.log(this.req.headers) return { quote: 'test_melt_quote_id', amount: 2000, @@ -33,16 +34,16 @@ describe('requests', () => { }); const wallet = new CashuWallet(mint, { unit }); - await wallet.getMeltQuote(invoice); + await wallet.getMeltQuote('test'); expect(request).toBeDefined(); - expect(request!['content-type']).toContain('application/json'); + // expect(request!['content-type']).toContain('application/json'); expect(request!['accept']).toContain('application/json, text/plain, */*'); }); test('global custom headers can be set', async () => { const mint = new CashuMint(mintUrl); nock(mintUrl) - .post('/v1/melt/quote/bolt11') + .get('/v1/melt/quote/bolt11/test') .reply(200, function () { request = this.req.headers; return { @@ -54,7 +55,8 @@ describe('requests', () => { const wallet = new CashuWallet(mint, { unit }); setGlobalRequestOptions({ headers: { 'x-cashu': 'xyz-123-abc' } }); - await wallet.getMeltQuote(invoice); + + await wallet.getMeltQuote('test'); expect(request).toBeDefined(); expect(request!['x-cashu']).toContain('xyz-123-abc'); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index e32b300d3..47574b987 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -34,14 +34,14 @@ beforeEach(() => { describe('test fees', () => { test('test melt quote fees', async () => { - nock(mintUrl).post('/v1/melt/quote/bolt11').reply(200, { + nock(mintUrl).get('/v1/melt/quote/bolt11/test').reply(200, { quote: 'test_melt_quote_id', amount: 2000, fee_reserve: 20 }); const wallet = new CashuWallet(mint, { unit }); - const fee = await wallet.getMeltQuote(invoice); + const fee = await wallet.getMeltQuote('test'); const amount = 2000; expect(fee.fee_reserve + amount).toEqual(2020); @@ -220,12 +220,12 @@ describe('payLnInvoice', () => { ]; test('test payLnInvoice base case', async () => { nock(mintUrl) - .post('/v1/melt/quote/bolt11') + .get('/v1/melt/quote/bolt11/test') .reply(200, { quote: 'quote_id', amount: 123, fee_reserve: 0 }); nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, payment_preimage: '' }); const wallet = new CashuWallet(mint, { unit }); - const meltQuote = await wallet.getMeltQuote('lnbcabbc'); + const meltQuote = await wallet.getMeltQuote('test'); const result = await wallet.payLnInvoice(invoice, proofs, meltQuote); @@ -248,7 +248,7 @@ describe('payLnInvoice', () => { ] }); nock(mintUrl) - .post('/v1/melt/quote/bolt11') + .get('/v1/melt/quote/bolt11/test') .reply(200, { quote: 'quote_id', amount: 123, fee_reserve: 2 }); nock(mintUrl) .post('/v1/melt/bolt11') @@ -265,7 +265,7 @@ describe('payLnInvoice', () => { }); const wallet = new CashuWallet(mint, { unit }); - const meltQuote = await wallet.getMeltQuote('lnbcabbc'); + const meltQuote = await wallet.getMeltQuote('test'); const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }], meltQuote); expect(result.isPaid).toBe(true); From b37120c67ad8da1153a61137ca78fa9d91375316 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 6 Jun 2024 09:46:26 +0900 Subject: [PATCH 140/175] fix integration tests --- 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 c1826ec5f..f72209a52 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -38,7 +38,7 @@ describe('mint api', () => { const wallet = new CashuWallet(mint, { unit }); const request = await wallet.mintQuote(100); expect(request).toBeDefined(); - const mintQuote = await wallet.getMeltQuote(request.quote); + const mintQuote = await wallet.getMintQuote(request.quote); expect(mintQuote).toBeDefined(); }); test('mint tokens', async () => { @@ -76,17 +76,17 @@ describe('mint api', () => { const tokens = await wallet.mintTokens(100, request.quote); // expect no fee because local invoice - const meltQuote = await wallet.mintQuote(10); - const quote = await wallet.meltQuote(meltQuote.request); + const mintQuote = await wallet.mintQuote(10); + const quote = await wallet.meltQuote(mintQuote.request); const fee = quote.fee_reserve; expect(fee).toBe(0); // get the quote from the mint - const quote_ = await wallet.getMeltQuote(meltQuote.quote); + const quote_ = await wallet.getMeltQuote(quote.quote); expect(quote_).toBeDefined(); const sendResponse = await wallet.send(10, tokens.proofs); - const response = await wallet.payLnInvoice(meltQuote.request, sendResponse.send, quote); + const response = await wallet.payLnInvoice(mintQuote.request, sendResponse.send, quote); expect(response).toBeDefined(); // expect that we have received the fee back, since it was internal expect(response.change.reduce((a, b) => a + b.amount, 0)).toBe(fee); From d6b108e334f7683665c528d32536560f73e0cc44 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 6 Jun 2024 09:53:20 +0900 Subject: [PATCH 141/175] add workflows for staging --- .github/workflows/coverage.yml | 3 ++- .github/workflows/typedoc.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 94507fb8e..4210aac67 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -7,6 +7,7 @@ on: push: branches: - main + - staging jobs: build: @@ -29,5 +30,5 @@ jobs: uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - destination_dir: coverage + destination_dir: coverage/${{ github.head_ref || github.ref_name }} publish_dir: ./coverage/lcov-report diff --git a/.github/workflows/typedoc.yml b/.github/workflows/typedoc.yml index c8860313e..a41da2a88 100644 --- a/.github/workflows/typedoc.yml +++ b/.github/workflows/typedoc.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - staging jobs: build: @@ -28,5 +29,5 @@ jobs: uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - destination_dir: docs + destination_dir: docs/${{ github.head_ref || github.ref_name }} publish_dir: ./docs From 7a57ba989fc8004a7c1bcb2a3d8f042a053920bc Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 6 Jun 2024 11:19:26 +0900 Subject: [PATCH 142/175] fix version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89a642c84..0d465ecc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.6", diff --git a/package.json b/package.json index 8669695ea..2aadd10cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From 3de7f1bc608f43e436b22e43867499c10be1b42c Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 6 Jun 2024 11:19:35 +0900 Subject: [PATCH 143/175] 1.0.0-rc.6 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d465ecc2..c48640f19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.5", + "version": "1.0.0-rc.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.5", + "version": "1.0.0-rc.6", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.6", diff --git a/package.json b/package.json index 2aadd10cf..ef6118c85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.5", + "version": "1.0.0-rc.6", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From 6c4c189e3c60ceeffd7ff6e0428fe5f5134ffb1d Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 6 Jun 2024 17:57:52 +0900 Subject: [PATCH 144/175] remove multimint receive --- src/CashuMint.ts | 4 +- src/CashuWallet.ts | 63 +++++++++------------------- src/model/types/index.ts | 9 +--- test/integration.test.ts | 22 ++++------ test/request.test.ts | 6 +-- test/wallet.test.ts | 88 ++++++++++++---------------------------- 6 files changed, 62 insertions(+), 130 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 3cbead7cc..fc9873fb9 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -134,7 +134,7 @@ class CashuMint { const requestInstance = customRequest || request; return requestInstance({ endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11', quote), - method: 'GET', + method: 'GET' }); } /** @@ -231,7 +231,7 @@ class CashuMint { const requestInstance = customRequest || request; const data = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/melt/quote/bolt11', quote), - method: 'GET', + method: 'GET' }); if ( diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 519912324..5ec2e1b10 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -21,13 +21,14 @@ import { type Token, type TokenEntry, CheckStateEnum, - SerializedBlindedSignature, + SerializedBlindedSignature } from './model/types/index.js'; import { bytesToNumber, cleanToken, getDecodedToken, getDefaultAmountPreference, + getEncodedToken, splitAmount } from './utils.js'; import { validateMnemonic } from '@scure/bip39'; @@ -106,7 +107,7 @@ class CashuWallet { } /** - * Receive an encoded or raw Cashu token + * Receive an encoded or raw Cashu token (only supports single tokens. It will only process the first token in the token array) * @param {(string|Token)} token - Cashu token * @param preference optional preference for splitting proofs into specific amounts * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect @@ -123,41 +124,23 @@ class CashuWallet { pubkey?: string; privkey?: string; } - ): Promise { - let decodedToken: Array; + ): Promise> { if (typeof token === 'string') { - decodedToken = cleanToken(getDecodedToken(token)).token; - } else { - decodedToken = token.token; + token = getDecodedToken(token); } - const tokenEntries: Array = []; - const tokenEntriesWithError: Array = []; - for (const tokenEntry of decodedToken) { - if (!tokenEntry?.proofs?.length) { - continue; - } - try { - const { proofs, proofsWithError } = await this.receiveTokenEntry(tokenEntry, { - keysetId: options?.keysetId, - preference: options?.preference, - counter: options?.counter, - pubkey: options?.pubkey, - privkey: options?.privkey - }); - if (proofsWithError?.length) { - tokenEntriesWithError.push(tokenEntry); - continue; - } - tokenEntries.push({ mint: tokenEntry.mint, proofs: [...proofs] }); - } catch (error) { - console.error(error); - tokenEntriesWithError.push(tokenEntry); - } + const tokenEntries: Array = token.token; + try { + const proofs = await this.receiveTokenEntry(tokenEntries[0], { + keysetId: options?.keysetId, + preference: options?.preference, + counter: options?.counter, + pubkey: options?.pubkey, + privkey: options?.privkey + }); + return proofs; + } catch (error) { + throw new Error('Error when receiving'); } - return { - token: { token: tokenEntries }, - tokensWithErrors: tokenEntriesWithError.length ? { token: tokenEntriesWithError } : undefined - }; } /** @@ -178,8 +161,7 @@ class CashuWallet { pubkey?: string; privkey?: string; } - ): Promise { - const proofsWithError: Array = []; + ): Promise> { const proofs: Array = []; try { const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); @@ -206,13 +188,9 @@ class CashuWallet { ); proofs.push(...newProofs); } catch (error) { - console.error(error); - proofsWithError.push(...tokenEntry.proofs); + throw new Error('Error receiving token entry'); } - return { - proofs, - proofsWithError: proofsWithError.length ? proofsWithError : undefined - }; + return proofs; } /** @@ -375,7 +353,6 @@ class CashuWallet { return await this.mint.getMintQuote(quote); } - /** * Mint tokens for a given mint quote * @param amount amount to request diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 66626112b..30b9d3a00 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -89,10 +89,6 @@ export type ReceiveTokenEntryResponse = { * Received proofs */ proofs: Array; - /** - * Proofs that could not be received. Doesn't throw an error, but if this field is populated it should be handled by the implementation accordingly - */ - proofsWithError: Array | undefined; }; /** @@ -174,7 +170,6 @@ export type MeltQuoteResponse = { * Timestamp of when the quote expires */ expiry: number; - } & ApiError; /** @@ -290,7 +285,7 @@ export type MintQuotePayload = { */ export type MintQuoteResponse = { /** - * Payment request + * Payment request */ request: string; /** @@ -299,7 +294,7 @@ export type MintQuoteResponse = { quote: string; /** * Whether the quote has been paid. - */ + */ paid: boolean; /** * Timestamp of when the quote expires diff --git a/test/integration.test.ts b/test/integration.test.ts index f72209a52..5776334a4 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -170,8 +170,6 @@ describe('mint api', () => { }); const response = await wallet.receive(encoded); expect(response).toBeDefined(); - expect(response.token).toBeDefined(); - expect(response.tokensWithErrors).toBeUndefined(); }); test('receive tokens with previous mint', async () => { const mint = new CashuMint(mintUrl); @@ -183,8 +181,6 @@ describe('mint api', () => { }); const response = await wallet.receive(encoded); expect(response).toBeDefined(); - expect(response.token).toBeDefined(); - expect(response.tokensWithErrors).toBeUndefined(); }); test('send and receive p2pk', async () => { const mint = new CashuMint(mintUrl); @@ -204,19 +200,17 @@ describe('mint api', () => { token: [{ mint: mintUrl, proofs: send }] }); - const res = await wallet.receive(encoded, { privkey: bytesToHex(privKeyAlice) }).catch(); - expect(res.token.token).toEqual([]); - expect(res.tokensWithErrors?.token.length).toBe(1); + const result = await wallet + .receive(encoded, { privkey: bytesToHex(privKeyAlice) }) + .catch((e) => e); + expect(result).toEqual(new Error('Error when receiving')); - const { token } = await wallet.receive(encoded, { privkey: bytesToHex(privKeyBob) }); + const proofs = await wallet.receive(encoded, { privkey: bytesToHex(privKeyBob) }); expect( - token.token - .map((t) => t.proofs) - .flat() - .reduce((curr, acc) => { - return curr + acc.amount; - }, 0) + proofs.reduce((curr, acc) => { + return curr + acc.amount; + }, 0) ).toBe(64); }); }); diff --git a/test/request.test.ts b/test/request.test.ts index 32bd9cff1..bdb6d30dc 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -24,8 +24,8 @@ describe('requests', () => { nock(mintUrl) .get('/v1/melt/quote/bolt11/test') .reply(200, function () { - request = this.req.headers - console.log(this.req.headers) + request = this.req.headers; + console.log(this.req.headers); return { quote: 'test_melt_quote_id', amount: 2000, @@ -55,7 +55,7 @@ describe('requests', () => { const wallet = new CashuWallet(mint, { unit }); setGlobalRequestOptions({ headers: { 'x-cashu': 'xyz-123-abc' } }); - + await wallet.getMeltQuote('test'); expect(request).toBeDefined(); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 47574b987..4f0e92dcf 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -4,6 +4,7 @@ import { CashuWallet } from '../src/CashuWallet.js'; import { MeltQuoteResponse, ReceiveResponse } from '../src/model/types/index.js'; import { cleanToken, getDecodedToken } from '../src/utils.js'; import { AmountPreference } from '../src/model/types/index'; +import { Proof } from '@cashu/crypto/modules/common'; const dummyKeysResp = { keysets: [ @@ -65,17 +66,12 @@ describe('receive', () => { }); const wallet = new CashuWallet(mint, { unit }); - const response: ReceiveResponse = await wallet.receive(tokenInput); + const proofs = await wallet.receive(tokenInput); - expect(response.token.token).toHaveLength(1); - expect(response.token.token[0].proofs).toHaveLength(1); - expect(response.token.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: '009a1f293253e41e' }], - mint: mintUrl - }); - expect(/[0-9a-f]{64}/.test(response.token.token[0].proofs[0].C)).toBe(true); - expect(/[0-9a-f]{64}/.test(response.token.token[0].proofs[0].secret)).toBe(true); - expect(response.tokensWithErrors).toBe(undefined); + expect(proofs).toHaveLength(1); + expect(proofs).toMatchObject([{ amount: 1, id: '009a1f293253e41e' }]); + expect(/[0-9a-f]{64}/.test(proofs[0].C)).toBe(true); + expect(/[0-9a-f]{64}/.test(proofs[0].secret)).toBe(true); }); test('test receive raw token', async () => { @@ -94,17 +90,12 @@ describe('receive', () => { }); const wallet = new CashuWallet(mint); - const { token: t, tokensWithErrors } = await wallet.receive(decodedInput); + const proofs = await wallet.receive(decodedInput); - expect(t.token).toHaveLength(1); - expect(t.token[0].proofs).toHaveLength(1); - expect(t.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: 'z32vUtKgNCm1' }], - mint: 'http://localhost:3338' - }); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); - expect(tokensWithErrors).toBe(undefined); + expect(proofs).toHaveLength(1); + expect(proofs).toMatchObject([{ amount: 1, id: 'z32vUtKgNCm1' }]); + expect(/[0-9a-f]{64}/.test(proofs[0].C)).toBe(true); + expect(/[0-9a-f]{64}/.test(proofs[0].secret)).toBe(true); }); test('test receive custom split', async () => { nock(mintUrl) @@ -133,58 +124,33 @@ describe('receive', () => { const token3sat = 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0='; - const response: ReceiveResponse = await wallet.receive(token3sat, { + const proofs = await wallet.receive(token3sat, { preference: [{ amount: 1, count: 3 }] }); - expect(response.token.token).toHaveLength(1); - expect(response.token.token[0].proofs).toHaveLength(3); - expect(response.token.token[0]).toMatchObject({ - proofs: [ - { amount: 1, id: '009a1f293253e41e' }, - { amount: 1, id: '009a1f293253e41e' }, - { amount: 1, id: '009a1f293253e41e' } - ] - }); - expect(/[0-9a-f]{64}/.test(response.token.token[0].proofs[0].C)).toBe(true); - expect(/[0-9a-f]{64}/.test(response.token.token[0].proofs[0].secret)).toBe(true); - expect(response.tokensWithErrors).toBe(undefined); + expect(proofs).toHaveLength(3); + expect(proofs).toMatchObject([ + { amount: 1, id: '009a1f293253e41e' }, + { amount: 1, id: '009a1f293253e41e' }, + { amount: 1, id: '009a1f293253e41e' } + ]); + expect(/[0-9a-f]{64}/.test(proofs[0].C)).toBe(true); + expect(/[0-9a-f]{64}/.test(proofs[0].secret)).toBe(true); }); test('test receive tokens already spent', async () => { const msg = 'tokens already spent. Secret: asdasdasd'; - nock(mintUrl).post('/v1/swap').reply(200, { detail: msg }); + nock(mintUrl).post('/v1/swap').reply(400, { detail: msg }); const wallet = new CashuWallet(mint, { unit }); - - const { tokensWithErrors } = await wallet.receive(tokenInput); - const t = tokensWithErrors!; - - expect(tokensWithErrors).toBeDefined(); - expect(t.token).toHaveLength(1); - expect(t.token[0].proofs).toHaveLength(1); - expect(t.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: '009a1f293253e41e' }], - mint: 'http://localhost:3338' - }); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); + const result = await wallet.receive(tokenInput).catch((e) => e); + expect(result).toEqual(new Error('Error when receiving')); }); + test('test receive could not verify proofs', async () => { - nock(mintUrl).post('/v1/swap').reply(200, { code: 0, error: 'could not verify proofs.' }); + nock(mintUrl).post('/v1/swap').reply(400, { code: 0, error: 'could not verify proofs.' }); const wallet = new CashuWallet(mint, { unit }); - - const { tokensWithErrors } = await wallet.receive(tokenInput); - const t = tokensWithErrors!; - - expect(tokensWithErrors).toBeDefined(); - expect(t.token).toHaveLength(1); - expect(t.token[0].proofs).toHaveLength(1); - expect(t.token[0]).toMatchObject({ - proofs: [{ amount: 1, id: '009a1f293253e41e' }], - mint: 'http://localhost:3338' - }); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].C)).toBe(true); - expect(/[0-9a-f]{64}/.test(t.token[0].proofs[0].secret)).toBe(true); + const result = await wallet.receive(tokenInput).catch((e) => e); + expect(result).toEqual(new Error('Error when receiving')); }); }); From acb91f4593d664729a991fd63edbd33970806ed2 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 6 Jun 2024 18:07:39 +0900 Subject: [PATCH 145/175] remove unused stuff --- src/CashuWallet.ts | 19 ++++++++----------- src/utils.ts | 32 -------------------------------- 2 files changed, 8 insertions(+), 43 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 5ec2e1b10..5f23141f3 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -11,8 +11,6 @@ import { type MeltTokensResponse, type MintPayload, type Proof, - type ReceiveResponse, - type ReceiveTokenEntryResponse, type MintQuotePayload, type MeltQuotePayload, type SendResponse, @@ -21,14 +19,12 @@ import { type Token, type TokenEntry, CheckStateEnum, - SerializedBlindedSignature + SerializedBlindedSignature, } from './model/types/index.js'; import { bytesToNumber, - cleanToken, getDecodedToken, getDefaultAmountPreference, - getEncodedToken, splitAmount } from './utils.js'; import { validateMnemonic } from '@scure/bip39'; @@ -125,11 +121,11 @@ class CashuWallet { privkey?: string; } ): Promise> { - if (typeof token === 'string') { - token = getDecodedToken(token); - } - const tokenEntries: Array = token.token; try { + if (typeof token === 'string') { + token = getDecodedToken(token) + } + const tokenEntries: Array = token.token const proofs = await this.receiveTokenEntry(tokenEntries[0], { keysetId: options?.keysetId, preference: options?.preference, @@ -137,7 +133,7 @@ class CashuWallet { pubkey: options?.pubkey, privkey: options?.privkey }); - return proofs; + return proofs } catch (error) { throw new Error('Error when receiving'); } @@ -188,7 +184,7 @@ class CashuWallet { ); proofs.push(...newProofs); } catch (error) { - throw new Error('Error receiving token entry'); + throw new Error("Error receiving token entry"); } return proofs; } @@ -353,6 +349,7 @@ class CashuWallet { return await this.mint.getMintQuote(quote); } + /** * Mint tokens for a given mint quote * @param amount amount to request diff --git a/src/utils.ts b/src/utils.ts index 1fcbd11da..3485bec73 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -136,38 +136,6 @@ function mergeUInt8Arrays(a1: Uint8Array, a2: Uint8Array): Uint8Array { return mergedArray; } -/** - * merge proofs from same mint, - * removes TokenEntrys with no proofs or no mint field - * and sorts proofs by id - * - * @export - * @param {Token} token - * @return {*} {Token} - */ -export function cleanToken(token: Token): Token { - const tokenEntryMap: { [key: string]: TokenEntry } = {}; - for (const tokenEntry of token.token) { - if (!tokenEntry?.proofs?.length || !tokenEntry?.mint) { - continue; - } - if (tokenEntryMap[tokenEntry.mint]) { - tokenEntryMap[tokenEntry.mint].proofs.push(...[...tokenEntry.proofs]); - continue; - } - tokenEntryMap[tokenEntry.mint] = { - mint: tokenEntry.mint, - proofs: [...tokenEntry.proofs] - }; - } - return { - memo: token?.memo, - token: Object.values(tokenEntryMap).map((x) => ({ - ...x, - proofs: sortProofsById(x.proofs) - })) - }; -} export function sortProofsById(proofs: Array) { return proofs.sort((a, b) => a.id.localeCompare(b.id)); } From 29de140bdb9a3ebae5221264037a19e22d61e5d5 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 6 Jun 2024 18:07:59 +0900 Subject: [PATCH 146/175] format --- src/CashuWallet.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 5f23141f3..1d0712db0 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -19,7 +19,7 @@ import { type Token, type TokenEntry, CheckStateEnum, - SerializedBlindedSignature, + SerializedBlindedSignature } from './model/types/index.js'; import { bytesToNumber, @@ -123,9 +123,9 @@ class CashuWallet { ): Promise> { try { if (typeof token === 'string') { - token = getDecodedToken(token) + token = getDecodedToken(token); } - const tokenEntries: Array = token.token + const tokenEntries: Array = token.token; const proofs = await this.receiveTokenEntry(tokenEntries[0], { keysetId: options?.keysetId, preference: options?.preference, @@ -133,7 +133,7 @@ class CashuWallet { pubkey: options?.pubkey, privkey: options?.privkey }); - return proofs + return proofs; } catch (error) { throw new Error('Error when receiving'); } @@ -184,7 +184,7 @@ class CashuWallet { ); proofs.push(...newProofs); } catch (error) { - throw new Error("Error receiving token entry"); + throw new Error('Error receiving token entry'); } return proofs; } @@ -349,7 +349,6 @@ class CashuWallet { return await this.mint.getMintQuote(quote); } - /** * Mint tokens for a given mint quote * @param amount amount to request From 31991a609d2f9eff597e06237865e7f9ee771362 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 6 Jun 2024 18:12:02 +0900 Subject: [PATCH 147/175] remove more unneded stuff --- test/utils.test.ts | 118 -------------------------------------------- test/wallet.test.ts | 4 +- 2 files changed, 2 insertions(+), 120 deletions(-) diff --git a/test/utils.test.ts b/test/utils.test.ts index 898ae22bb..c43b36fe9 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -182,124 +182,6 @@ describe('test decode token', () => { }); }); -describe('test cleanToken', () => { - test('testing cleanToken no mint', async () => { - const obj = { - token: [ - { - proofs: [ - { - C: '02195081e622f98bfc19a05ebe2341d955c0d12588c5948c858d07adec007bc1e4', - amount: 1, - id: 'I2yN+iRYfkzT', - secret: '97zfmmaGf5k8Mg0gajpnbmpervTtEeE8wwKri7rWpUs=' - } - ], - mint: '' - } - ] - }; - const result = utils.cleanToken(obj); - expect(result).toStrictEqual({ token: [], memo: undefined }); - }); - test('testing cleanToken no proofs', async () => { - const obj = { - token: [ - { - proofs: [], - mint: 'test' - } - ] - }; - const result = utils.cleanToken(obj); - expect(result).toStrictEqual({ token: [], memo: undefined }); - }); - test('testing cleanToken 2 tokenEntries from same mint', async () => { - const obj = { - token: [ - { - proofs: [ - { - C: '02195081e622f98bfc19a05ebe2341d955c0d12588c5948c858d07adec007bc1e4', - amount: 1, - id: 'I2yN+iRYfkzT', - secret: '97zfmmaGf5k8Mg0gajpnbmpervTtEeE8wwKri7rWpUs=' - } - ], - mint: 'test' - }, - { - proofs: [ - { - C: '02195081e622f98bfc19a05ebe2341d955c0d12588c5948c858d07adec007bc1e4', - amount: 1, - id: 'I2yN+iRYfkzT', - secret: '97zfmmaGf5k8Mg0gajpnbmpervTtEeE8wwKri7rWpUs=' - } - ], - mint: 'test' - } - ] - }; - const result = utils.cleanToken(obj); - - expect(result.token).toHaveLength(1); - expect(result).toStrictEqual({ - token: [ - { - mint: 'test', - proofs: obj.token.flatMap((x) => x.proofs) - } - ], - memo: undefined - }); - }); - test('testing cleanToken proofs sorted', async () => { - const obj = { - token: [ - { - proofs: [ - { - C: '02195081e622f98bfc19a05ebe2341d955c0d12588c5948c858d07adec007bc1e4', - amount: 1, - id: 'I2yN+iRYfkzT', - secret: '97zfmmaGf5k8Mg0gajpnbmpervTtEeE8wwKri7rWpUs=' - }, - { - C: '02195081e622f98bfc19a05ebe2341d955c0d12588c5948c858d07adec007bc1e4', - amount: 1, - id: 'test', - secret: '97zfmmaGf5k8Mg0gajpnbmpervTtEeE8wwKri7rWpUs=' - }, - { - C: '02195081e622f98bfc19a05ebe2341d955c0d12588c5948c858d07adec007bc1e4', - amount: 1, - id: 'I2yN+iRYfkzT', - secret: '97zfmmaGf5k8Mg0gajpnbmpervTtEeE8wwKri7rWpUs=' - } - ], - mint: 'test' - } - ] - }; - const result = utils.cleanToken(obj); - expect(result.token[0].proofs).toHaveLength(3); - expect(result.token[0].proofs[0].id).toBe('I2yN+iRYfkzT'); - expect(result.token[0].proofs[1].id).toBe('I2yN+iRYfkzT'); - expect(result.token[0].proofs[2].id).toBe('test'); - }); - test('testing joining urls', () => { - const mint_url = 'http://localhost:3338'; - const info_url = utils.joinUrls(mint_url, 'info'); - - expect(info_url).toBe('http://localhost:3338/info'); - - const mint_url_trailing_slash = 'http://localhost:3338/'; - const mint_info_url = utils.joinUrls(mint_url_trailing_slash, 'info'); - expect(mint_info_url).toBe('http://localhost:3338/info'); - }); -}); - describe('test keyset derivation', () => { test('derive', () => { const keys = PUBKEYS; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 4f0e92dcf..9b575b36b 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -2,7 +2,7 @@ import nock from 'nock'; import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; import { MeltQuoteResponse, ReceiveResponse } from '../src/model/types/index.js'; -import { cleanToken, getDecodedToken } from '../src/utils.js'; +import { getDecodedToken } from '../src/utils.js'; import { AmountPreference } from '../src/model/types/index'; import { Proof } from '@cashu/crypto/modules/common'; @@ -75,7 +75,7 @@ describe('receive', () => { }); test('test receive raw token', async () => { - const decodedInput = cleanToken(getDecodedToken(tokenInput)); + const decodedInput = getDecodedToken(tokenInput); nock(mintUrl) .post('/v1/swap') From a5475a81dc7144d647aa1c0fe216fd14350924d6 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 8 Jun 2024 17:12:14 +0900 Subject: [PATCH 148/175] migration --- migration-1.0.0.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/migration-1.0.0.md b/migration-1.0.0.md index 2e4b0d1ac..5489fb816 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -25,6 +25,10 @@ Decoding LN invoices is no longer used inside the lib. ### `CashuWallet` interface changes +**`receive()` does no longer support multi-token tokens** + +To reduce complexity, simplify error handling and to prepare for token V4, this feature has been removed. only the first token inside a token will be processed + **optional function AND constructor parameters are now in an onpional `options?` Object** Utility functions now have an `options` object for optional parameters, instead of passing them directly From 4c78513d4b8f8f8232066f4135386ba7341e69ce Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Sat, 8 Jun 2024 17:12:33 +0900 Subject: [PATCH 149/175] 1.0.0-rc.7 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c48640f19..cd63d7480 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.6", + "version": "1.0.0-rc.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.6", + "version": "1.0.0-rc.7", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.6", diff --git a/package.json b/package.json index ef6118c85..d312f5b5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.6", + "version": "1.0.0-rc.7", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From dbd3c0c87ef21c291a0c0d0ea1e82e77ef5b0bec Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 24 Jun 2024 21:29:04 +0900 Subject: [PATCH 150/175] fix import --- src/base64.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/base64.ts b/src/base64.ts index 547856d10..47bedbb74 100644 --- a/src/base64.ts +++ b/src/base64.ts @@ -1,4 +1,4 @@ -import { Buffer } from 'buffer/'; +import { Buffer } from 'buffer'; function encodeUint8toBase64(uint8array: Uint8Array): string { return Buffer.from(uint8array).toString('base64'); @@ -20,11 +20,11 @@ function encodeBase64ToJson(base64String: string): T { } function base64urlToBase64(str: string) { - return str.replace(/-/g, '+').replace(/_/g, '/'); + return str.replace(/-/g, '+').replace(/_/g, '/').replace(/./g, '='); } function base64urlFromBase64(str: string) { - return str.replace(/\+/g, '-').replace(/\//g, '_').split('=')[0]; + return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '.'); } export { encodeUint8toBase64, encodeBase64toUint8, encodeJsonToBase64, encodeBase64ToJson }; From 16021817cb21f367ff03b8a5b22120a026f5f5e5 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Mon, 24 Jun 2024 21:29:40 +0900 Subject: [PATCH 151/175] 1.0.0-rc.8 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cd63d7480..97a3bc62d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.7", + "version": "1.0.0-rc.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.7", + "version": "1.0.0-rc.8", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.6", diff --git a/package.json b/package.json index d312f5b5d..957514411 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.7", + "version": "1.0.0-rc.8", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From c251e261bf77717a65332c4a9e63782ed997c2ab Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 4 Jul 2024 17:20:59 +0900 Subject: [PATCH 152/175] fix borked base64 url-save --- src/base64.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/base64.ts b/src/base64.ts index 47bedbb74..a84b9a974 100644 --- a/src/base64.ts +++ b/src/base64.ts @@ -20,11 +20,13 @@ function encodeBase64ToJson(base64String: string): T { } function base64urlToBase64(str: string) { - return str.replace(/-/g, '+').replace(/_/g, '/').replace(/./g, '='); + return str.replace(/-/g, '+').replace(/_/g, '/').split('=')[0] + // .replace(/./g, '='); } function base64urlFromBase64(str: string) { - return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '.'); + return str.replace(/\+/g, '-').replace(/\//g, '_').split('=')[0] + // .replace(/=/g, '.'); } export { encodeUint8toBase64, encodeBase64toUint8, encodeJsonToBase64, encodeBase64ToJson }; From 243a4a794ae282e79930e52d44731164f58d056c Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 4 Jul 2024 17:22:19 +0900 Subject: [PATCH 153/175] 1.0.0-rc.9 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 97a3bc62d..13e09e24e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.8", + "version": "1.0.0-rc.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.8", + "version": "1.0.0-rc.9", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.6", diff --git a/package.json b/package.json index 957514411..4d45351be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.8", + "version": "1.0.0-rc.9", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From 53f015a2320c1037cbd9ec47cc8f98be566e3bf7 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:57:48 +0200 Subject: [PATCH 154/175] NUT-06: new info endpoint with monkeypatch for old format, see https://github.com/cashubtc/nuts/pull/117 --- src/CashuMint.ts | 18 +++++++++++++++++- src/CashuWallet.ts | 8 ++++++++ src/model/types/index.ts | 7 ++++++- test/wallet.test.ts | 21 +++++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index fc9873fb9..1c26dd13b 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -48,7 +48,23 @@ class CashuMint { customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; - return requestInstance({ endpoint: joinUrls(mintUrl, '/v1/info') }); + const data = await requestInstance({ + endpoint: joinUrls(mintUrl, '/v1/info') + }); + // BEGIN DEPRECATED + // Monkey patch old contact field ["email", "me@mail.com"] Array<[string, string]>; to new contact field [{method: "email", info: "me@mail.com"}] Array + // This is to maintain backwards compatibility with older versions of the mint + if (Array.isArray(data?.contact) && data?.contact.length > 0) { + data.contact = data.contact.map((contact: any) => { + if (Array.isArray(contact) && contact.length === 2 && typeof contact[0] === 'string' && typeof contact[1] === 'string') { + return { method: contact[0], info: contact[1] }; + } + return contact; + }); + } + // END DEPRECATED + + return data; } /** * fetches mints info at the /info endpoint diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 1d0712db0..db2884331 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -102,6 +102,14 @@ class CashuWallet { this._unit = keys.unit; } + /** + * Get information about the mint + * @returns mint info + */ + async getMintInfo() { + return this.mint.getInfo(); + } + /** * Receive an encoded or raw Cashu token (only supports single tokens. It will only process the first token in the token array) * @param {(string|Token)} token - Cashu token diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 30b9d3a00..2ac83aa3b 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -473,6 +473,11 @@ export type BlindedMessageData = { rs: Array; }; +export type MintContactInfo = { + method: string; + info: string; +}; + /** * Response from mint at /info endpoint */ @@ -482,7 +487,7 @@ export type GetInfoResponse = { version: string; description?: string; description_long?: string; - contact: Array<[string, string]>; + contact: Array; nuts: { '4': { methods: Array; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 9b575b36b..41a030b98 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -33,6 +33,27 @@ beforeEach(() => { nock(mintUrl).get('/v1/keys/009a1f293253e41e').reply(200, dummyKeysResp); }); +describe('test info', () => { + const mintInfoResp = JSON.parse('{"name":"Testnut mint","pubkey":"0296d0aa13b6a31cf0cd974249f28c7b7176d7274712c95a41c7d8066d3f29d679","version":"Nutshell/0.16.0","description":"Mint for testing Cashu wallets","description_long":"This mint usually runs the latest main branch of the nutshell repository. All your Lightning invoices will always be marked paid so that you can test minting and melting ecash via Lightning.","contact":[{"method":"email","info":"contact@me.com"},{"method":"twitter","info":"@me"},{"method":"nostr","info":"npub..."}],"motd":"This is a message of the day field. You should display this field to your users if the content changes!","nuts":{"4":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"5":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"7":{"supported":true},"8":{"supported":true},"9":{"supported":true},"10":{"supported":true},"11":{"supported":true},"12":{"supported":true},"17":[{"method":"bolt11","unit":"sat","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]},{"method":"bolt11","unit":"usd","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]}]}}'); + test('test info', async () => { + nock(mintUrl).get('/v1/info').reply(200, mintInfoResp); + const wallet = new CashuWallet(mint, { unit }); + + const info = await wallet.getMintInfo(); + expect(info.contact).toEqual([{ method: 'email', info: 'contact@me.com' }, { method: 'twitter', info: '@me' }, { method: 'nostr', info: 'npub...' }]); + expect(info).toEqual(mintInfoResp); + }); + test('test info with deprecated contact field', async () => { + // mintInfoRespDeprecated is the same as mintInfoResp but with the contact field in the old format + const mintInfoRespDeprecated = JSON.parse('{"name":"Testnut mint","pubkey":"0296d0aa13b6a31cf0cd974249f28c7b7176d7274712c95a41c7d8066d3f29d679","version":"Nutshell/0.16.0","description":"Mint for testing Cashu wallets","description_long":"This mint usually runs the latest main branch of the nutshell repository. All your Lightning invoices will always be marked paid so that you can test minting and melting ecash via Lightning.","contact":[["email","contact@me.com"],["twitter","@me"],["nostr","npub..."]],"motd":"This is a message of the day field. You should display this field to your users if the content changes!","nuts":{"4":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"5":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"7":{"supported":true},"8":{"supported":true},"9":{"supported":true},"10":{"supported":true},"11":{"supported":true},"12":{"supported":true},"17":[{"method":"bolt11","unit":"sat","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]},{"method":"bolt11","unit":"usd","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]}]}}'); + nock(mintUrl).get('/v1/info').reply(200, mintInfoRespDeprecated); + const wallet = new CashuWallet(mint, { unit }); + const info = await wallet.getMintInfo(); + expect(info.contact).toEqual([{ method: 'email', info: 'contact@me.com' }, { method: 'twitter', info: '@me' }, { method: 'nostr', info: 'npub...' }]); + expect(info).toEqual(mintInfoResp); + }); +}); + describe('test fees', () => { test('test melt quote fees', async () => { nock(mintUrl).get('/v1/melt/quote/bolt11/test').reply(200, { From 042bf2b5a6b6838a25f60e559e6ddcc9e37c1072 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:23:01 +0200 Subject: [PATCH 155/175] npm run format --- src/CashuMint.ts | 9 +++++++-- src/base64.ts | 4 ++-- test/wallet.test.ts | 20 ++++++++++++++++---- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 1c26dd13b..18d507ae5 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -51,12 +51,17 @@ class CashuMint { const data = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/info') }); - // BEGIN DEPRECATED + // BEGIN DEPRECATED // Monkey patch old contact field ["email", "me@mail.com"] Array<[string, string]>; to new contact field [{method: "email", info: "me@mail.com"}] Array // This is to maintain backwards compatibility with older versions of the mint if (Array.isArray(data?.contact) && data?.contact.length > 0) { data.contact = data.contact.map((contact: any) => { - if (Array.isArray(contact) && contact.length === 2 && typeof contact[0] === 'string' && typeof contact[1] === 'string') { + if ( + Array.isArray(contact) && + contact.length === 2 && + typeof contact[0] === 'string' && + typeof contact[1] === 'string' + ) { return { method: contact[0], info: contact[1] }; } return contact; diff --git a/src/base64.ts b/src/base64.ts index a84b9a974..7f1b09237 100644 --- a/src/base64.ts +++ b/src/base64.ts @@ -20,12 +20,12 @@ function encodeBase64ToJson(base64String: string): T { } function base64urlToBase64(str: string) { - return str.replace(/-/g, '+').replace(/_/g, '/').split('=')[0] + return str.replace(/-/g, '+').replace(/_/g, '/').split('=')[0]; // .replace(/./g, '='); } function base64urlFromBase64(str: string) { - return str.replace(/\+/g, '-').replace(/\//g, '_').split('=')[0] + return str.replace(/\+/g, '-').replace(/\//g, '_').split('=')[0]; // .replace(/=/g, '.'); } diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 41a030b98..3d3575883 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -34,22 +34,34 @@ beforeEach(() => { }); describe('test info', () => { - const mintInfoResp = JSON.parse('{"name":"Testnut mint","pubkey":"0296d0aa13b6a31cf0cd974249f28c7b7176d7274712c95a41c7d8066d3f29d679","version":"Nutshell/0.16.0","description":"Mint for testing Cashu wallets","description_long":"This mint usually runs the latest main branch of the nutshell repository. All your Lightning invoices will always be marked paid so that you can test minting and melting ecash via Lightning.","contact":[{"method":"email","info":"contact@me.com"},{"method":"twitter","info":"@me"},{"method":"nostr","info":"npub..."}],"motd":"This is a message of the day field. You should display this field to your users if the content changes!","nuts":{"4":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"5":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"7":{"supported":true},"8":{"supported":true},"9":{"supported":true},"10":{"supported":true},"11":{"supported":true},"12":{"supported":true},"17":[{"method":"bolt11","unit":"sat","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]},{"method":"bolt11","unit":"usd","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]}]}}'); + const mintInfoResp = JSON.parse( + '{"name":"Testnut mint","pubkey":"0296d0aa13b6a31cf0cd974249f28c7b7176d7274712c95a41c7d8066d3f29d679","version":"Nutshell/0.16.0","description":"Mint for testing Cashu wallets","description_long":"This mint usually runs the latest main branch of the nutshell repository. All your Lightning invoices will always be marked paid so that you can test minting and melting ecash via Lightning.","contact":[{"method":"email","info":"contact@me.com"},{"method":"twitter","info":"@me"},{"method":"nostr","info":"npub..."}],"motd":"This is a message of the day field. You should display this field to your users if the content changes!","nuts":{"4":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"5":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"7":{"supported":true},"8":{"supported":true},"9":{"supported":true},"10":{"supported":true},"11":{"supported":true},"12":{"supported":true},"17":[{"method":"bolt11","unit":"sat","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]},{"method":"bolt11","unit":"usd","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]}]}}' + ); test('test info', async () => { nock(mintUrl).get('/v1/info').reply(200, mintInfoResp); const wallet = new CashuWallet(mint, { unit }); const info = await wallet.getMintInfo(); - expect(info.contact).toEqual([{ method: 'email', info: 'contact@me.com' }, { method: 'twitter', info: '@me' }, { method: 'nostr', info: 'npub...' }]); + expect(info.contact).toEqual([ + { method: 'email', info: 'contact@me.com' }, + { method: 'twitter', info: '@me' }, + { method: 'nostr', info: 'npub...' } + ]); expect(info).toEqual(mintInfoResp); }); test('test info with deprecated contact field', async () => { // mintInfoRespDeprecated is the same as mintInfoResp but with the contact field in the old format - const mintInfoRespDeprecated = JSON.parse('{"name":"Testnut mint","pubkey":"0296d0aa13b6a31cf0cd974249f28c7b7176d7274712c95a41c7d8066d3f29d679","version":"Nutshell/0.16.0","description":"Mint for testing Cashu wallets","description_long":"This mint usually runs the latest main branch of the nutshell repository. All your Lightning invoices will always be marked paid so that you can test minting and melting ecash via Lightning.","contact":[["email","contact@me.com"],["twitter","@me"],["nostr","npub..."]],"motd":"This is a message of the day field. You should display this field to your users if the content changes!","nuts":{"4":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"5":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"7":{"supported":true},"8":{"supported":true},"9":{"supported":true},"10":{"supported":true},"11":{"supported":true},"12":{"supported":true},"17":[{"method":"bolt11","unit":"sat","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]},{"method":"bolt11","unit":"usd","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]}]}}'); + const mintInfoRespDeprecated = JSON.parse( + '{"name":"Testnut mint","pubkey":"0296d0aa13b6a31cf0cd974249f28c7b7176d7274712c95a41c7d8066d3f29d679","version":"Nutshell/0.16.0","description":"Mint for testing Cashu wallets","description_long":"This mint usually runs the latest main branch of the nutshell repository. All your Lightning invoices will always be marked paid so that you can test minting and melting ecash via Lightning.","contact":[["email","contact@me.com"],["twitter","@me"],["nostr","npub..."]],"motd":"This is a message of the day field. You should display this field to your users if the content changes!","nuts":{"4":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"5":{"methods":[{"method":"bolt11","unit":"sat"},{"method":"bolt11","unit":"usd"}],"disabled":false},"7":{"supported":true},"8":{"supported":true},"9":{"supported":true},"10":{"supported":true},"11":{"supported":true},"12":{"supported":true},"17":[{"method":"bolt11","unit":"sat","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]},{"method":"bolt11","unit":"usd","commands":["bolt11_melt_quote","proof_state","bolt11_mint_quote"]}]}}' + ); nock(mintUrl).get('/v1/info').reply(200, mintInfoRespDeprecated); const wallet = new CashuWallet(mint, { unit }); const info = await wallet.getMintInfo(); - expect(info.contact).toEqual([{ method: 'email', info: 'contact@me.com' }, { method: 'twitter', info: '@me' }, { method: 'nostr', info: 'npub...' }]); + expect(info.contact).toEqual([ + { method: 'email', info: 'contact@me.com' }, + { method: 'twitter', info: '@me' }, + { method: 'nostr', info: 'npub...' } + ]); expect(info).toEqual(mintInfoResp); }); }); From ff1f3163ac99c32b2d0f0b799da257ab86d2251c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:07:37 +0200 Subject: [PATCH 156/175] contact field explicit type --- src/CashuMint.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 18d507ae5..a41b4a28c 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -16,7 +16,8 @@ import type { MintResponse, PostRestorePayload, MeltQuotePayload, - MeltQuoteResponse + MeltQuoteResponse, + MintContactInfo } from './model/types/index.js'; import request from './request.js'; import { isObj, joinUrls, sanitizeUrl } from './utils.js'; @@ -55,14 +56,14 @@ class CashuMint { // Monkey patch old contact field ["email", "me@mail.com"] Array<[string, string]>; to new contact field [{method: "email", info: "me@mail.com"}] Array // This is to maintain backwards compatibility with older versions of the mint if (Array.isArray(data?.contact) && data?.contact.length > 0) { - data.contact = data.contact.map((contact: any) => { + data.contact = data.contact.map((contact: MintContactInfo) => { if ( Array.isArray(contact) && contact.length === 2 && typeof contact[0] === 'string' && typeof contact[1] === 'string' ) { - return { method: contact[0], info: contact[1] }; + return { method: contact[0], info: contact[1] } as MintContactInfo; } return contact; }); From 00d3bc675228d14efc4252be2f7cce4c7907a628 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:00:16 +0200 Subject: [PATCH 157/175] add state enum changes of NUT-04 and NUT-05 --- src/CashuMint.ts | 45 ++++++++++++++++++++++++++++---------- src/CashuWallet.ts | 5 +++-- src/base64.ts | 4 ++-- src/legacy/nut-04.ts | 21 ++++++++++++++++++ src/legacy/nut-05.ts | 21 ++++++++++++++++++ src/model/types/index.ts | 47 +++++++++++++++++++++------------------- test/request.test.ts | 13 +++++++---- test/wallet.test.ts | 47 +++++++++++++++++++++++++++++++--------- 8 files changed, 151 insertions(+), 52 deletions(-) create mode 100644 src/legacy/nut-04.ts create mode 100644 src/legacy/nut-05.ts diff --git a/src/CashuMint.ts b/src/CashuMint.ts index fc9873fb9..e28635cf2 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -3,7 +3,6 @@ import type { CheckStateResponse, GetInfoResponse, MeltPayload, - MeltResponse, MintActiveKeys, MintAllKeysets, PostRestoreResponse, @@ -18,9 +17,17 @@ import type { MeltQuotePayload, MeltQuoteResponse } from './model/types/index.js'; +import { MeltQuoteState } from './model/types/index.js'; import request from './request.js'; import { isObj, joinUrls, sanitizeUrl } from './utils.js'; - +import { + MeltQuoteResponsePaidDeprecated, + handleMeltQuoteResponseDeprecated +} from './legacy/nut-05.js'; +import { + MintQuoteResponsePaidDeprecated, + handleMintQuoteResponseDeprecated +} from './legacy/nut-04.js'; /** * Class represents Cashu Mint API. This class contains Lower level functions that are implemented by CashuWallet. */ @@ -104,11 +111,13 @@ class CashuMint { customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; - return requestInstance({ + const response = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11'), method: 'POST', requestBody: mintQuotePayload }); + const data = handleMintQuoteResponseDeprecated(response); + return data; } /** * Requests a new mint quote from the mint. @@ -132,10 +141,13 @@ class CashuMint { customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; - return requestInstance({ + const response = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11', quote), method: 'GET' }); + + const data = handleMintQuoteResponseDeprecated(response); + return data; } /** * Gets an existing mint quote from the mint. @@ -192,12 +204,14 @@ class CashuMint { customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; - const data = await requestInstance({ + const response = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/melt/quote/bolt11'), method: 'POST', requestBody: meltQuotePayload }); + const data = handleMeltQuoteResponseDeprecated(response); + if ( !isObj(data) || typeof data?.amount !== 'number' || @@ -229,16 +243,20 @@ class CashuMint { customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; - const data = await requestInstance({ + const response = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/melt/quote/bolt11', quote), method: 'GET' }); + const data = handleMeltQuoteResponseDeprecated(response); + if ( !isObj(data) || typeof data?.amount !== 'number' || typeof data?.fee_reserve !== 'number' || - typeof data?.quote !== 'string' + typeof data?.quote !== 'string' || + typeof data?.state !== 'string' || + !Object.values(MeltQuoteState).includes(data.state) ) { throw new Error('bad response'); } @@ -265,9 +283,9 @@ class CashuMint { mintUrl: string, meltPayload: MeltPayload, customRequest?: typeof request - ): Promise { + ): Promise { const requestInstance = customRequest || request; - const data = await requestInstance({ + const data = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/melt/bolt11'), method: 'POST', requestBody: meltPayload @@ -275,8 +293,11 @@ class CashuMint { if ( !isObj(data) || - typeof data?.paid !== 'boolean' || - (data?.payment_preimage !== null && typeof data?.payment_preimage !== 'string') + typeof data?.amount !== 'number' || + typeof data?.fee_reserve !== 'number' || + typeof data?.quote !== 'string' || + typeof data?.state !== 'string' || + !Object.values(MeltQuoteState).includes(data.state) ) { throw new Error('bad response'); } @@ -288,7 +309,7 @@ class CashuMint { * @param meltPayload * @returns */ - async melt(meltPayload: MeltPayload): Promise { + async melt(meltPayload: MeltPayload): Promise { return CashuMint.melt(this._mintUrl, meltPayload, this._customRequest); } /** diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 1d0712db0..d872f53b7 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -19,7 +19,8 @@ import { type Token, type TokenEntry, CheckStateEnum, - SerializedBlindedSignature + SerializedBlindedSignature, + MeltQuoteState } from './model/types/index.js'; import { bytesToNumber, @@ -439,7 +440,7 @@ class CashuWallet { const meltResponse = await this.mint.melt(meltPayload); return { - isPaid: meltResponse.paid ?? false, + isPaid: meltResponse.state === MeltQuoteState.PAID, preimage: meltResponse.payment_preimage, change: meltResponse?.change ? this.constructProofs(meltResponse.change, rs, secrets, keys) diff --git a/src/base64.ts b/src/base64.ts index a84b9a974..7f1b09237 100644 --- a/src/base64.ts +++ b/src/base64.ts @@ -20,12 +20,12 @@ function encodeBase64ToJson(base64String: string): T { } function base64urlToBase64(str: string) { - return str.replace(/-/g, '+').replace(/_/g, '/').split('=')[0] + return str.replace(/-/g, '+').replace(/_/g, '/').split('=')[0]; // .replace(/./g, '='); } function base64urlFromBase64(str: string) { - return str.replace(/\+/g, '-').replace(/\//g, '_').split('=')[0] + return str.replace(/\+/g, '-').replace(/\//g, '_').split('=')[0]; // .replace(/=/g, '.'); } diff --git a/src/legacy/nut-04.ts b/src/legacy/nut-04.ts new file mode 100644 index 000000000..b1a5e8b9b --- /dev/null +++ b/src/legacy/nut-04.ts @@ -0,0 +1,21 @@ +import type { MintQuoteResponse } from '../model/types/index.js'; +import { MintQuoteState } from '../model/types/index.js'; + +export type MintQuoteResponsePaidDeprecated = { + paid?: boolean; +}; + +export function handleMintQuoteResponseDeprecated( + response: MintQuoteResponse & MintQuoteResponsePaidDeprecated +): MintQuoteResponse { + // if the response MeltQuoteResponse has a "paid" flag, we monkey patch it to the state enum + if (!response.state) { + console.warn( + "Deprecated field 'paid' found in MintQuoteResponse. Update NUT-04 of mint: https://github.com/cashubtc/nuts/pull/141)" + ); + } + if (typeof response.paid === 'boolean') { + response.state = response.paid ? MintQuoteState.PAID : MintQuoteState.UNPAID; + } + return response; +} diff --git a/src/legacy/nut-05.ts b/src/legacy/nut-05.ts new file mode 100644 index 000000000..7c13fc0a5 --- /dev/null +++ b/src/legacy/nut-05.ts @@ -0,0 +1,21 @@ +import type { MeltQuoteResponse } from '../model/types/index.js'; +import { MeltQuoteState } from '../model/types/index.js'; + +export type MeltQuoteResponsePaidDeprecated = { + paid?: boolean; +}; + +export function handleMeltQuoteResponseDeprecated( + response: MeltQuoteResponse & MeltQuoteResponsePaidDeprecated +): MeltQuoteResponse { + // if the response MeltQuoteResponse has a "paid" flag, we monkey patch it to the state enum + if (!response.state) { + console.warn( + "Deprecated field 'paid' found in MeltQuoteResponse. Update NUT-05 of mint: https://github.com/cashubtc/nuts/pull/136)" + ); + } + if (typeof response.paid === 'boolean') { + response.state = response.paid ? MeltQuoteState.PAID : MeltQuoteState.UNPAID; + } + return response; +} diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 30b9d3a00..e09a0f096 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -146,6 +146,12 @@ export type MeltQuotePayload = { request: string; }; +export enum MeltQuoteState { + UNPAID = 'UNPAID', + PENDING = 'PENDING', + PAID = 'PAID' +} + /** * Response from the mint after requesting a melt quote */ @@ -163,13 +169,21 @@ export type MeltQuoteResponse = { */ fee_reserve: number; /** - * Whether the quote has been paid. + * State of the melt quote */ - paid: boolean; + state: MeltQuoteState; /** * Timestamp of when the quote expires */ expiry: number; + /** + * preimage of the paid invoice. is null if it the invoice has not been paid yet. can be null, depending on which LN-backend the mint uses + */ + payment_preimage: string | null; + /** + * Return/Change from overpaid fees. This happens due to Lighting fee estimation being inaccurate + */ + change?: Array; } & ApiError; /** @@ -190,24 +204,6 @@ export type MeltPayload = { outputs: Array; }; -/** - * Response from the mint after paying a lightning invoice (melt) - */ -export type MeltResponse = { - /** - * if false, the proofs have not been invalidated and the payment can be tried later again with the same proofs - */ - paid: boolean; - /** - * preimage of the paid invoice. can be null, depending on which LN-backend the mint uses - */ - payment_preimage: string | null; - /** - * Return/Change from overpaid fees. This happens due to Lighting fee estimation being inaccurate - */ - change?: Array; -} & ApiError; - /** * Response after paying a Lightning invoice */ @@ -280,6 +276,13 @@ export type MintQuotePayload = { */ amount: number; }; + +export enum MintQuoteState { + UNPAID = 'UNPAID', + PAID = 'PAID', + ISSUED = 'ISSUED' +} + /** * Response from the mint after requesting a mint */ @@ -293,9 +296,9 @@ export type MintQuoteResponse = { */ quote: string; /** - * Whether the quote has been paid. + * State of the mint quote */ - paid: boolean; + state: MintQuoteState; /** * Timestamp of when the quote expires */ diff --git a/test/request.test.ts b/test/request.test.ts index bdb6d30dc..d57d31bee 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -2,6 +2,7 @@ import nock from 'nock'; import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; import { setGlobalRequestOptions } from '../src/request.js'; +import { MeltQuoteResponse } from '../src/model/types/index.js'; let request: Record | undefined; const mintUrl = 'https://localhost:3338'; @@ -29,8 +30,10 @@ describe('requests', () => { return { quote: 'test_melt_quote_id', amount: 2000, - fee_reserve: 20 - }; + fee_reserve: 20, + payment_preimage: null, + state: 'UNPAID' + } as MeltQuoteResponse; }); const wallet = new CashuWallet(mint, { unit }); @@ -49,8 +52,10 @@ describe('requests', () => { return { quote: 'test_melt_quote_id', amount: 2000, - fee_reserve: 20 - }; + fee_reserve: 20, + payment_preimage: null, + state: 'UNPAID' + } as MeltQuoteResponse; }); const wallet = new CashuWallet(mint, { unit }); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 9b575b36b..2de6aeffc 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -35,11 +35,15 @@ beforeEach(() => { describe('test fees', () => { test('test melt quote fees', async () => { - nock(mintUrl).get('/v1/melt/quote/bolt11/test').reply(200, { - quote: 'test_melt_quote_id', - amount: 2000, - fee_reserve: 20 - }); + nock(mintUrl) + .get('/v1/melt/quote/bolt11/test') + .reply(200, { + quote: 'test_melt_quote_id', + amount: 2000, + fee_reserve: 20, + payment_preimage: null, + state: 'UNPAID' + } as MeltQuoteResponse); const wallet = new CashuWallet(mint, { unit }); const fee = await wallet.getMeltQuote('test'); @@ -187,15 +191,29 @@ describe('payLnInvoice', () => { test('test payLnInvoice base case', async () => { nock(mintUrl) .get('/v1/melt/quote/bolt11/test') - .reply(200, { quote: 'quote_id', amount: 123, fee_reserve: 0 }); - nock(mintUrl).post('/v1/melt/bolt11').reply(200, { paid: true, payment_preimage: '' }); + .reply(200, { + quote: 'test_melt_quote_id', + amount: 2000, + fee_reserve: 20, + payment_preimage: null, + state: 'PAID' + } as MeltQuoteResponse); + nock(mintUrl) + .post('/v1/melt/bolt11') + .reply(200, { + quote: 'test_melt_quote_id', + amount: 2000, + fee_reserve: 20, + payment_preimage: null, + state: 'PAID' + } as MeltQuoteResponse); const wallet = new CashuWallet(mint, { unit }); const meltQuote = await wallet.getMeltQuote('test'); const result = await wallet.payLnInvoice(invoice, proofs, meltQuote); - expect(result).toEqual({ isPaid: true, preimage: '', change: [] }); + expect(result).toEqual({ isPaid: true, preimage: null, change: [] }); }); test('test payLnInvoice change', async () => { nock.cleanAll(); @@ -215,12 +233,21 @@ describe('payLnInvoice', () => { }); nock(mintUrl) .get('/v1/melt/quote/bolt11/test') - .reply(200, { quote: 'quote_id', amount: 123, fee_reserve: 2 }); + .reply(200, { + quote: 'test_melt_quote_id', + amount: 2000, + fee_reserve: 20, + payment_preimage: 'asd', + state: 'PAID' + } as MeltQuoteResponse); nock(mintUrl) .post('/v1/melt/bolt11') .reply(200, { - paid: true, + quote: 'test_melt_quote_id', + amount: 2000, + fee_reserve: 20, payment_preimage: 'asd', + state: 'PAID', change: [ { id: '009a1f293253e41e', From a247b7fcc3aaac0c07056e5e8100f641ffd24ca1 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:25:12 +0200 Subject: [PATCH 158/175] console log instead of warnings --- src/legacy/nut-04.ts | 4 ++-- src/legacy/nut-05.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/legacy/nut-04.ts b/src/legacy/nut-04.ts index b1a5e8b9b..c12343bdb 100644 --- a/src/legacy/nut-04.ts +++ b/src/legacy/nut-04.ts @@ -10,8 +10,8 @@ export function handleMintQuoteResponseDeprecated( ): MintQuoteResponse { // if the response MeltQuoteResponse has a "paid" flag, we monkey patch it to the state enum if (!response.state) { - console.warn( - "Deprecated field 'paid' found in MintQuoteResponse. Update NUT-04 of mint: https://github.com/cashubtc/nuts/pull/141)" + console.log( + "Field 'state' not found in MintQuoteResponse. Update NUT-04 of mint: https://github.com/cashubtc/nuts/pull/141)" ); } if (typeof response.paid === 'boolean') { diff --git a/src/legacy/nut-05.ts b/src/legacy/nut-05.ts index 7c13fc0a5..7337d3c7d 100644 --- a/src/legacy/nut-05.ts +++ b/src/legacy/nut-05.ts @@ -10,8 +10,8 @@ export function handleMeltQuoteResponseDeprecated( ): MeltQuoteResponse { // if the response MeltQuoteResponse has a "paid" flag, we monkey patch it to the state enum if (!response.state) { - console.warn( - "Deprecated field 'paid' found in MeltQuoteResponse. Update NUT-05 of mint: https://github.com/cashubtc/nuts/pull/136)" + console.log( + "Field 'state' not found in MeltQuoteResponse. Update NUT-05 of mint: https://github.com/cashubtc/nuts/pull/136)" ); } if (typeof response.paid === 'boolean') { From ef8111f625bf3a21e8363e2df8002a753ece50dc Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:43:11 +0200 Subject: [PATCH 159/175] fix errors in test --- src/CashuMint.ts | 7 +++---- src/legacy/nut-04.ts | 8 ++++---- src/legacy/nut-05.ts | 8 ++++---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index e28635cf2..318de2262 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -285,17 +285,16 @@ class CashuMint { customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; - const data = await requestInstance({ + const response = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/melt/bolt11'), method: 'POST', requestBody: meltPayload }); + const data = handleMeltQuoteResponseDeprecated(response); + if ( !isObj(data) || - typeof data?.amount !== 'number' || - typeof data?.fee_reserve !== 'number' || - typeof data?.quote !== 'string' || typeof data?.state !== 'string' || !Object.values(MeltQuoteState).includes(data.state) ) { diff --git a/src/legacy/nut-04.ts b/src/legacy/nut-04.ts index c12343bdb..63ab349c4 100644 --- a/src/legacy/nut-04.ts +++ b/src/legacy/nut-04.ts @@ -10,12 +10,12 @@ export function handleMintQuoteResponseDeprecated( ): MintQuoteResponse { // if the response MeltQuoteResponse has a "paid" flag, we monkey patch it to the state enum if (!response.state) { - console.log( + console.warn( "Field 'state' not found in MintQuoteResponse. Update NUT-04 of mint: https://github.com/cashubtc/nuts/pull/141)" ); - } - if (typeof response.paid === 'boolean') { - response.state = response.paid ? MintQuoteState.PAID : MintQuoteState.UNPAID; + if (typeof response.paid === 'boolean') { + response.state = response.paid ? MintQuoteState.PAID : MintQuoteState.UNPAID; + } } return response; } diff --git a/src/legacy/nut-05.ts b/src/legacy/nut-05.ts index 7337d3c7d..ccc6582e5 100644 --- a/src/legacy/nut-05.ts +++ b/src/legacy/nut-05.ts @@ -10,12 +10,12 @@ export function handleMeltQuoteResponseDeprecated( ): MeltQuoteResponse { // if the response MeltQuoteResponse has a "paid" flag, we monkey patch it to the state enum if (!response.state) { - console.log( + console.warn( "Field 'state' not found in MeltQuoteResponse. Update NUT-05 of mint: https://github.com/cashubtc/nuts/pull/136)" ); - } - if (typeof response.paid === 'boolean') { - response.state = response.paid ? MeltQuoteState.PAID : MeltQuoteState.UNPAID; + if (typeof response.paid === 'boolean') { + response.state = response.paid ? MeltQuoteState.PAID : MeltQuoteState.UNPAID; + } } return response; } From 18855eb0f35a0bfc30e45d57dc3d7e63bdb5921c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:52:30 +0200 Subject: [PATCH 160/175] refactor depercated contact handler --- src/CashuMint.ts | 22 +++------------------- src/legacy/nut-06.ts | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 19 deletions(-) create mode 100644 src/legacy/nut-06.ts diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 9e33b81f9..92981ef8b 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -29,6 +29,7 @@ import { MintQuoteResponsePaidDeprecated, handleMintQuoteResponseDeprecated } from './legacy/nut-04.js'; +import { handeMintInfoContactFieldDeprecated } from './legacy/nut-06.js'; /** * Class represents Cashu Mint API. This class contains Lower level functions that are implemented by CashuWallet. */ @@ -56,27 +57,10 @@ class CashuMint { customRequest?: typeof request ): Promise { const requestInstance = customRequest || request; - const data = await requestInstance({ + const response = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/info') }); - // BEGIN DEPRECATED - // Monkey patch old contact field ["email", "me@mail.com"] Array<[string, string]>; to new contact field [{method: "email", info: "me@mail.com"}] Array - // This is to maintain backwards compatibility with older versions of the mint - if (Array.isArray(data?.contact) && data?.contact.length > 0) { - data.contact = data.contact.map((contact: MintContactInfo) => { - if ( - Array.isArray(contact) && - contact.length === 2 && - typeof contact[0] === 'string' && - typeof contact[1] === 'string' - ) { - return { method: contact[0], info: contact[1] } as MintContactInfo; - } - return contact; - }); - } - // END DEPRECATED - + const data = handeMintInfoContactFieldDeprecated(response); return data; } /** diff --git a/src/legacy/nut-06.ts b/src/legacy/nut-06.ts new file mode 100644 index 000000000..8540292b0 --- /dev/null +++ b/src/legacy/nut-06.ts @@ -0,0 +1,21 @@ +import type { MintContactInfo, GetInfoResponse } from '../model/types/index.js'; + +export function handeMintInfoContactFieldDeprecated(data: GetInfoResponse) { + // Monkey patch old contact field ["email", "me@mail.com"] Array<[string, string]>; to new contact field [{method: "email", info: "me@mail.com"}] Array + // This is to maintain backwards compatibility with older versions of the mint + if (Array.isArray(data?.contact) && data?.contact.length > 0) { + data.contact = data.contact.map((contact: MintContactInfo) => { + if ( + Array.isArray(contact) && + contact.length === 2 && + typeof contact[0] === 'string' && + typeof contact[1] === 'string' + ) { + return { method: contact[0], info: contact[1] } as MintContactInfo; + } + console.warn("Mint returned deprecated 'contact' field. Update NUT-06: https://github.com/cashubtc/nuts/pull/117"); + return contact; + }); + } + return data; +} \ No newline at end of file From a752c0d97ac5482d7a4cbe2b997bae6b1b353cca Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:52:45 +0200 Subject: [PATCH 161/175] typo --- src/CashuMint.ts | 4 ++-- src/legacy/nut-06.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 92981ef8b..3cb6ef7af 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -29,7 +29,7 @@ import { MintQuoteResponsePaidDeprecated, handleMintQuoteResponseDeprecated } from './legacy/nut-04.js'; -import { handeMintInfoContactFieldDeprecated } from './legacy/nut-06.js'; +import { handleMintInfoContactFieldDeprecated } from './legacy/nut-06.js'; /** * Class represents Cashu Mint API. This class contains Lower level functions that are implemented by CashuWallet. */ @@ -60,7 +60,7 @@ class CashuMint { const response = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/info') }); - const data = handeMintInfoContactFieldDeprecated(response); + const data = handleMintInfoContactFieldDeprecated(response); return data; } /** diff --git a/src/legacy/nut-06.ts b/src/legacy/nut-06.ts index 8540292b0..f685046bc 100644 --- a/src/legacy/nut-06.ts +++ b/src/legacy/nut-06.ts @@ -1,6 +1,6 @@ import type { MintContactInfo, GetInfoResponse } from '../model/types/index.js'; -export function handeMintInfoContactFieldDeprecated(data: GetInfoResponse) { +export function handleMintInfoContactFieldDeprecated(data: GetInfoResponse) { // Monkey patch old contact field ["email", "me@mail.com"] Array<[string, string]>; to new contact field [{method: "email", info: "me@mail.com"}] Array // This is to maintain backwards compatibility with older versions of the mint if (Array.isArray(data?.contact) && data?.contact.length > 0) { From 70e93f47518883467c866967656cfcb831a9bc7c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:54:53 +0200 Subject: [PATCH 162/175] npm run format --- src/legacy/nut-06.ts | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/legacy/nut-06.ts b/src/legacy/nut-06.ts index f685046bc..ded610490 100644 --- a/src/legacy/nut-06.ts +++ b/src/legacy/nut-06.ts @@ -1,21 +1,23 @@ import type { MintContactInfo, GetInfoResponse } from '../model/types/index.js'; export function handleMintInfoContactFieldDeprecated(data: GetInfoResponse) { - // Monkey patch old contact field ["email", "me@mail.com"] Array<[string, string]>; to new contact field [{method: "email", info: "me@mail.com"}] Array - // This is to maintain backwards compatibility with older versions of the mint - if (Array.isArray(data?.contact) && data?.contact.length > 0) { - data.contact = data.contact.map((contact: MintContactInfo) => { - if ( - Array.isArray(contact) && - contact.length === 2 && - typeof contact[0] === 'string' && - typeof contact[1] === 'string' - ) { - return { method: contact[0], info: contact[1] } as MintContactInfo; - } - console.warn("Mint returned deprecated 'contact' field. Update NUT-06: https://github.com/cashubtc/nuts/pull/117"); - return contact; - }); - } - return data; -} \ No newline at end of file + // Monkey patch old contact field ["email", "me@mail.com"] Array<[string, string]>; to new contact field [{method: "email", info: "me@mail.com"}] Array + // This is to maintain backwards compatibility with older versions of the mint + if (Array.isArray(data?.contact) && data?.contact.length > 0) { + data.contact = data.contact.map((contact: MintContactInfo) => { + if ( + Array.isArray(contact) && + contact.length === 2 && + typeof contact[0] === 'string' && + typeof contact[1] === 'string' + ) { + return { method: contact[0], info: contact[1] } as MintContactInfo; + } + console.warn( + "Mint returned deprecated 'contact' field. Update NUT-06: https://github.com/cashubtc/nuts/pull/117" + ); + return contact; + }); + } + return data; +} From 559080d7282cfbc929b9591ce009c741395f01d0 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:59:42 +0200 Subject: [PATCH 163/175] Format checker in gihtub pipeline --- .github/workflows/format.yml | 21 +++++++++++++++++++++ package.json | 1 + 2 files changed, 22 insertions(+) create mode 100644 .github/workflows/format.yml diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 000000000..53bac82dc --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,21 @@ +name: Format Check + +on: [push] + +jobs: + tests: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm run check-format diff --git a/package.json b/package.json index 4d45351be..2f15c9fbd 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dev": "tsc --watch", "lint": "eslint --ext .js,.ts . --fix", "format": "prettier --write .", + "check-format": "prettier --check .", "typedoc": "typedoc src/index.ts" }, "keywords": [ From 0952172ba6dfcdc0fc17227272aafd24705eeddd Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:00:07 +0200 Subject: [PATCH 164/175] test format mismatch, should fail --- src/CashuMint.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 318de2262..4c6281e21 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -51,6 +51,8 @@ class CashuMint { * @param customRequest */ public static async getInfo( + + mintUrl: string, customRequest?: typeof request ): Promise { From 097f7ec9f5307d3d771bd1ff9d15b8e6610f0f33 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:01:40 +0200 Subject: [PATCH 165/175] install dep[s --- .github/workflows/format.yml | 5 ++++- src/CashuMint.ts | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 53bac82dc..2cd51ebd3 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -18,4 +18,7 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: 'npm' - - run: npm run check-format + - name: Install dependencies + run: npm install + - name: Check format + run: npm run check-format diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 4c6281e21..318de2262 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -51,8 +51,6 @@ class CashuMint { * @param customRequest */ public static async getInfo( - - mintUrl: string, customRequest?: typeof request ): Promise { From f34276cab717572d2fa7b8e636dbb51b529dde57 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:02:25 +0200 Subject: [PATCH 166/175] fix format, should succeed --- .github/workflows/format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 2cd51ebd3..db5f86b07 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -19,6 +19,6 @@ jobs: node-version: ${{ matrix.node-version }} cache: 'npm' - name: Install dependencies - run: npm install + run: npm install - name: Check format run: npm run check-format From 53c1af36adf4be1e16460f1f35c239c73160bb70 Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 5 Jul 2024 11:24:17 +0200 Subject: [PATCH 167/175] updated naming on mint / melt --- src/CashuMint.ts | 12 ++++++------ src/CashuWallet.ts | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 3cb6ef7af..ba3f4f660 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -111,7 +111,7 @@ class CashuMint { * @param customRequest * @returns the mint will create and return a new mint quote containing a payment request for the specified amount and unit */ - public static async mintQuote( + public static async postMintQuote( mintUrl: string, mintQuotePayload: MintQuotePayload, customRequest?: typeof request @@ -130,8 +130,8 @@ class CashuMint { * @param mintQuotePayload Payload for creating a new mint quote * @returns the mint will create and return a new mint quote containing a payment request for the specified amount and unit */ - async mintQuote(mintQuotePayload: MintQuotePayload): Promise { - return CashuMint.mintQuote(this._mintUrl, mintQuotePayload, this._customRequest); + async postMintQuote(mintQuotePayload: MintQuotePayload): Promise { + return CashuMint.postMintQuote(this._mintUrl, mintQuotePayload, this._customRequest); } /** @@ -204,7 +204,7 @@ class CashuMint { * @param MeltQuotePayload * @returns */ - public static async meltQuote( + public static async postMeltQuote( mintUrl: string, meltQuotePayload: MeltQuotePayload, customRequest?: typeof request @@ -233,8 +233,8 @@ class CashuMint { * @param MeltQuotePayload * @returns */ - async meltQuote(meltQuotePayload: MeltQuotePayload): Promise { - return CashuMint.meltQuote(this._mintUrl, meltQuotePayload, this._customRequest); + async postMeltQuote(meltQuotePayload: MeltQuotePayload): Promise { + return CashuMint.postMeltQuote(this._mintUrl, meltQuotePayload, this._customRequest); } /** diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 7b25ab34c..ae8292fff 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -341,12 +341,12 @@ class CashuWallet { * @param amount Amount requesting for mint. * @returns the mint will return a mint quote with a Lightning invoice for minting tokens of the specified amount and unit */ - async mintQuote(amount: number) { + async createMintQuote(amount: number) { const mintQuotePayload: MintQuotePayload = { unit: this._unit, amount: amount }; - return await this.mint.mintQuote(mintQuotePayload); + return await this.mint.postMintQuote(mintQuotePayload); } /** @@ -354,7 +354,7 @@ class CashuWallet { * @param quote Quote ID * @returns the mint will create and return a Lightning invoice for the specified amount */ - async getMintQuote(quote: string) { + async checkMintQuote(quote: string) { return await this.mint.getMintQuote(quote); } @@ -397,12 +397,12 @@ class CashuWallet { * @param invoice LN invoice that needs to get a fee estimate * @returns the mint will create and return a melt quote for the invoice with an amount and fee reserve */ - async meltQuote(invoice: string): Promise { + async createMeltQuote(invoice: string): Promise { const meltQuotePayload: MeltQuotePayload = { unit: this._unit, request: invoice }; - const meltQuote = await this.mint.meltQuote(meltQuotePayload); + const meltQuote = await this.mint.postMeltQuote(meltQuotePayload); return meltQuote; } @@ -411,7 +411,7 @@ class CashuWallet { * @param quote ID of the melt quote * @returns the mint will return an existing melt quote */ - async getMeltQuote(quote: string): Promise { + async checkMeltQuote(quote: string): Promise { const meltQuote = await this.mint.getMeltQuote(quote); return meltQuote; } @@ -476,7 +476,7 @@ class CashuWallet { } ): Promise { if (!meltQuote) { - meltQuote = await this.mint.meltQuote({ unit: this._unit, request: invoice }); + meltQuote = await this.mint.postMeltQuote({ unit: this._unit, request: invoice }); } return await this.meltTokens(meltQuote, proofsToSend, { keysetId: options?.keysetId, From c5b509a2611270a2de291d58cf5fe0963c625786 Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 5 Jul 2024 11:24:26 +0200 Subject: [PATCH 168/175] updated tests / all green --- test/integration.test.ts | 36 ++++++++++++++++++------------------ test/request.test.ts | 4 ++-- test/wallet.test.ts | 6 +++--- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index 5776334a4..bb43f253d 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -36,15 +36,15 @@ describe('mint api', () => { test('request mint', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.mintQuote(100); + const request = await wallet.createMintQuote(100); expect(request).toBeDefined(); - const mintQuote = await wallet.getMintQuote(request.quote); + const mintQuote = await wallet.checkMintQuote(request.quote); expect(mintQuote).toBeDefined(); }); test('mint tokens', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.mintQuote(1337); + const request = await wallet.createMintQuote(1337); expect(request).toBeDefined(); expect(request.request).toContain('lnbc1337'); const tokens = await wallet.mintTokens(1337, request.quote); @@ -55,8 +55,8 @@ describe('mint api', () => { test('get fee for local invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.mintQuote(100); - const fee = (await wallet.meltQuote(request.request)).fee_reserve; + const request = await wallet.createMintQuote(100); + const fee = (await wallet.createMeltQuote(request.request)).fee_reserve; expect(fee).toBeDefined(); // because local invoice, fee should be 0 expect(fee).toBe(0); @@ -64,7 +64,7 @@ describe('mint api', () => { test('get fee for external invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const fee = (await wallet.meltQuote(externalInvoice)).fee_reserve; + const fee = (await wallet.createMeltQuote(externalInvoice)).fee_reserve; expect(fee).toBeDefined(); // because external invoice, fee should be > 0 expect(fee).toBeGreaterThan(0); @@ -72,17 +72,17 @@ describe('mint api', () => { test('pay local invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.mintQuote(100); + const request = await wallet.createMintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); // expect no fee because local invoice - const mintQuote = await wallet.mintQuote(10); - const quote = await wallet.meltQuote(mintQuote.request); + const mintQuote = await wallet.createMintQuote(10); + const quote = await wallet.createMeltQuote(mintQuote.request); const fee = quote.fee_reserve; expect(fee).toBe(0); // get the quote from the mint - const quote_ = await wallet.getMeltQuote(quote.quote); + const quote_ = await wallet.checkMeltQuote(quote.quote); expect(quote_).toBeDefined(); const sendResponse = await wallet.send(10, tokens.proofs); @@ -104,15 +104,15 @@ describe('mint api', () => { test('pay external invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.mintQuote(3000); + const request = await wallet.createMintQuote(3000); const tokens = await wallet.mintTokens(3000, request.quote); - const meltQuote = await wallet.meltQuote(externalInvoice); + const meltQuote = await wallet.createMeltQuote(externalInvoice); const fee = meltQuote.fee_reserve; expect(fee).toBeGreaterThan(0); // get the quote from the mint - const quote_ = await wallet.getMeltQuote(meltQuote.quote); + const quote_ = await wallet.checkMeltQuote(meltQuote.quote); expect(quote_).toBeDefined(); const sendResponse = await wallet.send(2000 + fee, tokens.proofs); @@ -135,7 +135,7 @@ describe('mint api', () => { test('test send tokens exact without previous split', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.mintQuote(64); + const request = await wallet.createMintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); const sendResponse = await wallet.send(64, tokens.proofs); @@ -148,7 +148,7 @@ describe('mint api', () => { test('test send tokens with change', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.mintQuote(100); + const request = await wallet.createMintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); const sendResponse = await wallet.send(10, tokens.proofs); @@ -161,7 +161,7 @@ describe('mint api', () => { test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.mintQuote(100); + const request = await wallet.createMintQuote(100); const tokens = await wallet.mintTokens(100, request.quote); const sendResponse = await wallet.send(10, tokens.proofs); @@ -174,7 +174,7 @@ describe('mint api', () => { test('receive tokens with previous mint', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); - const request = await wallet.mintQuote(64); + const request = await wallet.createMintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); const encoded = getEncodedToken({ token: [{ mint: mintUrl, proofs: tokens.proofs }] @@ -192,7 +192,7 @@ describe('mint api', () => { const privKeyBob = secp256k1.utils.randomPrivateKey(); const pubKeyBob = secp256k1.getPublicKey(privKeyBob); - const request = await wallet.mintQuote(64); + const request = await wallet.createMintQuote(64); const tokens = await wallet.mintTokens(64, request.quote); const { send } = await wallet.send(64, tokens.proofs, { pubkey: bytesToHex(pubKeyBob) }); diff --git a/test/request.test.ts b/test/request.test.ts index d57d31bee..5ff7bcd24 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -37,7 +37,7 @@ describe('requests', () => { }); const wallet = new CashuWallet(mint, { unit }); - await wallet.getMeltQuote('test'); + await wallet.checkMeltQuote('test'); expect(request).toBeDefined(); // expect(request!['content-type']).toContain('application/json'); @@ -61,7 +61,7 @@ describe('requests', () => { const wallet = new CashuWallet(mint, { unit }); setGlobalRequestOptions({ headers: { 'x-cashu': 'xyz-123-abc' } }); - await wallet.getMeltQuote('test'); + await wallet.checkMeltQuote('test'); expect(request).toBeDefined(); expect(request!['x-cashu']).toContain('xyz-123-abc'); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 3cc6032c9..b8bd97d86 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -79,7 +79,7 @@ describe('test fees', () => { } as MeltQuoteResponse); const wallet = new CashuWallet(mint, { unit }); - const fee = await wallet.getMeltQuote('test'); + const fee = await wallet.checkMeltQuote('test'); const amount = 2000; expect(fee.fee_reserve + amount).toEqual(2020); @@ -242,7 +242,7 @@ describe('payLnInvoice', () => { } as MeltQuoteResponse); const wallet = new CashuWallet(mint, { unit }); - const meltQuote = await wallet.getMeltQuote('test'); + const meltQuote = await wallet.checkMeltQuote('test'); const result = await wallet.payLnInvoice(invoice, proofs, meltQuote); @@ -291,7 +291,7 @@ describe('payLnInvoice', () => { }); const wallet = new CashuWallet(mint, { unit }); - const meltQuote = await wallet.getMeltQuote('test'); + const meltQuote = await wallet.checkMeltQuote('test'); const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }], meltQuote); expect(result.isPaid).toBe(true); From 13c115fa9b21d453943d839eda1e1ddf5e0c11b8 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 15 Jul 2024 13:38:50 +0200 Subject: [PATCH 169/175] updated migration doc --- migration-1.0.0.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migration-1.0.0.md b/migration-1.0.0.md index 5489fb816..2f42612d0 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -33,7 +33,7 @@ To reduce complexity, simplify error handling and to prepare for token V4, this Utility functions now have an `options` object for optional parameters, instead of passing them directly -**`requestMint(amount: number)` --> `mintQuote(amount: number)`** +**`requestMint(amount: number)` --> `createMintQuote(amount: number)`** Now returns the following: ```typescript @@ -51,7 +51,7 @@ where `request` is the invoice to be paid, and `quote` is the identifier used to --- -**`getMeltQuote(invoice: string)`** is now used to get fee estimation and conversion quotes instead of `getFee()` and returns: +**`createMeltQuote(invoice: string)`** is now used to get fee estimation and conversion quotes instead of `getFee()` and returns: ```typescript type MeltQuoteResponse = { From 4ce07168dbc1c92692dfe59ea955b39b448f8961 Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 16 Jul 2024 16:49:58 +0200 Subject: [PATCH 170/175] mint and wallet same naming --- src/CashuMint.ts | 24 ++++++++++++------------ src/CashuWallet.ts | 10 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index ba3f4f660..3729ae028 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -111,7 +111,7 @@ class CashuMint { * @param customRequest * @returns the mint will create and return a new mint quote containing a payment request for the specified amount and unit */ - public static async postMintQuote( + public static async createMintQuote( mintUrl: string, mintQuotePayload: MintQuotePayload, customRequest?: typeof request @@ -130,8 +130,8 @@ class CashuMint { * @param mintQuotePayload Payload for creating a new mint quote * @returns the mint will create and return a new mint quote containing a payment request for the specified amount and unit */ - async postMintQuote(mintQuotePayload: MintQuotePayload): Promise { - return CashuMint.postMintQuote(this._mintUrl, mintQuotePayload, this._customRequest); + async createMintQuote(mintQuotePayload: MintQuotePayload): Promise { + return CashuMint.createMintQuote(this._mintUrl, mintQuotePayload, this._customRequest); } /** @@ -141,7 +141,7 @@ class CashuMint { * @param customRequest * @returns the mint will create and return a Lightning invoice for the specified amount */ - public static async getMintQuote( + public static async checkMintQuote( mintUrl: string, quote: string, customRequest?: typeof request @@ -160,8 +160,8 @@ class CashuMint { * @param quote Quote ID * @returns the mint will create and return a Lightning invoice for the specified amount */ - async getMintQuote(quote: string): Promise { - return CashuMint.getMintQuote(this._mintUrl, quote, this._customRequest); + async checkMintQuote(quote: string): Promise { + return CashuMint.checkMintQuote(this._mintUrl, quote, this._customRequest); } /** @@ -204,7 +204,7 @@ class CashuMint { * @param MeltQuotePayload * @returns */ - public static async postMeltQuote( + public static async createMeltQuote( mintUrl: string, meltQuotePayload: MeltQuotePayload, customRequest?: typeof request @@ -233,8 +233,8 @@ class CashuMint { * @param MeltQuotePayload * @returns */ - async postMeltQuote(meltQuotePayload: MeltQuotePayload): Promise { - return CashuMint.postMeltQuote(this._mintUrl, meltQuotePayload, this._customRequest); + async createMeltQuote(meltQuotePayload: MeltQuotePayload): Promise { + return CashuMint.createMeltQuote(this._mintUrl, meltQuotePayload, this._customRequest); } /** @@ -243,7 +243,7 @@ class CashuMint { * @param quote Quote ID * @returns */ - public static async getMeltQuote( + public static async checkMeltQuote( mintUrl: string, quote: string, customRequest?: typeof request @@ -274,8 +274,8 @@ class CashuMint { * @param quote Quote ID * @returns */ - async getMeltQuote(quote: string): Promise { - return CashuMint.getMeltQuote(this._mintUrl, quote, this._customRequest); + async checkMeltQuote(quote: string): Promise { + return CashuMint.checkMeltQuote(this._mintUrl, quote, this._customRequest); } /** diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index ae8292fff..23a31fae7 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -346,7 +346,7 @@ class CashuWallet { unit: this._unit, amount: amount }; - return await this.mint.postMintQuote(mintQuotePayload); + return await this.mint.createMintQuote(mintQuotePayload); } /** @@ -355,7 +355,7 @@ class CashuWallet { * @returns the mint will create and return a Lightning invoice for the specified amount */ async checkMintQuote(quote: string) { - return await this.mint.getMintQuote(quote); + return await this.mint.checkMintQuote(quote); } /** @@ -402,7 +402,7 @@ class CashuWallet { unit: this._unit, request: invoice }; - const meltQuote = await this.mint.postMeltQuote(meltQuotePayload); + const meltQuote = await this.mint.createMeltQuote(meltQuotePayload); return meltQuote; } @@ -412,7 +412,7 @@ class CashuWallet { * @returns the mint will return an existing melt quote */ async checkMeltQuote(quote: string): Promise { - const meltQuote = await this.mint.getMeltQuote(quote); + const meltQuote = await this.mint.checkMeltQuote(quote); return meltQuote; } @@ -476,7 +476,7 @@ class CashuWallet { } ): Promise { if (!meltQuote) { - meltQuote = await this.mint.postMeltQuote({ unit: this._unit, request: invoice }); + meltQuote = await this.mint.createMeltQuote({ unit: this._unit, request: invoice }); } return await this.meltTokens(meltQuote, proofsToSend, { keysetId: options?.keysetId, From 4909e396d2ac9432ca6fb8052f1267bb93ae1cf4 Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 16 Jul 2024 16:58:16 +0200 Subject: [PATCH 171/175] 1.0.0-rc.10 --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 13e09e24e..fec4573a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -696,9 +696,9 @@ "dev": true }, "node_modules/@cashu/crypto": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@cashu/crypto/-/crypto-0.2.6.tgz", - "integrity": "sha512-qjytcY26MRntG6nJc9U2tSeDw+BApKQaIch58POjEiTuc7MbIxgR/l/xU5NzXa/nGrSLdNZQwl/o5RQDhc2otw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@cashu/crypto/-/crypto-0.2.7.tgz", + "integrity": "sha512-1aaDfUjiHNXoJqg8nW+341TLWV9W28DsVNXJUKcHL0yAmwLs5+56SSnb8LLDJzPamLVoYL0U0bda91klAzptig==", "dependencies": { "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", @@ -6922,9 +6922,9 @@ "dev": true }, "@cashu/crypto": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@cashu/crypto/-/crypto-0.2.6.tgz", - "integrity": "sha512-qjytcY26MRntG6nJc9U2tSeDw+BApKQaIch58POjEiTuc7MbIxgR/l/xU5NzXa/nGrSLdNZQwl/o5RQDhc2otw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@cashu/crypto/-/crypto-0.2.7.tgz", + "integrity": "sha512-1aaDfUjiHNXoJqg8nW+341TLWV9W28DsVNXJUKcHL0yAmwLs5+56SSnb8LLDJzPamLVoYL0U0bda91klAzptig==", "requires": { "@noble/curves": "^1.3.0", "@noble/hashes": "^1.3.3", diff --git a/package.json b/package.json index 4d45351be..918b37cbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.9", + "version": "1.0.0-rc.10", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From 142d671e8ae9418bb6457de0b5d6813316e49db3 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:03:27 +0200 Subject: [PATCH 172/175] remove unused import --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 3485bec73..48a2a00cd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import { encodeBase64ToJson, encodeJsonToBase64 } from './base64.js'; -import { AmountPreference, Keys, Proof, Token, TokenEntry, TokenV2 } from './model/types/index.js'; +import { AmountPreference, Keys, Proof, Token, TokenV2 } from './model/types/index.js'; import { TOKEN_PREFIX, TOKEN_VERSION } from './utils/Constants.js'; import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils'; import { sha256 } from '@noble/hashes/sha256'; From ce81a59d602d59eaddef039f42309ffef735d109 Mon Sep 17 00:00:00 2001 From: Egge Date: Tue, 16 Jul 2024 17:32:30 +0200 Subject: [PATCH 173/175] 1.0.0-rc.11 --- README.md | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 79071cb99..75244c600 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ const tokens = await wallet.mintTokens(64, mintQuote.quote); Contributions are very welcome. -If you want to contribute, please open an Issue or a PR. -If you open a PR, please do so from the `development` branch as the base branch. +If you want to contribute, please open an Issue or a PR. +If you open a PR, please do so from the `development` branch as the base branch. ### Version @@ -80,17 +80,17 @@ If you open a PR, please do so from the `development` branch as the base branch. | | * `hotfix` | | | * `staging` -| |\ +| |\ | |\ \ | | | * `bugfix` | | | -| | * `development` -| | |\ +| | * `development` +| | |\ | | | * `feature1` | | | | | | |/ | | * -| | |\ +| | |\ | | | * `feature2` | | |/ | |/ diff --git a/package.json b/package.json index 1e9cbe34b..744b0a78b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.10", + "version": "1.0.0-rc.11", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js", From 4e3869b36a0247ce56524a62898cb5b89a50157c Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 17 Jul 2024 09:43:55 +0900 Subject: [PATCH 174/175] audit fix deps --- package-lock.json | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index fec4573a7..6f93ce615 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.9", + "version": "1.0.0-rc.11", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.9", + "version": "1.0.0-rc.11", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.6", @@ -2099,12 +2099,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3344,10 +3345,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3932,6 +3934,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -5878,6 +5881,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -7984,12 +7988,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browserslist": { @@ -8885,9 +8889,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" From 7c95ca4b142b9c4370a611fbe5fda830ad4cec73 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Wed, 17 Jul 2024 09:44:05 +0900 Subject: [PATCH 175/175] 1.0.0-rc.12 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6f93ce615..973669c62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.11", + "version": "1.0.0-rc.12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.11", + "version": "1.0.0-rc.12", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.6", diff --git a/package.json b/package.json index 744b0a78b..f7b58aa05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.0.0-rc.11", + "version": "1.0.0-rc.12", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js",