From ec76cc3848908b58a6131addf47be8a087a0663f Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 18 Jun 2024 12:44:49 +0200 Subject: [PATCH 01/70] getPreferences checks against keyset amounts, not necessarily powers of 2 --- src/CashuWallet.ts | 17 ++++++++--------- src/utils.ts | 31 +++++++++++++++++++------------ test/utils.test.ts | 34 ++++++++++++++++++++++++---------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 1f9d1dca..3bb42230 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -174,10 +174,10 @@ class CashuWallet { try { const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); let preference = options?.preference; + const keys = await this.getKeys(options?.keysetId); if (!preference) { - preference = getDefaultAmountPreference(amount); + preference = getDefaultAmountPreference(amount, keys); } - const keys = await this.getKeys(options?.keysetId); const { payload, blindedMessages } = this.createSplitPayload( amount, tokenEntry.proofs, @@ -226,7 +226,6 @@ class CashuWallet { counter?: number; pubkey?: string; privkey?: string; - keysetId?: string; } ): Promise { if (options?.preference) { @@ -376,7 +375,7 @@ class CashuWallet { const keyset = await this.getKeys(options?.keysetId); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, - options?.keysetId ?? keyset.id, + keyset, options?.amountPreference, options?.counter, options?.pubkey @@ -520,7 +519,7 @@ class CashuWallet { const totalAmount = proofsToSend.reduce((total, curr) => total + curr.amount, 0); const keepBlindedMessages = this.createRandomBlindedMessages( totalAmount - amount, - keyset.id, + keyset, undefined, counter ); @@ -529,7 +528,7 @@ class CashuWallet { } const sendBlindedMessages = this.createRandomBlindedMessages( amount, - keyset.id, + keyset, preference, counter, pubkey @@ -604,13 +603,13 @@ class CashuWallet { */ private createRandomBlindedMessages( amount: number, - keysetId: string, + keyset: MintKeys, amountPreference?: Array, counter?: number, pubkey?: string ): BlindedMessageData & { amounts: Array } { - const amounts = splitAmount(amount, amountPreference); - return this.createBlindedMessages(amounts, keysetId, counter, pubkey); + const amounts = splitAmount(amount, keyset.keys, amountPreference); + return this.createBlindedMessages(amounts, keyset.id, counter, pubkey); } /** diff --git a/src/utils.ts b/src/utils.ts index 1fcbd11d..dfb0482e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,15 +4,18 @@ import { TOKEN_PREFIX, TOKEN_VERSION } from './utils/Constants.js'; import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils'; import { sha256 } from '@noble/hashes/sha256'; -function splitAmount(value: number, amountPreference?: Array): Array { +function splitAmount(value: number, keyset: Keys, amountPreference?: Array): Array { const chunks: Array = []; if (amountPreference) { - chunks.push(...getPreference(value, amountPreference)); - value = - value - - chunks.reduce((curr, acc) => { - return curr + acc; - }, 0); + if (amountPreference.length > 0) { + chunks.push(...getPreference(value, keyset, amountPreference)); + value = + value - + chunks.reduce((curr, acc) => { + return curr + acc; + }, 0); + return chunks; + } } for (let i = 0; i < 32; i++) { const mask: number = 1 << i; @@ -27,13 +30,17 @@ function isPowerOfTwo(number: number) { return number && !(number & (number - 1)); } -function getPreference(amount: number, preferredAmounts: Array): Array { +function hasCorrespondingKey(amount: number, keyset: Keys) { + return amount in keyset; +} + +function getPreference(amount: number, keyset: Keys, preferredAmounts: Array): Array { const chunks: Array = []; let accumulator = 0; preferredAmounts.forEach((pa) => { - if (!isPowerOfTwo(pa.amount)) { + if (!hasCorrespondingKey(pa.amount, keyset)) { throw new Error( - 'Provided amount preferences contain non-power-of-2 numbers. Use only ^2 numbers' + 'Provided amount preferences do not match the amounts of the mint keyset.' ); } for (let i = 1; i <= pa.count; i++) { @@ -47,8 +54,8 @@ function getPreference(amount: number, preferredAmounts: Array return chunks; } -function getDefaultAmountPreference(amount: number): Array { - const amounts = splitAmount(amount); +function getDefaultAmountPreference(amount: number, keyset: Keys): Array { + const amounts = splitAmount(amount, keyset); return amounts.map((a) => { return { amount: a, count: 1 }; }); diff --git a/test/utils.test.ts b/test/utils.test.ts index ccc34160..4b7e0273 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,14 +1,19 @@ -import { AmountPreference } from '../src/model/types/index.js'; +import { AmountPreference, Keys } from '../src/model/types/index.js'; import * as utils from '../src/utils.js'; import { PUBKEYS } from './consts.js'; +const keys: Keys = {}; +for (let i=1; i<2048; i *= 2) { + keys[i] = "deadbeef"; +} + describe('test split amounts ', () => { test('testing amount 2561', async () => { - const chunks = utils.splitAmount(2561); + const chunks = utils.splitAmount(2561, keys); expect(chunks).toStrictEqual([1, 512, 2048]); }); test('testing amount 0', async () => { - const chunks = utils.splitAmount(0); + const chunks = utils.splitAmount(0, keys); expect(chunks).toStrictEqual([]); }); }); @@ -16,7 +21,7 @@ describe('test split amounts ', () => { describe('test split custom amounts ', () => { const fiveToOne: AmountPreference = { amount: 1, count: 5 }; test('testing amount 5', async () => { - const chunks = utils.splitAmount(5, [fiveToOne]); + const chunks = utils.splitAmount(5, keys, [fiveToOne]); expect(chunks).toStrictEqual([1, 1, 1, 1, 1]); }); const tenToOneAndTwo: Array = [ @@ -24,21 +29,30 @@ describe('test split custom amounts ', () => { { amount: 2, count: 4 } ]; test('testing amount 10', async () => { - const chunks = utils.splitAmount(10, tenToOneAndTwo); + const chunks = utils.splitAmount(10, keys, tenToOneAndTwo); expect(chunks).toStrictEqual([1, 1, 2, 2, 2, 2]); }); - const fiveTwelve: Array = [{ amount: 512, count: 2 }]; - test('testing amount 516', async () => { - const chunks = utils.splitAmount(518, fiveTwelve); + const fiveTwelve: Array = [ + { amount: 512, count: 1 }, + { amount: 2, count: 1 }, + { amount: 4, count: 1} + ]; + test('testing amount 518', async () => { + const chunks = utils.splitAmount(518, keys, fiveTwelve); expect(chunks).toStrictEqual([512, 2, 4]); }); const illegal: Array = [{ amount: 3, count: 2 }]; test('testing non pow2', async () => { - expect(() => utils.splitAmount(6, illegal)).toThrowError(); + expect(() => utils.splitAmount(6, keys, illegal)).toThrowError(); }); const empty: Array = []; test('testing empty', async () => { - const chunks = utils.splitAmount(5, empty); + const chunks = utils.splitAmount(5, keys, empty); + expect(chunks).toStrictEqual([1, 4]); + }); + const undef = undefined; + test('testing undefined', async () => { + const chunks = utils.splitAmount(5, keys, undef); expect(chunks).toStrictEqual([1, 4]); }); }); From a5e4cd217604cbd6842b84bf7e548e3915a725e1 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 18 Jun 2024 13:01:15 +0200 Subject: [PATCH 02/70] remove splitAmount fix for "filling up the rest" behaviour --- src/utils.ts | 15 ++++++--------- test/utils.test.ts | 6 +----- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index dfb0482e..5e6a899d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,15 +7,12 @@ import { sha256 } from '@noble/hashes/sha256'; function splitAmount(value: number, keyset: Keys, amountPreference?: Array): Array { const chunks: Array = []; if (amountPreference) { - if (amountPreference.length > 0) { - chunks.push(...getPreference(value, keyset, amountPreference)); - value = - value - - chunks.reduce((curr, acc) => { - return curr + acc; - }, 0); - return chunks; - } + chunks.push(...getPreference(value, keyset, amountPreference)); + value = + value - + chunks.reduce((curr, acc) => { + return curr + acc; + }, 0); } for (let i = 0; i < 32; i++) { const mask: number = 1 << i; diff --git a/test/utils.test.ts b/test/utils.test.ts index 4b7e0273..3a39605e 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -32,11 +32,7 @@ describe('test split custom amounts ', () => { const chunks = utils.splitAmount(10, keys, tenToOneAndTwo); expect(chunks).toStrictEqual([1, 1, 2, 2, 2, 2]); }); - const fiveTwelve: Array = [ - { amount: 512, count: 1 }, - { amount: 2, count: 1 }, - { amount: 4, count: 1} - ]; + const fiveTwelve: Array = [{ amount: 512, count: 2 }]; test('testing amount 518', async () => { const chunks = utils.splitAmount(518, keys, fiveTwelve); expect(chunks).toStrictEqual([512, 2, 4]); From 4d51bf96c824304e12c2663ea74e4cd985c5eced Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 18 Jun 2024 16:57:12 +0200 Subject: [PATCH 03/70] sendPreference, keepPreference --- src/CashuWallet.ts | 16 +++++++++------- src/model/types/index.ts | 5 +++++ test/wallet.test.ts | 4 ++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 3bb42230..039aaf23 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -20,7 +20,8 @@ import { type SplitPayload, type Token, type TokenEntry, - CheckStateEnum + CheckStateEnum, + Preferences } from './model/types/index.js'; import { bytesToNumber, @@ -178,11 +179,12 @@ class CashuWallet { if (!preference) { preference = getDefaultAmountPreference(amount, keys); } + let pref: Preferences = {sendPreference: preference}; const { payload, blindedMessages } = this.createSplitPayload( amount, tokenEntry.proofs, keys, - preference, + pref, options?.counter, options?.pubkey, options?.privkey @@ -222,14 +224,14 @@ class CashuWallet { proofs: Array, options?: { keysetId?: string; - preference?: Array; + preference?: Preferences; counter?: number; pubkey?: string; privkey?: string; } ): Promise { if (options?.preference) { - amount = options?.preference?.reduce((acc, curr) => acc + curr.amount * curr.count, 0); + amount = options?.preference?.sendPreference.reduce((acc, curr) => acc + curr.amount * curr.count, 0); } const keyset = await this.getKeys(options?.keysetId); let amountAvailable = 0; @@ -508,7 +510,7 @@ class CashuWallet { amount: number, proofsToSend: Array, keyset: MintKeys, - preference?: Array, + preference?: Preferences, counter?: number, pubkey?: string, privkey?: string @@ -520,7 +522,7 @@ class CashuWallet { const keepBlindedMessages = this.createRandomBlindedMessages( totalAmount - amount, keyset, - undefined, + preference?.keepPreference, counter ); if (this._seed && counter) { @@ -529,7 +531,7 @@ class CashuWallet { const sendBlindedMessages = this.createRandomBlindedMessages( amount, keyset, - preference, + preference?.sendPreference, counter, pubkey ); diff --git a/src/model/types/index.ts b/src/model/types/index.ts index da3ac722..e861504a 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -530,6 +530,11 @@ export type AmountPreference = { count: number; }; +export type Preferences = { + sendPreference: Array; + keepPreference?: Array; +} + export type InvoiceData = { paymentRequest: string; amountInSats?: number; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 27fcb722..da613d38 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -465,7 +465,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: { sendPreference: [{ amount: 1, count: 4 }] }}); expect(result.send).toHaveLength(4); expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); @@ -520,7 +520,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: { sendPreference: [{ amount: 1, count: 3 }]} }); expect(result.send).toHaveLength(3); expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); From bd00ff66b716268b463467c6f2a4de36afa54e29 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 18 Jun 2024 21:38:38 +0200 Subject: [PATCH 04/70] splitAmount: split is now based on key amounts, not powers of 2. --- src/utils.ts | 24 ++++++++++++++++-------- test/utils.test.ts | 43 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 5e6a899d..2f4067ef 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,7 +4,12 @@ import { TOKEN_PREFIX, TOKEN_VERSION } from './utils/Constants.js'; import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils'; import { sha256 } from '@noble/hashes/sha256'; -function splitAmount(value: number, keyset: Keys, amountPreference?: Array): Array { +function splitAmount( + value: number, + keyset: Keys, + amountPreference?: Array, + order?: string +): Array { const chunks: Array = []; if (amountPreference) { chunks.push(...getPreference(value, keyset, amountPreference)); @@ -14,13 +19,16 @@ function splitAmount(value: number, keyset: Keys, amountPreference?: Array = Object.keys(keyset) + .map(k => parseInt(k)) + .sort((a, b) => b-a); + sortedKeyAmounts.forEach(amt => { + let q = Math.floor(value / amt); + for (let i=0; i (order === "asc") ? (a-b) : (b-a)); } function isPowerOfTwo(number: number) { diff --git a/test/utils.test.ts b/test/utils.test.ts index 3a39605e..7520a138 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -3,13 +3,23 @@ import * as utils from '../src/utils.js'; import { PUBKEYS } from './consts.js'; const keys: Keys = {}; -for (let i=1; i<2048; i *= 2) { +for (let i=1; i<=2048; i *= 2) { keys[i] = "deadbeef"; } +const keys_base10: Keys = {}; +for (let i=1; i<=10000; i *= 10) { + keys_base10[i] = "deadbeef"; +} + +const keys_base16: Keys = {}; +for (let i=1; i<=0x10000; i *= 16) { + keys_base16[i] = "deadbeef"; +} + describe('test split amounts ', () => { test('testing amount 2561', async () => { - const chunks = utils.splitAmount(2561, keys); + const chunks = utils.splitAmount(2561, keys, undefined, "asc"); expect(chunks).toStrictEqual([1, 512, 2048]); }); test('testing amount 0', async () => { @@ -29,13 +39,13 @@ describe('test split custom amounts ', () => { { amount: 2, count: 4 } ]; test('testing amount 10', async () => { - const chunks = utils.splitAmount(10, keys, tenToOneAndTwo); + const chunks = utils.splitAmount(10, keys, tenToOneAndTwo, "asc"); expect(chunks).toStrictEqual([1, 1, 2, 2, 2, 2]); }); const fiveTwelve: Array = [{ amount: 512, count: 2 }]; test('testing amount 518', async () => { const chunks = utils.splitAmount(518, keys, fiveTwelve); - expect(chunks).toStrictEqual([512, 2, 4]); + expect(chunks).toStrictEqual([512, 4, 2]); }); const illegal: Array = [{ amount: 3, count: 2 }]; test('testing non pow2', async () => { @@ -44,15 +54,36 @@ describe('test split custom amounts ', () => { const empty: Array = []; test('testing empty', async () => { const chunks = utils.splitAmount(5, keys, empty); - expect(chunks).toStrictEqual([1, 4]); + expect(chunks).toStrictEqual([4, 1]); }); const undef = undefined; test('testing undefined', async () => { - const chunks = utils.splitAmount(5, keys, undef); + const chunks = utils.splitAmount(5, keys, undef, "asc"); expect(chunks).toStrictEqual([1, 4]); }); }); +describe('test split different key amount', () => { + test('testing amount 68251', async () => { + const chunks = utils.splitAmount(68251, keys_base10); + expect(chunks).toStrictEqual([ + 10000, 10000, 10000, 10000, 10000, 10000, + 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, + 100, 100, + 10, 10, 10, 10, 10, + 1 + ]); + }); + test('testing amount 1917', async () => { + const chunks = utils.splitAmount(1917, keys_base16); + expect(chunks).toStrictEqual([ + 256, 256, 256, 256, 256, 256, 256, + 16, 16, 16, 16, 16, 16, 16, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ]); + }); +}); + describe('test decode token', () => { test('testing v1 Token', () => { const token = From f7f37a606df2ae9cd4dfdcd706c7a2dfc924018d Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Wed, 19 Jun 2024 16:12:10 +0200 Subject: [PATCH 05/70] fix for integration test --- src/CashuWallet.ts | 4 +++- src/model/types/index.ts | 2 +- src/utils.ts | 23 ++++++++++++----------- test/utils.test.ts | 30 +++++++++++++----------------- test/wallet.test.ts | 8 ++++++-- 5 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 039aaf23..b0a77ff0 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -237,7 +237,9 @@ class CashuWallet { let amountAvailable = 0; const proofsToSend: Array = []; const proofsToKeep: Array = []; - proofs.forEach((proof) => { + // Start from smallest amounts and work your way up. + // This limits small change. + proofs.reverse().forEach((proof) => { if (amountAvailable >= amount) { proofsToKeep.push(proof); return; diff --git a/src/model/types/index.ts b/src/model/types/index.ts index e861504a..af1d2671 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -533,7 +533,7 @@ export type AmountPreference = { export type Preferences = { sendPreference: Array; keepPreference?: Array; -} +}; export type InvoiceData = { paymentRequest: string; diff --git a/src/utils.ts b/src/utils.ts index 2f4067ef..b8516111 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,7 +5,7 @@ import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils'; import { sha256 } from '@noble/hashes/sha256'; function splitAmount( - value: number, + value: number, keyset: Keys, amountPreference?: Array, order?: string @@ -20,15 +20,14 @@ function splitAmount( }, 0); } const sortedKeyAmounts: Array = Object.keys(keyset) - .map(k => parseInt(k)) - .sort((a, b) => b-a); - sortedKeyAmounts.forEach(amt => { + .map((k) => parseInt(k)) + .sort((a, b) => b - a); + sortedKeyAmounts.forEach((amt) => { let q = Math.floor(value / amt); - for (let i=0; i (order === "asc") ? (a-b) : (b-a)); + return chunks.sort((a, b) => (order === 'asc' ? a - b : b - a)); } function isPowerOfTwo(number: number) { @@ -39,14 +38,16 @@ function hasCorrespondingKey(amount: number, keyset: Keys) { return amount in keyset; } -function getPreference(amount: number, keyset: Keys, preferredAmounts: Array): Array { +function getPreference( + amount: number, + keyset: Keys, + preferredAmounts: Array +): Array { const chunks: Array = []; let accumulator = 0; preferredAmounts.forEach((pa) => { if (!hasCorrespondingKey(pa.amount, keyset)) { - throw new Error( - 'Provided amount preferences do not match the amounts of the mint keyset.' - ); + throw new Error('Provided amount preferences do not match the amounts of the mint keyset.'); } for (let i = 1; i <= pa.count; i++) { accumulator += pa.amount; diff --git a/test/utils.test.ts b/test/utils.test.ts index 7520a138..9c27873d 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -3,23 +3,23 @@ import * as utils from '../src/utils.js'; import { PUBKEYS } from './consts.js'; const keys: Keys = {}; -for (let i=1; i<=2048; i *= 2) { - keys[i] = "deadbeef"; +for (let i = 1; i <= 2048; i *= 2) { + keys[i] = 'deadbeef'; } const keys_base10: Keys = {}; -for (let i=1; i<=10000; i *= 10) { - keys_base10[i] = "deadbeef"; +for (let i = 1; i <= 10000; i *= 10) { + keys_base10[i] = 'deadbeef'; } const keys_base16: Keys = {}; -for (let i=1; i<=0x10000; i *= 16) { - keys_base16[i] = "deadbeef"; +for (let i = 1; i <= 0x10000; i *= 16) { + keys_base16[i] = 'deadbeef'; } describe('test split amounts ', () => { test('testing amount 2561', async () => { - const chunks = utils.splitAmount(2561, keys, undefined, "asc"); + const chunks = utils.splitAmount(2561, keys, undefined, 'asc'); expect(chunks).toStrictEqual([1, 512, 2048]); }); test('testing amount 0', async () => { @@ -39,7 +39,7 @@ describe('test split custom amounts ', () => { { amount: 2, count: 4 } ]; test('testing amount 10', async () => { - const chunks = utils.splitAmount(10, keys, tenToOneAndTwo, "asc"); + const chunks = utils.splitAmount(10, keys, tenToOneAndTwo, 'asc'); expect(chunks).toStrictEqual([1, 1, 2, 2, 2, 2]); }); const fiveTwelve: Array = [{ amount: 512, count: 2 }]; @@ -58,7 +58,7 @@ describe('test split custom amounts ', () => { }); const undef = undefined; test('testing undefined', async () => { - const chunks = utils.splitAmount(5, keys, undef, "asc"); + const chunks = utils.splitAmount(5, keys, undef, 'asc'); expect(chunks).toStrictEqual([1, 4]); }); }); @@ -67,19 +67,15 @@ describe('test split different key amount', () => { test('testing amount 68251', async () => { const chunks = utils.splitAmount(68251, keys_base10); expect(chunks).toStrictEqual([ - 10000, 10000, 10000, 10000, 10000, 10000, - 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, - 100, 100, - 10, 10, 10, 10, 10, - 1 + 10000, 10000, 10000, 10000, 10000, 10000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 100, + 100, 10, 10, 10, 10, 10, 1 ]); }); test('testing amount 1917', async () => { const chunks = utils.splitAmount(1917, keys_base16); expect(chunks).toStrictEqual([ - 256, 256, 256, 256, 256, 256, 256, - 16, 16, 16, 16, 16, 16, 16, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + 256, 256, 256, 256, 256, 256, 256, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1 ]); }); }); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index da613d38..ef7efc7d 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -465,7 +465,9 @@ describe('send', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; - const result = await wallet.send(4, overpayProofs, { preference: { sendPreference: [{ amount: 1, count: 4 }] }}); + const result = await wallet.send(4, overpayProofs, { + preference: { sendPreference: [{ amount: 1, count: 4 }] } + }); expect(result.send).toHaveLength(4); expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); @@ -520,7 +522,9 @@ describe('send', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; - const result = await wallet.send(4, overpayProofs, { preference: { sendPreference: [{ amount: 1, count: 3 }]} }); + const result = await wallet.send(4, overpayProofs, { + preference: { sendPreference: [{ amount: 1, count: 3 }] } + }); expect(result.send).toHaveLength(3); expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); From 63b2c1ecd78ee73c796724990cd3a0cd17c2128e Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Thu, 20 Jun 2024 15:36:40 +0200 Subject: [PATCH 06/70] primary order is ascending amounts --- src/CashuWallet.ts | 4 +--- src/utils.ts | 2 +- test/utils.test.ts | 16 ++++++++-------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index b0a77ff0..039aaf23 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -237,9 +237,7 @@ class CashuWallet { let amountAvailable = 0; const proofsToSend: Array = []; const proofsToKeep: Array = []; - // Start from smallest amounts and work your way up. - // This limits small change. - proofs.reverse().forEach((proof) => { + proofs.forEach((proof) => { if (amountAvailable >= amount) { proofsToKeep.push(proof); return; diff --git a/src/utils.ts b/src/utils.ts index b8516111..8b1b1ed6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -27,7 +27,7 @@ function splitAmount( for (let i = 0; i < q; ++i) chunks.push(amt); value %= amt; }); - return chunks.sort((a, b) => (order === 'asc' ? a - b : b - a)); + return chunks.sort((a, b) => (order === 'desc' ? b - a : a - b)); } function isPowerOfTwo(number: number) { diff --git a/test/utils.test.ts b/test/utils.test.ts index 9c27873d..72284e6f 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -19,7 +19,7 @@ for (let i = 1; i <= 0x10000; i *= 16) { describe('test split amounts ', () => { test('testing amount 2561', async () => { - const chunks = utils.splitAmount(2561, keys, undefined, 'asc'); + const chunks = utils.splitAmount(2561, keys); expect(chunks).toStrictEqual([1, 512, 2048]); }); test('testing amount 0', async () => { @@ -39,12 +39,12 @@ describe('test split custom amounts ', () => { { amount: 2, count: 4 } ]; test('testing amount 10', async () => { - const chunks = utils.splitAmount(10, keys, tenToOneAndTwo, 'asc'); + const chunks = utils.splitAmount(10, keys, tenToOneAndTwo); expect(chunks).toStrictEqual([1, 1, 2, 2, 2, 2]); }); const fiveTwelve: Array = [{ amount: 512, count: 2 }]; test('testing amount 518', async () => { - const chunks = utils.splitAmount(518, keys, fiveTwelve); + const chunks = utils.splitAmount(518, keys, fiveTwelve, 'desc'); expect(chunks).toStrictEqual([512, 4, 2]); }); const illegal: Array = [{ amount: 3, count: 2 }]; @@ -53,19 +53,19 @@ describe('test split custom amounts ', () => { }); const empty: Array = []; test('testing empty', async () => { - const chunks = utils.splitAmount(5, keys, empty); + const chunks = utils.splitAmount(5, keys, empty, 'desc'); expect(chunks).toStrictEqual([4, 1]); }); const undef = undefined; test('testing undefined', async () => { - const chunks = utils.splitAmount(5, keys, undef, 'asc'); + const chunks = utils.splitAmount(5, keys, undef); expect(chunks).toStrictEqual([1, 4]); }); }); describe('test split different key amount', () => { test('testing amount 68251', async () => { - const chunks = utils.splitAmount(68251, keys_base10); + const chunks = utils.splitAmount(68251, keys_base10, undefined, 'desc'); expect(chunks).toStrictEqual([ 10000, 10000, 10000, 10000, 10000, 10000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 100, 100, 10, 10, 10, 10, 10, 1 @@ -74,8 +74,8 @@ describe('test split different key amount', () => { test('testing amount 1917', async () => { const chunks = utils.splitAmount(1917, keys_base16); expect(chunks).toStrictEqual([ - 256, 256, 256, 256, 256, 256, 256, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 16, 16, 16, 16, 16, 16, 16, 256, 256, 256, 256, 256, 256, 256 ]); }); }); From 4766e90fe5f1b9babab69b8c38676df913713687 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Thu, 20 Jun 2024 15:42:58 +0200 Subject: [PATCH 07/70] npm run format --- src/CashuWallet.ts | 7 +++++-- test/utils.test.ts | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 039aaf23..2bb25dae 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -179,7 +179,7 @@ class CashuWallet { if (!preference) { preference = getDefaultAmountPreference(amount, keys); } - let pref: Preferences = {sendPreference: preference}; + let pref: Preferences = { sendPreference: preference }; const { payload, blindedMessages } = this.createSplitPayload( amount, tokenEntry.proofs, @@ -231,7 +231,10 @@ class CashuWallet { } ): Promise { if (options?.preference) { - amount = options?.preference?.sendPreference.reduce((acc, curr) => acc + curr.amount * curr.count, 0); + amount = options?.preference?.sendPreference.reduce( + (acc, curr) => acc + curr.amount * curr.count, + 0 + ); } const keyset = await this.getKeys(options?.keysetId); let amountAvailable = 0; diff --git a/test/utils.test.ts b/test/utils.test.ts index 72284e6f..86727a3e 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -74,8 +74,8 @@ describe('test split different key amount', () => { test('testing amount 1917', async () => { const chunks = utils.splitAmount(1917, keys_base16); expect(chunks).toStrictEqual([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 16, 16, 16, 16, 16, 16, 16, 256, 256, 256, 256, 256, 256, 256 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 16, 16, 16, 16, 16, 16, 256, 256, 256, 256, 256, + 256, 256 ]); }); }); From 9a0e589701d397f4e4276045e156b65fa4e1d565 Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 28 Jun 2024 11:22:23 +0200 Subject: [PATCH 08/70] updated tests --- test/utils.test.ts | 138 +++++++++++++++++++-------------------------- 1 file changed, 59 insertions(+), 79 deletions(-) diff --git a/test/utils.test.ts b/test/utils.test.ts index c43b36fe..15fa721a 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,4 +1,4 @@ -import { AmountPreference } from '../src/model/types/index.js'; +import { AmountPreference, Token } from '../src/model/types/index.js'; import * as utils from '../src/utils.js'; import { PUBKEYS } from './consts.js'; @@ -47,87 +47,20 @@ describe('test decode token', () => { test('testing v1 Token', () => { const token = 'W3siaWQiOiIwTkkzVFVBczFTZnkiLCJhbW91bnQiOjIsInNlY3JldCI6Ild6ZC9vNUVHdmVKb3hTQVlGcjZ1U3lnUmFWSUFrOFc4MXNLTlRxdVd4UjQ9IiwiQyI6IjAzNWNiZmQwOTNiOWZlMWRjNjU2MGEwNDM3YzQyNDQxZjA0ZDIyYzk4MDY2NGMyNGExMGZlZGFiNTlmZWY0YmZjOSJ9LHsiaWQiOiIwTkkzVFVBczFTZnkiLCJhbW91bnQiOjQsInNlY3JldCI6InU0N2lWUkhneUNuUFhCNWxOdFpGaTBOeHpPZ1lyRk1WODV2aFpyRThIbWM9IiwiQyI6IjAyNThiYmZkZWJmZGQzYjk0OTljZDk1YzFkMWZiYTVjZTQ1MWFjOGNlZTE0NzM1Yzk2MGFiMDc1ZmI2ZTQ4ZjBkYyJ9LHsiaWQiOiIwTkkzVFVBczFTZnkiLCJhbW91bnQiOjY0LCJzZWNyZXQiOiJ1YTFaT0hjeVB3T0M0UUxPaWthQVV1MThJM2pEUDJCSVNYREFGcW91N1VNPSIsIkMiOiIwMjU2MWNhNjcyNTdlNzdhNjNjN2U3NWQ4MGVkYTI3ZDlhMmEyYzUxZTA0NGM4ZjhmODVlNzc0OTZlMGRlM2U2NWIifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50IjoxLCJzZWNyZXQiOiJ5ZTlNRCtaQ25VUHlHOTBscmYyZ2tudnA3N2I4V05wNUxRT2ZtcERjRGNFPSIsIkMiOiIwM2UwN2M1NjExNzcwMmNmODg3MDFlYjAyOTM2YjA5MDNhZmEyMTQwZDcwNTY1N2ZkODVkM2YxZWI5MzRiYTBjYzMifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50IjoyLCJzZWNyZXQiOiJIUHpzRmZPUDFWRU1BMW8vTnFHVXFhRXdaV2RiN3VERzM4T1grLzlZTURzPSIsIkMiOiIwMmQ3ZDE1YTBhZmIyNThjMjlhZDdmOWY4N2ZmMzIxZWRmNTgyOTM0ZWI0NWExNTE2MjhiNTJjMDExZjQ2MWZkOGEifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50IjoxLCJzZWNyZXQiOiJnMVR1YXdha1RVQkJBTW9tZGpDVHkrRENNTnBaUmd3dWluNXB5V2xoTVVNPSIsIkMiOiIwMzU4Y2IxMGE5NWEzY2E1YmE5MTc5MTllMWNhODA1NjZmMTg5NTI4Njk1MTJjYWFjMDlmYmQ5MGYxN2QyZTZlYmEifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50IjoyLCJzZWNyZXQiOiJRMTFyamNXWk55Q2dkRmxqRThaNkdwNFhDYllKcndzRGhncXVQOTU1VWU0PSIsIkMiOiIwMjAxNjBmODIwNGU4MGIxNDg4NmFlMzZjMzRiMjI3ODllMzMxZmM5MjVhNGMwOGE3ZWYxZDZjYzMyYTIwNjZjZWUifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50Ijo4LCJzZWNyZXQiOiI1MVZrUXFYT2kwM0k2a0pzM0tlSEI0OVVCQTFSRktrWnMyMFljZEtOSW1JPSIsIkMiOiIwMjZiYWU2YTgzOWE3OTdjNmU5NGZlNGM5MWZlNTIwOGU4MDE3MTg2Y2NkMDk0ZmI4ZTNkZjYyNjAyZWJmMjczMjUifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50IjoxNiwic2VjcmV0IjoiVk4ySlMwUENKdGQ3MjJUTXUxdGFxNUZSMXg0dDlXM28xNndWRGVweXBxYz0iLCJDIjoiMDIxMmM4ZGE5NWE4NDEyYjgyMDE4MTgxNzQxZWY1YWQ0ZjYzMTU1NjBhMWFmODM5ZjMxOTU4NTcwZTVlYzI2ZDQyIn1d'; - - const result = utils.getDecodedToken(token); - expect(result.token[0].proofs.reduce((c, p) => c + p.amount, 0)).toEqual(100); - expect(result.token[0].mint).toStrictEqual(''); - }); - test('test corrupt v1 token', () => { - const token = - 'W3siaWQiOiIwTkkzVFVBczFTZnkiLCJhbW91bnQiOjIsInNlY3JldCI6Ild6ZC9vNUVHdmVKb3hTQVlGcjZ1U3lnUmFWSUFrOFc4MXNLTlRxdVd4UjQ9IiwiQyI6IjAzNWNiZmQwOTNiOWZlMWRjNjU2MGEwNDM3YzQyNDQxZjA0ZDIyYzk4MDY2NGMyNGExMGZlZGFiNTlmZWY0YmZjOSJ9LHsiaWQiOiIwTkkzVFVBczFTZnkiLCJhbW91bnQiOjQsInNlY3JldCI6InU0N2lWUkhneUNuUFhCNWxOdFpGaTBOIkMiOiIwMmQ3ZDE1YTBhZmIyNThjMjlhZDdmOWY4N2ZmMzIxZWRmNTgyOTM0ZWI0NWExNTE2MjhiNTJjMDExZjQ2MWZkOGEifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50IjoxLCJzZWNyZXQiOiJnMVR1YXdha1RVQkJBTW9tZGpDVHkrRENNTnBaUmd3dWluNXB5V2xoTVVNPSIsIkMiOiIwMzU4Y2IxMGE5NWEzY2E1YmE5MTc5MTllMWNhODA1NjZmMTg5NTI4Njk1MTJjYWFjMDlmYmQ5MGYxN2QyZTZlYmEifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50IjoyLCJzZWNyZXQiOiJRMTFyamNXWk55Q2dkRmxqRThaNkdwNFhDYllKcndzRGhncXVQOTU1VWU0PSIsIkMiOiIwMjAxNjBmODIwNGU4MGIxNDg4NmFlMzZjMzRiMjI3ODllMzMxZmM5MjVhNGMwOGE3ZWYxZDZjYzMyYTIwNjZjZWUifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50Ijo4LCJzZWNyZXQiOiI1MVZrUXFYT2kwM0k2a0pzM0tlSEI0OVVCQTFSRktrWnMyMFljZEtOSW1JPSIsIkMiOiIwMjZiYWU2YTgzOWE3OTdjNmU5NGZlNGM5MWZlNTIwOGU4MDE3MTg2Y2NkMDk0ZmI4ZTNkZjYyNjAyZWJmMjczMjUifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50IjoxNiwic2VjcmV0IjoiVk4ySlMwUENKdGQ3MjJUTXUxdGFxNUZSMXg0dDlXM28xNndWRGVweXBxYz0iLCJDIjoiMDIxMmM4ZGE5NWE4NDEyYjgyMDE4MTgxNzQxZWY1YWQ0ZjYzMTU1NjBhMWFmODM5ZjMxOTU4NTcwZTVlYzI2ZDQyIn1d'; - expect(() => utils.getDecodedToken(token)).toThrowError(); + let result: Token | undefined; + expect(() => { + result = utils.getDecodedToken(token); + }).toThrow(); + expect(result).toBe(undefined); }); test('testing v2 Token', async () => { const token = 'eyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6IkkyeU4raVJZZmt6VCIsImFtb3VudCI6MSwic2VjcmV0IjoiOTd6Zm1tYUdmNWs4TWcwZ2FqcG5ibXBlcnZUdEVlRTh3d0tyaTdyV3BVcz0iLCJDIjoiMDIxOTUwODFlNjIyZjk4YmZjMTlhMDVlYmUyMzQxZDk1NWMwZDEyNTg4YzU5NDhjODU4ZDA3YWRlYzAwN2JjMWU0In1dLCJtaW50IjoiaHR0cDovL2xvY2FsaG9zdDozMzM4In1dfQ'; - - const result = utils.getDecodedToken(token); - expect(result).toStrictEqual({ - token: [ - { - proofs: [ - { - id: 'I2yN+iRYfkzT', - amount: 1, - secret: '97zfmmaGf5k8Mg0gajpnbmpervTtEeE8wwKri7rWpUs=', - C: '02195081e622f98bfc19a05ebe2341d955c0d12588c5948c858d07adec007bc1e4' - } - ], - mint: 'http://localhost:3338' - } - ] - }); - }); - test('testing v2 Token 2', () => { - const token = - 'eyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6IjBOSTNUVUFzMVNmeSIsImFtb3VudCI6MSwic2VjcmV0Ijoia3hxWFlwYkNWb0l1eDRsbVdJRFh4M29NMi83S1ZzaHFqSklJZXh0cU1hVT0iLCJDIjoiMDJhNDIxNDBkMWJiZDU5Y2E0YzViYTllYjczZDAyMGYzMWY2OGY0ZTMwNzhmZDNhZjFlZTY0ZWJlY2U1MmI2ZWRhIn1dLCJtaW50IjoiaHR0cDovL2xvY2FsaG9zdDozMzM4In1dfQ'; - - const result = utils.getDecodedToken(token); - expect(result).toStrictEqual({ - token: [ - { - proofs: [ - { - id: '0NI3TUAs1Sfy', - amount: 1, - secret: 'kxqXYpbCVoIux4lmWIDXx3oM2/7KVshqjJIIextqMaU=', - C: '02a42140d1bbd59ca4c5ba9eb73d020f31f68f4e3078fd3af1ee64ebece52b6eda' - } - ], - mint: 'http://localhost:3338' - } - ] - }); - }); - test('test corrupt v2 token', () => { - const token = - 'W3siaWQiOiIwTkkzVFVBczFTZnkiLCJhbW91bnQiOjIsInNlY3JldCI6Ild6ZC9vNUVHdmVKb3hTQVlGcjZ1U3lnUmFWSUFrOFc4MXNLTlRxdVd4UjQ9IiwiQyI6IjAzNWNiZmQwOTNiOWZlMWRjNjU2MGEwNDM3YzQyNDQxZjA0ZDIyYzk4MDY2NGMyNGExMGZlZGFiNTlmZWY0YmZjOSJ9LHsiaWQiOiIwTkkzVFVBczFTZnkiLCJhbW91bnQiOjQsInNlY3JldCI6InU0N2lWUkhneUNuUFhCNWxOdFpGaTBOIkMiOiIwMmQ3ZDE1YTBhZmIyNThjMjlhZDdmOWY4N2ZmMzIxZWRmNTgyOTM0ZWI0NWExNTE2MjhiNTJjMDExZjQ2MWZkOGEifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50IjoxLCJzZWNyZXQiOiJnMVR1YXdha1RVQkJBTW9tZGpDkMiOiIwMjZiYWU2YTgzOWE3OTdjNmU5NGZlNGM5MWZlNTIwOGU4MDE3MTg2Y2NkMDk0ZmI4ZTNkZjYyNjAyZWJmMjczMjUifSx7ImlkIjoiME5JM1RVQXMxU2Z5IiwiYW1vdW50IjoxNiwic2VjcmV0IjoiVk4ySlMwUENKdGQ3MjJUTXUxdGFxNUZSMXg0dDlXM28xNndWRGVweXBxYz0iLCJDIjoiMDIxMmM4ZGE5NWE4NDEyYjgyMDE4MTgxNzQxZWY1YWQ0ZjYzMTU1NjBhMWFmODM5ZjMxOTU4NTcwZTVlYzI2ZDQyIn1d'; - - expect(() => utils.getDecodedToken(token)).toThrowError(); - }); -}); - -describe('test encode token', () => { - test('testing v3 Token', async () => { - const token = - 'cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCIsInByb29mcyI6W3siaWQiOiJJMnlOK2lSWWZrelQiLCJhbW91bnQiOjEsInNlY3JldCI6Ijk3emZtbWFHZjVrOE1nMGdhanBuYm1wZXJ2VHRFZUU4d3dLcmk3cldwVXM9IiwiQyI6IjAyMTk1MDgxZTYyMmY5OGJmYzE5YTA1ZWJlMjM0MWQ5NTVjMGQxMjU4OGM1OTQ4Yzg1OGQwN2FkZWMwMDdiYzFlNCJ9XX1dfQ'; - - const obj = { - proofs: [ - { - id: 'I2yN+iRYfkzT', - amount: 1, - secret: '97zfmmaGf5k8Mg0gajpnbmpervTtEeE8wwKri7rWpUs=', - C: '02195081e622f98bfc19a05ebe2341d955c0d12588c5948c858d07adec007bc1e4' - } - ], - mints: [{ url: 'http://localhost:3338', ids: ['L3zxxRB/I8uE', 'I2yN+iRYfkzT'] }] - }; - - const result = utils.getEncodedToken({ - token: [{ mint: obj.mints[0].url, proofs: obj.proofs }] - }); - expect(result).toEqual(token); + let result: Token | undefined; + expect(() => { + result = utils.getDecodedToken(token); + }).toThrow(); + expect(result).toBe(undefined); }); }); @@ -176,10 +109,57 @@ describe('test decode token', () => { }; const token = - 'eyJ0b2tlbiI6W3sibWludCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCIsInByb29mcyI6W3siaWQiOiJJMnlOK2lSWWZrelQiLCJhbW91bnQiOjEsInNlY3JldCI6Ijk3emZtbWFHZjVrOE1nMGdhanBuYm1wZXJ2VHRFZUU4d3dLcmk3cldwVXM9IiwiQyI6IjAyMTk1MDgxZTYyMmY5OGJmYzE5YTA1ZWJlMjM0MWQ5NTVjMGQxMjU4OGM1OTQ4Yzg1OGQwN2FkZWMwMDdiYzFlNCJ9XX1dfQ'; + 'AeyJ0b2tlbiI6W3sibWludCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCIsInByb29mcyI6W3siaWQiOiJJMnlOK2lSWWZrelQiLCJhbW91bnQiOjEsInNlY3JldCI6Ijk3emZtbWFHZjVrOE1nMGdhanBuYm1wZXJ2VHRFZUU4d3dLcmk3cldwVXM9IiwiQyI6IjAyMTk1MDgxZTYyMmY5OGJmYzE5YTA1ZWJlMjM0MWQ5NTVjMGQxMjU4OGM1OTQ4Yzg1OGQwN2FkZWMwMDdiYzFlNCJ9XX1dfQ'; const result = utils.getDecodedToken(token); expect(result).toStrictEqual(obj); }); + test('testing v4 Token', () => { + const v3Token = { + memo: '', + token: [ + { + mint: 'https://mint.minibits.cash/Bitcoin', + proofs: [ + { + secret: '7e98535c6f8cd7a5eff150963a2743613a91e9498150fd5af8d2bfcfd5babe68', + C: '03022a28d163cf63792c1533e6660112f2b75db2fe46aa840e7f5d0f979a2c6cfd', + id: '00500550f0494146', + amount: 16 + }, + { + amount: 4, + secret: '96bd8480717673311bc70e92818b5babcb665edee39b639defad5584d8d18b1f', + C: '030936759e03235867f9cea58f047c043acdd7455f604c92c75839e5e08a91e198', + id: '00500550f0494146' + }, + { + secret: 'e145fa7fba21a9cd3c8743c9de5e4de33e0095abc50b262f1b3831b69b8f63df', + id: '00500550f0494146', + C: '03eba391a31e101e1ba1853db1e4bbb6a166d4fbbb1e181e82892c3301e4e02015', + amount: 1 + } + ] + } + ] + }; + + const token = + 'cashuBuQACYXSBuQACYXCDuQADYWEQYXN4QDdlOTg1MzVjNmY4Y2Q3YTVlZmYxNTA5NjNhMjc0MzYxM2E5MWU5NDk4MTUwZmQ1YWY4ZDJiZmNmZDViYWJlNjhhY3hCMDMwMjJhMjhkMTYzY2Y2Mzc5MmMxNTMzZTY2NjAxMTJmMmI3NWRiMmZlNDZhYTg0MGU3ZjVkMGY5NzlhMmM2Y2ZkuQADYWEEYXN4QDk2YmQ4NDgwNzE3NjczMzExYmM3MGU5MjgxOGI1YmFiY2I2NjVlZGVlMzliNjM5ZGVmYWQ1NTg0ZDhkMThiMWZhY3hCMDMwOTM2NzU5ZTAzMjM1ODY3ZjljZWE1OGYwNDdjMDQzYWNkZDc0NTVmNjA0YzkyYzc1ODM5ZTVlMDhhOTFlMTk4uQADYWEBYXN4QGUxNDVmYTdmYmEyMWE5Y2QzYzg3NDNjOWRlNWU0ZGUzM2UwMDk1YWJjNTBiMjYyZjFiMzgzMWI2OWI4ZjYzZGZhY3hCMDNlYmEzOTFhMzFlMTAxZTFiYTE4NTNkYjFlNGJiYjZhMTY2ZDRmYmJiMWUxODFlODI4OTJjMzMwMWU0ZTAyMDE1YWlwMDA1MDA1NTBmMDQ5NDE0NmFteCJodHRwczovL21pbnQubWluaWJpdHMuY2FzaC9CaXRjb2lu'; + + const result = utils.getDecodedToken(token); + console.log(JSON.stringify(result)); + expect(result).toStrictEqual(v3Token); + }); + 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', () => { From 6ed41a99073257ece044947acb9c4e327cb81b4d Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 28 Jun 2024 11:22:49 +0200 Subject: [PATCH 09/70] added cbor-x --- package-lock.json | 2747 +++++++++++++++++++++++---------------------- package.json | 3 +- 2 files changed, 1433 insertions(+), 1317 deletions(-) diff --git a/package-lock.json b/package-lock.json index 973669c6..f4b4f45e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", "@scure/bip39": "^1.2.2", - "buffer": "^6.0.3" + "buffer": "^6.0.3", + "cbor-x": "^1.5.9" }, "devDependencies": { "@types/jest": "^29.5.1", @@ -51,119 +52,48 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", - "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.20.7", - "@babel/helpers": "^7.20.7", - "@babel/parser": "^7.20.7", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7", - "convert-source-map": "^1.7.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -173,21 +103,15 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -195,186 +119,188 @@ } }, "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", - "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.10", - "@babel/types": "^7.20.7" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.20.2" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", - "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -452,9 +378,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -524,12 +450,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", - "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -626,12 +552,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", - "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -641,33 +567,33 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -676,13 +602,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -707,6 +633,78 @@ "buffer": "^6.0.3" } }, + "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz", + "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-darwin-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz", + "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz", + "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz", + "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz", + "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-win32-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz", + "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "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", @@ -889,16 +887,16 @@ } }, "node_modules/@jest/console": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", - "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -906,37 +904,37 @@ } }, "node_modules/@jest/core": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", - "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/reporters": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-resolve-dependencies": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "jest-watcher": "^29.5.0", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -953,89 +951,89 @@ } }, "node_modules/@jest/environment": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", - "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "dependencies": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "dependencies": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", - "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", - "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", - "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -1043,13 +1041,13 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1067,25 +1065,53 @@ } } }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, @@ -1094,13 +1120,13 @@ } }, "node_modules/@jest/test-result": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", - "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -1109,14 +1135,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", - "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "dependencies": { - "@jest/test-result": "^29.5.0", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1124,22 +1150,22 @@ } }, "node_modules/@jest/transform": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", - "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1150,12 +1176,12 @@ } }, "node_modules/@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1189,9 +1215,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1204,13 +1230,13 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@noble/curves": { @@ -1304,27 +1330,27 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "dependencies": { - "@sinonjs/commons": "^2.0.0" + "@sinonjs/commons": "^3.0.0" } }, "node_modules/@tsconfig/node10": { @@ -1352,9 +1378,9 @@ "dev": true }, "node_modules/@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -1365,18 +1391,18 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -1384,12 +1410,12 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, "dependencies": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "node_modules/@types/graceful-fs": { @@ -1463,12 +1489,6 @@ "form-data": "^3.0.0" } }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, "node_modules/@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", @@ -1973,15 +1993,15 @@ "dev": true }, "node_modules/babel-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", - "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "dependencies": { - "@jest/transform": "^29.5.0", + "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", + "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -2010,9 +2030,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", - "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", @@ -2048,12 +2068,12 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", - "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^29.5.0", + "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { @@ -2112,9 +2132,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "funding": [ { @@ -2124,13 +2144,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" }, "bin": { "browserslist": "cli.js" @@ -2263,9 +2287,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001441", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz", - "integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==", + "version": "1.0.30001638", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz", + "integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==", "dev": true, "funding": [ { @@ -2275,9 +2299,42 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, + "node_modules/cbor-extract": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz", + "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.1.1" + }, + "bin": { + "download-cbor-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", + "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", + "@cbor-extract/cbor-extract-linux-x64": "2.2.0", + "@cbor-extract/cbor-extract-win32-x64": "2.2.0" + } + }, + "node_modules/cbor-x": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.5.9.tgz", + "integrity": "sha512-OEI5rEu3MeR0WWNUXuIGkxmbXVhABP+VtgAXzm48c9ulkrsvxshjjk94XSOGphyAKeNGLPfAxxzEtgQ6rEVpYQ==", + "optionalDependencies": { + "cbor-extract": "^2.2.0" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2313,9 +2370,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, "node_modules/cliui": { @@ -2343,9 +2400,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "node_modules/color-convert": { @@ -2390,6 +2447,27 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2428,10 +2506,18 @@ } }, "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } }, "node_modules/deep-is": { "version": "0.1.4", @@ -2473,6 +2559,15 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2492,9 +2587,9 @@ } }, "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -2525,9 +2620,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "version": "1.4.814", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.814.tgz", + "integrity": "sha512-GVulpHjFu1Y9ZvikvbArHmAhZXtm3wHlpjTMcXNGKl4IQ4jMQjlnz8yMQYYqdLHKi/jEL2+CBC2akWVCoIGUdw==", "dev": true }, "node_modules/emittery": { @@ -2623,9 +2718,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -3253,16 +3348,16 @@ } }, "node_modules/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4077,17 +4172,17 @@ } }, "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/istanbul-lib-source-maps": { @@ -4105,9 +4200,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -4118,15 +4213,15 @@ } }, "node_modules/jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.5.0" + "jest-cli": "^29.7.0" }, "bin": { "jest": "bin/jest.js" @@ -4144,12 +4239,13 @@ } }, "node_modules/jest-changed-files": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", - "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "dependencies": { "execa": "^5.0.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0" }, "engines": { @@ -4157,28 +4253,28 @@ } }, "node_modules/jest-circus": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", - "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "dedent": "^0.7.0", + "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.5.0", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -4188,22 +4284,21 @@ } }, "node_modules/jest-cli": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", - "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "dependencies": { - "@jest/core": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", + "create-jest": "^29.7.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "prompts": "^2.0.1", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "bin": { @@ -4222,31 +4317,31 @@ } }, "node_modules/jest-config": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", - "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.5.0", - "jest-environment-node": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -4267,24 +4362,24 @@ } }, "node_modules/jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", - "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" @@ -4294,62 +4389,62 @@ } }, "node_modules/jest-each": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", - "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", - "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", - "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -4361,46 +4456,46 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", - "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", - "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -4409,14 +4504,14 @@ } }, "node_modules/jest-mock": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", - "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.5.0" + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4440,26 +4535,26 @@ } }, "node_modules/jest-regex-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", - "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -4469,43 +4564,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", - "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.5.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", - "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/environment": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-leak-detector": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-resolve": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-util": "^29.5.0", - "jest-watcher": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -4514,31 +4609,31 @@ } }, "node_modules/jest-runtime": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", - "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/globals": "^29.5.0", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -4547,59 +4642,41 @@ } }, "node_modules/jest-snapshot": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", - "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.5.0", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^29.5.0", - "semver": "^7.3.5" + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -4607,19 +4684,13 @@ "node": ">=10" } }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -4631,17 +4702,17 @@ } }, "node_modules/jest-validate": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", - "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^29.5.0" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -4660,18 +4731,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", - "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "dependencies": { - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "string-length": "^4.0.1" }, "engines": { @@ -4679,13 +4750,13 @@ } }, "node_modules/jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -4874,20 +4945,32 @@ "dev": true }, "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5047,6 +5130,20 @@ } } }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5054,9 +5151,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", - "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/normalize-path": { @@ -5298,9 +5395,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -5361,12 +5458,12 @@ } }, "node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -5418,9 +5515,9 @@ } }, "node_modules/pure-rand": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", - "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -6184,9 +6281,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "funding": [ { @@ -6196,14 +6293,18 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -6225,25 +6326,19 @@ "dev": true }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", @@ -6373,9 +6468,9 @@ "dev": true }, "node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -6433,260 +6528,196 @@ } }, "@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "requires": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" } }, "@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "dev": true }, "@babel/core": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", - "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.20.7", - "@babel/helpers": "^7.20.7", - "@babel/parser": "^7.20.7", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7", - "convert-source-map": "^1.7.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "dependencies": { - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - } + "json5": "^2.2.3", + "semver": "^6.3.1" } }, "@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dev": true, "requires": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" } } } }, "@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dev": true, "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "semver": "^6.3.1" } }, "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dev": true, + "requires": { + "@babel/types": "^7.24.7" + } }, "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dev": true, "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dev": true, "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-module-transforms": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", - "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.10", - "@babel/types": "^7.20.7" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" } }, "@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "dev": true }, "@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "requires": { - "@babel/types": "^7.20.2" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "dev": true }, "@babel/helpers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", - "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dev": true, "requires": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "dependencies": { "ansi-styles": { @@ -6748,9 +6779,9 @@ } }, "@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -6799,12 +6830,12 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", - "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -6871,51 +6902,51 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", - "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" } }, @@ -6937,6 +6968,42 @@ "buffer": "^6.0.3" } }, + "@cbor-extract/cbor-extract-darwin-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz", + "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", + "optional": true + }, + "@cbor-extract/cbor-extract-darwin-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz", + "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", + "optional": true + }, + "@cbor-extract/cbor-extract-linux-arm": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz", + "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", + "optional": true + }, + "@cbor-extract/cbor-extract-linux-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz", + "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", + "optional": true + }, + "@cbor-extract/cbor-extract-linux-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz", + "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", + "optional": true + }, + "@cbor-extract/cbor-extract-win32-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz", + "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", + "optional": true + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -7071,124 +7138,124 @@ "dev": true }, "@jest/console": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", - "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "requires": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "@jest/core": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", - "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "requires": { - "@jest/console": "^29.5.0", - "@jest/reporters": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-resolve-dependencies": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "jest-watcher": "^29.5.0", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" } }, "@jest/environment": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", - "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "requires": { - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0" + "jest-mock": "^29.7.0" } }, "@jest/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "requires": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" } }, "@jest/expect-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "requires": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" } }, "@jest/fake-timers": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", - "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "requires": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" } }, "@jest/globals": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", - "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "requires": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" } }, "@jest/reporters": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", - "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -7196,80 +7263,101 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" + }, + "dependencies": { + "istanbul-lib-instrument": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "dev": true, + "requires": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + } + }, + "semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true + } } }, "@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "requires": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" } }, "@jest/source-map": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.15", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "@jest/test-result": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", - "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "requires": { - "@jest/console": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", - "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "requires": { - "@jest/test-result": "^29.5.0", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "@jest/transform": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", - "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "requires": { "@babel/core": "^7.11.6", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -7277,12 +7365,12 @@ } }, "@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "requires": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -7307,9 +7395,9 @@ "dev": true }, "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true }, "@jridgewell/sourcemap-codec": { @@ -7319,13 +7407,13 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@noble/curves": { @@ -7392,27 +7480,27 @@ } }, "@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "requires": { - "@sinonjs/commons": "^2.0.0" + "@sinonjs/commons": "^3.0.0" } }, "@tsconfig/node10": { @@ -7440,9 +7528,9 @@ "dev": true }, "@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "requires": { "@babel/parser": "^7.20.7", @@ -7453,18 +7541,18 @@ } }, "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "requires": { "@babel/types": "^7.0.0" } }, "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -7472,12 +7560,12 @@ } }, "@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, "requires": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "@types/graceful-fs": { @@ -7551,12 +7639,6 @@ "form-data": "^3.0.0" } }, - "@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, "@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", @@ -7897,15 +7979,15 @@ "dev": true }, "babel-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", - "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "requires": { - "@jest/transform": "^29.5.0", + "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", + "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -7925,9 +8007,9 @@ } }, "babel-plugin-jest-hoist": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", - "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "requires": { "@babel/template": "^7.3.3", @@ -7957,12 +8039,12 @@ } }, "babel-preset-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", - "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^29.5.0", + "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" } }, @@ -7997,15 +8079,15 @@ } }, "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" } }, "bs-logger": { @@ -8099,11 +8181,34 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001441", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz", - "integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==", + "version": "1.0.30001638", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz", + "integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==", "dev": true }, + "cbor-extract": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz", + "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", + "optional": true, + "requires": { + "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", + "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", + "@cbor-extract/cbor-extract-linux-x64": "2.2.0", + "@cbor-extract/cbor-extract-win32-x64": "2.2.0", + "node-gyp-build-optional-packages": "5.1.1" + } + }, + "cbor-x": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.5.9.tgz", + "integrity": "sha512-OEI5rEu3MeR0WWNUXuIGkxmbXVhABP+VtgAXzm48c9ulkrsvxshjjk94XSOGphyAKeNGLPfAxxzEtgQ6rEVpYQ==", + "requires": { + "cbor-extract": "^2.2.0" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8127,9 +8232,9 @@ "dev": true }, "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, "cliui": { @@ -8150,9 +8255,9 @@ "dev": true }, "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "color-convert": { @@ -8191,6 +8296,21 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + } + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -8218,10 +8338,11 @@ } }, "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "requires": {} }, "deep-is": { "version": "0.1.4", @@ -8251,6 +8372,12 @@ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, + "detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "optional": true + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -8264,9 +8391,9 @@ "dev": true }, "diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true }, "dir-glob": { @@ -8288,9 +8415,9 @@ } }, "electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "version": "1.4.814", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.814.tgz", + "integrity": "sha512-GVulpHjFu1Y9ZvikvbArHmAhZXtm3wHlpjTMcXNGKl4IQ4jMQjlnz8yMQYYqdLHKi/jEL2+CBC2akWVCoIGUdw==", "dev": true }, "emittery": { @@ -8368,9 +8495,9 @@ } }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true }, "escape-string-regexp": { @@ -8807,16 +8934,16 @@ "dev": true }, "expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "requires": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" } }, "fast-deep-equal": { @@ -9390,13 +9517,13 @@ } }, "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "requires": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, @@ -9412,9 +9539,9 @@ } }, "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -9422,227 +9549,227 @@ } }, "jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "requires": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.5.0" + "jest-cli": "^29.7.0" } }, "jest-changed-files": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", - "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "requires": { "execa": "^5.0.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "jest-circus": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", - "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "requires": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "dedent": "^0.7.0", + "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.5.0", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "jest-cli": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", - "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "requires": { - "@jest/core": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", + "create-jest": "^29.7.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "prompts": "^2.0.1", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "yargs": "^17.3.1" } }, "jest-config": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", - "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "requires": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.5.0", - "jest-environment-node": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" } }, "jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" } }, "jest-docblock": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", - "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "requires": { "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", - "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "requires": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" } }, "jest-environment-node": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", - "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "requires": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" } }, "jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true }, "jest-haste-map": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", - "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "requires": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "^2.3.2", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" } }, "jest-leak-detector": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", - "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "requires": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" } }, "jest-matcher-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", - "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" } }, "jest-message-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "jest-mock": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", - "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "requires": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.5.0" + "jest-util": "^29.7.0" } }, "jest-pnp-resolver": { @@ -9653,161 +9780,140 @@ "requires": {} }, "jest-regex-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true }, "jest-resolve": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", - "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "requires": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "jest-resolve-dependencies": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", - "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "requires": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.5.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" } }, "jest-runner": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", - "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "requires": { - "@jest/console": "^29.5.0", - "@jest/environment": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-leak-detector": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-resolve": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-util": "^29.5.0", - "jest-watcher": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "jest-runtime": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", - "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", - "dev": true, - "requires": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/globals": "^29.5.0", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "jest-snapshot": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", - "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "requires": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.5.0", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^29.5.0", - "semver": "^7.3.5" + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true } } }, "jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "requires": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -9816,17 +9922,17 @@ } }, "jest-validate": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", - "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "requires": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^29.5.0" + "pretty-format": "^29.7.0" }, "dependencies": { "camelcase": { @@ -9838,29 +9944,29 @@ } }, "jest-watcher": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", - "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "requires": { - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "requires": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -10011,12 +10117,20 @@ "dev": true }, "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "requires": { - "semver": "^6.0.0" + "semver": "^7.5.3" + }, + "dependencies": { + "semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true + } } }, "make-error": { @@ -10137,6 +10251,15 @@ "whatwg-url": "^5.0.0" } }, + "node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "optional": true, + "requires": { + "detect-libc": "^2.0.1" + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -10144,9 +10267,9 @@ "dev": true }, "node-releases": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", - "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "normalize-path": { @@ -10318,9 +10441,9 @@ "dev": true }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "picomatch": { @@ -10357,12 +10480,12 @@ "dev": true }, "pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "requires": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -10398,9 +10521,9 @@ "dev": true }, "pure-rand": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", - "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true }, "queue-microtask": { @@ -10915,13 +11038,13 @@ } }, "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" } }, "uri-js": { @@ -10940,22 +11063,14 @@ "dev": true }, "v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "dependencies": { - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - } + "convert-source-map": "^2.0.0" } }, "vscode-oniguruma": { @@ -11063,9 +11178,9 @@ "dev": true }, "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { "cliui": "^8.0.1", diff --git a/package.json b/package.json index f7b58aa0..41cbb2a3 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", "@scure/bip39": "^1.2.2", - "buffer": "^6.0.3" + "buffer": "^6.0.3", + "cbor-x": "^1.5.9" } } From 1060428e7cf99948fa7d3713100fcdb51823ad4c Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 28 Jun 2024 11:23:08 +0200 Subject: [PATCH 10/70] added tokenv4 parsing / removed depracated token --- src/utils.ts | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 48a2a00c..87dcf43b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -81,9 +81,9 @@ function getEncodedToken(token: Token): string { * @param token an encoded cashu token (cashuAey...) * @returns cashu token object */ -function getDecodedToken(token: string): Token { +function getDecodedToken(token: string) { // remove prefixes - const uriPrefixes = ['web+cashu://', 'cashu://', 'cashu:', 'cashuA']; + const uriPrefixes = ['web+cashu://', 'cashu://', 'cashu:', 'cashu']; uriPrefixes.forEach((prefix) => { if (!token.startsWith(prefix)) { return; @@ -97,21 +97,29 @@ function getDecodedToken(token: string): Token { * @param token * @returns */ -function handleTokens(token: string): Token { - const obj = encodeBase64ToJson | Token>(token); - - // check if v3 - if ('token' in obj) { - return obj; - } - - // check if v1 - if (Array.isArray(obj)) { - return { token: [{ proofs: obj, mint: '' }] }; +function handleTokens(token: string): Token | undefined { + const version = token.slice(0, 1); + const encodedToken = token.slice(1); + if (version === 'A') { + return encodeBase64ToJson(encodedToken); + } else if (version === 'B') { + const uInt8Token = encodeBase64toUint8(encodedToken); + const tokenData = decode(uInt8Token) as { + t: { p: { a: number; s: string; c: string }[]; i: string }[]; + m: string; + }; + const tokenEntries = tokenData.t.map( + (tokenEntry): TokenEntry => ({ + mint: tokenData.m, + proofs: tokenEntry.p.map( + (p): Proof => ({ secret: p.s, C: p.c, amount: p.a, id: tokenEntry.i }) + ) + }) + ); + return { token: tokenEntries, memo: '' }; + } else { + throw new Error('Token version is not supported'); } - - // if v2 token return v3 format - return { token: [{ proofs: obj.proofs, mint: obj?.mints[0]?.url ?? '' }] }; } /** * Returns the keyset id of a set of keys From b5ec29b41c1aa5b11a1285da4ecdf9292bb1e10c Mon Sep 17 00:00:00 2001 From: Egge Date: Sun, 30 Jun 2024 06:33:31 +0200 Subject: [PATCH 11/70] remove cbor dependency --- package-lock.json | 201 +----------------------------------------- package.json | 3 +- src/cbor.ts | 216 ++++++++++++++++++++++++++++++++++++++++++++++ src/utils.ts | 7 +- 4 files changed, 222 insertions(+), 205 deletions(-) create mode 100644 src/cbor.ts diff --git a/package-lock.json b/package-lock.json index f4b4f45e..a3c6104f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,7 @@ "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", "@scure/bip39": "^1.2.2", - "buffer": "^6.0.3", - "cbor-x": "^1.5.9" + "buffer": "^6.0.3" }, "devDependencies": { "@types/jest": "^29.5.1", @@ -633,78 +632,6 @@ "buffer": "^6.0.3" } }, - "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz", - "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@cbor-extract/cbor-extract-darwin-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz", - "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@cbor-extract/cbor-extract-linux-arm": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz", - "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cbor-extract/cbor-extract-linux-arm64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz", - "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cbor-extract/cbor-extract-linux-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz", - "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cbor-extract/cbor-extract-win32-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz", - "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, "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", @@ -2306,35 +2233,6 @@ } ] }, - "node_modules/cbor-extract": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz", - "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.1.1" - }, - "bin": { - "download-cbor-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", - "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", - "@cbor-extract/cbor-extract-linux-arm": "2.2.0", - "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", - "@cbor-extract/cbor-extract-linux-x64": "2.2.0", - "@cbor-extract/cbor-extract-win32-x64": "2.2.0" - } - }, - "node_modules/cbor-x": { - "version": "1.5.9", - "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.5.9.tgz", - "integrity": "sha512-OEI5rEu3MeR0WWNUXuIGkxmbXVhABP+VtgAXzm48c9ulkrsvxshjjk94XSOGphyAKeNGLPfAxxzEtgQ6rEVpYQ==", - "optionalDependencies": { - "cbor-extract": "^2.2.0" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2559,15 +2457,6 @@ "node": ">=0.4.0" } }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -5130,20 +5019,6 @@ } } }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", - "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6968,42 +6843,6 @@ "buffer": "^6.0.3" } }, - "@cbor-extract/cbor-extract-darwin-arm64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz", - "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", - "optional": true - }, - "@cbor-extract/cbor-extract-darwin-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz", - "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", - "optional": true - }, - "@cbor-extract/cbor-extract-linux-arm": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz", - "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", - "optional": true - }, - "@cbor-extract/cbor-extract-linux-arm64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz", - "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", - "optional": true - }, - "@cbor-extract/cbor-extract-linux-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz", - "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", - "optional": true - }, - "@cbor-extract/cbor-extract-win32-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz", - "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", - "optional": true - }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -8186,29 +8025,6 @@ "integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==", "dev": true }, - "cbor-extract": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz", - "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", - "optional": true, - "requires": { - "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", - "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", - "@cbor-extract/cbor-extract-linux-arm": "2.2.0", - "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", - "@cbor-extract/cbor-extract-linux-x64": "2.2.0", - "@cbor-extract/cbor-extract-win32-x64": "2.2.0", - "node-gyp-build-optional-packages": "5.1.1" - } - }, - "cbor-x": { - "version": "1.5.9", - "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.5.9.tgz", - "integrity": "sha512-OEI5rEu3MeR0WWNUXuIGkxmbXVhABP+VtgAXzm48c9ulkrsvxshjjk94XSOGphyAKeNGLPfAxxzEtgQ6rEVpYQ==", - "requires": { - "cbor-extract": "^2.2.0" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8372,12 +8188,6 @@ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, - "detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "optional": true - }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -10251,15 +10061,6 @@ "whatwg-url": "^5.0.0" } }, - "node-gyp-build-optional-packages": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", - "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", - "optional": true, - "requires": { - "detect-libc": "^2.0.1" - } - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index 41cbb2a3..f7b58aa0 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "@noble/hashes": "^1.3.3", "@scure/bip32": "^1.3.3", "@scure/bip39": "^1.2.2", - "buffer": "^6.0.3", - "cbor-x": "^1.5.9" + "buffer": "^6.0.3" } } diff --git a/src/cbor.ts b/src/cbor.ts new file mode 100644 index 00000000..a998caf2 --- /dev/null +++ b/src/cbor.ts @@ -0,0 +1,216 @@ +export function encodeCBOR(value: any) { + const buffer: Array = []; + encodeItem(value, buffer); + return new Uint8Array(buffer); +} + +function encodeItem(value: any, buffer: Array) { + if (value === null) { + buffer.push(0xf6); + } else if (value === undefined) { + buffer.push(0xf7); + } else if (typeof value === 'boolean') { + buffer.push(value ? 0xf5 : 0xf4); + } else if (typeof value === 'number') { + encodeUnsigned(value, buffer); + } else if (typeof value === 'string') { + encodeString(value, buffer); + } else if (Array.isArray(value)) { + encodeArray(value, buffer); + } else if (typeof value === 'object') { + encodeObject(value, buffer); + } else { + throw new Error('Unsupported type'); + } +} + +function encodeUnsigned(value: number, buffer: Array) { + if (value < 24) { + buffer.push(value); + } else if (value < 256) { + buffer.push(0x18, value); + } else if (value < 65536) { + buffer.push(0x19, value >> 8, value & 0xff); + } else if (value < 4294967296) { + buffer.push(0x1a, value >> 24, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff); + } else { + throw new Error('Unsupported integer size'); + } +} + +function encodeString(value: string, buffer: Array) { + const utf8 = new TextEncoder().encode(value); + encodeUnsigned(utf8.length, buffer); + buffer[buffer.length - 1] |= 0x60; + utf8.forEach((b) => buffer.push(b)); +} + +function encodeArray(value: Array, buffer: Array) { + encodeUnsigned(value.length, buffer); + buffer[buffer.length - 1] |= 0x80; + for (const item of value) { + encodeItem(item, buffer); + } +} + +function encodeObject(value: { [key: string]: any }, buffer: Array) { + const keys = Object.keys(value); + encodeUnsigned(keys.length, buffer); + buffer[buffer.length - 1] |= 0xa0; + for (const key of keys) { + encodeString(key, buffer); + encodeItem(value[key], buffer); + } +} +type DecodeResult = { + value: any; + offset: number; +}; + +export function decodeCBOR(data: Uint8Array): any { + const view = new DataView(data.buffer, data.byteOffset, data.byteLength); + const result = decodeItem(view, 0); + return result.value; +} + +function decodeItem(view: DataView, offset: number): DecodeResult { + if (offset >= view.byteLength) { + throw new Error('Unexpected end of data'); + } + const initialByte = view.getUint8(offset++); + const majorType = initialByte >> 5; + const additionalInfo = initialByte & 0x1f; + + switch (majorType) { + case 0: + return decodeUnsigned(view, offset, additionalInfo); + case 1: + return decodeSigned(view, offset, additionalInfo); + case 2: + return decodeByteString(view, offset, additionalInfo); + case 3: + return decodeString(view, offset, additionalInfo); + case 4: + return decodeArray(view, offset, additionalInfo); + case 5: + return decodeMap(view, offset, additionalInfo); + case 7: + return decodeSimpleAndFloat(view, offset, additionalInfo); + default: + throw new Error(`Unsupported major type: ${majorType}`); + } +} + +function decodeLength(view: DataView, offset: number, additionalInfo: number): DecodeResult { + if (additionalInfo < 24) return { value: additionalInfo, offset }; + if (additionalInfo === 24) return { value: view.getUint8(offset++), offset }; + if (additionalInfo === 25) { + const value = view.getUint16(offset, false); + offset += 2; + return { value, offset }; + } + if (additionalInfo === 26) { + const value = view.getUint32(offset, false); + offset += 4; + return { value, offset }; + } + if (additionalInfo === 27) { + const hi = view.getUint32(offset, false); + const lo = view.getUint32(offset + 4, false); + offset += 8; + return { value: hi * 2 ** 32 + lo, offset }; + } + throw new Error(`Unsupported length: ${additionalInfo}`); +} + +function decodeUnsigned(view: DataView, offset: number, additionalInfo: number): DecodeResult { + const { value, offset: newOffset } = decodeLength(view, offset, additionalInfo); + return { value, offset: newOffset }; +} + +function decodeSigned(view: DataView, offset: number, additionalInfo: number): DecodeResult { + const { value, offset: newOffset } = decodeLength(view, offset, additionalInfo); + return { value: -1 - value, offset: newOffset }; +} + +function decodeByteString(view: DataView, offset: number, additionalInfo: number): DecodeResult { + const { value: length, offset: newOffset } = decodeLength(view, offset, additionalInfo); + if (newOffset + length > view.byteLength) { + throw new Error('Byte string length exceeds data length'); + } + const value = new Uint8Array(view.buffer, view.byteOffset + newOffset, length); + return { value, offset: newOffset + length }; +} + +function decodeString(view: DataView, offset: number, additionalInfo: number): DecodeResult { + const { value: length, offset: newOffset } = decodeLength(view, offset, additionalInfo); + if (newOffset + length > view.byteLength) { + throw new Error('String length exceeds data length'); + } + const bytes = new Uint8Array(view.buffer, view.byteOffset + newOffset, length); + const value = new TextDecoder().decode(bytes); + return { value, offset: newOffset + length }; +} + +function decodeArray(view: DataView, offset: number, additionalInfo: number): DecodeResult { + const { value: length, offset: newOffset } = decodeLength(view, offset, additionalInfo); + const array = []; + let currentOffset = newOffset; + for (let i = 0; i < length; i++) { + const result = decodeItem(view, currentOffset); + array.push(result.value); + currentOffset = result.offset; + } + return { value: array, offset: currentOffset }; +} + +function decodeMap(view: DataView, offset: number, additionalInfo: number): DecodeResult { + const { value: length, offset: newOffset } = decodeLength(view, offset, additionalInfo); + const map: { [key: string]: any } = {}; + let currentOffset = newOffset; + for (let i = 0; i < length; i++) { + const keyResult = decodeItem(view, currentOffset); + const valueResult = decodeItem(view, keyResult.offset); + map[keyResult.value] = valueResult.value; + currentOffset = valueResult.offset; + } + return { value: map, offset: currentOffset }; +} + +function decodeSimpleAndFloat( + view: DataView, + offset: number, + additionalInfo: number +): DecodeResult { + if (additionalInfo < 24) { + switch (additionalInfo) { + case 20: + return { value: false, offset }; + case 21: + return { value: true, offset }; + case 22: + return { value: null, offset }; + case 23: + return { value: undefined, offset }; + default: + throw new Error(`Unknown simple value: ${additionalInfo}`); + } + } + if (additionalInfo === 24) return { value: view.getUint8(offset++), offset }; + if (additionalInfo === 25) { + const value = view.getUint16(offset, false); + offset += 2; + return { value, offset }; + } + if (additionalInfo === 26) { + const value = view.getFloat32(offset, false); + offset += 4; + return { value, offset }; + } + if (additionalInfo === 27) { + const value = view.getFloat64(offset, false); + offset += 8; + return { value, offset }; + } + throw new Error(`Unknown simple or float value: ${additionalInfo}`); +} diff --git a/src/utils.ts b/src/utils.ts index 87dcf43b..5ffc70d8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,9 @@ -import { encodeBase64ToJson, encodeJsonToBase64 } from './base64.js'; -import { AmountPreference, Keys, Proof, Token, TokenV2 } from './model/types/index.js'; +import { encodeBase64ToJson, encodeBase64toUint8, encodeJsonToBase64 } from './base64.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'; +import { decodeCBOR } from './cbor.js'; function splitAmount(value: number, amountPreference?: Array): Array { const chunks: Array = []; @@ -104,7 +105,7 @@ function handleTokens(token: string): Token | undefined { return encodeBase64ToJson(encodedToken); } else if (version === 'B') { const uInt8Token = encodeBase64toUint8(encodedToken); - const tokenData = decode(uInt8Token) as { + const tokenData = decodeCBOR(uInt8Token) as { t: { p: { a: number; s: string; c: string }[]; i: string }[]; m: string; }; From a34c439b9f9fae6e35f3c38865b919032b25deda Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 1 Jul 2024 10:16:35 +0200 Subject: [PATCH 12/70] added byte id and C --- src/utils.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 5ffc70d8..dfcd3479 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -106,14 +106,19 @@ function handleTokens(token: string): Token | undefined { } else if (version === 'B') { const uInt8Token = encodeBase64toUint8(encodedToken); const tokenData = decodeCBOR(uInt8Token) as { - t: { p: { a: number; s: string; c: string }[]; i: string }[]; + t: { p: { a: number; s: string; c: Uint8Array }[]; i: Uint8Array }[]; m: string; }; const tokenEntries = tokenData.t.map( (tokenEntry): TokenEntry => ({ mint: tokenData.m, proofs: tokenEntry.p.map( - (p): Proof => ({ secret: p.s, C: p.c, amount: p.a, id: tokenEntry.i }) + (p): Proof => ({ + secret: p.s, + C: bytesToHex(p.c), + amount: p.a, + id: bytesToHex(tokenEntry.i) + }) ) }) ); From 7f23bf208b9fb5e3664d60040e1f52b8e58c8996 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 1 Jul 2024 15:28:02 +0200 Subject: [PATCH 13/70] added testcases and multi token --- src/utils.ts | 23 ++++++++++----------- test/utils.test.ts | 51 +++++++++++++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index dfcd3479..84285302 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -108,21 +108,20 @@ function handleTokens(token: string): Token | undefined { const tokenData = decodeCBOR(uInt8Token) as { t: { p: { a: number; s: string; c: Uint8Array }[]; i: Uint8Array }[]; m: string; + d: string; }; - const tokenEntries = tokenData.t.map( - (tokenEntry): TokenEntry => ({ - mint: tokenData.m, - proofs: tokenEntry.p.map( - (p): Proof => ({ - secret: p.s, - C: bytesToHex(p.c), - amount: p.a, - id: bytesToHex(tokenEntry.i) - }) - ) + const mergedTokenEntry: TokenEntry = { mint: tokenData.m, proofs: [] }; + tokenData.t.forEach((tokenEntry) => + tokenEntry.p.forEach((p) => { + mergedTokenEntry.proofs.push({ + secret: p.s, + C: bytesToHex(p.c), + amount: p.a, + id: bytesToHex(tokenEntry.i) + }); }) ); - return { token: tokenEntries, memo: '' }; + return { token: [mergedTokenEntry], memo: tokenData.d || '' }; } else { throw new Error('Token version is not supported'); } diff --git a/test/utils.test.ts b/test/utils.test.ts index 15fa721a..8e2117d5 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -114,28 +114,52 @@ describe('test decode token', () => { expect(result).toStrictEqual(obj); }); test('testing v4 Token', () => { + const v3Token = { + memo: 'Thank you', + token: [ + { + mint: 'http://localhost:3338', + proofs: [ + { + secret: '9a6dbb847bd232ba76db0df197216b29d3b8cc14553cd27827fc1cc942fedb4e', + C: '038618543ffb6b8695df4ad4babcde92a34a96bdcd97dcee0d7ccf98d472126792', + id: '00ad268c4d1f5826', + amount: 1 + } + ] + } + ] + }; + + const token = + 'cashuBpGF0gaJhaUgArSaMTR9YJmFwgaNhYQFhc3hAOWE2ZGJiODQ3YmQyMzJiYTc2ZGIwZGYxOTcyMTZiMjlkM2I4Y2MxNDU1M2NkMjc4MjdmYzFjYzk0MmZlZGI0ZWFjWCEDhhhUP_trhpXfStS6vN6So0qWvc2X3O4NfM-Y1HISZ5JhZGlUaGFuayB5b3VhbXVodHRwOi8vbG9jYWxob3N0OjMzMzhhdWNzYXQ='; + + const result = utils.getDecodedToken(token); + expect(result).toStrictEqual(v3Token); + }); + test('testing v4 Token with multi keyset', () => { const v3Token = { memo: '', token: [ { - mint: 'https://mint.minibits.cash/Bitcoin', + mint: 'http://localhost:3338', proofs: [ { - secret: '7e98535c6f8cd7a5eff150963a2743613a91e9498150fd5af8d2bfcfd5babe68', - C: '03022a28d163cf63792c1533e6660112f2b75db2fe46aa840e7f5d0f979a2c6cfd', - id: '00500550f0494146', - amount: 16 + secret: 'acc12435e7b8484c3cf1850149218af90f716a52bf4a5ed347e48ecc13f77388', + C: '0244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cf', + id: '00ffd48b8f5ecf80', + amount: 1 }, { - amount: 4, - secret: '96bd8480717673311bc70e92818b5babcb665edee39b639defad5584d8d18b1f', - C: '030936759e03235867f9cea58f047c043acdd7455f604c92c75839e5e08a91e198', - id: '00500550f0494146' + secret: '1323d3d4707a58ad2e23ada4e9f1f49f5a5b4ac7b708eb0d61f738f48307e8ee', + C: '023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94d', + id: '00ad268c4d1f5826', + amount: 2 }, { - secret: 'e145fa7fba21a9cd3c8743c9de5e4de33e0095abc50b262f1b3831b69b8f63df', - id: '00500550f0494146', - C: '03eba391a31e101e1ba1853db1e4bbb6a166d4fbbb1e181e82892c3301e4e02015', + secret: '56bcbcbb7cc6406b3fa5d57d2174f4eff8b4402b176926d3a57d3c3dcbb59d57', + C: '0273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63', + id: '00ad268c4d1f5826', amount: 1 } ] @@ -144,10 +168,9 @@ describe('test decode token', () => { }; const token = - 'cashuBuQACYXSBuQACYXCDuQADYWEQYXN4QDdlOTg1MzVjNmY4Y2Q3YTVlZmYxNTA5NjNhMjc0MzYxM2E5MWU5NDk4MTUwZmQ1YWY4ZDJiZmNmZDViYWJlNjhhY3hCMDMwMjJhMjhkMTYzY2Y2Mzc5MmMxNTMzZTY2NjAxMTJmMmI3NWRiMmZlNDZhYTg0MGU3ZjVkMGY5NzlhMmM2Y2ZkuQADYWEEYXN4QDk2YmQ4NDgwNzE3NjczMzExYmM3MGU5MjgxOGI1YmFiY2I2NjVlZGVlMzliNjM5ZGVmYWQ1NTg0ZDhkMThiMWZhY3hCMDMwOTM2NzU5ZTAzMjM1ODY3ZjljZWE1OGYwNDdjMDQzYWNkZDc0NTVmNjA0YzkyYzc1ODM5ZTVlMDhhOTFlMTk4uQADYWEBYXN4QGUxNDVmYTdmYmEyMWE5Y2QzYzg3NDNjOWRlNWU0ZGUzM2UwMDk1YWJjNTBiMjYyZjFiMzgzMWI2OWI4ZjYzZGZhY3hCMDNlYmEzOTFhMzFlMTAxZTFiYTE4NTNkYjFlNGJiYjZhMTY2ZDRmYmJiMWUxODFlODI4OTJjMzMwMWU0ZTAyMDE1YWlwMDA1MDA1NTBmMDQ5NDE0NmFteCJodHRwczovL21pbnQubWluaWJpdHMuY2FzaC9CaXRjb2lu'; + 'cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA=='; const result = utils.getDecodedToken(token); - console.log(JSON.stringify(result)); expect(result).toStrictEqual(v3Token); }); test('testing joining urls', () => { From da1c60354e1a9a5230f14c6dfa3d55d2d853bc23 Mon Sep 17 00:00:00 2001 From: Egge Date: Mon, 1 Jul 2024 18:40:22 +0200 Subject: [PATCH 14/70] specified return type --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 84285302..1ace779b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -98,7 +98,7 @@ function getDecodedToken(token: string) { * @param token * @returns */ -function handleTokens(token: string): Token | undefined { +function handleTokens(token: string): Token { const version = token.slice(0, 1); const encodedToken = token.slice(1); if (version === 'A') { From c68de1af53825da6a2ff5eff028f7d57274161a2 Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 4 Jul 2024 10:57:57 +0200 Subject: [PATCH 15/70] cleanup rebase --- test/utils.test.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/utils.test.ts b/test/utils.test.ts index 8e2117d5..1569cd78 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -173,16 +173,6 @@ describe('test decode token', () => { const result = utils.getDecodedToken(token); expect(result).toStrictEqual(v3Token); }); - 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', () => { From c91c27e9a54fab41a9dc6f9d734d36a01df577f7 Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 18 Jul 2024 16:08:52 +0200 Subject: [PATCH 16/70] added cbor test cases --- test/cbor.test.ts | 294 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 test/cbor.test.ts diff --git a/test/cbor.test.ts b/test/cbor.test.ts new file mode 100644 index 00000000..e9df53b1 --- /dev/null +++ b/test/cbor.test.ts @@ -0,0 +1,294 @@ +import { decodeCBOR } from '../src/cbor'; + +const tests = [ + { + cbor: 'AA==', + hex: '00', + roundtrip: true, + decoded: 0 + }, + { + cbor: 'AQ==', + hex: '01', + roundtrip: true, + decoded: 1 + }, + { + cbor: 'Cg==', + hex: '0a', + roundtrip: true, + decoded: 10 + }, + { + cbor: 'Fw==', + hex: '17', + roundtrip: true, + decoded: 23 + }, + { + cbor: 'GBg=', + hex: '1818', + roundtrip: true, + decoded: 24 + }, + { + cbor: 'GBk=', + hex: '1819', + roundtrip: true, + decoded: 25 + }, + { + cbor: 'GGQ=', + hex: '1864', + roundtrip: true, + decoded: 100 + }, + { + cbor: 'GQPo', + hex: '1903e8', + roundtrip: true, + decoded: 1000 + }, + { + cbor: 'GgAPQkA=', + hex: '1a000f4240', + roundtrip: true, + decoded: 1000000 + }, + { + cbor: 'GwAAAOjUpRAA', + hex: '1b000000e8d4a51000', + roundtrip: true, + decoded: 1000000000000 + }, + { + cbor: 'IA==', + hex: '20', + roundtrip: true, + decoded: -1 + }, + { + cbor: 'KQ==', + hex: '29', + roundtrip: true, + decoded: -10 + }, + { + cbor: 'OGM=', + hex: '3863', + roundtrip: true, + decoded: -100 + }, + { + cbor: 'OQPn', + hex: '3903e7', + roundtrip: true, + decoded: -1000 + }, + { + cbor: '+QAA', + hex: 'f90000', + roundtrip: true, + decoded: 0.0 + }, + { + cbor: '+TwA', + hex: 'f93c00', + roundtrip: true, + decoded: 1.0 + }, + { + cbor: '+z/xmZmZmZma', + hex: 'fb3ff199999999999a', + roundtrip: true, + decoded: 1.1 + }, + { + cbor: '+T4A', + hex: 'f93e00', + roundtrip: true, + decoded: 1.5 + }, + { + cbor: '+Xv/', + hex: 'f97bff', + roundtrip: true, + decoded: 65504.0 + }, + { + cbor: '+kfDUAA=', + hex: 'fa47c35000', + roundtrip: true, + decoded: 100000.0 + }, + { + cbor: '+n9///8=', + hex: 'fa7f7fffff', + roundtrip: true, + decoded: 3.4028234663852886e38 + }, + { + cbor: '+3435DyIAHWc', + hex: 'fb7e37e43c8800759c', + roundtrip: true, + decoded: 1.0e300 + }, + { + cbor: '+QAB', + hex: 'f90001', + roundtrip: true, + decoded: 5.960464477539063e-8 + }, + { + cbor: '+QQA', + hex: 'f90400', + roundtrip: true, + decoded: 6.103515625e-5 + }, + { + cbor: '+cQA', + hex: 'f9c400', + roundtrip: true, + decoded: -4.0 + }, + { + cbor: '+8AQZmZmZmZm', + hex: 'fbc010666666666666', + roundtrip: true, + decoded: -4.1 + }, + { + cbor: '9A==', + hex: 'f4', + roundtrip: true, + decoded: false + }, + { + cbor: '9Q==', + hex: 'f5', + roundtrip: true, + decoded: true + }, + { + cbor: '9g==', + hex: 'f6', + roundtrip: true, + decoded: null + }, + { + cbor: 'YA==', + hex: '60', + roundtrip: true, + decoded: '' + }, + { + cbor: 'YWE=', + hex: '6161', + roundtrip: true, + decoded: 'a' + }, + { + cbor: 'ZElFVEY=', + hex: '6449455446', + roundtrip: true, + decoded: 'IETF' + }, + { + cbor: 'YiJc', + hex: '62225c', + roundtrip: true, + decoded: '"\\' + }, + { + cbor: 'YsO8', + hex: '62c3bc', + roundtrip: true, + decoded: 'ü' + }, + { + cbor: 'Y+awtA==', + hex: '63e6b0b4', + roundtrip: true, + decoded: '水' + }, + { + cbor: 'ZPCQhZE=', + hex: '64f0908591', + roundtrip: true, + decoded: '𐅑' + }, + { + cbor: 'gA==', + hex: '80', + roundtrip: true, + decoded: [] + }, + { + cbor: 'gwECAw==', + hex: '83010203', + roundtrip: true, + decoded: [1, 2, 3] + }, + { + cbor: 'gwGCAgOCBAU=', + hex: '8301820203820405', + roundtrip: true, + decoded: [1, [2, 3], [4, 5]] + }, + { + cbor: 'mBkBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgYGBk=', + hex: '98190102030405060708090a0b0c0d0e0f101112131415161718181819', + roundtrip: true, + decoded: [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 + ] + }, + { + cbor: 'oA==', + hex: 'a0', + roundtrip: true, + decoded: {} + }, + { + cbor: 'omFhAWFiggID', + hex: 'a26161016162820203', + roundtrip: true, + decoded: { + a: 1, + b: [2, 3] + } + }, + { + cbor: 'gmFhoWFiYWM=', + hex: '826161a161626163', + roundtrip: true, + decoded: [ + 'a', + { + b: 'c' + } + ] + }, + { + cbor: 'pWFhYUFhYmFCYWNhQ2FkYURhZWFF', + hex: 'a56161614161626142616361436164614461656145', + roundtrip: true, + decoded: { + a: 'A', + b: 'B', + c: 'C', + d: 'D', + e: 'E' + } + } +]; + +describe('cbor decoder', () => { + test.each(tests)('given $hex as arguments, returns $decoded', ({ hex, decoded }) => { + //@ts-ignore + const res = decodeCBOR(Buffer.from(hex, 'hex')); + console.log(decoded); + console.log(res); + expect(res).toEqual(decoded); + }); +}); From 79cefe540cf70cda1095727f2fe8ae898572a397 Mon Sep 17 00:00:00 2001 From: Egge Date: Thu, 18 Jul 2024 16:09:05 +0200 Subject: [PATCH 17/70] fixed 16 bit float parsing --- src/cbor.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/cbor.ts b/src/cbor.ts index a998caf2..4a78889c 100644 --- a/src/cbor.ts +++ b/src/cbor.ts @@ -177,6 +177,19 @@ function decodeMap(view: DataView, offset: number, additionalInfo: number): Deco return { value: map, offset: currentOffset }; } +function decodeFloat16(uint16: number): number { + const exponent = (uint16 & 0x7c00) >> 10; + const fraction = uint16 & 0x03ff; + const sign = uint16 & 0x8000 ? -1 : 1; + + if (exponent === 0) { + return sign * 2 ** -14 * (fraction / 1024); + } else if (exponent === 0x1f) { + return fraction ? NaN : sign * Infinity; + } + return sign * 2 ** (exponent - 15) * (1 + fraction / 1024); +} + function decodeSimpleAndFloat( view: DataView, offset: number, @@ -198,7 +211,7 @@ function decodeSimpleAndFloat( } if (additionalInfo === 24) return { value: view.getUint8(offset++), offset }; if (additionalInfo === 25) { - const value = view.getUint16(offset, false); + const value = decodeFloat16(view.getUint16(offset, false)); offset += 2; return { value, offset }; } From b2f70a57467c4aec3b7de364406a79e2af408ea7 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 22 Jul 2024 13:49:20 +0200 Subject: [PATCH 18/70] wip --- src/CashuWallet.ts | 144 ++++++++++++++++++++++++++++----------- src/legacy/nut-06.ts | 38 +++++------ src/model/types/index.ts | 4 ++ 3 files changed, 126 insertions(+), 60 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 23a31fae..8b33c14e 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -8,6 +8,7 @@ import { type MeltPayload, type MeltQuoteResponse, type MintKeys, + type MintKeyset, type MeltTokensResponse, type MintPayload, type Proof, @@ -49,7 +50,9 @@ import { type Proof as NUT11Proof } from '@cashu/crypto/modules/common/index'; * This class should act as the entry point for this library */ class CashuWallet { - private _keys: MintKeys | undefined; + private _keys: Map = new Map(); + private _keyset_id: string | undefined; + private _keysets: Array = []; private _seed: Uint8Array | undefined; private _unit = 'sat'; mint: CashuMint; @@ -65,27 +68,26 @@ class CashuWallet { mint: CashuMint, options?: { unit?: string; - keys?: MintKeys; + keys?: Array; + keysets?: Array; mnemonicOrSeed?: string | Uint8Array; } ) { this.mint = mint; if (options?.unit) this._unit = options?.unit; - if (options?.keys) { - this._keys = options.keys; - this._unit = options.keys.unit; - } + if (options?.keys) options.keys.forEach((key) => this._keys.set(key.id, key)); + if (options?.keysets) this._keysets = options.keysets; + if (!options?.mnemonicOrSeed) { return; - } - if (options?.mnemonicOrSeed instanceof Uint8Array) { + } else if (options?.mnemonicOrSeed instanceof Uint8Array) { this._seed = options.mnemonicOrSeed; - return; - } - if (!validateMnemonic(options.mnemonicOrSeed, wordlist)) { - throw new Error('Tried to instantiate with mnemonic, but mnemonic was invalid'); + } else { + if (!validateMnemonic(options.mnemonicOrSeed, wordlist)) { + throw new Error('Tried to instantiate with mnemonic, but mnemonic was invalid'); + } + this._seed = deriveSeedFromMnemonic(options.mnemonicOrSeed); } - this._seed = deriveSeedFromMnemonic(options.mnemonicOrSeed); } get unit(): string { @@ -93,14 +95,23 @@ class CashuWallet { } get keys(): MintKeys { - if (!this._keys) { + if (!this._keyset_id || !this._keys.get(this._keyset_id)) { throw new Error('Keys are not set'); } - return this._keys; + return this._keys.get(this._keyset_id) as MintKeys; } set keys(keys: MintKeys) { - this._keys = keys; - this._unit = keys.unit; + if (keys.unit !== this._unit) { + throw new Error('Unit of keyset does not match the unit of the wallet'); + } + this._keys.set(keys.id, keys); + this._keyset_id = keys.id; + } + get keysets(): Array { + return this._keysets; + } + set keysets(keysets: Array) { + this._keysets = keysets; } /** @@ -111,6 +122,81 @@ class CashuWallet { return this.mint.getInfo(); } + /** + * Load mint information, keysets and keys. This function can be called if no keysets are passed in the constructor + */ + async loadMint() { + await this.getMintInfo(); + if (!this._keys.size) { + await this.getKeys() + } else { + await this.getKeySets(); + // get all keysets from this._keysets which are not already in this._keys + this._keysets.forEach(async (keyset) => { + if (!this._keys.get(keyset.id)) { + await this.getKeys(keyset.id); + } + }); + } + } + + /** + * Get keysets from the mint with the unit of the wallet + * @returns keysets + */ + async getKeySets(): Promise> { + const allKeysets = await this.mint.getKeySets(); + const unitKeysets = allKeysets.keysets.filter((k) => k.unit === this._unit); + this._keysets = unitKeysets; + return this._keysets; + } + + /** + * Get public keys from the mint. + * If a keysetId is set, it will fetch and return that speficic keyset. + * Otherwise, we select an active keyset with the unit of the wallet. + * + * @param keysetId optional keysetId to get keys for + * @param unit optional unit to get keys for + * @returns keyset + */ + async getKeys(keysetId?: string): Promise { + if (keysetId) { + if (this._keys.get(keysetId)) { + this._keyset_id = keysetId; + return this._keys.get(keysetId) as MintKeys; + } + const allKeysets = await this.mint.getKeys(keysetId) + const keyset = allKeysets.keysets[0]; + if (!keyset) { + throw new Error(`could not initialize keys. No keyset with id '${keysetId}' found`); + } + this._keys.set(keysetId, keyset); + this._keyset_id = keysetId; + return keyset; + } + + // no keysetId was set, so we get the active keyset with the unit of the wallet with the lowest fees + const allKeysets = await this.mint.getKeySets(); + const keysetToActivate = allKeysets.keysets + .filter((k) => k.unit === this._unit && k.active) + .sort((a, b) => (a.input_fees_ppk ?? 0) - (b.input_fees_ppk ?? 0))[0]; + if (!keysetToActivate) { + throw new Error(`could not initialize keys. No active keyset with unit '${this._unit}' found`); + } + + if (!this._keys.get(keysetToActivate.id)) { + const keysetGet = await this.mint.getKeys(keysetToActivate.id); + const keys = keysetGet.keysets.find((k) => k.id === keysetToActivate.id); + if (!keys) { + throw new Error(`could not initialize keys. No keyset with id '${keysetToActivate.id}' found`); + } + this._keys.set(keys.id, keys); + } + this._keyset_id = keysetToActivate.id; + return this._keys.get(keysetToActivate.id) as MintKeys; + } + /** * 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 @@ -312,30 +398,6 @@ class CashuWallet { }; } - /** - * Initialize the wallet with the mints public keys - */ - private async getKeys(keysetId?: string, unit?: string): Promise { - if (!this._keys || (keysetId !== undefined && this._keys.id !== keysetId)) { - 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` - ); - } - if (!this._keys) { - this._keys = keys; - } - } - return this._keys; - } - /** * 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. diff --git a/src/legacy/nut-06.ts b/src/legacy/nut-06.ts index ded61049..eff911bc 100644 --- a/src/legacy/nut-06.ts +++ b/src/legacy/nut-06.ts @@ -1,23 +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; + // 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' + ) { + console.warn( + `Mint returned deprecated 'contact' field: Update NUT-06: https://github.com/cashubtc/nuts/pull/117` + ); + return { method: contact[0], info: contact[1] } as MintContactInfo; + } + return contact; + }); + } + return data; } diff --git a/src/model/types/index.ts b/src/model/types/index.ts index e4cc4e44..07fd331f 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -79,6 +79,10 @@ export type MintKeyset = { * Whether the keyset is active or not. */ active: boolean; + /** + * Input fees for keyset (in ppk) + */ + input_fees_ppk?: number; }; /** From 0197df4b6819cb6d0ed425ab0d289de406b87fbe Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:01:18 +0200 Subject: [PATCH 19/70] tests working again --- src/CashuWallet.ts | 4 ++-- test/wallet.test.ts | 34 +++++++++++++++------------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 8b33c14e..196cd58a 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -53,7 +53,7 @@ class CashuWallet { private _keys: Map = new Map(); private _keyset_id: string | undefined; private _keysets: Array = []; - private _seed: Uint8Array | undefined; + private _seed: Uint8Array | undefined = undefined; private _unit = 'sat'; mint: CashuMint; @@ -230,7 +230,7 @@ class CashuWallet { }); return proofs; } catch (error) { - throw new Error('Error when receiving'); + throw new Error(`Error receiving token: ${error}`); } } diff --git a/test/wallet.test.ts b/test/wallet.test.ts index b8bd97d8..6e8544c0 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -11,7 +11,17 @@ const dummyKeysResp = { { id: '009a1f293253e41e', unit: 'sat', - keys: { 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181' } + keys: { 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181', 2: '03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5' } + } + ] +}; +const dummyKeysetResp = { + keysets: [ + { + id: '009a1f293253e41e', + unit: 'sat', + active: true, + input_fees_ppk: 0, } ] }; @@ -31,6 +41,7 @@ beforeEach(() => { nock.cleanAll(); nock(mintUrl).get('/v1/keys').reply(200, dummyKeysResp); nock(mintUrl).get('/v1/keys/009a1f293253e41e').reply(200, dummyKeysResp); + nock(mintUrl).get('/v1/keysets').reply(200, dummyKeysetResp); }); describe('test info', () => { @@ -180,14 +191,14 @@ describe('receive', () => { nock(mintUrl).post('/v1/swap').reply(400, { detail: msg }); const wallet = new CashuWallet(mint, { unit }); const result = await wallet.receive(tokenInput).catch((e) => e); - expect(result).toEqual(new Error('Error when receiving')); + expect(result).toEqual(new Error('Error receiving token: Error: Error receiving token entry')); }); test('test receive could not verify proofs', async () => { nock(mintUrl).post('/v1/swap').reply(400, { code: 0, error: 'could not verify proofs.' }); const wallet = new CashuWallet(mint, { unit }); const result = await wallet.receive(tokenInput).catch((e) => e); - expect(result).toEqual(new Error('Error when receiving')); + expect(result).toEqual(new Error('Error receiving token: Error: Error receiving token entry')); }); }); @@ -249,21 +260,6 @@ describe('payLnInvoice', () => { expect(result).toEqual({ isPaid: true, preimage: null, change: [] }); }); 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) .get('/v1/melt/quote/bolt11/test') .reply(200, { @@ -352,7 +348,7 @@ describe('send', () => { ]; test('test send base case', async () => { nock(mintUrl) - .post('/split') + .post('/v1/swap') .reply(200, { signatures: [ { From 64236c3e7b62ca35b3cd6f55e476f75aff9243a1 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:24:27 +0200 Subject: [PATCH 20/70] wip: working --- src/CashuMint.ts | 8 +- src/CashuWallet.ts | 297 ++++++++++++++++++++++++++------------- src/legacy/nut-06.ts | 4 +- src/model/types/index.ts | 8 +- src/utils.ts | 6 +- test/wallet.test.ts | 4 +- 6 files changed, 215 insertions(+), 112 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 3729ae02..a13ff4ee 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -1,7 +1,7 @@ import type { CheckStatePayload, CheckStateResponse, - GetInfoResponse, + MintInfo, MeltPayload, MintActiveKeys, MintAllKeysets, @@ -55,9 +55,9 @@ class CashuMint { public static async getInfo( mintUrl: string, customRequest?: typeof request - ): Promise { + ): Promise { const requestInstance = customRequest || request; - const response = await requestInstance({ + const response = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/info') }); const data = handleMintInfoContactFieldDeprecated(response); @@ -66,7 +66,7 @@ class CashuMint { /** * fetches mints info at the /info endpoint */ - async getInfo(): Promise { + async getInfo(): Promise { return CashuMint.getInfo(this._mintUrl, this._customRequest); } diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 196cd58a..098b264b 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -21,13 +21,15 @@ import { type TokenEntry, CheckStateEnum, SerializedBlindedSignature, - MeltQuoteState + MeltQuoteState, + MintInfo } from './model/types/index.js'; import { bytesToNumber, getDecodedToken, getDefaultAmountPreference, - splitAmount + splitAmount, + sumProofs } from './utils.js'; import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; @@ -51,16 +53,20 @@ import { type Proof as NUT11Proof } from '@cashu/crypto/modules/common/index'; */ class CashuWallet { private _keys: Map = new Map(); - private _keyset_id: string | undefined; + private _keysetId: string | undefined; private _keysets: Array = []; private _seed: Uint8Array | undefined = undefined; private _unit = 'sat'; + private _mintInfo: MintInfo | undefined = undefined; + mint: CashuMint; /** - * @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 unit optionally set unit (default is 'sat') + * @param keys public keys from the mint (will be fetched from mint if not provided) + * @param keysets keysets from the mint (will be fetched from mint if not provided) + * @param mintInfo mint info from the mint (will be fetched from mint if not provided) * @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 */ @@ -68,14 +74,21 @@ class CashuWallet { mint: CashuMint, options?: { unit?: string; - keys?: Array; + keys?: Array | MintKeys; keysets?: Array; + mintInfo?: MintInfo; mnemonicOrSeed?: string | Uint8Array; } ) { + let keys: Array = []; + if (options?.keys && !Array.isArray(options.keys)) { + keys = [options.keys]; + } else if (options?.keys && Array.isArray(options?.keys)) { + keys = options?.keys; + } this.mint = mint; if (options?.unit) this._unit = options?.unit; - if (options?.keys) options.keys.forEach((key) => this._keys.set(key.id, key)); + if (keys) keys.forEach((key) => this._keys.set(key.id, key)); if (options?.keysets) this._keysets = options.keysets; if (!options?.mnemonicOrSeed) { @@ -93,33 +106,35 @@ class CashuWallet { get unit(): string { return this._unit; } - - get keys(): MintKeys { - if (!this._keyset_id || !this._keys.get(this._keyset_id)) { - throw new Error('Keys are not set'); - } - return this._keys.get(this._keyset_id) as MintKeys; + get keys(): Map { + return this._keys } - set keys(keys: MintKeys) { - if (keys.unit !== this._unit) { - throw new Error('Unit of keyset does not match the unit of the wallet'); + get keysetId(): string { + if (!this._keysetId) { + throw new Error('No keysetId set'); } - this._keys.set(keys.id, keys); - this._keyset_id = keys.id; + return this._keysetId; + } + set keysetId(keysetId: string) { + this._keysetId = keysetId; } get keysets(): Array { return this._keysets; } - set keysets(keysets: Array) { - this._keysets = keysets; + get mintInfo(): MintInfo { + if (!this._mintInfo) { + throw new Error('Mint info not loaded'); + } + return this._mintInfo; } /** * Get information about the mint * @returns mint info */ - async getMintInfo() { - return this.mint.getInfo(); + async getMintInfo(): Promise { + this._mintInfo = await this.mint.getInfo(); + return this._mintInfo; } /** @@ -127,17 +142,8 @@ class CashuWallet { */ async loadMint() { await this.getMintInfo(); - if (!this._keys.size) { - await this.getKeys() - } else { - await this.getKeySets(); - // get all keysets from this._keysets which are not already in this._keys - this._keysets.forEach(async (keyset) => { - if (!this._keys.get(keyset.id)) { - await this.getKeys(keyset.id); - } - }); - } + await this.getKeySets(); + await this.getAllKeys(); } /** @@ -151,9 +157,16 @@ class CashuWallet { return this._keysets; } + async getAllKeys(): Promise> { + const keysets = await this.mint.getKeys(); + this._keys = new Map(keysets.keysets.map((k) => [k.id, k])); + return keysets.keysets; + } + /** - * Get public keys from the mint. - * If a keysetId is set, it will fetch and return that speficic keyset. + * Get public keys from the mint. If keys were already fetched, it will return those. + * + * If `keysetId` is set, it will fetch and return that specific keyset. * Otherwise, we select an active keyset with the unit of the wallet. * * @param keysetId optional keysetId to get keys for @@ -163,7 +176,7 @@ class CashuWallet { async getKeys(keysetId?: string): Promise { if (keysetId) { if (this._keys.get(keysetId)) { - this._keyset_id = keysetId; + this.keysetId = keysetId; return this._keys.get(keysetId) as MintKeys; } const allKeysets = await this.mint.getKeys(keysetId) @@ -172,15 +185,15 @@ class CashuWallet { throw new Error(`could not initialize keys. No keyset with id '${keysetId}' found`); } this._keys.set(keysetId, keyset); - this._keyset_id = keysetId; + this.keysetId = keysetId; return keyset; } - // no keysetId was set, so we get the active keyset with the unit of the wallet with the lowest fees + // no keysetId was set, so we select an active keyset with the unit of the wallet with the lowest fees and use that const allKeysets = await this.mint.getKeySets(); const keysetToActivate = allKeysets.keysets .filter((k) => k.unit === this._unit && k.active) - .sort((a, b) => (a.input_fees_ppk ?? 0) - (b.input_fees_ppk ?? 0))[0]; + .sort((a, b) => (a.input_fee_ppk ?? 0) - (b.input_fee_ppk ?? 0))[0]; if (!keysetToActivate) { throw new Error(`could not initialize keys. No active keyset with unit '${this._unit}' found`); } @@ -193,7 +206,7 @@ class CashuWallet { } this._keys.set(keys.id, keys); } - this._keyset_id = keysetToActivate.id; + this.keysetId = keysetToActivate.id; return this._keys.get(keysetToActivate.id) as MintKeys; } @@ -284,6 +297,111 @@ class CashuWallet { return proofs; } + async send( + amount: number, + proofs: Array, + options?: { + preference?: Array; + counter?: number; + pubkey?: string; + privkey?: string; + keysetId?: string; + offline?: boolean, + } + ): Promise { + if (sumProofs(proofs) < amount) { + throw new Error('Not enough funds available to send'); + } + // try to select the exact amount of proofs to send (without fees) + console.log("calling selectProofsToSend"); + console.log(`proofs: ${sumProofs(proofs)}, amount: ${amount}`); + const { returnChange: keepProofsOffline, send: sendProofOffline } = this.selectProofsToSend(proofs, amount); + console.log(`keepProofsOffline: ${sumProofs(keepProofsOffline)}, sendProofOffline: ${sumProofs(sendProofOffline)}, amount: ${amount}`); + const fees = this.getFeesForProofs(sendProofOffline); + console.log(`fees: ${fees}`); + + if ( + sumProofs(sendProofOffline) != amount || // if the exact amount cannot be selected + options?.preference || options?.pubkey || options?.privkey || options?.keysetId // these options require a swap + ) { + console.log(`>>>>>>> must do swap with ${sumProofs(proofs)} sat`); + const { returnChange: keepProofsSelect, send: sendProofs } = this.selectProofsToSend(proofs, amount, true); + console.log(`keepProofsSelect: ${sumProofs(keepProofsSelect)}, sendProofs: ${sumProofs(sendProofs)}, amount: ${amount}`); + const { returnChange, send } = await this.swap(amount, sendProofs, options); + console.log(`returnChange: ${sumProofs(returnChange)}, send: ${sumProofs(send)}`); + const returnChangeProofs = keepProofsSelect.concat(returnChange); + console.log(`returnChangeProofs: ${sumProofs(returnChangeProofs)}`); + return { returnChange: returnChangeProofs, send }; + } + + return { returnChange: keepProofsOffline, send: sendProofOffline }; + + } + + + private selectProofsToSend( + proofs: Array, + amountToSend: number, + includeFees = false + ): SendResponse { + const sortedProofs = proofs.sort((a, b) => a.amount - b.amount); + const smallerProofs = sortedProofs.filter((p) => p.amount <= amountToSend).sort((a, b) => b.amount - a.amount); + const biggerProofs = sortedProofs.filter((p) => p.amount > amountToSend).sort((a, b) => a.amount - b.amount); + const nextBigger = biggerProofs[0]; + + console.log(`> enter select with proofs: ${sumProofs(proofs)}, amountToSend: ${amountToSend}, smallerProofs: ${sumProofs(smallerProofs)}, biggerProofs: ${sumProofs(biggerProofs)}, nextBigger: ${nextBigger?.amount}`); + + if (!smallerProofs.length && nextBigger) { + console.log(`! no smallerProofs and nextBigger: ${nextBigger.amount}`); + console.log(`< 0 select ${nextBigger.amount}, return: ${sumProofs(proofs.filter((p) => p.id !== nextBigger.id))}`); + return { + returnChange: proofs.filter((p) => p.id !== nextBigger.id), + send: [nextBigger] + }; + } + + if (!smallerProofs.length && !nextBigger) { + console.log(`! no smallerProofs and no nextBigger, change: ${sumProofs(proofs)}`); + console.log(`< 1 select: 0, return: ${sumProofs(proofs)}`); + return { + returnChange: proofs, + send: [] + }; + } + + let remainder = amountToSend; + let selectedProofs = [smallerProofs[0]]; + console.log(`>> select ${smallerProofs[0].amount} – rest: ${sumProofs(proofs.filter((p) => !selectedProofs.includes(p)))} – total: ${sumProofs(proofs)}`); + const returnedProofs = [] + const feePPK = includeFees ? this.getFeesForProofs(selectedProofs) : 0; + remainder -= smallerProofs[0].amount - feePPK / 1000; + if (remainder > 0) { + const { returnChange, send } = this.selectProofsToSend(smallerProofs.slice(1), remainder, includeFees); + selectedProofs.push(...send); + returnedProofs.push(...returnChange); + } + console.log(`>> EXIT select ${smallerProofs[0].amount} – rest: ${sumProofs(proofs.filter((p) => !selectedProofs.includes(p)))} – total: ${sumProofs(proofs)} - returnedProofs: ${sumProofs(returnedProofs)}`); + + if (sumProofs(selectedProofs) < amountToSend && nextBigger) { + console.log(`! selectedProofs (${sumProofs(selectedProofs)}) < amountToSend (${amountToSend}) and nextBigger: ${nextBigger.amount}`); + console.log(`< 3 select ${nextBigger.amount}, return: ${sumProofs(proofs.filter((p) => p.id !== nextBigger.id))} – total: ${sumProofs(proofs)}`); + selectedProofs = [nextBigger] + } + console.log(`< 4 select: ${sumProofs(selectedProofs)}, return: ${sumProofs(proofs.filter((p) => !selectedProofs.includes(p)))} – total: ${sumProofs(proofs)}`); + return { + returnChange: proofs.filter((p) => !selectedProofs.includes(p)), + send: selectedProofs + }; + } + + getFeesForProofs(proofs: Array): number { + const fees = Math.floor(Math.max( + (proofs.reduce((total, curr) => total + (this._keysets.find((k) => k.id === curr.id)?.input_fee_ppk || 0), 0) + 999) / 1000, + 0 + )) + return fees; + } + /** * Splits and creates sendable tokens * if no amount is specified, the amount is implied by the cumulative amount of all proofs @@ -296,7 +414,7 @@ class CashuWallet { * @param privkey? will create a signature on the @param proofs secrets if set * @returns promise of the change- and send-proofs */ - async send( + async swap( amount: number, proofs: Array, options?: { @@ -311,57 +429,46 @@ class CashuWallet { amount = options?.preference?.reduce((acc, curr) => acc + curr.amount * curr.count, 0); } const keyset = await this.getKeys(options?.keysetId); - let amountAvailable = 0; - const proofsToSend: Array = []; - const proofsToKeep: Array = []; - proofs.forEach((proof) => { - if (amountAvailable >= amount) { - proofsToKeep.push(proof); + const proofsToSend = proofs; + const amountAvailable = sumProofs(proofs); + if (amount + this.getFeesForProofs(proofsToSend) > amountAvailable) { + throw new Error('Not enough funds available'); + } + const amountToSend = amount + this.getFeesForProofs(proofsToSend) + const amountToKeep = sumProofs(proofsToSend) - amountToSend + console.log(`amountToKeep: ${amountToKeep}, amountToSend: ${amountToSend}`); + const { payload, blindedMessages } = this.createSwapPayload( + amountToSend, + proofsToSend, + keyset, + options?.preference, + options?.counter, + options?.pubkey, + options?.privkey + ); + const { signatures } = await this.mint.split(payload); + const swapProofs = this.constructProofs( + signatures, + blindedMessages.rs, + blindedMessages.secrets, + keyset + ); + + const splitProofsToKeep: Array = []; + const splitProofsToSend: Array = []; + let amountToKeepCounter = 0; + swapProofs.forEach((proof) => { + if (amountToKeepCounter < amountToKeep) { + amountToKeepCounter += proof.amount; + splitProofsToKeep.push(proof); return; } - amountAvailable = amountAvailable + proof.amount; - proofsToSend.push(proof); + splitProofsToSend.push(proof); }); - - if (amount > amountAvailable) { - throw new Error('Not enough funds available'); - } - if (amount < amountAvailable || options?.preference || options?.pubkey) { - const { amountKeep, amountSend } = this.splitReceive(amount, amountAvailable); - const { payload, blindedMessages } = this.createSwapPayload( - amountSend, - proofsToSend, - keyset, - options?.preference, - options?.counter, - options?.pubkey, - options?.privkey - ); - const { signatures } = await this.mint.split(payload); - const proofs = this.constructProofs( - signatures, - blindedMessages.rs, - blindedMessages.secrets, - keyset - ); - // sum up proofs until amount2 is reached - const splitProofsToKeep: Array = []; - const splitProofsToSend: Array = []; - let amountKeepCounter = 0; - proofs.forEach((proof) => { - if (amountKeepCounter < amountKeep) { - amountKeepCounter += proof.amount; - splitProofsToKeep.push(proof); - return; - } - splitProofsToSend.push(proof); - }); - return { - returnChange: [...splitProofsToKeep, ...proofsToKeep], - send: splitProofsToSend - }; - } - return { returnChange: proofsToKeep, send: proofsToSend }; + return { + returnChange: splitProofsToKeep, + send: splitProofsToSend + }; } /** @@ -496,7 +603,6 @@ class CashuWallet { } ): Promise { const keys = await this.getKeys(options?.keysetId); - const { blindedMessages, secrets, rs } = this.createBlankOutputs( meltQuote.fee_reserve, keys.id, @@ -597,7 +703,7 @@ class CashuWallet { } { const totalAmount = proofsToSend.reduce((total, curr) => total + curr.amount, 0); const keepBlindedMessages = this.createRandomBlindedMessages( - totalAmount - amount, + totalAmount - amount - this.getFeesForProofs(proofsToSend), keyset.id, undefined, counter @@ -662,14 +768,6 @@ class CashuWallet { return state && state.state === CheckStateEnum.SPENT; }); } - private splitReceive( - amount: number, - amountAvailable: number - ): { amountKeep: number; amountSend: number } { - const amountKeep: number = amountAvailable - amount; - const amountSend: number = amount; - return { amountKeep, amountSend }; - } /** * Creates blinded messages for a given amount @@ -785,6 +883,7 @@ class CashuWallet { }) .map((p) => serializeProof(p) as Proof); } + } export { CashuWallet }; diff --git a/src/legacy/nut-06.ts b/src/legacy/nut-06.ts index eff911bc..f412032c 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'; +import type { MintContactInfo, MintInfo } from '../model/types/index.js'; -export function handleMintInfoContactFieldDeprecated(data: GetInfoResponse) { +export function handleMintInfoContactFieldDeprecated(data: MintInfo) { // 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) { diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 07fd331f..610bda7f 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -80,9 +80,9 @@ export type MintKeyset = { */ active: boolean; /** - * Input fees for keyset (in ppk) + * Input fee for keyset (in ppk) */ - input_fees_ppk?: number; + input_fee_ppk?: number; }; /** @@ -488,7 +488,7 @@ export type MintContactInfo = { /** * Response from mint at /info endpoint */ -export type GetInfoResponse = { +export type MintInfo = { name: string; pubkey: string; version: string; @@ -530,7 +530,7 @@ export type GetInfoResponse = { }; /** - * Ecash to other MoE swap method, displayed in @type {GetInfoResponse} + * Ecash to other MoE swap method, displayed in @type {MintInfo} */ export type SwapMethod = { method: string; diff --git a/src/utils.ts b/src/utils.ts index 48a2a00c..f6b3b186 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -162,6 +162,10 @@ export function sanitizeUrl(url: string): string { return url.replace(/\/$/, ''); } +export function sumProofs(proofs: Array) { + return proofs.reduce((acc, proof) => acc + proof.amount, 0); +} + export { bigIntStringify, bytesToNumber, @@ -169,5 +173,5 @@ export { getEncodedToken, hexToNumber, splitAmount, - getDefaultAmountPreference + getDefaultAmountPreference, }; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 6e8544c0..cff855f1 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -21,7 +21,7 @@ const dummyKeysetResp = { id: '009a1f293253e41e', unit: 'sat', active: true, - input_fees_ppk: 0, + input_fee_ppk: 0, } ] }; @@ -570,7 +570,7 @@ describe('send', () => { const result = await wallet.send(2, proofs).catch((e) => e); - expect(result).toEqual(new Error('Not enough funds available')); + expect(result).toEqual(new Error('Not enough funds available to send')); }); test('test send bad response', async () => { nock(mintUrl).post('/v1/swap').reply(200, {}); From 003c5fddb4a4f716766346264cf71aa4247301ae Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:26:45 +0200 Subject: [PATCH 21/70] cleanup --- src/CashuWallet.ts | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 098b264b..4805c2cd 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -312,25 +312,14 @@ class CashuWallet { if (sumProofs(proofs) < amount) { throw new Error('Not enough funds available to send'); } - // try to select the exact amount of proofs to send (without fees) - console.log("calling selectProofsToSend"); - console.log(`proofs: ${sumProofs(proofs)}, amount: ${amount}`); const { returnChange: keepProofsOffline, send: sendProofOffline } = this.selectProofsToSend(proofs, amount); - console.log(`keepProofsOffline: ${sumProofs(keepProofsOffline)}, sendProofOffline: ${sumProofs(sendProofOffline)}, amount: ${amount}`); - const fees = this.getFeesForProofs(sendProofOffline); - console.log(`fees: ${fees}`); - if ( sumProofs(sendProofOffline) != amount || // if the exact amount cannot be selected options?.preference || options?.pubkey || options?.privkey || options?.keysetId // these options require a swap ) { - console.log(`>>>>>>> must do swap with ${sumProofs(proofs)} sat`); const { returnChange: keepProofsSelect, send: sendProofs } = this.selectProofsToSend(proofs, amount, true); - console.log(`keepProofsSelect: ${sumProofs(keepProofsSelect)}, sendProofs: ${sumProofs(sendProofs)}, amount: ${amount}`); const { returnChange, send } = await this.swap(amount, sendProofs, options); - console.log(`returnChange: ${sumProofs(returnChange)}, send: ${sumProofs(send)}`); const returnChangeProofs = keepProofsSelect.concat(returnChange); - console.log(`returnChangeProofs: ${sumProofs(returnChangeProofs)}`); return { returnChange: returnChangeProofs, send }; } @@ -349,29 +338,14 @@ class CashuWallet { const biggerProofs = sortedProofs.filter((p) => p.amount > amountToSend).sort((a, b) => a.amount - b.amount); const nextBigger = biggerProofs[0]; - console.log(`> enter select with proofs: ${sumProofs(proofs)}, amountToSend: ${amountToSend}, smallerProofs: ${sumProofs(smallerProofs)}, biggerProofs: ${sumProofs(biggerProofs)}, nextBigger: ${nextBigger?.amount}`); - - if (!smallerProofs.length && nextBigger) { - console.log(`! no smallerProofs and nextBigger: ${nextBigger.amount}`); - console.log(`< 0 select ${nextBigger.amount}, return: ${sumProofs(proofs.filter((p) => p.id !== nextBigger.id))}`); - return { - returnChange: proofs.filter((p) => p.id !== nextBigger.id), - send: [nextBigger] - }; - } + if (!smallerProofs.length && nextBigger) + return { returnChange: proofs.filter((p) => p.id !== nextBigger.id), send: [nextBigger] }; - if (!smallerProofs.length && !nextBigger) { - console.log(`! no smallerProofs and no nextBigger, change: ${sumProofs(proofs)}`); - console.log(`< 1 select: 0, return: ${sumProofs(proofs)}`); - return { - returnChange: proofs, - send: [] - }; - } + if (!smallerProofs.length && !nextBigger) + return { returnChange: proofs, send: [] }; let remainder = amountToSend; let selectedProofs = [smallerProofs[0]]; - console.log(`>> select ${smallerProofs[0].amount} – rest: ${sumProofs(proofs.filter((p) => !selectedProofs.includes(p)))} – total: ${sumProofs(proofs)}`); const returnedProofs = [] const feePPK = includeFees ? this.getFeesForProofs(selectedProofs) : 0; remainder -= smallerProofs[0].amount - feePPK / 1000; @@ -380,14 +354,10 @@ class CashuWallet { selectedProofs.push(...send); returnedProofs.push(...returnChange); } - console.log(`>> EXIT select ${smallerProofs[0].amount} – rest: ${sumProofs(proofs.filter((p) => !selectedProofs.includes(p)))} – total: ${sumProofs(proofs)} - returnedProofs: ${sumProofs(returnedProofs)}`); - if (sumProofs(selectedProofs) < amountToSend && nextBigger) { - console.log(`! selectedProofs (${sumProofs(selectedProofs)}) < amountToSend (${amountToSend}) and nextBigger: ${nextBigger.amount}`); - console.log(`< 3 select ${nextBigger.amount}, return: ${sumProofs(proofs.filter((p) => p.id !== nextBigger.id))} – total: ${sumProofs(proofs)}`); + if (sumProofs(selectedProofs) < amountToSend && nextBigger) selectedProofs = [nextBigger] - } - console.log(`< 4 select: ${sumProofs(selectedProofs)}, return: ${sumProofs(proofs.filter((p) => !selectedProofs.includes(p)))} – total: ${sumProofs(proofs)}`); + return { returnChange: proofs.filter((p) => !selectedProofs.includes(p)), send: selectedProofs @@ -436,7 +406,6 @@ class CashuWallet { } const amountToSend = amount + this.getFeesForProofs(proofsToSend) const amountToKeep = sumProofs(proofsToSend) - amountToSend - console.log(`amountToKeep: ${amountToKeep}, amountToSend: ${amountToSend}`); const { payload, blindedMessages } = this.createSwapPayload( amountToSend, proofsToSend, From 59417ad9ce85694b0c2b3732041be46e6f1df4b5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:07:24 +0200 Subject: [PATCH 22/70] refactor --- src/CashuWallet.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 4805c2cd..100847c9 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -80,17 +80,17 @@ class CashuWallet { mnemonicOrSeed?: string | Uint8Array; } ) { + + this.mint = mint; let keys: Array = []; if (options?.keys && !Array.isArray(options.keys)) { keys = [options.keys]; } else if (options?.keys && Array.isArray(options?.keys)) { keys = options?.keys; } - this.mint = mint; - if (options?.unit) this._unit = options?.unit; if (keys) keys.forEach((key) => this._keys.set(key.id, key)); + if (options?.unit) this._unit = options?.unit; if (options?.keysets) this._keysets = options.keysets; - if (!options?.mnemonicOrSeed) { return; } else if (options?.mnemonicOrSeed instanceof Uint8Array) { From 65f5c8c73db21ae723dc87ee1bdcf36e73c515dd Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:38:44 +0200 Subject: [PATCH 23/70] wip --- src/CashuWallet.ts | 57 +++++++++++++++++++++------------------- src/model/types/index.ts | 6 ++--- src/utils.ts | 35 ++++++++++-------------- test/utils.test.ts | 15 +++++------ test/wallet.test.ts | 8 +++--- 5 files changed, 58 insertions(+), 63 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 7eb18435..bd0a069c 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -21,12 +21,11 @@ import { CheckStateEnum, SerializedBlindedSignature, MeltQuoteState, - Preferences + OutputAmounts } from './model/types/index.js'; import { bytesToNumber, getDecodedToken, - getDefaultAmountPreference, splitAmount } from './utils.js'; import { validateMnemonic } from '@scure/bip39'; @@ -115,7 +114,8 @@ class CashuWallet { /** * 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 preference? Deprecated. Use `outputAmounts` instead. Optional preference for splitting proofs into specific amounts. + * @param outputAmounts? optionally specify the output's amounts to keep and to send. * @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 @@ -126,6 +126,7 @@ class CashuWallet { options?: { keysetId?: string; preference?: Array; + outputAmounts?: OutputAmounts; counter?: number; pubkey?: string; privkey?: string; @@ -138,7 +139,7 @@ class CashuWallet { const tokenEntries: Array = token.token; const proofs = await this.receiveTokenEntry(tokenEntries[0], { keysetId: options?.keysetId, - preference: options?.preference, + outputAmounts: options?.outputAmounts, counter: options?.counter, pubkey: options?.pubkey, privkey: options?.privkey @@ -163,6 +164,7 @@ class CashuWallet { options?: { keysetId?: string; preference?: Array; + outputAmounts?: OutputAmounts; counter?: number; pubkey?: string; privkey?: string; @@ -171,17 +173,12 @@ class CashuWallet { const proofs: Array = []; try { const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); - let preference = options?.preference; const keys = await this.getKeys(options?.keysetId); - if (!preference) { - preference = getDefaultAmountPreference(amount, keys); - } - let pref: Preferences = { sendPreference: preference }; const { payload, blindedMessages } = this.createSwapPayload( amount, tokenEntry.proofs, keys, - pref, + options?.outputAmounts, options?.counter, options?.pubkey, options?.privkey @@ -206,7 +203,8 @@ class CashuWallet { * if both amount and preference are set, but the preference cannot fulfill the amount, then we use the default split * @param amount amount to send while performing the optimal split (least proofs possible). can be set to undefined if preference is set * @param proofs proofs matching that amount - * @param preference optional preference for splitting proofs into specific amounts. overrides amount param + * @param preference Deprecated. Use `outputAmounts` instead. Optional preference for splitting proofs into specific amounts. + * @param outputAmounts? optionally specify the output's amounts to keep and to send. * @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 @@ -216,19 +214,14 @@ class CashuWallet { amount: number, proofs: Array, options?: { - preference?: Preferences; + preference?: Array; + outputAmounts?: OutputAmounts; counter?: number; pubkey?: string; privkey?: string; keysetId?: string; } ): Promise { - if (options?.preference) { - amount = options?.preference?.sendPreference.reduce( - (acc, curr) => acc + curr.amount * curr.count, - 0 - ); - } const keyset = await this.getKeys(options?.keysetId); let amountAvailable = 0; const proofsToSend: Array = []; @@ -245,13 +238,13 @@ class CashuWallet { if (amount > amountAvailable) { throw new Error('Not enough funds available'); } - if (amount < amountAvailable || options?.preference || options?.pubkey) { + if (amount < amountAvailable || options?.outputAmounts || options?.pubkey) { const { amountKeep, amountSend } = this.splitReceive(amount, amountAvailable); const { payload, blindedMessages } = this.createSwapPayload( amountSend, proofsToSend, keyset, - options?.preference, + options?.outputAmounts, options?.counter, options?.pubkey, options?.privkey @@ -367,6 +360,12 @@ class CashuWallet { * Mint tokens for a given mint quote * @param amount amount to request * @param quote ID of mint quote + * @param options.keysetId? optionally set keysetId for blank outputs for returned change. + * @deprecated + * @param preference? Deprecated. Use `outputAmounts` instead. Optional preference for splitting proofs into specific amounts. + * @param outputAmounts? optionally specify the output's amounts to keep and to send. + * @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 proofs */ async mintTokens( @@ -375,6 +374,7 @@ class CashuWallet { options?: { keysetId?: string; preference?: Array; + outputAmounts?: OutputAmounts, counter?: number; pubkey?: string; } @@ -383,7 +383,7 @@ class CashuWallet { const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, keyset, - options?.preference, + options?.outputAmounts?.keepAmounts, options?.counter, options?.pubkey ); @@ -520,7 +520,7 @@ class CashuWallet { * Creates a split payload * @param amount amount to send * @param proofsToSend proofs to split* - * @param preference optional preference for splitting proofs into specific amounts. overrides amount param + * @param outputAmounts? optionally specify the output's amounts to keep and to send. * @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 @@ -530,7 +530,7 @@ class CashuWallet { amount: number, proofsToSend: Array, keyset: MintKeys, - preference?: Preferences, + outputAmounts?: OutputAmounts, counter?: number, pubkey?: string, privkey?: string @@ -539,10 +539,13 @@ class CashuWallet { blindedMessages: BlindedTransaction; } { const totalAmount = proofsToSend.reduce((total, curr) => total + curr.amount, 0); + if (outputAmounts && outputAmounts.sendAmounts && !outputAmounts.keepAmounts?.length) { + outputAmounts.keepAmounts = splitAmount(totalAmount - amount, keyset.keys); + } const keepBlindedMessages = this.createRandomBlindedMessages( totalAmount - amount, keyset, - preference?.keepPreference, + outputAmounts?.keepAmounts, counter ); if (this._seed && counter) { @@ -551,7 +554,7 @@ class CashuWallet { const sendBlindedMessages = this.createRandomBlindedMessages( amount, keyset, - preference?.sendPreference, + outputAmounts?.sendAmounts, counter, pubkey ); @@ -626,11 +629,11 @@ class CashuWallet { private createRandomBlindedMessages( amount: number, keyset: MintKeys, - amountPreference?: Array, + split?: Array, counter?: number, pubkey?: string ): BlindedMessageData & { amounts: Array } { - const amounts = splitAmount(amount, keyset.keys, amountPreference); + const amounts = splitAmount(amount, keyset.keys, split); return this.createBlindedMessages(amounts, keyset.id, counter, pubkey); } diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 39bd42e9..7ebb6f8b 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -556,9 +556,9 @@ export type AmountPreference = { count: number; }; -export type Preferences = { - sendPreference: Array; - keepPreference?: Array; +export type OutputAmounts = { + sendAmounts: Array; + keepAmounts?: Array; }; export type InvoiceData = { diff --git a/src/utils.ts b/src/utils.ts index 2ea94e27..f5b66479 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,12 +8,12 @@ import { decodeCBOR } from './cbor.js'; function splitAmount( value: number, keyset: Keys, - amountPreference?: Array, + split?: Array, order?: string ): Array { const chunks: Array = []; - if (amountPreference) { - chunks.push(...getPreference(value, keyset, amountPreference)); + if (split) { + chunks.push(...getPreference(value, keyset, split)); value = value - chunks.reduce((curr, acc) => { @@ -24,7 +24,7 @@ function splitAmount( .map((k) => parseInt(k)) .sort((a, b) => b - a); sortedKeyAmounts.forEach((amt) => { - let q = Math.floor(value / amt); + const q = Math.floor(value / amt); for (let i = 0; i < q; ++i) chunks.push(amt); value %= amt; }); @@ -42,32 +42,26 @@ function hasCorrespondingKey(amount: number, keyset: Keys) { function getPreference( amount: number, keyset: Keys, - preferredAmounts: Array + split: Array ): Array { const chunks: Array = []; let accumulator = 0; - preferredAmounts.forEach((pa) => { - if (!hasCorrespondingKey(pa.amount, keyset)) { + split.forEach((splitAmount) => { + if (!hasCorrespondingKey(splitAmount, keyset)) { throw new Error('Provided amount preferences do not match the amounts of the mint keyset.'); } - for (let i = 1; i <= pa.count; i++) { - accumulator += pa.amount; + const count = split.filter(value => value === splitAmount).length; + for (let i = 1; i <= count; i++) { + accumulator += splitAmount; if (accumulator > amount) { return; } - chunks.push(pa.amount); + chunks.push(splitAmount); } }); return chunks; } -function getDefaultAmountPreference(amount: number, keyset: Keys): Array { - const amounts = splitAmount(amount, keyset); - return amounts.map((a) => { - return { amount: a, count: 1 }; - }); -} - function bytesToNumber(bytes: Uint8Array): bigint { return hexToNumber(bytesToHex(bytes)); } @@ -119,7 +113,7 @@ function handleTokens(token: string): Token { } else if (version === 'B') { const uInt8Token = encodeBase64toUint8(encodedToken); const tokenData = decodeCBOR(uInt8Token) as { - t: { p: { a: number; s: string; c: Uint8Array }[]; i: Uint8Array }[]; + t: Array<{ p: Array<{ a: number; s: string; c: Uint8Array }>; i: Uint8Array }>; m: string; d: string; }; @@ -135,9 +129,9 @@ function handleTokens(token: string): Token { }) ); return { token: [mergedTokenEntry], memo: tokenData.d || '' }; - } else { - throw new Error('Token version is not supported'); } + throw new Error('Token version is not supported'); + } /** * Returns the keyset id of a set of keys @@ -195,5 +189,4 @@ export { getEncodedToken, hexToNumber, splitAmount, - getDefaultAmountPreference }; diff --git a/test/utils.test.ts b/test/utils.test.ts index cdb75d1b..238124c2 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -29,29 +29,26 @@ describe('test split amounts ', () => { }); describe('test split custom amounts ', () => { - const fiveToOne: AmountPreference = { amount: 1, count: 5 }; + const fiveToOne = [1, 1, 1, 1, 1]; test('testing amount 5', async () => { - const chunks = utils.splitAmount(5, keys, [fiveToOne]); + const chunks = utils.splitAmount(5, keys, fiveToOne); expect(chunks).toStrictEqual([1, 1, 1, 1, 1]); }); - const tenToOneAndTwo: Array = [ - { amount: 1, count: 2 }, - { amount: 2, count: 4 } - ]; + const tenToOneAndTwo = [1, 1, 2, 2, 2, 2]; test('testing amount 10', async () => { const chunks = utils.splitAmount(10, keys, tenToOneAndTwo); expect(chunks).toStrictEqual([1, 1, 2, 2, 2, 2]); }); - const fiveTwelve: Array = [{ amount: 512, count: 2 }]; + const fiveTwelve = [512, 512]; test('testing amount 518', async () => { const chunks = utils.splitAmount(518, keys, fiveTwelve, 'desc'); expect(chunks).toStrictEqual([512, 4, 2]); }); - const illegal: Array = [{ amount: 3, count: 2 }]; + const illegal = [3, 3]; test('testing non pow2', async () => { expect(() => utils.splitAmount(6, keys, illegal)).toThrowError(); }); - const empty: Array = []; + const empty: Array = []; test('testing empty', async () => { const chunks = utils.splitAmount(5, keys, empty, 'desc'); expect(chunks).toStrictEqual([4, 1]); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 93ca7c77..b32b4829 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -492,7 +492,8 @@ describe('send', () => { } ]; const result = await wallet.send(4, overpayProofs, { - preference: { sendPreference: [{ amount: 1, count: 4 }] } + // preference: { sendPreference: [{ amount: 1, count: 4 }] } + outputAmounts: { "sendAmounts": [1, 1, 1, 1], "keepAmounts": [] } }); expect(result.send).toHaveLength(4); @@ -548,8 +549,9 @@ describe('send', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; - const result = await wallet.send(4, overpayProofs, { - preference: { sendPreference: [{ amount: 1, count: 3 }] } + const result = await wallet.send(3, overpayProofs, { + // preference: { sendPreference: [{ amount: 1, count: 3 }] } + outputAmounts: { "sendAmounts": [1, 1, 1], "keepAmounts": [] } }); expect(result.send).toHaveLength(3); From 07db2ef4570d190af917dbee49c95f927d1da665 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 27 Jul 2024 00:06:57 +0200 Subject: [PATCH 24/70] owrks --- src/CashuWallet.ts | 8 ++++++-- src/utils.ts | 25 +++++++++++++++---------- test/utils.test.ts | 10 +++++++++- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index bd0a069c..fb9c735f 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -26,7 +26,8 @@ import { import { bytesToNumber, getDecodedToken, - splitAmount + splitAmount, + deprecatedPreferenceToOutputAmounts } from './utils.js'; import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; @@ -132,6 +133,7 @@ class CashuWallet { privkey?: string; } ): Promise> { + if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); try { if (typeof token === 'string') { token = getDecodedToken(token); @@ -170,6 +172,7 @@ class CashuWallet { privkey?: string; } ): Promise> { + if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); const proofs: Array = []; try { const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); @@ -222,6 +225,7 @@ class CashuWallet { keysetId?: string; } ): Promise { + if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); const keyset = await this.getKeys(options?.keysetId); let amountAvailable = 0; const proofsToSend: Array = []; @@ -361,7 +365,6 @@ class CashuWallet { * @param amount amount to request * @param quote ID of mint quote * @param options.keysetId? optionally set keysetId for blank outputs for returned change. - * @deprecated * @param preference? Deprecated. Use `outputAmounts` instead. Optional preference for splitting proofs into specific amounts. * @param outputAmounts? optionally specify the output's amounts to keep and to send. * @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect @@ -379,6 +382,7 @@ class CashuWallet { pubkey?: string; } ): Promise<{ proofs: Array }> { + if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); const keyset = await this.getKeys(options?.keysetId); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, diff --git a/src/utils.ts b/src/utils.ts index f5b66479..56362f4f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import { encodeBase64ToJson, encodeBase64toUint8, encodeJsonToBase64 } from './base64.js'; -import { AmountPreference, Keys, Proof, Token, TokenEntry, TokenV2 } from './model/types/index.js'; +import { AmountPreference, Keys, OutputAmounts, 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'; @@ -13,6 +13,9 @@ function splitAmount( ): Array { const chunks: Array = []; if (split) { + if (split.reduce((a, b) => a + b, 0) > value) { + throw new Error('Split amount is greater than the value'); + } chunks.push(...getPreference(value, keyset, split)); value = value - @@ -45,23 +48,24 @@ function getPreference( split: Array ): Array { const chunks: Array = []; - let accumulator = 0; split.forEach((splitAmount) => { if (!hasCorrespondingKey(splitAmount, keyset)) { throw new Error('Provided amount preferences do not match the amounts of the mint keyset.'); } - const count = split.filter(value => value === splitAmount).length; - for (let i = 1; i <= count; i++) { - accumulator += splitAmount; - if (accumulator > amount) { - return; - } - chunks.push(splitAmount); - } + chunks.push(splitAmount); }); return chunks; } +function deprecatedPreferenceToOutputAmounts(preference?: Array): OutputAmounts { + const sendAmounts: Array = []; + preference?.forEach(({ count, amount }) => { + for (let i = 0; i < count; i++) { + sendAmounts.push(amount); + } + }); + return { sendAmounts }; +} function bytesToNumber(bytes: Uint8Array): bigint { return hexToNumber(bytesToHex(bytes)); } @@ -189,4 +193,5 @@ export { getEncodedToken, hexToNumber, splitAmount, + deprecatedPreferenceToOutputAmounts, }; diff --git a/test/utils.test.ts b/test/utils.test.ts index 238124c2..8538064c 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -39,11 +39,19 @@ describe('test split custom amounts ', () => { const chunks = utils.splitAmount(10, keys, tenToOneAndTwo); expect(chunks).toStrictEqual([1, 1, 2, 2, 2, 2]); }); - const fiveTwelve = [512, 512]; + test('testing amount 12', async () => { + const chunks = utils.splitAmount(12, keys, tenToOneAndTwo); + expect(chunks).toStrictEqual([1, 1, 2, 2, 2, 2, 2]); + }); + const fiveTwelve = [512]; test('testing amount 518', async () => { const chunks = utils.splitAmount(518, keys, fiveTwelve, 'desc'); expect(chunks).toStrictEqual([512, 4, 2]); }); + const tooMuch = [512, 512]; + test('testing amount 512 but split too much', async () => { + expect(() => utils.splitAmount(512, keys, tooMuch)).toThrowError(); + }); const illegal = [3, 3]; test('testing non pow2', async () => { expect(() => utils.splitAmount(6, keys, illegal)).toThrowError(); From 865d93cea7f8c80c126130770034b14d6fd262f0 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 27 Jul 2024 00:08:12 +0200 Subject: [PATCH 25/70] npm run format --- src/CashuWallet.ts | 16 ++++++++++------ src/utils.ts | 19 +++++++++++-------- test/wallet.test.ts | 4 ++-- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index fb9c735f..94b6e974 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -133,7 +133,8 @@ class CashuWallet { privkey?: string; } ): Promise> { - if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); + if (options?.preference) + options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); try { if (typeof token === 'string') { token = getDecodedToken(token); @@ -172,7 +173,8 @@ class CashuWallet { privkey?: string; } ): Promise> { - if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); + if (options?.preference) + options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); const proofs: Array = []; try { const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); @@ -225,7 +227,8 @@ class CashuWallet { keysetId?: string; } ): Promise { - if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); + if (options?.preference) + options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); const keyset = await this.getKeys(options?.keysetId); let amountAvailable = 0; const proofsToSend: Array = []; @@ -377,12 +380,13 @@ class CashuWallet { options?: { keysetId?: string; preference?: Array; - outputAmounts?: OutputAmounts, + outputAmounts?: OutputAmounts; counter?: number; pubkey?: string; } ): Promise<{ proofs: Array }> { - if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); + if (options?.preference) + options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); const keyset = await this.getKeys(options?.keysetId); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, @@ -524,7 +528,7 @@ class CashuWallet { * Creates a split payload * @param amount amount to send * @param proofsToSend proofs to split* - * @param outputAmounts? optionally specify the output's amounts to keep and to send. + * @param outputAmounts? optionally specify the output's amounts to keep and to send. * @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 diff --git a/src/utils.ts b/src/utils.ts index 56362f4f..bc6919a3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,13 @@ import { encodeBase64ToJson, encodeBase64toUint8, encodeJsonToBase64 } from './base64.js'; -import { AmountPreference, Keys, OutputAmounts, Proof, Token, TokenEntry, TokenV2 } from './model/types/index.js'; +import { + AmountPreference, + Keys, + OutputAmounts, + 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'; @@ -42,11 +50,7 @@ function hasCorrespondingKey(amount: number, keyset: Keys) { return amount in keyset; } -function getPreference( - amount: number, - keyset: Keys, - split: Array -): Array { +function getPreference(amount: number, keyset: Keys, split: Array): Array { const chunks: Array = []; split.forEach((splitAmount) => { if (!hasCorrespondingKey(splitAmount, keyset)) { @@ -135,7 +139,6 @@ function handleTokens(token: string): Token { return { token: [mergedTokenEntry], memo: tokenData.d || '' }; } throw new Error('Token version is not supported'); - } /** * Returns the keyset id of a set of keys @@ -193,5 +196,5 @@ export { getEncodedToken, hexToNumber, splitAmount, - deprecatedPreferenceToOutputAmounts, + deprecatedPreferenceToOutputAmounts }; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index b32b4829..0e2bcb9e 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -493,7 +493,7 @@ describe('send', () => { ]; const result = await wallet.send(4, overpayProofs, { // preference: { sendPreference: [{ amount: 1, count: 4 }] } - outputAmounts: { "sendAmounts": [1, 1, 1, 1], "keepAmounts": [] } + outputAmounts: { sendAmounts: [1, 1, 1, 1], keepAmounts: [] } }); expect(result.send).toHaveLength(4); @@ -551,7 +551,7 @@ describe('send', () => { ]; const result = await wallet.send(3, overpayProofs, { // preference: { sendPreference: [{ amount: 1, count: 3 }] } - outputAmounts: { "sendAmounts": [1, 1, 1], "keepAmounts": [] } + outputAmounts: { sendAmounts: [1, 1, 1], keepAmounts: [] } }); expect(result.send).toHaveLength(3); From 69e1fcbf954a520786fedc4b1afcc4ba452f3504 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 28 Jul 2024 15:16:29 +0200 Subject: [PATCH 26/70] merge with preferences branch --- src/CashuMint.ts | 5 +- src/CashuWallet.ts | 157 ++++++++++++++++++++++++++++++++++--------- src/legacy/nut-06.ts | 38 +++++------ src/utils.ts | 35 ++++++++-- test/wallet.test.ts | 7 +- 5 files changed, 179 insertions(+), 63 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index a13ff4ee..73d20662 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -52,10 +52,7 @@ class CashuMint { * @param mintUrl * @param customRequest */ - public static async getInfo( - mintUrl: string, - customRequest?: typeof request - ): Promise { + public static async getInfo(mintUrl: string, customRequest?: typeof request): Promise { const requestInstance = customRequest || request; const response = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/info') diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 247cc644..295a456a 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -30,7 +30,8 @@ import { getDecodedToken, splitAmount, sumProofs, - deprecatedPreferenceToOutputAmounts + deprecatedPreferenceToOutputAmounts, + getKeepAmounts } from './utils.js'; import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; @@ -81,7 +82,6 @@ class CashuWallet { mnemonicOrSeed?: string | Uint8Array; } ) { - this.mint = mint; let keys: Array = []; if (options?.keys && !Array.isArray(options.keys)) { @@ -108,7 +108,7 @@ class CashuWallet { return this._unit; } get keys(): Map { - return this._keys + return this._keys; } get keysetId(): string { if (!this._keysetId) { @@ -166,10 +166,10 @@ class CashuWallet { /** * Get public keys from the mint. If keys were already fetched, it will return those. - * + * * If `keysetId` is set, it will fetch and return that specific keyset. * Otherwise, we select an active keyset with the unit of the wallet. - * + * * @param keysetId optional keysetId to get keys for * @param unit optional unit to get keys for * @returns keyset @@ -180,7 +180,7 @@ class CashuWallet { this.keysetId = keysetId; return this._keys.get(keysetId) as MintKeys; } - const allKeysets = await this.mint.getKeys(keysetId) + const allKeysets = await this.mint.getKeys(keysetId); const keyset = allKeysets.keysets[0]; if (!keyset) { throw new Error(`could not initialize keys. No keyset with id '${keysetId}' found`); @@ -196,14 +196,18 @@ class CashuWallet { .filter((k) => k.unit === this._unit && k.active) .sort((a, b) => (a.input_fee_ppk ?? 0) - (b.input_fee_ppk ?? 0))[0]; if (!keysetToActivate) { - throw new Error(`could not initialize keys. No active keyset with unit '${this._unit}' found`); + throw new Error( + `could not initialize keys. No active keyset with unit '${this._unit}' found` + ); } if (!this._keys.get(keysetToActivate.id)) { const keysetGet = await this.mint.getKeys(keysetToActivate.id); const keys = keysetGet.keysets.find((k) => k.id === keysetToActivate.id); if (!keys) { - throw new Error(`could not initialize keys. No keyset with id '${keysetToActivate.id}' found`); + throw new Error( + `could not initialize keys. No keyset with id '${keysetToActivate.id}' found` + ); } this._keys.set(keys.id, keys); } @@ -306,62 +310,130 @@ class CashuWallet { proofs: Array, options?: { preference?: Array; + outputAmounts?: OutputAmounts; counter?: number; pubkey?: string; privkey?: string; keysetId?: string; - offline?: boolean, + offline?: boolean; } ): Promise { + if (options?.preference) + options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); if (sumProofs(proofs) < amount) { throw new Error('Not enough funds available to send'); } - const { returnChange: keepProofsOffline, send: sendProofOffline } = this.selectProofsToSend(proofs, amount); + const { returnChange: keepProofsOffline, send: sendProofOffline } = this.selectProofsToSend( + proofs, + amount + ); if ( sumProofs(sendProofOffline) != amount || // if the exact amount cannot be selected - options?.preference || options?.pubkey || options?.privkey || options?.keysetId // these options require a swap + options?.outputAmounts || + options?.pubkey || + options?.privkey || + options?.keysetId // these options require a swap ) { - const { returnChange: keepProofsSelect, send: sendProofs } = this.selectProofsToSend(proofs, amount, true); + console.log('>> yes swap'); + const { returnChange: keepProofsSelect, send: sendProofs } = this.selectProofsToSend( + proofs, + amount, + true + ); + console.log( + `keepProofsSelect: ${sumProofs(keepProofsSelect)} | sendProofs: ${sumProofs(sendProofs)}` + ); + // if (options && !options?.outputAmounts?.keepAmounts) { + // options.outputAmounts = { + // keepAmounts: getKeepAmounts(keepProofsSelect, sumProofs(keepProofsSelect), this._keys.get(options?.keysetId || this.keysetId) as MintKeys, 3), + // sendAmounts: options?.outputAmounts?.sendAmounts || [] + // } + // } const { returnChange, send } = await this.swap(amount, sendProofs, options); + console.log(`returnChange: ${sumProofs(returnChange)} | send: ${sumProofs(send)}`); const returnChangeProofs = keepProofsSelect.concat(returnChange); + console.log(`returnChangeProofs: ${sumProofs(returnChangeProofs)}`); return { returnChange: returnChangeProofs, send }; } - + console.log('>> no swap'); + // console.log(`keepProofsOffline: ${sumProofs(keepProofsOffline)} | sendProofOffline: ${sumProofs(sendProofOffline)}`); return { returnChange: keepProofsOffline, send: sendProofOffline }; - } - private selectProofsToSend( proofs: Array, amountToSend: number, includeFees = false ): SendResponse { + // heavy logging in this function const sortedProofs = proofs.sort((a, b) => a.amount - b.amount); - const smallerProofs = sortedProofs.filter((p) => p.amount <= amountToSend).sort((a, b) => b.amount - a.amount); - const biggerProofs = sortedProofs.filter((p) => p.amount > amountToSend).sort((a, b) => a.amount - b.amount); + const smallerProofs = sortedProofs + .filter((p) => p.amount <= amountToSend) + .sort((a, b) => b.amount - a.amount); + const biggerProofs = sortedProofs + .filter((p) => p.amount > amountToSend) + .sort((a, b) => a.amount - b.amount); const nextBigger = biggerProofs[0]; + console.log( + `> enter | amountToSend: ${amountToSend} | proofs: ${sumProofs( + proofs + )} | smallerProofs: ${sumProofs(smallerProofs)} | nextBigger: ${nextBigger?.amount}` + ); + if (!smallerProofs.length && nextBigger) { + console.log( + `< [0] exit: no smallerProofs, nextBigger: ${nextBigger.amount} | keep: ${sumProofs( + proofs.filter((p) => p.secret !== nextBigger.secret) + )} | proofs: ${sumProofs(proofs)}` + ); + return { + returnChange: proofs.filter((p) => p.secret !== nextBigger.secret), + send: [nextBigger] + }; + } - if (!smallerProofs.length && nextBigger) - return { returnChange: proofs.filter((p) => p.id !== nextBigger.id), send: [nextBigger] }; - - if (!smallerProofs.length && !nextBigger) + if (!smallerProofs.length && !nextBigger) { + console.log( + `< [1] exit: no smallerProofs, no nextBigger | amountToSend: ${amountToSend} | keep: ${sumProofs( + proofs + )}` + ); return { returnChange: proofs, send: [] }; + } let remainder = amountToSend; let selectedProofs = [smallerProofs[0]]; - const returnedProofs = [] + console.log(`Selected proof: ${smallerProofs[0].amount}`); + const returnedProofs = []; const feePPK = includeFees ? this.getFeesForProofs(selectedProofs) : 0; remainder -= smallerProofs[0].amount - feePPK / 1000; if (remainder > 0) { - const { returnChange, send } = this.selectProofsToSend(smallerProofs.slice(1), remainder, includeFees); + const { returnChange, send } = this.selectProofsToSend( + smallerProofs.slice(1), + remainder, + includeFees + ); + // if (!send.length) { + // send = [nextBigger]; + // console.log(`< exit [2.1] no send | send: ${sumProofs(send)} | returnChange: ${sumProofs(returnChange)}`); + // } selectedProofs.push(...send); returnedProofs.push(...returnChange); } - if (sumProofs(selectedProofs) < amountToSend && nextBigger) - selectedProofs = [nextBigger] + if (sumProofs(selectedProofs) < amountToSend && nextBigger) { + selectedProofs = [nextBigger]; + console.log( + `< exit [2.2] selecting nextBigger | amountToSend: ${amountToSend} | selectedProofs: ${sumProofs( + selectedProofs + )} | returnedProofs: ${sumProofs(returnedProofs)}` + ); + } + console.log( + `< exit [2] selectProofsToSend | amountToSend: ${amountToSend} | selectedProofs: ${sumProofs( + selectedProofs + )} | returnedProofs: ${sumProofs(proofs.filter((p) => !selectedProofs.includes(p)))}` + ); return { returnChange: proofs.filter((p) => !selectedProofs.includes(p)), send: selectedProofs @@ -369,10 +441,18 @@ class CashuWallet { } getFeesForProofs(proofs: Array): number { - const fees = Math.floor(Math.max( - (proofs.reduce((total, curr) => total + (this._keysets.find((k) => k.id === curr.id)?.input_fee_ppk || 0), 0) + 999) / 1000, - 0 - )) + const fees = Math.floor( + Math.max( + (proofs.reduce( + (total, curr) => + total + (this._keysets.find((k) => k.id === curr.id)?.input_fee_ppk || 0), + 0 + ) + + 999) / + 1000, + 0 + ) + ); return fees; } @@ -407,10 +487,15 @@ class CashuWallet { const proofsToSend = proofs; const amountAvailable = sumProofs(proofs); if (amount + this.getFeesForProofs(proofsToSend) > amountAvailable) { + console.log( + `amount: ${amount} | fees: ${this.getFeesForProofs( + proofsToSend + )} | amountAvailable: ${amountAvailable}` + ); throw new Error('Not enough funds available'); } - const amountToSend = amount + this.getFeesForProofs(proofsToSend) - const amountToKeep = sumProofs(proofsToSend) - amountToSend + const amountToSend = amount + this.getFeesForProofs(proofsToSend); + const amountToKeep = sumProofs(proofsToSend) - amountToSend; const { payload, blindedMessages } = this.createSwapPayload( amountToSend, proofsToSend, @@ -525,11 +610,18 @@ class CashuWallet { ): Promise<{ proofs: Array }> { if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); + console.log( + `outputAmounts: ${ + options?.outputAmounts?.keepAmounts + } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a, b) => a + b, 0)}) | ${ + options?.outputAmounts?.sendAmounts + } (sum: ${options?.outputAmounts?.sendAmounts.reduce((a, b) => a + b, 0)})` + ); const keyset = await this.getKeys(options?.keysetId); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, keyset, - options?.outputAmounts?.keepAmounts, + options?.outputAmounts?.sendAmounts, options?.counter, options?.pubkey ); @@ -868,7 +960,6 @@ class CashuWallet { }) .map((p) => serializeProof(p) as Proof); } - } export { CashuWallet }; diff --git a/src/legacy/nut-06.ts b/src/legacy/nut-06.ts index f412032c..fc3600ae 100644 --- a/src/legacy/nut-06.ts +++ b/src/legacy/nut-06.ts @@ -1,23 +1,23 @@ import type { MintContactInfo, MintInfo } from '../model/types/index.js'; export function handleMintInfoContactFieldDeprecated(data: MintInfo) { - // 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' - ) { - console.warn( - `Mint returned deprecated 'contact' field: Update NUT-06: https://github.com/cashubtc/nuts/pull/117` - ); - return { method: contact[0], info: contact[1] } as MintContactInfo; - } - return contact; - }); - } - return data; + // 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' + ) { + console.warn( + `Mint returned deprecated 'contact' field: Update NUT-06: https://github.com/cashubtc/nuts/pull/117` + ); + return { method: contact[0], info: contact[1] } as MintContactInfo; + } + return contact; + }); + } + return data; } diff --git a/src/utils.ts b/src/utils.ts index 60feef10..5e9e662e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -22,7 +22,9 @@ function splitAmount( const chunks: Array = []; if (split) { if (split.reduce((a, b) => a + b, 0) > value) { - throw new Error('Split amount is greater than the value'); + throw new Error( + `Split is greater than total amount: ${split.reduce((a, b) => a + b, 0)} > ${value}` + ); } chunks.push(...getPreference(value, keyset, split)); value = @@ -31,9 +33,7 @@ function splitAmount( return curr + acc; }, 0); } - const sortedKeyAmounts: Array = Object.keys(keyset) - .map((k) => parseInt(k)) - .sort((a, b) => b - a); + const sortedKeyAmounts = getKeysetAmounts(keyset); sortedKeyAmounts.forEach((amt) => { const q = Math.floor(value / amt); for (let i = 0; i < q; ++i) chunks.push(amt); @@ -42,9 +42,33 @@ function splitAmount( return chunks.sort((a, b) => (order === 'desc' ? b - a : a - b)); } +function getKeepAmounts( + proofsWeHave: Array, + amountToKeep: number, + keyset: Keys, + targetCount: number +): Array { + // determines amounts we need to reach the targetCount for each amount based on the amounts of the proofs we have + // it tries to select amounts so that the proofs we have and the proofs we want reach the targetCount + const amountWeWant: Array = []; + const amountsWeHave = proofsWeHave.map((p) => p.amount); + const sortedKeyAmounts = getKeysetAmounts(keyset); + sortedKeyAmounts.forEach((amt) => { + const count = amountsWeHave.filter((a) => a === amt).length; + const q = Math.floor(targetCount - count); + for (let i = 0; i < q; ++i) amountWeWant.push(amt); + }); + return amountWeWant; +} + function isPowerOfTwo(number: number) { return number && !(number & (number - 1)); } +function getKeysetAmounts(keyset: Keys): Array { + return Object.keys(keyset) + .map((k) => parseInt(k)) + .sort((a, b) => b - a); +} function hasCorrespondingKey(amount: number, keyset: Keys) { return amount in keyset; @@ -200,5 +224,6 @@ export { getEncodedToken, hexToNumber, splitAmount, - deprecatedPreferenceToOutputAmounts + deprecatedPreferenceToOutputAmounts, + getKeepAmounts }; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 9987b24a..33331c32 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -11,7 +11,10 @@ const dummyKeysResp = { { id: '009a1f293253e41e', unit: 'sat', - keys: { 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181', 2: '03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5' } + keys: { + 1: '02f970b6ee058705c0dddc4313721cffb7efd3d142d96ea8e01d31c2b2ff09f181', + 2: '03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5' + } } ] }; @@ -21,7 +24,7 @@ const dummyKeysetResp = { id: '009a1f293253e41e', unit: 'sat', active: true, - input_fee_ppk: 0, + input_fee_ppk: 0 } ] }; From b8e90a3bd95c8e6298516dafb575c9d75b2021c5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 28 Jul 2024 15:18:10 +0200 Subject: [PATCH 27/70] use latest nutshell docker image for integration tests --- .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 8dafd40b..5925eba7 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.2 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:latest poetry run mint - name: Check running containers run: docker ps From 8414ae269b3659b91b2eda8c2e2263d7731886c5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 28 Jul 2024 15:19:05 +0200 Subject: [PATCH 28/70] npm run format --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 79071cb9..75244c60 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` | | |/ | |/ From 6aa0553e29a141d213d20094fc9843b67db5d99c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 28 Jul 2024 23:52:13 +0200 Subject: [PATCH 29/70] fix test --- package.json | 2 +- test/integration.test.ts | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f7b58aa0..38cf20ac 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "scripts": { "compile": "rm -rf dist/lib && tsc && tsc --build tsconfig.es5.json", - "test": "jest --coverage --testPathIgnorePatterns ./test/integration.test.ts", + "test": "jest --coverage", "test-integration": "jest --coverage --testPathPattern ./test/integration.test.ts", "dev": "tsc --watch", "lint": "eslint --ext .js,.ts . --fix", diff --git a/test/integration.test.ts b/test/integration.test.ts index bb43f253..baa0f2ee 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 { deriveKeysetId, getEncodedToken } from '../src/utils.js'; +import { deriveKeysetId, getEncodedToken, sumProofs } from '../src/utils.js'; import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/curves/abstract/utils'; dns.setDefaultResultOrder('ipv4first'); @@ -144,6 +144,7 @@ describe('mint api', () => { expect(sendResponse.returnChange).toBeDefined(); expect(sendResponse.send.length).toBe(1); expect(sendResponse.returnChange.length).toBe(0); + expect(sumProofs(sendResponse.send)).toBe(64); }); test('test send tokens with change', async () => { const mint = new CashuMint(mintUrl); @@ -156,8 +157,10 @@ describe('mint api', () => { expect(sendResponse.send).toBeDefined(); expect(sendResponse.returnChange).toBeDefined(); expect(sendResponse.send.length).toBe(2); - expect(sendResponse.returnChange.length).toBe(4); - }); + expect(sendResponse.returnChange.length).toBe(5); + expect(sumProofs(sendResponse.send)).toBe(10); + expect(sumProofs(sendResponse.returnChange)).toBe(90); + }, 10000000); test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); @@ -203,7 +206,7 @@ describe('mint api', () => { const result = await wallet .receive(encoded, { privkey: bytesToHex(privKeyAlice) }) .catch((e) => e); - expect(result).toEqual(new Error('Error when receiving')); + expect(result).toEqual(new Error('Error receiving token: Error: Error receiving token entry')); const proofs = await wallet.receive(encoded, { privkey: bytesToHex(privKeyBob) }); From 29a2915755d0a5991fe1d9ee2ca03272a2ba4cd9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 28 Jul 2024 23:55:25 +0200 Subject: [PATCH 30/70] fix test script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38cf20ac..f7b58aa0 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "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", From 5be771574f5d42f6c5b4cfae015b3c47a0d6b10b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:05:20 +0200 Subject: [PATCH 31/70] wip --- src/CashuWallet.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 295a456a..e70bd072 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -449,7 +449,7 @@ class CashuWallet { 0 ) + 999) / - 1000, + 1000, 0 ) ); @@ -611,10 +611,8 @@ class CashuWallet { if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); console.log( - `outputAmounts: ${ - options?.outputAmounts?.keepAmounts - } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a, b) => a + b, 0)}) | ${ - options?.outputAmounts?.sendAmounts + `outputAmounts: ${options?.outputAmounts?.keepAmounts + } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a, b) => a + b, 0)}) | ${options?.outputAmounts?.sendAmounts } (sum: ${options?.outputAmounts?.sendAmounts.reduce((a, b) => a + b, 0)})` ); const keyset = await this.getKeys(options?.keysetId); From 175373aef24d224857274d1772ea6d8c8daaa8d9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:17:23 +0200 Subject: [PATCH 32/70] wip: output selection --- .github/workflows/nutshell-integration.yml | 2 +- src/CashuWallet.ts | 134 ++++++++++++--------- src/utils.ts | 30 +++-- 3 files changed, 101 insertions(+), 65 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 5925eba7..94ccf849 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:latest 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.16.0 poetry run mint - name: Check running containers run: docker ps diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index e70bd072..aad990bf 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -145,8 +145,31 @@ class CashuWallet { await this.getMintInfo(); await this.getKeySets(); await this.getAllKeys(); + this.keysetId = this.getActiveKeyset(this._keysets).id; } + /** + * Choose a keyset to activate based on the lowest input fee + * + * Note: this function will filter out deprecated base64 keysets + * + * @param keysets keysets to choose from + * @returns active keyset + */ + getActiveKeyset(keysets: Array): MintKeyset { + let activeKeysets = keysets.filter((k) => k.active) + // begin deprecated: if there are keyset IDs that are not hex strings, we need to filter them out + const hexKeysets = activeKeysets.filter((k) => /^[0-9a-fA-F]+$/.test(k.id)); + if (hexKeysets.length > 0) { + activeKeysets = hexKeysets; + } + // end deprecated + const activeKeyset = activeKeysets.sort((a, b) => (a.input_fee_ppk ?? 0) - (b.input_fee_ppk ?? 0))[0]; + if (!activeKeyset) { + throw new Error('No active keyset found'); + } + return activeKeyset; + } /** * Get keysets from the mint with the unit of the wallet * @returns keysets @@ -192,27 +215,9 @@ class CashuWallet { // no keysetId was set, so we select an active keyset with the unit of the wallet with the lowest fees and use that const allKeysets = await this.mint.getKeySets(); - const keysetToActivate = allKeysets.keysets - .filter((k) => k.unit === this._unit && k.active) - .sort((a, b) => (a.input_fee_ppk ?? 0) - (b.input_fee_ppk ?? 0))[0]; - if (!keysetToActivate) { - throw new Error( - `could not initialize keys. No active keyset with unit '${this._unit}' found` - ); - } - - if (!this._keys.get(keysetToActivate.id)) { - const keysetGet = await this.mint.getKeys(keysetToActivate.id); - const keys = keysetGet.keysets.find((k) => k.id === keysetToActivate.id); - if (!keys) { - throw new Error( - `could not initialize keys. No keyset with id '${keysetToActivate.id}' found` - ); - } - this._keys.set(keys.id, keys); - } - this.keysetId = keysetToActivate.id; - return this._keys.get(keysetToActivate.id) as MintKeys; + const keysetToActivate = this.getActiveKeyset(allKeysets.keysets) + const keyset = await this.getKeys(keysetToActivate.id); + return keyset } /** @@ -311,6 +316,7 @@ class CashuWallet { options?: { preference?: Array; outputAmounts?: OutputAmounts; + proofsWeHave?: Array; counter?: number; pubkey?: string; privkey?: string; @@ -343,12 +349,12 @@ class CashuWallet { console.log( `keepProofsSelect: ${sumProofs(keepProofsSelect)} | sendProofs: ${sumProofs(sendProofs)}` ); - // if (options && !options?.outputAmounts?.keepAmounts) { - // options.outputAmounts = { - // keepAmounts: getKeepAmounts(keepProofsSelect, sumProofs(keepProofsSelect), this._keys.get(options?.keysetId || this.keysetId) as MintKeys, 3), - // sendAmounts: options?.outputAmounts?.sendAmounts || [] - // } - // } + if (options && !options?.outputAmounts?.keepAmounts && options?.proofsWeHave) { + options.outputAmounts = { + keepAmounts: getKeepAmounts(options.proofsWeHave, sumProofs(keepProofsSelect), this._keys.get(options?.keysetId || this.keysetId) as MintKeys, 3), + sendAmounts: options?.outputAmounts?.sendAmounts || [] + } + } const { returnChange, send } = await this.swap(amount, sendProofs, options); console.log(`returnChange: ${sumProofs(returnChange)} | send: ${sumProofs(send)}`); const returnChangeProofs = keepProofsSelect.concat(returnChange); @@ -374,17 +380,17 @@ class CashuWallet { .filter((p) => p.amount > amountToSend) .sort((a, b) => a.amount - b.amount); const nextBigger = biggerProofs[0]; - console.log( - `> enter | amountToSend: ${amountToSend} | proofs: ${sumProofs( - proofs - )} | smallerProofs: ${sumProofs(smallerProofs)} | nextBigger: ${nextBigger?.amount}` - ); + // console.log( + // `> enter | amountToSend: ${amountToSend} | proofs: ${sumProofs( + // proofs + // )} | smallerProofs: ${sumProofs(smallerProofs)} | nextBigger: ${nextBigger?.amount}` + // ); if (!smallerProofs.length && nextBigger) { - console.log( - `< [0] exit: no smallerProofs, nextBigger: ${nextBigger.amount} | keep: ${sumProofs( - proofs.filter((p) => p.secret !== nextBigger.secret) - )} | proofs: ${sumProofs(proofs)}` - ); + // console.log( + // `< [0] exit: no smallerProofs, nextBigger: ${nextBigger.amount} | keep: ${sumProofs( + // proofs.filter((p) => p.secret !== nextBigger.secret) + // )} | proofs: ${sumProofs(proofs)}` + // ); return { returnChange: proofs.filter((p) => p.secret !== nextBigger.secret), send: [nextBigger] @@ -392,17 +398,17 @@ class CashuWallet { } if (!smallerProofs.length && !nextBigger) { - console.log( - `< [1] exit: no smallerProofs, no nextBigger | amountToSend: ${amountToSend} | keep: ${sumProofs( - proofs - )}` - ); + // console.log( + // `< [1] exit: no smallerProofs, no nextBigger | amountToSend: ${amountToSend} | keep: ${sumProofs( + // proofs + // )}` + // ); return { returnChange: proofs, send: [] }; } let remainder = amountToSend; let selectedProofs = [smallerProofs[0]]; - console.log(`Selected proof: ${smallerProofs[0].amount}`); + // console.log(`Selected proof: ${smallerProofs[0].amount}`); const returnedProofs = []; const feePPK = includeFees ? this.getFeesForProofs(selectedProofs) : 0; remainder -= smallerProofs[0].amount - feePPK / 1000; @@ -422,18 +428,18 @@ class CashuWallet { if (sumProofs(selectedProofs) < amountToSend && nextBigger) { selectedProofs = [nextBigger]; - console.log( - `< exit [2.2] selecting nextBigger | amountToSend: ${amountToSend} | selectedProofs: ${sumProofs( - selectedProofs - )} | returnedProofs: ${sumProofs(returnedProofs)}` - ); - } - - console.log( - `< exit [2] selectProofsToSend | amountToSend: ${amountToSend} | selectedProofs: ${sumProofs( - selectedProofs - )} | returnedProofs: ${sumProofs(proofs.filter((p) => !selectedProofs.includes(p)))}` - ); + // console.log( + // `< exit [2.2] selecting nextBigger | amountToSend: ${amountToSend} | selectedProofs: ${sumProofs( + // selectedProofs + // )} | returnedProofs: ${sumProofs(returnedProofs)}` + // ); + } + + // console.log( + // `< exit [2] selectProofsToSend | amountToSend: ${amountToSend} | selectedProofs: ${sumProofs( + // selectedProofs + // )} | returnedProofs: ${sumProofs(proofs.filter((p) => !selectedProofs.includes(p)))}` + // ); return { returnChange: proofs.filter((p) => !selectedProofs.includes(p)), send: selectedProofs @@ -604,18 +610,34 @@ class CashuWallet { keysetId?: string; preference?: Array; outputAmounts?: OutputAmounts; + proofsWeHave?: Array; counter?: number; pubkey?: string; } ): Promise<{ proofs: Array }> { + const keyset = await this.getKeys(options?.keysetId); + if (options?.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); + + if (!options?.outputAmounts && options?.proofsWeHave) { + options.outputAmounts = { + keepAmounts: getKeepAmounts( + options.proofsWeHave, + amount, + keyset.keys, + 3 + ), + sendAmounts: [] + }; + } console.log( `outputAmounts: ${options?.outputAmounts?.keepAmounts } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a, b) => a + b, 0)}) | ${options?.outputAmounts?.sendAmounts } (sum: ${options?.outputAmounts?.sendAmounts.reduce((a, b) => a + b, 0)})` ); - const keyset = await this.getKeys(options?.keysetId); + console.log(JSON.stringify(options?.outputAmounts)); + const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, keyset, diff --git a/src/utils.ts b/src/utils.ts index 5e9e662e..ca518c4f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -45,20 +45,34 @@ function splitAmount( function getKeepAmounts( proofsWeHave: Array, amountToKeep: number, - keyset: Keys, + keys: Keys, targetCount: number ): Array { // determines amounts we need to reach the targetCount for each amount based on the amounts of the proofs we have // it tries to select amounts so that the proofs we have and the proofs we want reach the targetCount - const amountWeWant: Array = []; + const amountsWeWant: Array = []; const amountsWeHave = proofsWeHave.map((p) => p.amount); - const sortedKeyAmounts = getKeysetAmounts(keyset); + const sortedKeyAmounts = getKeysetAmounts(keys); sortedKeyAmounts.forEach((amt) => { - const count = amountsWeHave.filter((a) => a === amt).length; - const q = Math.floor(targetCount - count); - for (let i = 0; i < q; ++i) amountWeWant.push(amt); + const countWeHave = amountsWeHave.filter((a) => a === amt).length; + const countWeWant = Math.floor(targetCount - countWeHave); + for (let i = 0; i < countWeWant; ++i) { + if (amountsWeWant.reduce((a, b) => a + b, 0) + amt > amountToKeep) { + break; + } + amountsWeWant.push(amt) + } }); - return amountWeWant; + // use splitAmount to fill the rest between the sum of amountsWeHave and amountToKeep + const amountDiff = amountToKeep - amountsWeWant.reduce((a, b) => a + b, 0) + if (amountDiff) { + const remainingAmounts = splitAmount(amountDiff, keys) + remainingAmounts.forEach((amt) => { amountsWeWant.push(amt) }) + } + const sortedAmountsWeWant = amountsWeWant.sort((a, b) => a - b) + console.log(`amountsWeHave: ${amountsWeHave}`) + console.log(`amountsWeWant: ${sortedAmountsWeWant}`); + return sortedAmountsWeWant; } function isPowerOfTwo(number: number) { @@ -67,7 +81,7 @@ function isPowerOfTwo(number: number) { function getKeysetAmounts(keyset: Keys): Array { return Object.keys(keyset) .map((k) => parseInt(k)) - .sort((a, b) => b - a); + .sort((a, b) => a - b); } function hasCorrespondingKey(amount: number, keyset: Keys) { From d32e69790a76b6cae1fe6517909da538f973c508 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 27 Aug 2024 19:11:23 +0200 Subject: [PATCH 33/70] tmp --- src/CashuWallet.ts | 6 ++++-- src/utils.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index aad990bf..163f404e 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -350,11 +350,13 @@ class CashuWallet { `keepProofsSelect: ${sumProofs(keepProofsSelect)} | sendProofs: ${sumProofs(sendProofs)}` ); if (options && !options?.outputAmounts?.keepAmounts && options?.proofsWeHave) { + const keyset = await this.getKeys(options?.keysetId); options.outputAmounts = { - keepAmounts: getKeepAmounts(options.proofsWeHave, sumProofs(keepProofsSelect), this._keys.get(options?.keysetId || this.keysetId) as MintKeys, 3), + keepAmounts: getKeepAmounts(options.proofsWeHave, amount, keyset.keys, 3), sendAmounts: options?.outputAmounts?.sendAmounts || [] } } + console.log(`keepAmounts: ${options?.outputAmounts?.keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}`) const { returnChange, send } = await this.swap(amount, sendProofs, options); console.log(`returnChange: ${sumProofs(returnChange)} | send: ${sumProofs(send)}`); const returnChangeProofs = keepProofsSelect.concat(returnChange); @@ -641,7 +643,7 @@ class CashuWallet { const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, keyset, - options?.outputAmounts?.sendAmounts, + // options?.outputAmounts?.keepAmounts, options?.counter, options?.pubkey ); diff --git a/src/utils.ts b/src/utils.ts index ca518c4f..0a823437 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -69,7 +69,7 @@ function getKeepAmounts( const remainingAmounts = splitAmount(amountDiff, keys) remainingAmounts.forEach((amt) => { amountsWeWant.push(amt) }) } - const sortedAmountsWeWant = amountsWeWant.sort((a, b) => a - b) + const sortedAmountsWeWant = amountsWeWant.sort((a, b) => a - b); console.log(`amountsWeHave: ${amountsWeHave}`) console.log(`amountsWeWant: ${sortedAmountsWeWant}`); return sortedAmountsWeWant; From 013590caa397166bc52ba0114989f8370749ca8d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 1 Oct 2024 19:38:16 +0200 Subject: [PATCH 34/70] working agian --- src/CashuWallet.ts | 13 ++++++++----- src/utils.ts | 15 +++++++++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 163f404e..8b5442d3 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -340,20 +340,23 @@ class CashuWallet { options?.privkey || options?.keysetId // these options require a swap ) { - console.log('>> yes swap'); + console.log(`>> yes swap | sendProofOffline: ${sumProofs(sendProofOffline)} | amount: ${amount}`); const { returnChange: keepProofsSelect, send: sendProofs } = this.selectProofsToSend( proofs, amount, true ); + const returnAmount = sumProofs(sendProofs) - amount; console.log( - `keepProofsSelect: ${sumProofs(keepProofsSelect)} | sendProofs: ${sumProofs(sendProofs)}` + `keepProofsSelect: ${sumProofs(keepProofsSelect)} | sendProofs: ${sumProofs(sendProofs)} | sendProofs amounts: ${sendProofs.map( + (p) => p.amount + )}` ); if (options && !options?.outputAmounts?.keepAmounts && options?.proofsWeHave) { const keyset = await this.getKeys(options?.keysetId); options.outputAmounts = { - keepAmounts: getKeepAmounts(options.proofsWeHave, amount, keyset.keys, 3), - sendAmounts: options?.outputAmounts?.sendAmounts || [] + keepAmounts: getKeepAmounts(keepProofsSelect, returnAmount, keyset.keys, 3), + sendAmounts: options?.outputAmounts?.sendAmounts || splitAmount(amount, keyset.keys) } } console.log(`keepAmounts: ${options?.outputAmounts?.keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}`) @@ -643,7 +646,7 @@ class CashuWallet { const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, keyset, - // options?.outputAmounts?.keepAmounts, + options?.outputAmounts?.keepAmounts, options?.counter, options?.pubkey ); diff --git a/src/utils.ts b/src/utils.ts index 0a823437..2ed8c623 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -52,7 +52,7 @@ function getKeepAmounts( // it tries to select amounts so that the proofs we have and the proofs we want reach the targetCount const amountsWeWant: Array = []; const amountsWeHave = proofsWeHave.map((p) => p.amount); - const sortedKeyAmounts = getKeysetAmounts(keys); + const sortedKeyAmounts = getKeysetAmounts(keys, 'asc'); sortedKeyAmounts.forEach((amt) => { const countWeHave = amountsWeHave.filter((a) => a === amt).length; const countWeWant = Math.floor(targetCount - countWeHave); @@ -70,18 +70,25 @@ function getKeepAmounts( remainingAmounts.forEach((amt) => { amountsWeWant.push(amt) }) } const sortedAmountsWeWant = amountsWeWant.sort((a, b) => a - b); - console.log(`amountsWeHave: ${amountsWeHave}`) - console.log(`amountsWeWant: ${sortedAmountsWeWant}`); + console.log(`# getKeepAmounts: amountToKeep: ${amountToKeep}`) + console.log(`# getKeepAmounts: amountsWeHave: ${amountsWeHave}`) + console.log(`# getKeepAmounts: amountsWeWant: ${sortedAmountsWeWant}`); return sortedAmountsWeWant; } function isPowerOfTwo(number: number) { return number && !(number & (number - 1)); } -function getKeysetAmounts(keyset: Keys): Array { +function getKeysetAmounts(keyset: Keys, order = 'desc'): Array { + if (order == 'desc') { + return Object.keys(keyset) + .map((k) => parseInt(k)) + .sort((a, b) => b - a); + } return Object.keys(keyset) .map((k) => parseInt(k)) .sort((a, b) => a - b); + } function hasCorrespondingKey(amount: number, keyset: Keys) { From 2a5d834e353adcca8cc8ea8b43f7f3792b910e7c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 1 Oct 2024 20:27:16 +0200 Subject: [PATCH 35/70] npm run format --- src/CashuMint.ts | 5 ++++- src/CashuWallet.ts | 45 +++++++++++++++++++++------------------- src/utils.ts | 16 +++++++------- test/integration.test.ts | 4 ---- 4 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 6a65e239..8df76b83 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -51,7 +51,10 @@ class CashuMint { * @param mintUrl * @param customRequest */ - public static async getInfo(mintUrl: string, customRequest?: typeof request): Promise { + public static async getInfo( + mintUrl: string, + customRequest?: typeof request + ): Promise { const requestInstance = customRequest || request; const response = await requestInstance({ endpoint: joinUrls(mintUrl, '/v1/info') diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 2f7712d5..edd9291b 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -151,21 +151,23 @@ class CashuWallet { /** * Choose a keyset to activate based on the lowest input fee - * + * * Note: this function will filter out deprecated base64 keysets - * + * * @param keysets keysets to choose from * @returns active keyset */ getActiveKeyset(keysets: Array): MintKeyset { - let activeKeysets = keysets.filter((k) => k.active) + let activeKeysets = keysets.filter((k) => k.active); // begin deprecated: if there are keyset IDs that are not hex strings, we need to filter them out const hexKeysets = activeKeysets.filter((k) => /^[0-9a-fA-F]+$/.test(k.id)); if (hexKeysets.length > 0) { activeKeysets = hexKeysets; } // end deprecated - const activeKeyset = activeKeysets.sort((a, b) => (a.input_fee_ppk ?? 0) - (b.input_fee_ppk ?? 0))[0]; + const activeKeyset = activeKeysets.sort( + (a, b) => (a.input_fee_ppk ?? 0) - (b.input_fee_ppk ?? 0) + )[0]; if (!activeKeyset) { throw new Error('No active keyset found'); } @@ -216,9 +218,9 @@ class CashuWallet { // no keysetId was set, so we select an active keyset with the unit of the wallet with the lowest fees and use that const allKeysets = await this.mint.getKeySets(); - const keysetToActivate = this.getActiveKeyset(allKeysets.keysets) + const keysetToActivate = this.getActiveKeyset(allKeysets.keysets); const keyset = await this.getKeys(keysetToActivate.id); - return keyset + return keyset; } /** @@ -333,7 +335,9 @@ class CashuWallet { options?.privkey || options?.keysetId // these options require a swap ) { - console.log(`>> yes swap | sendProofOffline: ${sumProofs(sendProofOffline)} | amount: ${amount}`); + console.log( + `>> yes swap | sendProofOffline: ${sumProofs(sendProofOffline)} | amount: ${amount}` + ); const { returnChange: keepProofsSelect, send: sendProofs } = this.selectProofsToSend( proofs, amount, @@ -341,18 +345,20 @@ class CashuWallet { ); const returnAmount = sumProofs(sendProofs) - amount; console.log( - `keepProofsSelect: ${sumProofs(keepProofsSelect)} | sendProofs: ${sumProofs(sendProofs)} | sendProofs amounts: ${sendProofs.map( - (p) => p.amount - )}` + `keepProofsSelect: ${sumProofs(keepProofsSelect)} | sendProofs: ${sumProofs( + sendProofs + )} | sendProofs amounts: ${sendProofs.map((p) => p.amount)}` ); if (options && !options?.outputAmounts?.keepAmounts && options?.proofsWeHave) { const keyset = await this.getKeys(options?.keysetId); options.outputAmounts = { keepAmounts: getKeepAmounts(keepProofsSelect, returnAmount, keyset.keys, 3), sendAmounts: options?.outputAmounts?.sendAmounts || splitAmount(amount, keyset.keys) - } + }; } - console.log(`keepAmounts: ${options?.outputAmounts?.keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}`) + console.log( + `keepAmounts: ${options?.outputAmounts?.keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}` + ); const { returnChange, send } = await this.swap(amount, sendProofs, options); console.log(`returnChange: ${sumProofs(returnChange)} | send: ${sumProofs(send)}`); const returnChangeProofs = keepProofsSelect.concat(returnChange); @@ -453,7 +459,7 @@ class CashuWallet { 0 ) + 999) / - 1000, + 1000, 0 ) ); @@ -624,18 +630,15 @@ class CashuWallet { if (!options?.outputAmounts && options?.proofsWeHave) { options.outputAmounts = { - keepAmounts: getKeepAmounts( - options.proofsWeHave, - amount, - keyset.keys, - 3 - ), + keepAmounts: getKeepAmounts(options.proofsWeHave, amount, keyset.keys, 3), sendAmounts: [] }; } console.log( - `outputAmounts: ${options?.outputAmounts?.keepAmounts - } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a, b) => a + b, 0)}) | ${options?.outputAmounts?.sendAmounts + `outputAmounts: ${ + options?.outputAmounts?.keepAmounts + } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a, b) => a + b, 0)}) | ${ + options?.outputAmounts?.sendAmounts } (sum: ${options?.outputAmounts?.sendAmounts.reduce((a, b) => a + b, 0)})` ); console.log(JSON.stringify(options?.outputAmounts)); diff --git a/src/utils.ts b/src/utils.ts index a7805f8a..29178500 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -67,23 +67,24 @@ function getKeepAmounts( if (amountsWeWant.reduce((a, b) => a + b, 0) + amt > amountToKeep) { break; } - amountsWeWant.push(amt) + amountsWeWant.push(amt); } }); // use splitAmount to fill the rest between the sum of amountsWeHave and amountToKeep - const amountDiff = amountToKeep - amountsWeWant.reduce((a, b) => a + b, 0) + const amountDiff = amountToKeep - amountsWeWant.reduce((a, b) => a + b, 0); if (amountDiff) { - const remainingAmounts = splitAmount(amountDiff, keys) - remainingAmounts.forEach((amt) => { amountsWeWant.push(amt) }) + const remainingAmounts = splitAmount(amountDiff, keys); + remainingAmounts.forEach((amt) => { + amountsWeWant.push(amt); + }); } const sortedAmountsWeWant = amountsWeWant.sort((a, b) => a - b); - console.log(`# getKeepAmounts: amountToKeep: ${amountToKeep}`) - console.log(`# getKeepAmounts: amountsWeHave: ${amountsWeHave}`) + console.log(`# getKeepAmounts: amountToKeep: ${amountToKeep}`); + console.log(`# getKeepAmounts: amountsWeHave: ${amountsWeHave}`); console.log(`# getKeepAmounts: amountsWeWant: ${sortedAmountsWeWant}`); return sortedAmountsWeWant; } - // function isPowerOfTwo(number: number) { // return number && !(number & (number - 1)); // } @@ -96,7 +97,6 @@ function getKeysetAmounts(keyset: Keys, order = 'desc'): Array { return Object.keys(keyset) .map((k: string) => parseInt(k)) .sort((a: number, b: number) => a - b); - } function hasCorrespondingKey(amount: number, keyset: Keys) { diff --git a/test/integration.test.ts b/test/integration.test.ts index 1dd9f161..0c060f17 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -213,11 +213,7 @@ describe('mint api', () => { const result = await wallet .receive(encoded, { privkey: bytesToHex(privKeyAlice) }) .catch((e) => e); -<<<<<<< HEAD - expect(result).toEqual(new Error('Error receiving token: Error: Error receiving token entry')); -======= expect(result).toEqual(new Error('no valid signature provided for input.')); ->>>>>>> development const proofs = await wallet.receive(encoded, { privkey: bytesToHex(privKeyBob) }); From 7e72950e01da1718df6185b5ce474c69274195ba Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 1 Oct 2024 20:30:32 +0200 Subject: [PATCH 36/70] remove empty file --- src/legacy/cashu-ts.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/legacy/cashu-ts.ts diff --git a/src/legacy/cashu-ts.ts b/src/legacy/cashu-ts.ts deleted file mode 100644 index e69de29b..00000000 From cbca9b0b3a63f2acf5298f74caeb07670b18fccf Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 1 Oct 2024 22:47:59 +0200 Subject: [PATCH 37/70] send works with fees --- src/CashuWallet.ts | 159 +++++++++++++++++++++++---------------------- src/utils.ts | 8 +-- 2 files changed, 84 insertions(+), 83 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index edd9291b..c32b9642 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -90,7 +90,7 @@ class CashuWallet { } else if (options?.keys && Array.isArray(options?.keys)) { keys = options?.keys; } - if (keys) keys.forEach((key) => this._keys.set(key.id, key)); + if (keys) keys.forEach((key: MintKeys) => this._keys.set(key.id, key)); if (options?.unit) this._unit = options?.unit; if (options?.keysets) this._keysets = options.keysets; if (!options?.mnemonicOrSeed) { @@ -158,15 +158,15 @@ class CashuWallet { * @returns active keyset */ getActiveKeyset(keysets: Array): MintKeyset { - let activeKeysets = keysets.filter((k) => k.active); + let activeKeysets = keysets.filter((k: MintKeyset) => k.active); // begin deprecated: if there are keyset IDs that are not hex strings, we need to filter them out - const hexKeysets = activeKeysets.filter((k) => /^[0-9a-fA-F]+$/.test(k.id)); + const hexKeysets = activeKeysets.filter((k: MintKeyset) => /^[0-9a-fA-F]+$/.test(k.id)); if (hexKeysets.length > 0) { activeKeysets = hexKeysets; } // end deprecated const activeKeyset = activeKeysets.sort( - (a, b) => (a.input_fee_ppk ?? 0) - (b.input_fee_ppk ?? 0) + (a: MintKeyset, b: MintKeyset) => (a.input_fee_ppk ?? 0) - (b.input_fee_ppk ?? 0) )[0]; if (!activeKeyset) { throw new Error('No active keyset found'); @@ -179,14 +179,14 @@ class CashuWallet { */ async getKeySets(): Promise> { const allKeysets = await this.mint.getKeySets(); - const unitKeysets = allKeysets.keysets.filter((k) => k.unit === this._unit); + const unitKeysets = allKeysets.keysets.filter((k: MintKeyset) => k.unit === this._unit); this._keysets = unitKeysets; return this._keysets; } async getAllKeys(): Promise> { const keysets = await this.mint.getKeys(); - this._keys = new Map(keysets.keysets.map((k) => [k.id, k])); + this._keys = new Map(keysets.keysets.map((k: MintKeys) => [k.id, k])); return keysets.keysets; } @@ -245,6 +245,7 @@ class CashuWallet { } ): Promise> { if (options?.preference) + // preference is only kept for backwards compatibility options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); if (typeof token === 'string') { token = getDecodedToken(token); @@ -263,7 +264,8 @@ 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. + * @param preference? Deprecated. Use `outputAmounts` instead. Optional preference for splitting proofs into specific amounts. + * @param outputAmounts? optionally specify the output's amounts to keep. * @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 @@ -281,9 +283,10 @@ class CashuWallet { } ): Promise> { if (options?.preference) + // preference is only kept for backwards compatibility options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); const proofs: Array = []; - const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); + const amount = tokenEntry.proofs.reduce((total: number, curr: Proof) => total + curr.amount, 0); const keys = await this.getKeys(options?.keysetId); const { payload, blindedMessages } = this.createSwapPayload( amount, @@ -319,8 +322,6 @@ class CashuWallet { offline?: boolean; } ): Promise { - if (options?.preference) - options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); if (sumProofs(proofs) < amount) { throw new Error('Not enough funds available to send'); } @@ -338,27 +339,33 @@ class CashuWallet { console.log( `>> yes swap | sendProofOffline: ${sumProofs(sendProofOffline)} | amount: ${amount}` ); + // input selection const { returnChange: keepProofsSelect, send: sendProofs } = this.selectProofsToSend( proofs, amount, true ); - const returnAmount = sumProofs(sendProofs) - amount; - console.log( - `keepProofsSelect: ${sumProofs(keepProofsSelect)} | sendProofs: ${sumProofs( - sendProofs - )} | sendProofs amounts: ${sendProofs.map((p) => p.amount)}` - ); - if (options && !options?.outputAmounts?.keepAmounts && options?.proofsWeHave) { - const keyset = await this.getKeys(options?.keysetId); - options.outputAmounts = { - keepAmounts: getKeepAmounts(keepProofsSelect, returnAmount, keyset.keys, 3), - sendAmounts: options?.outputAmounts?.sendAmounts || splitAmount(amount, keyset.keys) - }; - } - console.log( - `keepAmounts: ${options?.outputAmounts?.keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}` - ); + // add keepProofsSelect to options?.proofsWeHave: + options?.proofsWeHave?.push(...keepProofsSelect); + + + + // const returnAmount = sumProofs(sendProofs) - amount; + // console.log( + // `keepProofsSelect: ${sumProofs(keepProofsSelect)} | sendProofs: ${sumProofs( + // sendProofs + // )} | sendProofs amounts: ${sendProofs.map((p: Proof) => p.amount)}` + // ); + // if (options && !options?.outputAmounts?.keepAmounts && options?.proofsWeHave) { + // const keyset = await this.getKeys(options?.keysetId); + // options.outputAmounts = { + // keepAmounts: getKeepAmounts(keepProofsSelect, returnAmount, keyset.keys, 3), + // sendAmounts: options?.outputAmounts?.sendAmounts || splitAmount(amount, keyset.keys) + // }; + // } + // console.log( + // `keepAmounts: ${options?.outputAmounts?.keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}` + // ); const { returnChange, send } = await this.swap(amount, sendProofs, options); console.log(`returnChange: ${sumProofs(returnChange)} | send: ${sumProofs(send)}`); const returnChangeProofs = keepProofsSelect.concat(returnChange); @@ -376,43 +383,27 @@ class CashuWallet { includeFees = false ): SendResponse { // heavy logging in this function - const sortedProofs = proofs.sort((a, b) => a.amount - b.amount); + const sortedProofs = proofs.sort((a: Proof, b: Proof) => a.amount - b.amount); const smallerProofs = sortedProofs - .filter((p) => p.amount <= amountToSend) - .sort((a, b) => b.amount - a.amount); + .filter((p: Proof) => p.amount <= amountToSend) + .sort((a: Proof, b: Proof) => b.amount - a.amount); const biggerProofs = sortedProofs - .filter((p) => p.amount > amountToSend) - .sort((a, b) => a.amount - b.amount); + .filter((p: Proof) => p.amount > amountToSend) + .sort((a: Proof, b: Proof) => a.amount - b.amount); const nextBigger = biggerProofs[0]; - // console.log( - // `> enter | amountToSend: ${amountToSend} | proofs: ${sumProofs( - // proofs - // )} | smallerProofs: ${sumProofs(smallerProofs)} | nextBigger: ${nextBigger?.amount}` - // ); if (!smallerProofs.length && nextBigger) { - // console.log( - // `< [0] exit: no smallerProofs, nextBigger: ${nextBigger.amount} | keep: ${sumProofs( - // proofs.filter((p) => p.secret !== nextBigger.secret) - // )} | proofs: ${sumProofs(proofs)}` - // ); return { - returnChange: proofs.filter((p) => p.secret !== nextBigger.secret), + returnChange: proofs.filter((p: Proof) => p.secret !== nextBigger.secret), send: [nextBigger] }; } if (!smallerProofs.length && !nextBigger) { - // console.log( - // `< [1] exit: no smallerProofs, no nextBigger | amountToSend: ${amountToSend} | keep: ${sumProofs( - // proofs - // )}` - // ); return { returnChange: proofs, send: [] }; } let remainder = amountToSend; let selectedProofs = [smallerProofs[0]]; - // console.log(`Selected proof: ${smallerProofs[0].amount}`); const returnedProofs = []; const feePPK = includeFees ? this.getFeesForProofs(selectedProofs) : 0; remainder -= smallerProofs[0].amount - feePPK / 1000; @@ -422,30 +413,15 @@ class CashuWallet { remainder, includeFees ); - // if (!send.length) { - // send = [nextBigger]; - // console.log(`< exit [2.1] no send | send: ${sumProofs(send)} | returnChange: ${sumProofs(returnChange)}`); - // } selectedProofs.push(...send); returnedProofs.push(...returnChange); } if (sumProofs(selectedProofs) < amountToSend && nextBigger) { selectedProofs = [nextBigger]; - // console.log( - // `< exit [2.2] selecting nextBigger | amountToSend: ${amountToSend} | selectedProofs: ${sumProofs( - // selectedProofs - // )} | returnedProofs: ${sumProofs(returnedProofs)}` - // ); } - - // console.log( - // `< exit [2] selectProofsToSend | amountToSend: ${amountToSend} | selectedProofs: ${sumProofs( - // selectedProofs - // )} | returnedProofs: ${sumProofs(proofs.filter((p) => !selectedProofs.includes(p)))}` - // ); return { - returnChange: proofs.filter((p) => !selectedProofs.includes(p)), + returnChange: proofs.filter((p: Proof) => !selectedProofs.includes(p)), send: selectedProofs }; } @@ -454,12 +430,12 @@ class CashuWallet { const fees = Math.floor( Math.max( (proofs.reduce( - (total, curr) => - total + (this._keysets.find((k) => k.id === curr.id)?.input_fee_ppk || 0), + (total: number, curr: Proof) => + total + (this._keysets.find((k: MintKeyset) => k.id === curr.id)?.input_fee_ppk || 0), 0 ) + 999) / - 1000, + 1000, 0 ) ); @@ -485,17 +461,24 @@ class CashuWallet { options?: { preference?: Array; outputAmounts?: OutputAmounts; + proofsWeHave?: Array; counter?: number; pubkey?: string; privkey?: string; keysetId?: string; } ): Promise { - if (options?.preference) + if (!options) { + options = {}; + } + if (options.preference) options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); - const keyset = await this.getKeys(options?.keysetId); + const keyset = await this.getKeys(options.keysetId); const proofsToSend = proofs; + const amountToSend = amount const amountAvailable = sumProofs(proofs); + const amountToKeep = amountAvailable - amountToSend - this.getFeesForProofs(proofsToSend); + if (amount + this.getFeesForProofs(proofsToSend) > amountAvailable) { console.log( `amount: ${amount} | fees: ${this.getFeesForProofs( @@ -504,8 +487,29 @@ class CashuWallet { ); throw new Error('Not enough funds available'); } - const amountToSend = amount + this.getFeesForProofs(proofsToSend); - const amountToKeep = sumProofs(proofsToSend) - amountToSend; + // output selection + if (options.proofsWeHave) { + console.log( + `proofsWeHave: ${sumProofs(options.proofsWeHave)} | sendProofs: ${sumProofs( + proofsToSend + )} | sendProofs amounts: ${proofsToSend.map((p: Proof) => p.amount)}` + ); + } + let keepAmounts; + if (options && !options.outputAmounts?.keepAmounts && options.proofsWeHave) { + keepAmounts = getKeepAmounts(options.proofsWeHave, amountToKeep, keyset.keys, 3) + } else if (options.outputAmounts) { + keepAmounts = options.outputAmounts.keepAmounts; + } + const sendAmounts = options?.outputAmounts?.sendAmounts || splitAmount(amount, keyset.keys); + options.outputAmounts = { + keepAmounts: keepAmounts, + sendAmounts: sendAmounts + }; + console.log( + `keepAmounts: ${keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}` + ); + console.log(`>> amountToSend: ${amountToSend}`); const { payload, blindedMessages } = this.createSwapPayload( amountToSend, proofsToSend, @@ -522,11 +526,10 @@ class CashuWallet { blindedMessages.secrets, keyset ); - const splitProofsToKeep: Array = []; const splitProofsToSend: Array = []; let amountToKeepCounter = 0; - swapProofs.forEach((proof) => { + swapProofs.forEach((proof: Proof) => { if (amountToKeepCounter < amountToKeep) { amountToKeepCounter += proof.amount; splitProofsToKeep.push(proof); @@ -635,11 +638,9 @@ class CashuWallet { }; } console.log( - `outputAmounts: ${ - options?.outputAmounts?.keepAmounts - } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a, b) => a + b, 0)}) | ${ - options?.outputAmounts?.sendAmounts - } (sum: ${options?.outputAmounts?.sendAmounts.reduce((a, b) => a + b, 0)})` + `outputAmounts: ${options?.outputAmounts?.keepAmounts + } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a: number, b: number) => a + b, 0)}) | ${options?.outputAmounts?.sendAmounts + } (sum: ${options?.outputAmounts?.sendAmounts.reduce((a: number, b: number) => a + b, 0)})` ); console.log(JSON.stringify(options?.outputAmounts)); @@ -818,7 +819,7 @@ class CashuWallet { payload: SwapPayload; blindedMessages: BlindedTransaction; } { - const totalAmount = proofsToSend.reduce((total, curr) => total + curr.amount, 0); + const totalAmount = proofsToSend.reduce((total: number, curr: Proof) => total + curr.amount, 0); if (outputAmounts && outputAmounts.sendAmounts && !outputAmounts.keepAmounts?.length) { outputAmounts.keepAmounts = splitAmount(totalAmount - amount, keyset.keys); } diff --git a/src/utils.ts b/src/utils.ts index 29178500..9710a8a4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -28,9 +28,9 @@ function splitAmount( ): Array { const chunks: Array = []; if (split) { - if (split.reduce((a, b) => a + b, 0) > value) { + if (split.reduce((a: number, b: number) => a + b, 0) > value) { throw new Error( - `Split is greater than total amount: ${split.reduce((a, b) => a + b, 0)} > ${value}` + `Split is greater than total amount: ${split.reduce((a: number, b: number) => a + b, 0)} > ${value}` ); } chunks.push(...getPreference(value, keyset, split)); @@ -74,7 +74,7 @@ function getKeepAmounts( const amountDiff = amountToKeep - amountsWeWant.reduce((a, b) => a + b, 0); if (amountDiff) { const remainingAmounts = splitAmount(amountDiff, keys); - remainingAmounts.forEach((amt) => { + remainingAmounts.forEach((amt: number) => { amountsWeWant.push(amt); }); } @@ -288,7 +288,7 @@ export function sanitizeUrl(url: string): string { } export function sumProofs(proofs: Array) { - return proofs.reduce((acc, proof) => acc + proof.amount, 0); + return proofs.reduce((acc: number, proof: Proof) => acc + proof.amount, 0); } export { From 5d3563d848680fa856f0a468eba1e795da86deca Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 1 Oct 2024 22:58:00 +0200 Subject: [PATCH 38/70] receive works with fees --- src/CashuWallet.ts | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index c32b9642..bf38811e 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -239,6 +239,7 @@ class CashuWallet { keysetId?: string; preference?: Array; outputAmounts?: OutputAmounts; + proofsWeHave?: Array; counter?: number; pubkey?: string; privkey?: string; @@ -286,7 +287,7 @@ class CashuWallet { // preference is only kept for backwards compatibility options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); const proofs: Array = []; - const amount = tokenEntry.proofs.reduce((total: number, curr: Proof) => total + curr.amount, 0); + const amount = tokenEntry.proofs.reduce((total: number, curr: Proof) => total + curr.amount, 0) - this.getFeesForProofs(tokenEntry.proofs); const keys = await this.getKeys(options?.keysetId); const { payload, blindedMessages } = this.createSwapPayload( amount, @@ -297,7 +298,7 @@ class CashuWallet { options?.pubkey, options?.privkey ); - const { signatures } = await CashuMint.split(tokenEntry.mint, payload); + const { signatures } = await this.mint.split(payload); const newProofs = this.constructProofs( signatures, blindedMessages.rs, @@ -345,27 +346,7 @@ class CashuWallet { amount, true ); - // add keepProofsSelect to options?.proofsWeHave: options?.proofsWeHave?.push(...keepProofsSelect); - - - - // const returnAmount = sumProofs(sendProofs) - amount; - // console.log( - // `keepProofsSelect: ${sumProofs(keepProofsSelect)} | sendProofs: ${sumProofs( - // sendProofs - // )} | sendProofs amounts: ${sendProofs.map((p: Proof) => p.amount)}` - // ); - // if (options && !options?.outputAmounts?.keepAmounts && options?.proofsWeHave) { - // const keyset = await this.getKeys(options?.keysetId); - // options.outputAmounts = { - // keepAmounts: getKeepAmounts(keepProofsSelect, returnAmount, keyset.keys, 3), - // sendAmounts: options?.outputAmounts?.sendAmounts || splitAmount(amount, keyset.keys) - // }; - // } - // console.log( - // `keepAmounts: ${options?.outputAmounts?.keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}` - // ); const { returnChange, send } = await this.swap(amount, sendProofs, options); console.log(`returnChange: ${sumProofs(returnChange)} | send: ${sumProofs(send)}`); const returnChangeProofs = keepProofsSelect.concat(returnChange); @@ -820,7 +801,7 @@ class CashuWallet { blindedMessages: BlindedTransaction; } { const totalAmount = proofsToSend.reduce((total: number, curr: Proof) => total + curr.amount, 0); - if (outputAmounts && outputAmounts.sendAmounts && !outputAmounts.keepAmounts?.length) { + if (outputAmounts && outputAmounts.sendAmounts && !outputAmounts.keepAmounts) { outputAmounts.keepAmounts = splitAmount(totalAmount - amount, keyset.keys); } const keepBlindedMessages = this.createRandomBlindedMessages( From 1a11182333bcbefc06b1be2dfd6f1de7490d3bb9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:00:04 +0200 Subject: [PATCH 39/70] mintTokens and meltTokens are renamed to mintProofs and meltProofs --- README.md | 4 +- migration-1.0.0.md | 6 +- src/CashuWallet.ts | 88 ++++++++++++++--------------- src/model/types/wallet/responses.ts | 9 +-- src/utils.ts | 6 +- test/integration.test.ts | 20 +++---- test/wallet.test.ts | 12 ++-- 7 files changed, 71 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 1e1e6ca9..df416db3 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ const mintQuote = await wallet.createMintQuote(64); // pay the invoice here before you continue... const mintQuoteChecked = await wallet.checkMintQuote(mintQuote.quote); if (mintQuoteChecked.state == MintQuoteState.PAID) { - const { proofs } = await wallet.mintTokens(64, mintQuote.quote); + const { proofs } = await wallet.mintProofs(64, mintQuote.quote); } ``` @@ -88,7 +88,7 @@ const amountToSend = meltQuote.amount + meltQuote.fee_reserve; const { returnChange: proofsToKeep, send: proofsToSend } = await wallet.send(amountToSend, proofs); // store proofsToKeep in wallet .. -const meltResponse = await wallet.meltTokens(meltQuote, proofsToSend); +const meltResponse = await wallet.meltProofs(meltQuote, proofsToSend); // store meltResponse.change in wallet .. ``` diff --git a/migration-1.0.0.md b/migration-1.0.0.md index ecc6ac8c..c91aa9b5 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -46,9 +46,9 @@ type MintQuoteResponse = { }; ``` -where `request` is the invoice to be paid, and `quote` is the identifier used to pass to `mintTokens()`. +where `request` is the invoice to be paid, and `quote` is the identifier used to pass to `mintProofs()`. -**`requestTokens()` --> `mintTokens()`** +**`requestTokens()` --> `mintProofs()`** --- @@ -67,7 +67,7 @@ type MeltQuoteResponse = { }; ``` -where `quote` is the identifier to pass to `meltTokens()` +where `quote` is the identifier to pass to `meltProofs()` --- diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index bf38811e..8b6b752a 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -8,7 +8,7 @@ import { type MeltQuoteResponse, type MintKeys, type MintKeyset, - type MeltTokensResponse, + type meltProofsResponse, type MintPayload, type Proof, type MintQuotePayload, @@ -337,9 +337,9 @@ class CashuWallet { options?.privkey || options?.keysetId // these options require a swap ) { - console.log( - `>> yes swap | sendProofOffline: ${sumProofs(sendProofOffline)} | amount: ${amount}` - ); + // console.log( + // `>> yes swap | sendProofOffline: ${sumProofs(sendProofOffline)} | amount: ${amount}` + // ); // input selection const { returnChange: keepProofsSelect, send: sendProofs } = this.selectProofsToSend( proofs, @@ -348,12 +348,12 @@ class CashuWallet { ); options?.proofsWeHave?.push(...keepProofsSelect); const { returnChange, send } = await this.swap(amount, sendProofs, options); - console.log(`returnChange: ${sumProofs(returnChange)} | send: ${sumProofs(send)}`); + // console.log(`returnChange: ${sumProofs(returnChange)} | send: ${sumProofs(send)}`); const returnChangeProofs = keepProofsSelect.concat(returnChange); - console.log(`returnChangeProofs: ${sumProofs(returnChangeProofs)}`); + // console.log(`returnChangeProofs: ${sumProofs(returnChangeProofs)}`); return { returnChange: returnChangeProofs, send }; } - console.log('>> no swap'); + // console.log('>> no swap'); // console.log(`keepProofsOffline: ${sumProofs(keepProofsOffline)} | sendProofOffline: ${sumProofs(sendProofOffline)}`); return { returnChange: keepProofsOffline, send: sendProofOffline }; } @@ -461,21 +461,21 @@ class CashuWallet { const amountToKeep = amountAvailable - amountToSend - this.getFeesForProofs(proofsToSend); if (amount + this.getFeesForProofs(proofsToSend) > amountAvailable) { - console.log( - `amount: ${amount} | fees: ${this.getFeesForProofs( - proofsToSend - )} | amountAvailable: ${amountAvailable}` - ); + // console.log( + // `amount: ${amount} | fees: ${this.getFeesForProofs( + // proofsToSend + // )} | amountAvailable: ${amountAvailable}` + // ); throw new Error('Not enough funds available'); } // output selection - if (options.proofsWeHave) { - console.log( - `proofsWeHave: ${sumProofs(options.proofsWeHave)} | sendProofs: ${sumProofs( - proofsToSend - )} | sendProofs amounts: ${proofsToSend.map((p: Proof) => p.amount)}` - ); - } + // if (options.proofsWeHave) { + // console.log( + // `proofsWeHave: ${sumProofs(options.proofsWeHave)} | sendProofs: ${sumProofs( + // proofsToSend + // )} | sendProofs amounts: ${proofsToSend.map((p: Proof) => p.amount)}` + // ); + // } let keepAmounts; if (options && !options.outputAmounts?.keepAmounts && options.proofsWeHave) { keepAmounts = getKeepAmounts(options.proofsWeHave, amountToKeep, keyset.keys, 3) @@ -487,10 +487,10 @@ class CashuWallet { keepAmounts: keepAmounts, sendAmounts: sendAmounts }; - console.log( - `keepAmounts: ${keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}` - ); - console.log(`>> amountToSend: ${amountToSend}`); + // console.log( + // `keepAmounts: ${keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}` + // ); + // console.log(`>> amountToSend: ${amountToSend}`); const { payload, blindedMessages } = this.createSwapPayload( amountToSend, proofsToSend, @@ -585,7 +585,7 @@ class CashuWallet { } /** - * Mint tokens for a given mint quote + * Mint proofs for a given mint quote * @param amount amount to request * @param quote ID of mint quote * @param options.keysetId? optionally set keysetId for blank outputs for returned change. @@ -595,7 +595,7 @@ class CashuWallet { * @param pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! * @returns proofs */ - async mintTokens( + async mintProofs( amount: number, quote: string, options?: { @@ -618,12 +618,12 @@ class CashuWallet { sendAmounts: [] }; } - console.log( - `outputAmounts: ${options?.outputAmounts?.keepAmounts - } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a: number, b: number) => a + b, 0)}) | ${options?.outputAmounts?.sendAmounts - } (sum: ${options?.outputAmounts?.sendAmounts.reduce((a: number, b: number) => a + b, 0)})` - ); - console.log(JSON.stringify(options?.outputAmounts)); + // console.log( + // `outputAmounts: ${options?.outputAmounts?.keepAmounts + // } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a: number, b: number) => a + b, 0)}) | ${options?.outputAmounts?.sendAmounts + // } (sum: ${options?.outputAmounts?.sendAmounts.reduce((a: number, b: number) => a + b, 0)})` + // ); + // console.log(JSON.stringify(options?.outputAmounts)); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, @@ -667,8 +667,8 @@ class CashuWallet { } /** - * Melt tokens for a melt quote. proofsToSend must be at least amount+fee_reserve form the melt quote. - * Returns payment proof and change proofs + * Melt proofs for a melt quote. proofsToSend must be at least amount+fee_reserve form the melt quote. + * Returns melt quote 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. @@ -676,7 +676,7 @@ class CashuWallet { * @param options.privkey? optionally set a private key to unlock P2PK locked secrets * @returns */ - async meltTokens( + async meltProofs( meltQuote: MeltQuoteResponse, proofsToSend: Array, options?: { @@ -684,7 +684,7 @@ class CashuWallet { counter?: number; privkey?: string; } - ): Promise { + ): Promise { const keys = await this.getKeys(options?.keysetId); const { blindedMessages, secrets, rs } = this.createBlankOutputs( meltQuote.fee_reserve, @@ -710,13 +710,13 @@ class CashuWallet { outputs: [...blindedMessages] }; const meltResponse = await this.mint.melt(meltPayload); - + let change: Array = []; + if (meltResponse.change) { + change = this.constructProofs(meltResponse.change, rs, secrets, keys) + } return { - isPaid: meltResponse.state === MeltQuoteState.PAID, - preimage: meltResponse.payment_preimage, - change: meltResponse?.change - ? this.constructProofs(meltResponse.change, rs, secrets, keys) - : [] + quote: meltResponse, + change: change }; } @@ -740,11 +740,11 @@ class CashuWallet { counter?: number; privkey?: string; } - ): Promise { + ): Promise { if (!meltQuote) { meltQuote = await this.mint.createMeltQuote({ unit: this._unit, request: invoice }); } - return await this.meltTokens(meltQuote, proofsToSend, { + return await this.meltProofs(meltQuote, proofsToSend, { keysetId: options?.keysetId, counter: options?.counter, privkey: options?.privkey @@ -767,7 +767,7 @@ class CashuWallet { keysetId?: string; counter?: number; } - ): Promise { + ): Promise { const decodedToken = getDecodedToken(token); const proofs = decodedToken.token .filter((x: TokenEntry) => x.mint === this.mint.mintUrl) diff --git a/src/model/types/wallet/responses.ts b/src/model/types/wallet/responses.ts index 957b90f0..f14918d4 100644 --- a/src/model/types/wallet/responses.ts +++ b/src/model/types/wallet/responses.ts @@ -1,17 +1,14 @@ +import { MeltQuoteResponse } from '../mint'; import { Proof, Token } from './index'; /** * Response after paying a Lightning invoice */ -export type MeltTokensResponse = { +export type meltProofsResponse = { /** * if false, the proofs have not been invalidated and the payment can be tried later again with the same proofs */ - isPaid: boolean; - /** - * preimage of the paid invoice. can be null, depending on which LN-backend the mint uses - */ - preimage: string | null; + quote: MeltQuoteResponse; /** * Return/Change from overpaid fees. This happens due to Lighting fee estimation being inaccurate */ diff --git a/src/utils.ts b/src/utils.ts index 9710a8a4..b650363f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -79,9 +79,9 @@ function getKeepAmounts( }); } const sortedAmountsWeWant = amountsWeWant.sort((a, b) => a - b); - console.log(`# getKeepAmounts: amountToKeep: ${amountToKeep}`); - console.log(`# getKeepAmounts: amountsWeHave: ${amountsWeHave}`); - console.log(`# getKeepAmounts: amountsWeWant: ${sortedAmountsWeWant}`); + // console.log(`# getKeepAmounts: amountToKeep: ${amountToKeep}`); + // console.log(`# getKeepAmounts: amountsWeHave: ${amountsWeHave}`); + // console.log(`# getKeepAmounts: amountsWeWant: ${sortedAmountsWeWant}`); return sortedAmountsWeWant; } diff --git a/test/integration.test.ts b/test/integration.test.ts index 0c060f17..11bab234 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -47,7 +47,7 @@ describe('mint api', () => { const request = await wallet.createMintQuote(1337); expect(request).toBeDefined(); expect(request.request).toContain('lnbc1337'); - const tokens = await wallet.mintTokens(1337, request.quote); + const tokens = await wallet.mintProofs(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); @@ -80,7 +80,7 @@ describe('mint api', () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(100); - const tokens = await wallet.mintTokens(100, request.quote); + const tokens = await wallet.mintProofs(100, request.quote); // expect no fee because local invoice const mintQuote = await wallet.createMintQuote(10); @@ -112,7 +112,7 @@ describe('mint api', () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(3000); - const tokens = await wallet.mintTokens(3000, request.quote); + const tokens = await wallet.mintProofs(3000, request.quote); const meltQuote = await wallet.createMeltQuote(externalInvoice); const fee = meltQuote.fee_reserve; @@ -143,7 +143,7 @@ describe('mint api', () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(64); - const tokens = await wallet.mintTokens(64, request.quote); + const tokens = await wallet.mintProofs(64, request.quote); const sendResponse = await wallet.send(64, tokens.proofs); expect(sendResponse).toBeDefined(); @@ -157,7 +157,7 @@ describe('mint api', () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(100); - const tokens = await wallet.mintTokens(100, request.quote); + const tokens = await wallet.mintProofs(100, request.quote); const sendResponse = await wallet.send(10, tokens.proofs); expect(sendResponse).toBeDefined(); @@ -172,7 +172,7 @@ describe('mint api', () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(100); - const tokens = await wallet.mintTokens(100, request.quote); + const tokens = await wallet.mintProofs(100, request.quote); const sendResponse = await wallet.send(10, tokens.proofs); const encoded = getEncodedToken({ @@ -185,7 +185,7 @@ describe('mint api', () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(64); - const tokens = await wallet.mintTokens(64, request.quote); + const tokens = await wallet.mintProofs(64, request.quote); const encoded = getEncodedToken({ token: [{ mint: mintUrl, proofs: tokens.proofs }] }); @@ -203,7 +203,7 @@ describe('mint api', () => { const pubKeyBob = secp256k1.getPublicKey(privKeyBob); const request = await wallet.createMintQuote(64); - const tokens = await wallet.mintTokens(64, request.quote); + const tokens = await wallet.mintProofs(64, request.quote); const { send } = await wallet.send(64, tokens.proofs, { pubkey: bytesToHex(pubKeyBob) }); const encoded = getEncodedToken({ @@ -233,14 +233,14 @@ describe('mint api', () => { const mintRequest = await wallet.createMintQuote(3000); - const proofs = await wallet.mintTokens(3000, mintRequest.quote, { + const proofs = await wallet.mintProofs(3000, mintRequest.quote, { pubkey: bytesToHex(pubKeyBob) }); const meltRequest = await wallet.createMeltQuote(externalInvoice); const fee = meltRequest.fee_reserve; expect(fee).toBeGreaterThan(0); - const response = await wallet.meltTokens(meltRequest, proofs.proofs, { + const response = await wallet.meltProofs(meltRequest, proofs.proofs, { privkey: bytesToHex(privKeyBob) }); expect(response).toBeDefined(); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 09c0742e..0041984d 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -1,7 +1,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 { MeltQuoteResponse, MeltQuoteState, ReceiveResponse } from '../src/model/types/index.js'; import { getDecodedToken } from '../src/utils.js'; import { AmountPreference } from '../src/model/types/index'; import { Proof } from '@cashu/crypto/modules/common'; @@ -260,7 +260,7 @@ describe('payLnInvoice', () => { const result = await wallet.payLnInvoice(invoice, proofs, meltQuote); - expect(result).toEqual({ isPaid: true, preimage: null, change: [] }); + expect(result).toEqual({ quote: meltQuote, change: [] }); }); test('test payLnInvoice change', async () => { nock(mintUrl) @@ -293,8 +293,8 @@ describe('payLnInvoice', () => { const meltQuote = await wallet.checkMeltQuote('test'); const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }], meltQuote); - expect(result.isPaid).toBe(true); - expect(result.preimage).toBe('asd'); + expect(result.quote.state == MeltQuoteState.PAID).toBe(true); + expect(result.quote.payment_preimage).toBe('asd'); expect(result.change).toHaveLength(1); }); test('test payLnInvoice bad resonse', async () => { @@ -323,7 +323,7 @@ describe('requestTokens', () => { }); const wallet = new CashuWallet(mint, { unit }); - const { proofs } = await wallet.mintTokens(1, ''); + const { proofs } = await wallet.mintProofs(1, ''); expect(proofs).toHaveLength(1); expect(proofs[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); @@ -334,7 +334,7 @@ describe('requestTokens', () => { nock(mintUrl).post('/v1/mint/bolt11').reply(200, {}); const wallet = new CashuWallet(mint, { unit }); - const result = await wallet.mintTokens(1, '').catch((e) => e); + const result = await wallet.mintProofs(1, '').catch((e) => e); expect(result).toEqual(new Error('bad response')); }); From 568f07d8485e7573aab6c67a046d650a54d58d91 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:06:29 +0200 Subject: [PATCH 40/70] returnChange -> keep --- README.md | 2 +- src/CashuWallet.ts | 55 +++++++---------------------- src/model/types/wallet/responses.ts | 2 +- test/integration.test.ts | 26 +++++++------- test/wallet.test.ts | 24 ++++++------- 5 files changed, 39 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index df416db3..d679e97d 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ const amountToSend = meltQuote.amount + meltQuote.fee_reserve; // in a real wallet, we would coin select the correct amount of proofs from the wallet's storage // instead of that, here we swap `proofs` with the mint to get the correct amount of proofs -const { returnChange: proofsToKeep, send: proofsToSend } = await wallet.send(amountToSend, proofs); +const { keep: proofsToKeep, send: proofsToSend } = await wallet.send(amountToSend, proofs); // store proofsToKeep in wallet .. const meltResponse = await wallet.meltProofs(meltQuote, proofsToSend); diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 8b6b752a..0148c4a8 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -20,7 +20,6 @@ import { type TokenEntry, CheckStateEnum, SerializedBlindedSignature, - MeltQuoteState, GetInfoResponse, OutputAmounts, CheckStateEntry, @@ -326,7 +325,7 @@ class CashuWallet { if (sumProofs(proofs) < amount) { throw new Error('Not enough funds available to send'); } - const { returnChange: keepProofsOffline, send: sendProofOffline } = this.selectProofsToSend( + const { keep: keepProofsOffline, send: sendProofOffline } = this.selectProofsToSend( proofs, amount ); @@ -337,25 +336,18 @@ class CashuWallet { options?.privkey || options?.keysetId // these options require a swap ) { - // console.log( - // `>> yes swap | sendProofOffline: ${sumProofs(sendProofOffline)} | amount: ${amount}` - // ); // input selection - const { returnChange: keepProofsSelect, send: sendProofs } = this.selectProofsToSend( + const { keep: keepProofsSelect, send: sendProofs } = this.selectProofsToSend( proofs, amount, true ); options?.proofsWeHave?.push(...keepProofsSelect); - const { returnChange, send } = await this.swap(amount, sendProofs, options); - // console.log(`returnChange: ${sumProofs(returnChange)} | send: ${sumProofs(send)}`); - const returnChangeProofs = keepProofsSelect.concat(returnChange); - // console.log(`returnChangeProofs: ${sumProofs(returnChangeProofs)}`); - return { returnChange: returnChangeProofs, send }; + const { keep, send } = await this.swap(amount, sendProofs, options); + const keepProofs = keepProofsSelect.concat(keep); + return { keep: keepProofs, send }; } - // console.log('>> no swap'); - // console.log(`keepProofsOffline: ${sumProofs(keepProofsOffline)} | sendProofOffline: ${sumProofs(sendProofOffline)}`); - return { returnChange: keepProofsOffline, send: sendProofOffline }; + return { keep: keepProofsOffline, send: sendProofOffline }; } private selectProofsToSend( @@ -363,7 +355,6 @@ class CashuWallet { amountToSend: number, includeFees = false ): SendResponse { - // heavy logging in this function const sortedProofs = proofs.sort((a: Proof, b: Proof) => a.amount - b.amount); const smallerProofs = sortedProofs .filter((p: Proof) => p.amount <= amountToSend) @@ -374,13 +365,13 @@ class CashuWallet { const nextBigger = biggerProofs[0]; if (!smallerProofs.length && nextBigger) { return { - returnChange: proofs.filter((p: Proof) => p.secret !== nextBigger.secret), + keep: proofs.filter((p: Proof) => p.secret !== nextBigger.secret), send: [nextBigger] }; } if (!smallerProofs.length && !nextBigger) { - return { returnChange: proofs, send: [] }; + return { keep: proofs, send: [] }; } let remainder = amountToSend; @@ -389,20 +380,20 @@ class CashuWallet { const feePPK = includeFees ? this.getFeesForProofs(selectedProofs) : 0; remainder -= smallerProofs[0].amount - feePPK / 1000; if (remainder > 0) { - const { returnChange, send } = this.selectProofsToSend( + const { keep, send } = this.selectProofsToSend( smallerProofs.slice(1), remainder, includeFees ); selectedProofs.push(...send); - returnedProofs.push(...returnChange); + returnedProofs.push(...keep); } if (sumProofs(selectedProofs) < amountToSend && nextBigger) { selectedProofs = [nextBigger]; } return { - returnChange: proofs.filter((p: Proof) => !selectedProofs.includes(p)), + keep: proofs.filter((p: Proof) => !selectedProofs.includes(p)), send: selectedProofs }; } @@ -461,21 +452,9 @@ class CashuWallet { const amountToKeep = amountAvailable - amountToSend - this.getFeesForProofs(proofsToSend); if (amount + this.getFeesForProofs(proofsToSend) > amountAvailable) { - // console.log( - // `amount: ${amount} | fees: ${this.getFeesForProofs( - // proofsToSend - // )} | amountAvailable: ${amountAvailable}` - // ); throw new Error('Not enough funds available'); } // output selection - // if (options.proofsWeHave) { - // console.log( - // `proofsWeHave: ${sumProofs(options.proofsWeHave)} | sendProofs: ${sumProofs( - // proofsToSend - // )} | sendProofs amounts: ${proofsToSend.map((p: Proof) => p.amount)}` - // ); - // } let keepAmounts; if (options && !options.outputAmounts?.keepAmounts && options.proofsWeHave) { keepAmounts = getKeepAmounts(options.proofsWeHave, amountToKeep, keyset.keys, 3) @@ -487,10 +466,6 @@ class CashuWallet { keepAmounts: keepAmounts, sendAmounts: sendAmounts }; - // console.log( - // `keepAmounts: ${keepAmounts} | sendAmounts: ${options?.outputAmounts?.sendAmounts}` - // ); - // console.log(`>> amountToSend: ${amountToSend}`); const { payload, blindedMessages } = this.createSwapPayload( amountToSend, proofsToSend, @@ -519,7 +494,7 @@ class CashuWallet { splitProofsToSend.push(proof); }); return { - returnChange: splitProofsToKeep, + keep: splitProofsToKeep, send: splitProofsToSend }; } @@ -618,12 +593,6 @@ class CashuWallet { sendAmounts: [] }; } - // console.log( - // `outputAmounts: ${options?.outputAmounts?.keepAmounts - // } (sum: ${options?.outputAmounts?.keepAmounts?.reduce((a: number, b: number) => a + b, 0)}) | ${options?.outputAmounts?.sendAmounts - // } (sum: ${options?.outputAmounts?.sendAmounts.reduce((a: number, b: number) => a + b, 0)})` - // ); - // console.log(JSON.stringify(options?.outputAmounts)); const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages( amount, diff --git a/src/model/types/wallet/responses.ts b/src/model/types/wallet/responses.ts index f14918d4..6d830784 100644 --- a/src/model/types/wallet/responses.ts +++ b/src/model/types/wallet/responses.ts @@ -36,7 +36,7 @@ export type SendResponse = { /** * Proofs that exceeded the needed amount */ - returnChange: Array; + keep: Array; /** * Proofs to be sent, matching the chosen amount */ diff --git a/test/integration.test.ts b/test/integration.test.ts index 11bab234..0b4c4213 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -103,10 +103,10 @@ describe('mint api', () => { 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([]); + // expect none of the sendResponse.keep to be spent + const keepSpent = await wallet.checkProofsSpent(sendResponse.keep); + expect(keepSpent).toBeDefined(); + expect(keepSpent).toEqual([]); }); test('pay external invoice', async () => { const mint = new CashuMint(mintUrl); @@ -134,10 +134,10 @@ describe('mint api', () => { 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([]); + // expect none of the sendResponse.keep to be spent + const keepSpent = await wallet.checkProofsSpent(sendResponse.keep); + expect(keepSpent).toBeDefined(); + expect(keepSpent).toEqual([]); }); test('test send tokens exact without previous split', async () => { const mint = new CashuMint(mintUrl); @@ -148,9 +148,9 @@ describe('mint api', () => { const sendResponse = await wallet.send(64, tokens.proofs); expect(sendResponse).toBeDefined(); expect(sendResponse.send).toBeDefined(); - expect(sendResponse.returnChange).toBeDefined(); + expect(sendResponse.keep).toBeDefined(); expect(sendResponse.send.length).toBe(1); - expect(sendResponse.returnChange.length).toBe(0); + expect(sendResponse.keep.length).toBe(0); expect(sumProofs(sendResponse.send)).toBe(64); }); test('test send tokens with change', async () => { @@ -162,11 +162,11 @@ describe('mint api', () => { const sendResponse = await wallet.send(10, tokens.proofs); expect(sendResponse).toBeDefined(); expect(sendResponse.send).toBeDefined(); - expect(sendResponse.returnChange).toBeDefined(); + expect(sendResponse.keep).toBeDefined(); expect(sendResponse.send.length).toBe(2); - expect(sendResponse.returnChange.length).toBe(5); + expect(sendResponse.keep.length).toBe(5); expect(sumProofs(sendResponse.send)).toBe(10); - expect(sumProofs(sendResponse.returnChange)).toBe(90); + expect(sumProofs(sendResponse.keep)).toBe(90); }, 10000000); test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 0041984d..32f0f1c7 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -365,7 +365,7 @@ describe('send', () => { const result = await wallet.send(1, proofs); - expect(result.returnChange).toHaveLength(0); + expect(result.keep).toHaveLength(0); expect(result.send).toHaveLength(1); expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); @@ -403,10 +403,10 @@ describe('send', () => { expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).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: '009a1f293253e41e' }); - expect(/[0-9a-f]{64}/.test(result.returnChange[0].C)).toBe(true); - expect(/[0-9a-f]{64}/.test(result.returnChange[0].secret)).toBe(true); + expect(result.keep).toHaveLength(1); + expect(result.keep[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); + expect(/[0-9a-f]{64}/.test(result.keep[0].C)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.keep[0].secret)).toBe(true); }); test('test send over paying2', async () => { @@ -442,10 +442,10 @@ describe('send', () => { expect(result.send[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).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: '009a1f293253e41e' }); - expect(/[0-9a-f]{64}/.test(result.returnChange[0].C)).toBe(true); - expect(/[0-9a-f]{64}/.test(result.returnChange[0].secret)).toBe(true); + expect(result.keep).toHaveLength(1); + expect(result.keep[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); + expect(/[0-9a-f]{64}/.test(result.keep[0].C)).toBe(true); + expect(/[0-9a-f]{64}/.test(result.keep[0].secret)).toBe(true); }); test('test send preference', async () => { nock(mintUrl) @@ -502,7 +502,7 @@ describe('send', () => { expect(result.send[3]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).toBe(true); expect(/[0-9a-f]{64}/.test(result.send[0].secret)).toBe(true); - expect(result.returnChange).toHaveLength(0); + expect(result.keep).toHaveLength(0); }); test('test send preference overpay', async () => { @@ -559,8 +559,8 @@ describe('send', () => { expect(result.send[2]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); expect(/[0-9a-f]{64}/.test(result.send[0].C)).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: '009a1f293253e41e' }); + expect(result.keep).toHaveLength(1); + expect(result.keep[0]).toMatchObject({ amount: 1, id: '009a1f293253e41e' }); }); test('test send not enough funds', async () => { From 55e6acc6016dbe99230767e54591bdfa3104a0ea Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:14:28 +0200 Subject: [PATCH 41/70] create blank outputs for entire overpaid amount, not only for fee_reserve --- migration-1.0.0.md | 10 ++++++++++ src/CashuWallet.ts | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/migration-1.0.0.md b/migration-1.0.0.md index c91aa9b5..69d521f7 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -119,6 +119,16 @@ type BlindedMessage { --- +**`amountPreference`** is not used anymore. + +`preference?: Array;` -> `outputAmounts?: OutputAmounts;` + +- in `SendResponse`, `returnChange` is now called `keep` +- `mintTokens` is now called `mintProofs` +- `meltTokens` is now called `meltProofs` + +--- + ### 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. diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 0148c4a8..02a0f371 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -636,7 +636,7 @@ class CashuWallet { } /** - * Melt proofs for a melt quote. proofsToSend must be at least amount+fee_reserve form the melt quote. + * Melt proofs for a melt quote. proofsToSend must be at least amount+fee_reserve form the melt quote. This function does not perform coin selection!. * Returns melt quote and change proofs * @param meltQuote ID of the melt quote * @param proofsToSend proofs to melt @@ -656,7 +656,7 @@ class CashuWallet { ): Promise { const keys = await this.getKeys(options?.keysetId); const { blindedMessages, secrets, rs } = this.createBlankOutputs( - meltQuote.fee_reserve, + sumProofs(proofsToSend) - meltQuote.amount, keys.id, options?.counter ); @@ -910,17 +910,17 @@ 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 amount 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 */ private createBlankOutputs( - feeReserve: number, + amount: number, keysetId: string, counter?: number ): BlindedMessageData { - let count = Math.ceil(Math.log2(feeReserve)) || 1; + let count = Math.ceil(Math.log2(amount)) || 1; //Prevent count from being -Infinity if (count < 0) { count = 0; From cb54e70a5de8c51f5c506205540ca7ee242df2b3 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:16:50 +0200 Subject: [PATCH 42/70] rename CashuMint.split to CashuMint.swap --- migration-1.0.0.md | 1 + src/CashuMint.ts | 6 +++--- src/CashuWallet.ts | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/migration-1.0.0.md b/migration-1.0.0.md index 69d521f7..aad4c493 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -126,6 +126,7 @@ type BlindedMessage { - in `SendResponse`, `returnChange` is now called `keep` - `mintTokens` is now called `mintProofs` - `meltTokens` is now called `meltProofs` +- `CashuMint.split` is now called `CashuMint.swap` --- diff --git a/src/CashuMint.ts b/src/CashuMint.ts index 8df76b83..b86a07eb 100644 --- a/src/CashuMint.ts +++ b/src/CashuMint.ts @@ -76,7 +76,7 @@ class CashuMint { * @param customRequest * @returns signed outputs */ - public static async split( + public static async swap( mintUrl: string, swapPayload: SwapPayload, customRequest?: typeof request @@ -99,8 +99,8 @@ class CashuMint { * @param swapPayload payload containing inputs and outputs * @returns signed outputs */ - async split(swapPayload: SwapPayload): Promise { - return CashuMint.split(this._mintUrl, swapPayload, this._customRequest); + async swap(swapPayload: SwapPayload): Promise { + return CashuMint.swap(this._mintUrl, swapPayload, this._customRequest); } /** diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 02a0f371..4fee29b3 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -297,7 +297,7 @@ class CashuWallet { options?.pubkey, options?.privkey ); - const { signatures } = await this.mint.split(payload); + const { signatures } = await this.mint.swap(payload); const newProofs = this.constructProofs( signatures, blindedMessages.rs, @@ -475,7 +475,7 @@ class CashuWallet { options?.pubkey, options?.privkey ); - const { signatures } = await this.mint.split(payload); + const { signatures } = await this.mint.swap(payload); const swapProofs = this.constructProofs( signatures, blindedMessages.rs, From fd4ef99a2c4f02855dd26aa76f54ab71d2554a95 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:24:32 +0200 Subject: [PATCH 43/70] remove amountPreference and replace with outputAmounts --- src/CashuWallet.ts | 23 ++--------------------- src/model/types/index.ts | 4 ---- src/utils.ts | 12 ------------ test/wallet.test.ts | 5 ++--- 4 files changed, 4 insertions(+), 40 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 4fee29b3..40998d83 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -22,15 +22,13 @@ import { SerializedBlindedSignature, GetInfoResponse, OutputAmounts, - CheckStateEntry, - AmountPreference + CheckStateEntry } from './model/types/index.js'; import { bytesToNumber, getDecodedToken, splitAmount, sumProofs, - deprecatedPreferenceToOutputAmounts, getKeepAmounts } from './utils.js'; import { validateMnemonic } from '@scure/bip39'; @@ -236,7 +234,6 @@ class CashuWallet { token: string | Token, options?: { keysetId?: string; - preference?: Array; outputAmounts?: OutputAmounts; proofsWeHave?: Array; counter?: number; @@ -244,9 +241,6 @@ class CashuWallet { privkey?: string; } ): Promise> { - if (options?.preference) - // preference is only kept for backwards compatibility - options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); if (typeof token === 'string') { token = getDecodedToken(token); } @@ -275,16 +269,12 @@ class CashuWallet { tokenEntry: TokenEntry, options?: { keysetId?: string; - preference?: Array; outputAmounts?: OutputAmounts; counter?: number; pubkey?: string; privkey?: string; } ): Promise> { - if (options?.preference) - // preference is only kept for backwards compatibility - options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); const proofs: Array = []; const amount = tokenEntry.proofs.reduce((total: number, curr: Proof) => total + curr.amount, 0) - this.getFeesForProofs(tokenEntry.proofs); const keys = await this.getKeys(options?.keysetId); @@ -312,7 +302,6 @@ class CashuWallet { amount: number, proofs: Array, options?: { - preference?: Array; outputAmounts?: OutputAmounts; proofsWeHave?: Array; counter?: number; @@ -431,7 +420,6 @@ class CashuWallet { amount: number, proofs: Array, options?: { - preference?: Array; outputAmounts?: OutputAmounts; proofsWeHave?: Array; counter?: number; @@ -443,8 +431,6 @@ class CashuWallet { if (!options) { options = {}; } - if (options.preference) - options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); const keyset = await this.getKeys(options.keysetId); const proofsToSend = proofs; const amountToSend = amount @@ -575,7 +561,6 @@ class CashuWallet { quote: string, options?: { keysetId?: string; - preference?: Array; outputAmounts?: OutputAmounts; proofsWeHave?: Array; counter?: number; @@ -583,10 +568,6 @@ class CashuWallet { } ): Promise<{ proofs: Array }> { const keyset = await this.getKeys(options?.keysetId); - - if (options?.preference) - options.outputAmounts = deprecatedPreferenceToOutputAmounts(options.preference); - if (!options?.outputAmounts && options?.proofsWeHave) { options.outputAmounts = { keepAmounts: getKeepAmounts(options.proofsWeHave, amount, keyset.keys, 3), @@ -843,7 +824,7 @@ 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 split 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! diff --git a/src/model/types/index.ts b/src/model/types/index.ts index 2e08cf9e..8203aee6 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -7,10 +7,6 @@ export type OutputAmounts = { }; // deprecated -export type AmountPreference = { - amount: number; - count: number; -}; export type InvoiceData = { paymentRequest: string; diff --git a/src/utils.ts b/src/utils.ts index b650363f..3c264f8e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,8 +5,6 @@ import { encodeUint8toBase64Url } from './base64.js'; import { - AmountPreference, - OutputAmounts, Keys, Proof, Token, @@ -114,15 +112,6 @@ function getPreference(amount: number, keyset: Keys, split: Array): Arra return chunks; } -function deprecatedPreferenceToOutputAmounts(preference?: Array): OutputAmounts { - const sendAmounts: Array = []; - preference?.forEach(({ count, amount }) => { - for (let i = 0; i < count; i++) { - sendAmounts.push(amount); - } - }); - return { sendAmounts }; -} function bytesToNumber(bytes: Uint8Array): bigint { return hexToNumber(bytesToHex(bytes)); } @@ -299,6 +288,5 @@ export { getEncodedTokenV4, hexToNumber, splitAmount, - deprecatedPreferenceToOutputAmounts, getKeepAmounts }; diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 32f0f1c7..732c2494 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -1,9 +1,8 @@ import nock from 'nock'; import { CashuMint } from '../src/CashuMint.js'; import { CashuWallet } from '../src/CashuWallet.js'; -import { MeltQuoteResponse, MeltQuoteState, ReceiveResponse } from '../src/model/types/index.js'; +import { MeltQuoteResponse, MeltQuoteState, OutputAmounts } from '../src/model/types/index.js'; import { getDecodedToken } from '../src/utils.js'; -import { AmountPreference } from '../src/model/types/index'; import { Proof } from '@cashu/crypto/modules/common'; const dummyKeysResp = { @@ -176,7 +175,7 @@ describe('receive', () => { 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0='; const proofs = await wallet.receive(token3sat, { - preference: [{ amount: 1, count: 3 }] + outputAmounts: { keepAmounts: [1, 1, 1], sendAmounts: [] } }); expect(proofs).toHaveLength(3); From 2b6e0bd017688a45991804f4d060bcd1e47b2ef9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:29:48 +0200 Subject: [PATCH 44/70] fix tests --- 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 732c2494..c8db26e8 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -175,7 +175,7 @@ describe('receive', () => { 'cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMSwgInNlY3JldCI6ICJlN2MxYjc2ZDFiMzFlMmJjYTJiMjI5ZDE2MGJkZjYwNDZmMzNiYzQ1NzAyMjIzMDRiNjUxMTBkOTI2ZjdhZjg5IiwgIkMiOiAiMDM4OWNkOWY0Zjk4OGUzODBhNzk4OWQ0ZDQ4OGE3YzkxYzUyNzdmYjkzMDQ3ZTdhMmNjMWVkOGUzMzk2Yjg1NGZmIn0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImRlNTVjMTVmYWVmZGVkN2Y5Yzk5OWMzZDRjNjJmODFiMGM2ZmUyMWE3NTJmZGVmZjZiMDg0Y2YyZGYyZjVjZjMiLCAiQyI6ICIwMmRlNDBjNTlkOTAzODNiODg1M2NjZjNhNGIyMDg2NGFjODNiYTc1OGZjZTNkOTU5ZGJiODkzNjEwMDJlOGNlNDcifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzOCJ9XX0='; const proofs = await wallet.receive(token3sat, { - outputAmounts: { keepAmounts: [1, 1, 1], sendAmounts: [] } + outputAmounts: { keepAmounts: [], sendAmounts: [1, 1, 1] } }); expect(proofs).toHaveLength(3); From 29a4a93722d21480e1005ae1413001ad9330eace Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:06:33 +0200 Subject: [PATCH 45/70] fix --- test/integration.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index 0b4c4213..c5cf199e 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -5,6 +5,7 @@ import dns from 'node:dns'; import { deriveKeysetId, getEncodedToken, sumProofs } from '../src/utils.js'; import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/curves/abstract/utils'; +import { MeltQuoteState } from '../src/model/types/index.js'; dns.setDefaultResultOrder('ipv4first'); const externalInvoice = @@ -244,6 +245,6 @@ describe('mint api', () => { privkey: bytesToHex(privKeyBob) }); expect(response).toBeDefined(); - expect(response.isPaid).toBe(true); + expect(response.quote.state == MeltQuoteState.PAID).toBe(true); }); }); From 2cad1adc88d78ef6f2fd6973e4ea73aa6a047ea4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 19 Oct 2024 13:46:51 +0200 Subject: [PATCH 46/70] format --- src/CashuWallet.ts | 20 ++++++++------------ src/utils.ts | 5 ++++- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 40998d83..f7eda5fb 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -24,13 +24,7 @@ import { OutputAmounts, CheckStateEntry } from './model/types/index.js'; -import { - bytesToNumber, - getDecodedToken, - splitAmount, - sumProofs, - getKeepAmounts -} from './utils.js'; +import { bytesToNumber, getDecodedToken, splitAmount, sumProofs, getKeepAmounts } from './utils.js'; import { validateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; import { hashToCurve, pointFromHex } from '@cashu/crypto/modules/common'; @@ -276,7 +270,9 @@ class CashuWallet { } ): Promise> { const proofs: Array = []; - const amount = tokenEntry.proofs.reduce((total: number, curr: Proof) => total + curr.amount, 0) - this.getFeesForProofs(tokenEntry.proofs); + const amount = + tokenEntry.proofs.reduce((total: number, curr: Proof) => total + curr.amount, 0) - + this.getFeesForProofs(tokenEntry.proofs); const keys = await this.getKeys(options?.keysetId); const { payload, blindedMessages } = this.createSwapPayload( amount, @@ -396,7 +392,7 @@ class CashuWallet { 0 ) + 999) / - 1000, + 1000, 0 ) ); @@ -433,7 +429,7 @@ class CashuWallet { } const keyset = await this.getKeys(options.keysetId); const proofsToSend = proofs; - const amountToSend = amount + const amountToSend = amount; const amountAvailable = sumProofs(proofs); const amountToKeep = amountAvailable - amountToSend - this.getFeesForProofs(proofsToSend); @@ -443,7 +439,7 @@ class CashuWallet { // output selection let keepAmounts; if (options && !options.outputAmounts?.keepAmounts && options.proofsWeHave) { - keepAmounts = getKeepAmounts(options.proofsWeHave, amountToKeep, keyset.keys, 3) + keepAmounts = getKeepAmounts(options.proofsWeHave, amountToKeep, keyset.keys, 3); } else if (options.outputAmounts) { keepAmounts = options.outputAmounts.keepAmounts; } @@ -662,7 +658,7 @@ class CashuWallet { const meltResponse = await this.mint.melt(meltPayload); let change: Array = []; if (meltResponse.change) { - change = this.constructProofs(meltResponse.change, rs, secrets, keys) + change = this.constructProofs(meltResponse.change, rs, secrets, keys); } return { quote: meltResponse, diff --git a/src/utils.ts b/src/utils.ts index 3c264f8e..6e7c999f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -28,7 +28,10 @@ function splitAmount( if (split) { if (split.reduce((a: number, b: number) => a + b, 0) > value) { throw new Error( - `Split is greater than total amount: ${split.reduce((a: number, b: number) => a + b, 0)} > ${value}` + `Split is greater than total amount: ${split.reduce( + (a: number, b: number) => a + b, + 0 + )} > ${value}` ); } chunks.push(...getPreference(value, keyset, split)); From efe541ba6ed0ead8a515f3642bad63d01d5209d8 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 19 Oct 2024 13:49:11 +0200 Subject: [PATCH 47/70] fix import --- src/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 093ff8b1..2c22f72b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -299,6 +299,5 @@ export { hexToNumber, splitAmount, getKeepAmounts, - getDefaultAmountPreference, decodePaymentRequest }; From 814a0a02f73f62f7acea09c8631f72937a7456e4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 19 Oct 2024 15:33:20 +0200 Subject: [PATCH 48/70] uppercase --- src/CashuWallet.ts | 10 +++++----- src/model/types/wallet/responses.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index f7eda5fb..e84aefe6 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -8,7 +8,7 @@ import { type MeltQuoteResponse, type MintKeys, type MintKeyset, - type meltProofsResponse, + type MeltProofsResponse, type MintPayload, type Proof, type MintQuotePayload, @@ -392,7 +392,7 @@ class CashuWallet { 0 ) + 999) / - 1000, + 1000, 0 ) ); @@ -630,7 +630,7 @@ class CashuWallet { counter?: number; privkey?: string; } - ): Promise { + ): Promise { const keys = await this.getKeys(options?.keysetId); const { blindedMessages, secrets, rs } = this.createBlankOutputs( sumProofs(proofsToSend) - meltQuote.amount, @@ -686,7 +686,7 @@ class CashuWallet { counter?: number; privkey?: string; } - ): Promise { + ): Promise { if (!meltQuote) { meltQuote = await this.mint.createMeltQuote({ unit: this._unit, request: invoice }); } @@ -713,7 +713,7 @@ class CashuWallet { keysetId?: string; counter?: number; } - ): Promise { + ): Promise { const decodedToken = getDecodedToken(token); const proofs = decodedToken.token .filter((x: TokenEntry) => x.mint === this.mint.mintUrl) diff --git a/src/model/types/wallet/responses.ts b/src/model/types/wallet/responses.ts index 6d830784..c3f93ea6 100644 --- a/src/model/types/wallet/responses.ts +++ b/src/model/types/wallet/responses.ts @@ -4,7 +4,7 @@ import { Proof, Token } from './index'; /** * Response after paying a Lightning invoice */ -export type meltProofsResponse = { +export type MeltProofsResponse = { /** * if false, the proofs have not been invalidated and the payment can be tried later again with the same proofs */ From c3dfc43c1e4090e85d4d47373531ca7fe97b72e0 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 19 Oct 2024 22:44:31 +0200 Subject: [PATCH 49/70] test with fees --- .github/workflows/nutshell-integration.yml | 2 +- src/CashuWallet.ts | 45 ++++++++++++++++++---- test/integration.test.ts | 26 ++++++++----- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 94ccf849..3e881511 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.16.0 poetry run mint + docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_INPUT_FEE_PPK=100 -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.0 poetry run mint - name: Check running containers run: docker ps diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index e84aefe6..bfdef0c5 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -305,6 +305,7 @@ class CashuWallet { privkey?: string; keysetId?: string; offline?: boolean; + includeFees?: boolean } ): Promise { if (sumProofs(proofs) < amount) { @@ -312,7 +313,8 @@ class CashuWallet { } const { keep: keepProofsOffline, send: sendProofOffline } = this.selectProofsToSend( proofs, - amount + amount, + options?.includeFees ); if ( sumProofs(sendProofOffline) != amount || // if the exact amount cannot be selected @@ -321,13 +323,30 @@ class CashuWallet { options?.privkey || options?.keysetId // these options require a swap ) { + // we need to swap + console.log("NOW WE SWAP") // input selection const { keep: keepProofsSelect, send: sendProofs } = this.selectProofsToSend( proofs, amount, true ); + console.log(`Selected amount: ${sumProofs(sendProofs)}`); options?.proofsWeHave?.push(...keepProofsSelect); + if (options?.includeFees) { + const keyset = await this.getKeys(options.keysetId); + const outputAmounts = splitAmount(amount, keyset.keys) + // create dummyProofs which are `Proof` objects with amounts from the outputAmounts + const dummyProofs: Array = outputAmounts.map((amount: number) => { + return { + amount: amount, + id: keyset.id, + secret: "dummy", + C: "dummy" + } as Proof; + }); + amount += this.getFeesForProofs(dummyProofs); + } const { keep, send } = await this.swap(amount, sendProofs, options); const keepProofs = keepProofsSelect.concat(keep); return { keep: keepProofs, send }; @@ -338,7 +357,7 @@ class CashuWallet { private selectProofsToSend( proofs: Array, amountToSend: number, - includeFees = false + includeFees?: boolean, ): SendResponse { const sortedProofs = proofs.sort((a: Proof, b: Proof) => a.amount - b.amount); const smallerProofs = sortedProofs @@ -422,6 +441,7 @@ class CashuWallet { pubkey?: string; privkey?: string; keysetId?: string; + includeFees?: boolean; } ): Promise { if (!options) { @@ -433,9 +453,6 @@ class CashuWallet { const amountAvailable = sumProofs(proofs); const amountToKeep = amountAvailable - amountToSend - this.getFeesForProofs(proofsToSend); - if (amount + this.getFeesForProofs(proofsToSend) > amountAvailable) { - throw new Error('Not enough funds available'); - } // output selection let keepAmounts; if (options && !options.outputAmounts?.keepAmounts && options.proofsWeHave) { @@ -443,7 +460,16 @@ class CashuWallet { } else if (options.outputAmounts) { keepAmounts = options.outputAmounts.keepAmounts; } - const sendAmounts = options?.outputAmounts?.sendAmounts || splitAmount(amount, keyset.keys); + const sendAmounts = options?.outputAmounts?.sendAmounts || splitAmount(amountToSend, keyset.keys); + + if (amountToSend + this.getFeesForProofs(proofsToSend) > amountAvailable) { + throw new Error('Not enough funds available'); + } + + console.log(`# swap: amountToKeep: ${amountToKeep}`); + console.log(`# swap: amountToSend: ${amountToSend}`); + console.log(`# swap: keepAmounts: ${keepAmounts}`); + console.log(`# swap: sendAmounts: ${sendAmounts}`); options.outputAmounts = { keepAmounts: keepAmounts, sendAmounts: sendAmounts @@ -747,8 +773,11 @@ class CashuWallet { blindedMessages: BlindedTransaction; } { const totalAmount = proofsToSend.reduce((total: number, curr: Proof) => total + curr.amount, 0); + console.log(`# createSwapPayload: amount: ${amount} | input amount: ${totalAmount}`); if (outputAmounts && outputAmounts.sendAmounts && !outputAmounts.keepAmounts) { - outputAmounts.keepAmounts = splitAmount(totalAmount - amount, keyset.keys); + console.log(`# splitting amount: ${totalAmount} - ${amount}`); + outputAmounts.keepAmounts = splitAmount(totalAmount - amount - this.getFeesForProofs(proofsToSend), keyset.keys); + console.log(`# keepAmounts: ${outputAmounts.keepAmounts}`); } const keepBlindedMessages = this.createRandomBlindedMessages( totalAmount - amount - this.getFeesForProofs(proofsToSend), @@ -795,6 +824,7 @@ class CashuWallet { inputs: proofsToSend, outputs: [...blindedMessages.blindedMessages] }; + console.log(`# createSwapPayload: amount: ${amount}`); return { payload, blindedMessages }; } /** @@ -833,6 +863,7 @@ class CashuWallet { counter?: number, pubkey?: string ): BlindedMessageData & { amounts: Array } { + console.log(`# createRandomBlindedMessages: amount: ${amount} split: ${split}`); const amounts = splitAmount(amount, keyset.keys, split); return this.createBlindedMessages(amounts, keyset.id, counter, pubkey); } diff --git a/test/integration.test.ts b/test/integration.test.ts index c5cf199e..0c6916a3 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -80,6 +80,7 @@ describe('mint api', () => { test('pay local invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); + await wallet.loadMint(); const request = await wallet.createMintQuote(100); const tokens = await wallet.mintProofs(100, request.quote); @@ -93,8 +94,9 @@ describe('mint api', () => { const quote_ = await wallet.checkMeltQuote(quote.quote); expect(quote_).toBeDefined(); - const sendResponse = await wallet.send(10, tokens.proofs); - const response = await wallet.payLnInvoice(mintQuote.request, sendResponse.send, quote); + const sendResponse = await wallet.send(10, tokens.proofs, { includeFees: true }); + // const response = await wallet.payLnInvoice(mintQuote.request, sendResponse.send, quote); + const response = await wallet.meltProofs(quote, 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); @@ -112,6 +114,7 @@ describe('mint api', () => { test('pay external invoice', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); + await wallet.loadMint(); const request = await wallet.createMintQuote(3000); const tokens = await wallet.mintProofs(3000, request.quote); @@ -123,8 +126,9 @@ describe('mint api', () => { const quote_ = await wallet.checkMeltQuote(meltQuote.quote); expect(quote_).toBeDefined(); - const sendResponse = await wallet.send(2000 + fee, tokens.proofs); - const response = await wallet.payLnInvoice(externalInvoice, sendResponse.send, meltQuote); + const sendResponse = await wallet.send(2000 + fee, tokens.proofs, { includeFees: true }); + // const response = await wallet.payLnInvoice(externalInvoice, sendResponse.send, meltQuote); + const response = await wallet.meltProofs(meltQuote, sendResponse.send); expect(response).toBeDefined(); // expect that we have not received the fee back, since it was external @@ -157,21 +161,23 @@ describe('mint api', () => { test('test send tokens with change', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); + await wallet.loadMint(); const request = await wallet.createMintQuote(100); const tokens = await wallet.mintProofs(100, request.quote); - const sendResponse = await wallet.send(10, tokens.proofs); + const sendResponse = await wallet.send(10, tokens.proofs, { includeFees: false }); expect(sendResponse).toBeDefined(); expect(sendResponse.send).toBeDefined(); expect(sendResponse.keep).toBeDefined(); expect(sendResponse.send.length).toBe(2); expect(sendResponse.keep.length).toBe(5); expect(sumProofs(sendResponse.send)).toBe(10); - expect(sumProofs(sendResponse.keep)).toBe(90); + expect(sumProofs(sendResponse.keep)).toBe(89); }, 10000000); test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); + await wallet.loadMint(); const request = await wallet.createMintQuote(100); const tokens = await wallet.mintProofs(100, request.quote); @@ -185,6 +191,7 @@ describe('mint api', () => { test('receive tokens with previous mint', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint, { unit }); + await wallet.loadMint(); const request = await wallet.createMintQuote(64); const tokens = await wallet.mintProofs(64, request.quote); const encoded = getEncodedToken({ @@ -196,6 +203,7 @@ describe('mint api', () => { test('send and receive p2pk', async () => { const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); + await wallet.loadMint(); const privKeyAlice = secp256k1.utils.randomPrivateKey(); const pubKeyAlice = secp256k1.getPublicKey(privKeyAlice); @@ -203,8 +211,8 @@ describe('mint api', () => { const privKeyBob = secp256k1.utils.randomPrivateKey(); const pubKeyBob = secp256k1.getPublicKey(privKeyBob); - const request = await wallet.createMintQuote(64); - const tokens = await wallet.mintProofs(64, request.quote); + const request = await wallet.createMintQuote(128); + const tokens = await wallet.mintProofs(128, request.quote); const { send } = await wallet.send(64, tokens.proofs, { pubkey: bytesToHex(pubKeyBob) }); const encoded = getEncodedToken({ @@ -222,7 +230,7 @@ describe('mint api', () => { proofs.reduce((curr, acc) => { return curr + acc.amount; }, 0) - ).toBe(64); + ).toBe(63); }); test('mint and melt p2pk', async () => { From 12403575ac61a8f73f0b76201c9ef61f8347503a Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 19 Oct 2024 23:48:20 +0200 Subject: [PATCH 50/70] optimizations --- README.md | 17 +++- package.json | 2 +- src/CashuWallet.ts | 192 +++++++++++++++++++++++++++------------ test/integration.test.ts | 18 ++-- test/wallet.test.ts | 3 +- 5 files changed, 154 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index d679e97d..f12c0e70 100644 --- a/README.md +++ b/README.md @@ -77,21 +77,30 @@ if (mintQuoteChecked.state == MintQuoteState.PAID) { import { CashuMint, CashuWallet } from '@cashu/cashu-ts'; const mintUrl = 'http://localhost:3338'; // the mint URL const mint = new CashuMint(mintUrl); -const wallet = new CashuWallet(mint); +const wallet = new CashuWallet(mint, { loadMint: true }); // load the keysets of the mint const invoice = 'lnbc......'; // Lightning invoice to pay const meltQuote = await wallet.createMeltQuote(invoice); const amountToSend = meltQuote.amount + meltQuote.fee_reserve; -// in a real wallet, we would coin select the correct amount of proofs from the wallet's storage -// instead of that, here we swap `proofs` with the mint to get the correct amount of proofs -const { keep: proofsToKeep, send: proofsToSend } = await wallet.send(amountToSend, proofs); +// CashuWallet.send performs coin selection and swaps the proofs with the mint +// if no appropriate amount can be selected offline. We must include potential +// ecash fees that the mint might require to melt the resulting proofsToSend later. +const { keep: proofsToKeep, send: proofsToSend } = await wallet.send(amountToSend, proofs, { + includeFees: true +}); // store proofsToKeep in wallet .. const meltResponse = await wallet.meltProofs(meltQuote, proofsToSend); // store meltResponse.change in wallet .. ``` +#### Create a token and receive it + +```typescript + +``` + ## Contribute Contributions are very welcome. diff --git a/package.json b/package.json index 5e33c1f3..3ae405f2 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "scripts": { "compile": "rm -rf dist/lib && tsc && tsc --build tsconfig.es5.json", - "test": "jest --coverage --testPathIgnorePatterns ./test/integration.test.ts", + "test": "jest --coverage", "test-integration": "jest --coverage --testPathPattern ./test/integration.test.ts", "dev": "tsc --watch", "lint": "eslint --ext .js,.ts . --fix", diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index bfdef0c5..80eae6b8 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -41,6 +41,9 @@ import { import { createP2PKsecret, getSignedProofs } from '@cashu/crypto/modules/client/NUT11'; import { type Proof as NUT11Proof } from '@cashu/crypto/modules/common/index'; +const DEFAULT_DENOMINATION_TARGET = 3; +const DEFAULT_UNIT = 'sat'; + /** * Class that represents a Cashu wallet. * This class should act as the entry point for this library @@ -50,8 +53,9 @@ class CashuWallet { private _keysetId: string | undefined; private _keysets: Array = []; private _seed: Uint8Array | undefined = undefined; - private _unit = 'sat'; + private _unit = DEFAULT_UNIT; private _mintInfo: GetInfoResponse | undefined = undefined; + private _denominationTarget = DEFAULT_DENOMINATION_TARGET; mint: CashuMint; @@ -72,6 +76,8 @@ class CashuWallet { keysets?: Array; mintInfo?: GetInfoResponse; mnemonicOrSeed?: string | Uint8Array; + denominationTarget?: number; + loadMint?: boolean; } ) { this.mint = mint; @@ -84,6 +90,20 @@ class CashuWallet { if (keys) keys.forEach((key: MintKeys) => this._keys.set(key.id, key)); if (options?.unit) this._unit = options?.unit; if (options?.keysets) this._keysets = options.keysets; + if (options?.denominationTarget) { + this._denominationTarget = options.denominationTarget; + } + + if (options?.loadMint) { + this.loadMint() + .then(() => { + console.log('Mint loaded'); + }) + .catch((e: Error) => { + console.error('Failed to load mint', e); + }); + } + if (!options?.mnemonicOrSeed) { return; } else if (options?.mnemonicOrSeed instanceof Uint8Array) { @@ -136,8 +156,7 @@ class CashuWallet { async loadMint() { await this.getMintInfo(); await this.getKeySets(); - await this.getAllKeys(); - this.keysetId = this.getActiveKeyset(this._keysets).id; + await this.getKeys(); } /** @@ -178,6 +197,7 @@ class CashuWallet { async getAllKeys(): Promise> { const keysets = await this.mint.getKeys(); this._keys = new Map(keysets.keysets.map((k: MintKeys) => [k.id, k])); + this.keysetId = this.getActiveKeyset(this._keysets).id; return keysets.keysets; } @@ -192,25 +212,22 @@ class CashuWallet { * @returns keyset */ async getKeys(keysetId?: string): Promise { - if (keysetId) { - if (this._keys.get(keysetId)) { - this.keysetId = keysetId; - return this._keys.get(keysetId) as MintKeys; - } - const allKeysets = await this.mint.getKeys(keysetId); - const keyset = allKeysets.keysets[0]; - if (!keyset) { - throw new Error(`could not initialize keys. No keyset with id '${keysetId}' found`); - } - this._keys.set(keysetId, keyset); + if (!keysetId) { + const allKeysets = await this.mint.getKeySets(); + const keysetToActivate = this.getActiveKeyset(allKeysets.keysets); + keysetId = keysetToActivate.id; + } + if (this._keys.get(keysetId)) { this.keysetId = keysetId; - return keyset; + return this._keys.get(keysetId) as MintKeys; } - - // no keysetId was set, so we select an active keyset with the unit of the wallet with the lowest fees and use that - const allKeysets = await this.mint.getKeySets(); - const keysetToActivate = this.getActiveKeyset(allKeysets.keysets); - const keyset = await this.getKeys(keysetToActivate.id); + const allKeysets = await this.mint.getKeys(keysetId); + const keyset = allKeysets.keysets[0]; + if (!keyset) { + throw new Error(`could not initialize keys. No keyset with id '${keysetId}' found`); + } + this._keys.set(keysetId, keyset); + this.keysetId = keysetId; return keyset; } @@ -305,7 +322,7 @@ class CashuWallet { privkey?: string; keysetId?: string; offline?: boolean; - includeFees?: boolean + includeFees?: boolean; } ): Promise { if (sumProofs(proofs) < amount) { @@ -316,16 +333,18 @@ class CashuWallet { amount, options?.includeFees ); + const expectedFee = options?.includeFees ? this.getFeesForProofs(sendProofOffline) : 0; if ( - sumProofs(sendProofOffline) != amount || // if the exact amount cannot be selected - options?.outputAmounts || - options?.pubkey || - options?.privkey || - options?.keysetId // these options require a swap + !options?.offline && + (sumProofs(sendProofOffline) != amount + expectedFee || // if the exact amount cannot be selected + options?.outputAmounts || + options?.pubkey || + options?.privkey || + options?.keysetId) // these options require a swap ) { // we need to swap - console.log("NOW WE SWAP") - // input selection + console.log('NOW WE SWAP'); + // input selection, needs fees because of the swap const { keep: keepProofsSelect, send: sendProofs } = this.selectProofsToSend( proofs, amount, @@ -333,31 +352,36 @@ class CashuWallet { ); console.log(`Selected amount: ${sumProofs(sendProofs)}`); options?.proofsWeHave?.push(...keepProofsSelect); - if (options?.includeFees) { - const keyset = await this.getKeys(options.keysetId); - const outputAmounts = splitAmount(amount, keyset.keys) - // create dummyProofs which are `Proof` objects with amounts from the outputAmounts - const dummyProofs: Array = outputAmounts.map((amount: number) => { - return { - amount: amount, - id: keyset.id, - secret: "dummy", - C: "dummy" - } as Proof; - }); - amount += this.getFeesForProofs(dummyProofs); - } + + // // we need to include the fees to the outputs of the swap + // if (options?.includeFees) { + // const keyset = await this.getKeys(options.keysetId); + // const outputAmounts = splitAmount(amount, keyset.keys) + // const dummyProofs: Array = outputAmounts.map((amount: number) => { + // return { + // amount: amount, + // id: keyset.id, + // secret: "dummy", + // C: "dummy" + // } as Proof; + // }); + // amount += this.getFeesForProofs(dummyProofs); + // } const { keep, send } = await this.swap(amount, sendProofs, options); const keepProofs = keepProofsSelect.concat(keep); return { keep: keepProofs, send }; } + + if (sumProofs(sendProofOffline) < amount + expectedFee) { + throw new Error('Not enough funds available to send'); + } return { keep: keepProofsOffline, send: sendProofOffline }; } private selectProofsToSend( proofs: Array, amountToSend: number, - includeFees?: boolean, + includeFees?: boolean ): SendResponse { const sortedProofs = proofs.sort((a: Proof, b: Proof) => a.amount - b.amount); const smallerProofs = sortedProofs @@ -411,7 +435,19 @@ class CashuWallet { 0 ) + 999) / - 1000, + 1000, + 0 + ) + ); + return fees; + } + + getFeesForKeyset(nInputs: number, keysetId: string): number { + const fees = Math.floor( + Math.max( + (nInputs * (this._keysets.find((k: MintKeyset) => k.id === keysetId)?.input_fee_ppk || 0) + + 999) / + 1000, 0 ) ); @@ -444,32 +480,62 @@ class CashuWallet { includeFees?: boolean; } ): Promise { - if (!options) { - options = {}; - } + if (!options) options = {}; const keyset = await this.getKeys(options.keysetId); const proofsToSend = proofs; - const amountToSend = amount; + let amountToSend = amount; const amountAvailable = sumProofs(proofs); - const amountToKeep = amountAvailable - amountToSend - this.getFeesForProofs(proofsToSend); + let amountToKeep = amountAvailable - amountToSend - this.getFeesForProofs(proofsToSend); + + // send output selection + let sendAmounts = options?.outputAmounts?.sendAmounts || splitAmount(amountToSend, keyset.keys); + + // include the fees to spend the the outputs of the swap + if (options?.includeFees) { + let outputFee = this.getFeesForKeyset(sendAmounts.length, keyset.id); + let sendAmountsFee = splitAmount(outputFee, keyset.keys); + while ( + this.getFeesForKeyset(sendAmounts.concat(sendAmountsFee).length, keyset.id) > outputFee + ) { + outputFee++; + sendAmountsFee = splitAmount(outputFee, keyset.keys); + } + sendAmounts = sendAmounts.concat(sendAmountsFee); + amountToSend += outputFee; + amountToKeep -= outputFee; + } - // output selection + // keep output selection let keepAmounts; if (options && !options.outputAmounts?.keepAmounts && options.proofsWeHave) { - keepAmounts = getKeepAmounts(options.proofsWeHave, amountToKeep, keyset.keys, 3); + keepAmounts = getKeepAmounts( + options.proofsWeHave, + amountToKeep, + keyset.keys, + this._denominationTarget + ); } else if (options.outputAmounts) { + if ( + options.outputAmounts.keepAmounts?.reduce((a: number, b: number) => a + b, 0) != + amountToKeep + ) { + throw new Error('Keep amounts do not match amount to keep'); + } keepAmounts = options.outputAmounts.keepAmounts; } - const sendAmounts = options?.outputAmounts?.sendAmounts || splitAmount(amountToSend, keyset.keys); if (amountToSend + this.getFeesForProofs(proofsToSend) > amountAvailable) { - throw new Error('Not enough funds available'); + throw new Error('Not enough funds available for swap'); } - console.log(`# swap: amountToKeep: ${amountToKeep}`); - console.log(`# swap: amountToSend: ${amountToSend}`); - console.log(`# swap: keepAmounts: ${keepAmounts}`); - console.log(`# swap: sendAmounts: ${sendAmounts}`); + if (amountToSend + this.getFeesForProofs(proofsToSend) + amountToKeep != amountAvailable) { + throw new Error('Amounts do not match for swap'); + } + + // console.log(`# swap: amountToKeep: ${amountToKeep}`); + // console.log(`# swap: amountToSend: ${amountToSend}`); + // console.log(`# swap: keepAmounts: ${keepAmounts}`); + // console.log(`# swap: sendAmounts: ${sendAmounts}`); options.outputAmounts = { keepAmounts: keepAmounts, sendAmounts: sendAmounts @@ -592,7 +658,12 @@ class CashuWallet { const keyset = await this.getKeys(options?.keysetId); if (!options?.outputAmounts && options?.proofsWeHave) { options.outputAmounts = { - keepAmounts: getKeepAmounts(options.proofsWeHave, amount, keyset.keys, 3), + keepAmounts: getKeepAmounts( + options.proofsWeHave, + amount, + keyset.keys, + this._denominationTarget + ), sendAmounts: [] }; } @@ -776,7 +847,10 @@ class CashuWallet { console.log(`# createSwapPayload: amount: ${amount} | input amount: ${totalAmount}`); if (outputAmounts && outputAmounts.sendAmounts && !outputAmounts.keepAmounts) { console.log(`# splitting amount: ${totalAmount} - ${amount}`); - outputAmounts.keepAmounts = splitAmount(totalAmount - amount - this.getFeesForProofs(proofsToSend), keyset.keys); + outputAmounts.keepAmounts = splitAmount( + totalAmount - amount - this.getFeesForProofs(proofsToSend), + keyset.keys + ); console.log(`# keepAmounts: ${outputAmounts.keepAmounts}`); } const keepBlindedMessages = this.createRandomBlindedMessages( diff --git a/test/integration.test.ts b/test/integration.test.ts index 0c6916a3..be751ac3 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -79,8 +79,7 @@ describe('mint api', () => { }); test('pay local invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit }); - await wallet.loadMint(); + const wallet = new CashuWallet(mint, { unit, loadMint: true }); const request = await wallet.createMintQuote(100); const tokens = await wallet.mintProofs(100, request.quote); @@ -113,8 +112,7 @@ describe('mint api', () => { }); test('pay external invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit }); - await wallet.loadMint(); + const wallet = new CashuWallet(mint, { unit, loadMint: true }); const request = await wallet.createMintQuote(3000); const tokens = await wallet.mintProofs(3000, request.quote); @@ -160,8 +158,7 @@ describe('mint api', () => { }); test('test send tokens with change', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit }); - await wallet.loadMint(); + const wallet = new CashuWallet(mint, { unit, loadMint: true }); const request = await wallet.createMintQuote(100); const tokens = await wallet.mintProofs(100, request.quote); @@ -176,8 +173,7 @@ describe('mint api', () => { }, 10000000); test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit }); - await wallet.loadMint(); + const wallet = new CashuWallet(mint, { unit, loadMint: true }); const request = await wallet.createMintQuote(100); const tokens = await wallet.mintProofs(100, request.quote); @@ -190,8 +186,7 @@ describe('mint api', () => { }); test('receive tokens with previous mint', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit }); - await wallet.loadMint(); + const wallet = new CashuWallet(mint, { unit, loadMint: true }); const request = await wallet.createMintQuote(64); const tokens = await wallet.mintProofs(64, request.quote); const encoded = getEncodedToken({ @@ -202,8 +197,7 @@ describe('mint api', () => { }); test('send and receive p2pk', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint); - await wallet.loadMint(); + const wallet = new CashuWallet(mint, { unit, loadMint: true }); const privKeyAlice = secp256k1.utils.randomPrivateKey(); const pubKeyAlice = secp256k1.getPublicKey(privKeyAlice); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index c8db26e8..7a572fa3 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -548,8 +548,7 @@ describe('send', () => { } ]; const result = await wallet.send(3, overpayProofs, { - // preference: { sendPreference: [{ amount: 1, count: 3 }] } - outputAmounts: { sendAmounts: [1, 1, 1], keepAmounts: [] } + outputAmounts: { sendAmounts: [1, 1, 1], keepAmounts: [1] } }); expect(result.send).toHaveLength(3); From 57d6d02bfe8854424edb4e6c5b32dd8ea594d718 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 23 Oct 2024 01:16:25 +0200 Subject: [PATCH 51/70] works, still with comments --- src/CashuWallet.ts | 63 +++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 80eae6b8..0dbf4650 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -211,11 +211,16 @@ class CashuWallet { * @param unit optional unit to get keys for * @returns keyset */ - async getKeys(keysetId?: string): Promise { + async getKeys(keysetId?: string, forceRefresh?: boolean): Promise { if (!keysetId) { - const allKeysets = await this.mint.getKeySets(); - const keysetToActivate = this.getActiveKeyset(allKeysets.keysets); - keysetId = keysetToActivate.id; + if (!(this._keysets.length > 0) || forceRefresh) { + const allKeysets = await this.mint.getKeySets(); + const keysetToActivate = this.getActiveKeyset(allKeysets.keysets); + keysetId = keysetToActivate.id; + } else { + const localKeyset = this.getActiveKeyset(this._keysets); + keysetId = localKeyset.id; + } } if (this._keys.get(keysetId)) { this.keysetId = keysetId; @@ -252,6 +257,7 @@ class CashuWallet { privkey?: string; } ): Promise> { + console.log(`enter receive: token: ${token} | options: ${JSON.stringify(options)}`); if (typeof token === 'string') { token = getDecodedToken(token); } @@ -325,6 +331,7 @@ class CashuWallet { includeFees?: boolean; } ): Promise { + console.log(`enter send: amount: ${amount} | proofs: ${proofs.length} | options: ${JSON.stringify(options)}`); if (sumProofs(proofs) < amount) { throw new Error('Not enough funds available to send'); } @@ -343,30 +350,15 @@ class CashuWallet { options?.keysetId) // these options require a swap ) { // we need to swap - console.log('NOW WE SWAP'); // input selection, needs fees because of the swap const { keep: keepProofsSelect, send: sendProofs } = this.selectProofsToSend( proofs, amount, true ); - console.log(`Selected amount: ${sumProofs(sendProofs)}`); + console.log(`Keep proofs: ${keepProofsSelect.length} | Amount: ${sumProofs(keepProofsSelect)} <> Send proofs: ${sendProofs.length} | Amount: ${sumProofs(sendProofs)}`); options?.proofsWeHave?.push(...keepProofsSelect); - // // we need to include the fees to the outputs of the swap - // if (options?.includeFees) { - // const keyset = await this.getKeys(options.keysetId); - // const outputAmounts = splitAmount(amount, keyset.keys) - // const dummyProofs: Array = outputAmounts.map((amount: number) => { - // return { - // amount: amount, - // id: keyset.id, - // secret: "dummy", - // C: "dummy" - // } as Proof; - // }); - // amount += this.getFeesForProofs(dummyProofs); - // } const { keep, send } = await this.swap(amount, sendProofs, options); const keepProofs = keepProofsSelect.concat(keep); return { keep: keepProofs, send }; @@ -383,6 +375,7 @@ class CashuWallet { amountToSend: number, includeFees?: boolean ): SendResponse { + console.log(`selectProofsToSend: length: ${proofs.length} | sum: ${sumProofs(proofs)} | amountToSend: ${amountToSend} | includeFees: ${includeFees}`); const sortedProofs = proofs.sort((a: Proof, b: Proof) => a.amount - b.amount); const smallerProofs = sortedProofs .filter((p: Proof) => p.amount <= amountToSend) @@ -392,6 +385,7 @@ class CashuWallet { .sort((a: Proof, b: Proof) => a.amount - b.amount); const nextBigger = biggerProofs[0]; if (!smallerProofs.length && nextBigger) { + console.log(`No smaller proofs found. Next bigger proof: ${nextBigger.amount}`); return { keep: proofs.filter((p: Proof) => p.secret !== nextBigger.secret), send: [nextBigger] @@ -399,6 +393,7 @@ class CashuWallet { } if (!smallerProofs.length && !nextBigger) { + console.log('No proofs found, returning empty'); return { keep: proofs, send: [] }; } @@ -406,7 +401,8 @@ class CashuWallet { let selectedProofs = [smallerProofs[0]]; const returnedProofs = []; const feePPK = includeFees ? this.getFeesForProofs(selectedProofs) : 0; - remainder -= smallerProofs[0].amount - feePPK / 1000; + remainder -= selectedProofs[0].amount - feePPK / 1000; + console.log(`Selected proof: ${smallerProofs[0].amount} | Remainder: ${remainder} | Fee: ${feePPK}`); if (remainder > 0) { const { keep, send } = this.selectProofsToSend( smallerProofs.slice(1), @@ -417,9 +413,12 @@ class CashuWallet { returnedProofs.push(...keep); } - if (sumProofs(selectedProofs) < amountToSend && nextBigger) { + const selectedFeePPK = includeFees ? this.getFeesForProofs(selectedProofs) : 0; + if (sumProofs(selectedProofs) < amountToSend + selectedFeePPK && nextBigger) { + console.log("Selecting next bigger proof"); selectedProofs = [nextBigger]; } + console.log(`Selected proofs: ${selectedProofs.length} | Amount: ${sumProofs(selectedProofs)}`); return { keep: proofs.filter((p: Proof) => !selectedProofs.includes(p)), send: selectedProofs @@ -435,7 +434,7 @@ class CashuWallet { 0 ) + 999) / - 1000, + 1000, 0 ) ); @@ -447,7 +446,7 @@ class CashuWallet { Math.max( (nInputs * (this._keysets.find((k: MintKeyset) => k.id === keysetId)?.input_fee_ppk || 0) + 999) / - 1000, + 1000, 0 ) ); @@ -486,7 +485,7 @@ class CashuWallet { let amountToSend = amount; const amountAvailable = sumProofs(proofs); let amountToKeep = amountAvailable - amountToSend - this.getFeesForProofs(proofsToSend); - + console.log(`Amount to send: ${amountToSend} | Amount to keep: ${amountToKeep} | Amount available: ${amountAvailable}`); // send output selection let sendAmounts = options?.outputAmounts?.sendAmounts || splitAmount(amountToSend, keyset.keys); @@ -503,6 +502,7 @@ class CashuWallet { sendAmounts = sendAmounts.concat(sendAmountsFee); amountToSend += outputFee; amountToKeep -= outputFee; + console.log(`Amount to send: ${amountToSend} | Amount to keep: ${amountToKeep} | Output fee: ${outputFee}`); } // keep output selection @@ -525,17 +525,14 @@ class CashuWallet { } if (amountToSend + this.getFeesForProofs(proofsToSend) > amountAvailable) { - throw new Error('Not enough funds available for swap'); + console.error(`Not enough funds available (${amountAvailable}) for swap amountToSend: ${amountToSend} + fee: ${this.getFeesForProofs(proofsToSend)} | length: ${proofsToSend.length}`); + throw new Error(`Not enough funds available for swap`); } if (amountToSend + this.getFeesForProofs(proofsToSend) + amountToKeep != amountAvailable) { throw new Error('Amounts do not match for swap'); } - // console.log(`# swap: amountToKeep: ${amountToKeep}`); - // console.log(`# swap: amountToSend: ${amountToSend}`); - // console.log(`# swap: keepAmounts: ${keepAmounts}`); - // console.log(`# swap: sendAmounts: ${sendAmounts}`); options.outputAmounts = { keepAmounts: keepAmounts, sendAmounts: sendAmounts @@ -843,15 +840,13 @@ class CashuWallet { payload: SwapPayload; blindedMessages: BlindedTransaction; } { + console.log(`### createSwapPayload: amount: ${amount}, proofsToSend: ${proofsToSend.length}, keyset: ${keyset.id}, outputAmounts: ${outputAmounts}, counter: ${counter}, pubkey: ${pubkey}, privkey: ${privkey}`); const totalAmount = proofsToSend.reduce((total: number, curr: Proof) => total + curr.amount, 0); - console.log(`# createSwapPayload: amount: ${amount} | input amount: ${totalAmount}`); if (outputAmounts && outputAmounts.sendAmounts && !outputAmounts.keepAmounts) { - console.log(`# splitting amount: ${totalAmount} - ${amount}`); outputAmounts.keepAmounts = splitAmount( totalAmount - amount - this.getFeesForProofs(proofsToSend), keyset.keys ); - console.log(`# keepAmounts: ${outputAmounts.keepAmounts}`); } const keepBlindedMessages = this.createRandomBlindedMessages( totalAmount - amount - this.getFeesForProofs(proofsToSend), @@ -898,7 +893,6 @@ class CashuWallet { inputs: proofsToSend, outputs: [...blindedMessages.blindedMessages] }; - console.log(`# createSwapPayload: amount: ${amount}`); return { payload, blindedMessages }; } /** @@ -937,7 +931,6 @@ class CashuWallet { counter?: number, pubkey?: string ): BlindedMessageData & { amounts: Array } { - console.log(`# createRandomBlindedMessages: amount: ${amount} split: ${split}`); const amounts = splitAmount(amount, keyset.keys, split); return this.createBlindedMessages(amounts, keyset.id, counter, pubkey); } From 2f4df3c765de1d77f8a36f5c999ef37091cf11bd Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 23 Oct 2024 01:31:09 +0200 Subject: [PATCH 52/70] remove logging --- src/CashuWallet.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 0dbf4650..2dec36b9 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -257,7 +257,6 @@ class CashuWallet { privkey?: string; } ): Promise> { - console.log(`enter receive: token: ${token} | options: ${JSON.stringify(options)}`); if (typeof token === 'string') { token = getDecodedToken(token); } @@ -331,7 +330,7 @@ class CashuWallet { includeFees?: boolean; } ): Promise { - console.log(`enter send: amount: ${amount} | proofs: ${proofs.length} | options: ${JSON.stringify(options)}`); + console.log(`### SEND: ${amount} | ${sumProofs(proofs)}`); if (sumProofs(proofs) < amount) { throw new Error('Not enough funds available to send'); } @@ -356,7 +355,6 @@ class CashuWallet { amount, true ); - console.log(`Keep proofs: ${keepProofsSelect.length} | Amount: ${sumProofs(keepProofsSelect)} <> Send proofs: ${sendProofs.length} | Amount: ${sumProofs(sendProofs)}`); options?.proofsWeHave?.push(...keepProofsSelect); const { keep, send } = await this.swap(amount, sendProofs, options); @@ -367,6 +365,7 @@ class CashuWallet { if (sumProofs(sendProofOffline) < amount + expectedFee) { throw new Error('Not enough funds available to send'); } + return { keep: keepProofsOffline, send: sendProofOffline }; } @@ -375,7 +374,6 @@ class CashuWallet { amountToSend: number, includeFees?: boolean ): SendResponse { - console.log(`selectProofsToSend: length: ${proofs.length} | sum: ${sumProofs(proofs)} | amountToSend: ${amountToSend} | includeFees: ${includeFees}`); const sortedProofs = proofs.sort((a: Proof, b: Proof) => a.amount - b.amount); const smallerProofs = sortedProofs .filter((p: Proof) => p.amount <= amountToSend) @@ -385,7 +383,6 @@ class CashuWallet { .sort((a: Proof, b: Proof) => a.amount - b.amount); const nextBigger = biggerProofs[0]; if (!smallerProofs.length && nextBigger) { - console.log(`No smaller proofs found. Next bigger proof: ${nextBigger.amount}`); return { keep: proofs.filter((p: Proof) => p.secret !== nextBigger.secret), send: [nextBigger] @@ -393,7 +390,6 @@ class CashuWallet { } if (!smallerProofs.length && !nextBigger) { - console.log('No proofs found, returning empty'); return { keep: proofs, send: [] }; } @@ -402,7 +398,6 @@ class CashuWallet { const returnedProofs = []; const feePPK = includeFees ? this.getFeesForProofs(selectedProofs) : 0; remainder -= selectedProofs[0].amount - feePPK / 1000; - console.log(`Selected proof: ${smallerProofs[0].amount} | Remainder: ${remainder} | Fee: ${feePPK}`); if (remainder > 0) { const { keep, send } = this.selectProofsToSend( smallerProofs.slice(1), @@ -415,10 +410,8 @@ class CashuWallet { const selectedFeePPK = includeFees ? this.getFeesForProofs(selectedProofs) : 0; if (sumProofs(selectedProofs) < amountToSend + selectedFeePPK && nextBigger) { - console.log("Selecting next bigger proof"); selectedProofs = [nextBigger]; } - console.log(`Selected proofs: ${selectedProofs.length} | Amount: ${sumProofs(selectedProofs)}`); return { keep: proofs.filter((p: Proof) => !selectedProofs.includes(p)), send: selectedProofs @@ -485,7 +478,6 @@ class CashuWallet { let amountToSend = amount; const amountAvailable = sumProofs(proofs); let amountToKeep = amountAvailable - amountToSend - this.getFeesForProofs(proofsToSend); - console.log(`Amount to send: ${amountToSend} | Amount to keep: ${amountToKeep} | Amount available: ${amountAvailable}`); // send output selection let sendAmounts = options?.outputAmounts?.sendAmounts || splitAmount(amountToSend, keyset.keys); @@ -502,7 +494,6 @@ class CashuWallet { sendAmounts = sendAmounts.concat(sendAmountsFee); amountToSend += outputFee; amountToKeep -= outputFee; - console.log(`Amount to send: ${amountToSend} | Amount to keep: ${amountToKeep} | Output fee: ${outputFee}`); } // keep output selection @@ -840,7 +831,6 @@ class CashuWallet { payload: SwapPayload; blindedMessages: BlindedTransaction; } { - console.log(`### createSwapPayload: amount: ${amount}, proofsToSend: ${proofsToSend.length}, keyset: ${keyset.id}, outputAmounts: ${outputAmounts}, counter: ${counter}, pubkey: ${pubkey}, privkey: ${privkey}`); const totalAmount = proofsToSend.reduce((total: number, curr: Proof) => total + curr.amount, 0); if (outputAmounts && outputAmounts.sendAmounts && !outputAmounts.keepAmounts) { outputAmounts.keepAmounts = splitAmount( From 13519665ecad3c3af67b33e3171fff6676e416fd Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 23 Oct 2024 01:31:49 +0200 Subject: [PATCH 53/70] npm run format --- src/CashuWallet.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 2dec36b9..e2b5eaea 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -427,7 +427,7 @@ class CashuWallet { 0 ) + 999) / - 1000, + 1000, 0 ) ); @@ -439,7 +439,7 @@ class CashuWallet { Math.max( (nInputs * (this._keysets.find((k: MintKeyset) => k.id === keysetId)?.input_fee_ppk || 0) + 999) / - 1000, + 1000, 0 ) ); @@ -516,7 +516,11 @@ class CashuWallet { } if (amountToSend + this.getFeesForProofs(proofsToSend) > amountAvailable) { - console.error(`Not enough funds available (${amountAvailable}) for swap amountToSend: ${amountToSend} + fee: ${this.getFeesForProofs(proofsToSend)} | length: ${proofsToSend.length}`); + console.error( + `Not enough funds available (${amountAvailable}) for swap amountToSend: ${amountToSend} + fee: ${this.getFeesForProofs( + proofsToSend + )} | length: ${proofsToSend.length}` + ); throw new Error(`Not enough funds available for swap`); } From 3f55dba6c1ecf39e3da5c6d63e31d7123e9e27a3 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 23 Oct 2024 01:35:45 +0200 Subject: [PATCH 54/70] remove integration tests from unit test pipeline --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ae405f2..5e33c1f3 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "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", From e2247e01cb5d425feaba31da35777f5c18988f89 Mon Sep 17 00:00:00 2001 From: Egge Date: Wed, 23 Oct 2024 14:37:28 +0100 Subject: [PATCH 55/70] removed payLnInvoice handlers --- src/CashuWallet.ts | 58 ---------------------------------------------- 1 file changed, 58 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index e2b5eaea..c01b907b 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -755,64 +755,6 @@ 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 - * @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 - * @param options.privkey? optionally set a private key to unlock P2PK locked secrets - * @returns - */ - async payLnInvoice( - invoice: string, - proofsToSend: Array, - meltQuote?: MeltQuoteResponse, - options?: { - keysetId?: string; - counter?: number; - privkey?: string; - } - ): Promise { - if (!meltQuote) { - meltQuote = await this.mint.createMeltQuote({ unit: this._unit, request: invoice }); - } - return await this.meltProofs(meltQuote, proofsToSend, { - keysetId: options?.keysetId, - counter: options?.counter, - privkey: options?.privkey - }); - } - - /** - * Helper function to ingest a Cashu token and pay a Lightning invoice with it. - * @param invoice Lightning invoice - * @param token cashu token - * @param meltQuote melt quote for the invoice - * @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 - */ - async payLnInvoiceWithToken( - invoice: string, - token: string, - meltQuote: MeltQuoteResponse, - options?: { - keysetId?: string; - counter?: number; - } - ): Promise { - const decodedToken = getDecodedToken(token); - const proofs = decodedToken.token - .filter((x: TokenEntry) => x.mint === this.mint.mintUrl) - .flatMap((t: TokenEntry) => t.proofs); - return this.payLnInvoice(invoice, proofs, meltQuote, { - keysetId: options?.keysetId, - counter: options?.counter - }); - } - /** * Creates a split payload * @param amount amount to send From bfeade5dd4bbffe3f8e8e796f904b42a694d6299 Mon Sep 17 00:00:00 2001 From: Egge Date: Wed, 23 Oct 2024 14:37:39 +0100 Subject: [PATCH 56/70] remove payLn tests --- test/integration.test.ts | 2 - test/wallet.test.ts | 82 ---------------------------------------- 2 files changed, 84 deletions(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index be751ac3..b5332da9 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -94,7 +94,6 @@ describe('mint api', () => { expect(quote_).toBeDefined(); const sendResponse = await wallet.send(10, tokens.proofs, { includeFees: true }); - // const response = await wallet.payLnInvoice(mintQuote.request, sendResponse.send, quote); const response = await wallet.meltProofs(quote, sendResponse.send); expect(response).toBeDefined(); // expect that we have received the fee back, since it was internal @@ -125,7 +124,6 @@ describe('mint api', () => { expect(quote_).toBeDefined(); const sendResponse = await wallet.send(2000 + fee, tokens.proofs, { includeFees: true }); - // const response = await wallet.payLnInvoice(externalInvoice, sendResponse.send, meltQuote); const response = await wallet.meltProofs(meltQuote, sendResponse.send); expect(response).toBeDefined(); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index 7a572fa3..f8b5c51a 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -225,88 +225,6 @@ describe('checkProofsSpent', () => { }); }); -describe('payLnInvoice', () => { - const proofs = [ - { - id: '009a1f293253e41e', - amount: 1, - secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', - C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' - } - ]; - test('test payLnInvoice base case', async () => { - nock(mintUrl) - .get('/v1/melt/quote/bolt11/test') - .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.checkMeltQuote('test'); - - const result = await wallet.payLnInvoice(invoice, proofs, meltQuote); - - expect(result).toEqual({ quote: meltQuote, change: [] }); - }); - test('test payLnInvoice change', async () => { - nock(mintUrl) - .get('/v1/melt/quote/bolt11/test') - .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, { - quote: 'test_melt_quote_id', - amount: 2000, - fee_reserve: 20, - payment_preimage: 'asd', - state: 'PAID', - change: [ - { - id: '009a1f293253e41e', - amount: 2, - C_: '0361a2725cfd88f60ded718378e8049a4a6cee32e214a9870b44c3ffea2dc9e625' - } - ] - }); - - const wallet = new CashuWallet(mint, { unit }); - const meltQuote = await wallet.checkMeltQuote('test'); - const result = await wallet.payLnInvoice(invoice, [{ ...proofs[0], amount: 3 }], meltQuote); - - expect(result.quote.state == MeltQuoteState.PAID).toBe(true); - expect(result.quote.payment_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 - .payLnInvoice(invoice, proofs, {} as MeltQuoteResponse) - .catch((e) => e); - - expect(result).toEqual(new Error('bad response')); - }); -}); - describe('requestTokens', () => { test('test requestTokens', async () => { nock(mintUrl) From c6efe40930ae0408aae656ab2e62a6d9598e5a0d Mon Sep 17 00:00:00 2001 From: Egge Date: Wed, 23 Oct 2024 14:50:43 +0100 Subject: [PATCH 57/70] added removal to migration doc --- migration-2.0.0.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 migration-2.0.0.md diff --git a/migration-2.0.0.md b/migration-2.0.0.md new file mode 100644 index 00000000..8881ca82 --- /dev/null +++ b/migration-2.0.0.md @@ -0,0 +1,18 @@ +# Version 2.0.0 Migration guide + +⚠️ Upgrading to version 2.0.0 will come with breaking changes! Please follow the migration guide for a smooth transition to the new version. + +## Breaking changes + +### `CashuWallet` interface changes + +#### removed `payLnInvoice` helper + +The helper function was removed. Instead users will have to manage a melt quote manually: + +```ts +const quote = await wallet.createMeltQuote(invoice); +const totalAmount = quote.fee_reserve + invoiceAmount; +const { keep, send } = await wallet.send(totalAmount, proofs); +const payRes = await wallet.meltProofs(quote, send); +``` From c6a7ab7689a9f7e645fd7fcbbca4c2127248b6ee Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 24 Oct 2024 19:11:05 +0900 Subject: [PATCH 58/70] clean up version migration files --- migration-1.0.0.md | 17 +++-------------- migration-2.0.0.md | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/migration-1.0.0.md b/migration-1.0.0.md index aad4c493..ecc6ac8c 100644 --- a/migration-1.0.0.md +++ b/migration-1.0.0.md @@ -46,9 +46,9 @@ type MintQuoteResponse = { }; ``` -where `request` is the invoice to be paid, and `quote` is the identifier used to pass to `mintProofs()`. +where `request` is the invoice to be paid, and `quote` is the identifier used to pass to `mintTokens()`. -**`requestTokens()` --> `mintProofs()`** +**`requestTokens()` --> `mintTokens()`** --- @@ -67,7 +67,7 @@ type MeltQuoteResponse = { }; ``` -where `quote` is the identifier to pass to `meltProofs()` +where `quote` is the identifier to pass to `meltTokens()` --- @@ -119,17 +119,6 @@ type BlindedMessage { --- -**`amountPreference`** is not used anymore. - -`preference?: Array;` -> `outputAmounts?: OutputAmounts;` - -- in `SendResponse`, `returnChange` is now called `keep` -- `mintTokens` is now called `mintProofs` -- `meltTokens` is now called `meltProofs` -- `CashuMint.split` is now called `CashuMint.swap` - ---- - ### 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. diff --git a/migration-2.0.0.md b/migration-2.0.0.md index 8881ca82..cde883d3 100644 --- a/migration-2.0.0.md +++ b/migration-2.0.0.md @@ -16,3 +16,30 @@ const totalAmount = quote.fee_reserve + invoiceAmount; const { keep, send } = await wallet.send(totalAmount, proofs); const payRes = await wallet.meltProofs(quote, send); ``` + +--- + +#### Preference for outputs are now passed as a object of simple arrays + +**`AmountPreference`** is not used anymore. + +`preference?: Array;` -> `outputAmounts?: OutputAmounts;` + +where + +```typescript + +export type OutputAmounts = { + sendAmounts: Array; + keepAmounts?: Array; +}; + + +``` + +#### renamed functions + +- in `SendResponse`, `returnChange` is now called `keep` +- `CashuWallet.mintTokens()` is now called `CashuWallet.mintProofs()` +- `CashuWallet.meltTokens()` is now called `CashuWallet.meltProofs()` +- `CashuMint.split()` is now called `CashuMint.swap()` \ No newline at end of file From cfad6576b736fa5532825ea659efe6b9ab3c0945 Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 24 Oct 2024 19:21:35 +0900 Subject: [PATCH 59/70] union type for order --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 2c22f72b..964029bb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -25,7 +25,7 @@ function splitAmount( value: number, keyset: Keys, split?: Array, - order?: string + order?: "desc" | "asc" ): Array { const chunks: Array = []; if (split) { From a06ada13afc4b0c8530b8aefbbcc370f2e1499fb Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 24 Oct 2024 21:26:14 +0900 Subject: [PATCH 60/70] some review fixes & docs completion --- src/CashuWallet.ts | 96 +++++++++++++++++++++++---------- src/utils.ts | 132 +++++++++++++++++++++++++++------------------ 2 files changed, 148 insertions(+), 80 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index c01b907b..7fb6ec12 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -41,7 +41,15 @@ import { import { createP2PKsecret, getSignedProofs } from '@cashu/crypto/modules/client/NUT11'; import { type Proof as NUT11Proof } from '@cashu/crypto/modules/common/index'; + +/** + * The default number of proofs per denomination to keep in a wallet. +*/ const DEFAULT_DENOMINATION_TARGET = 3; + +/** + * The default unit for the wallet, if not specified in constructor. + */ const DEFAULT_UNIT = 'sat'; /** @@ -67,6 +75,7 @@ class CashuWallet { * @param mintInfo mint info from the mint (will be fetched from mint if not provided) * @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 + * @param loadMint if set to true info will be loaded from mint */ constructor( mint: CashuMint, @@ -183,9 +192,10 @@ class CashuWallet { } return activeKeyset; } + /** * Get keysets from the mint with the unit of the wallet - * @returns keysets + * @returns keysets with wallets unit */ async getKeySets(): Promise> { const allKeysets = await this.mint.getKeySets(); @@ -194,6 +204,10 @@ class CashuWallet { return this._keysets; } + /** + * Get all active keys from the mint and set the keyset with the lowest fees as the active wallet keyset. + * @returns keyset + */ async getAllKeys(): Promise> { const keysets = await this.mint.getKeys(); this._keys = new Map(keysets.keysets.map((k: MintKeys) => [k.id, k])); @@ -208,7 +222,7 @@ class CashuWallet { * Otherwise, we select an active keyset with the unit of the wallet. * * @param keysetId optional keysetId to get keys for - * @param unit optional unit to get keys for + * @param forceRefresh? if set to true, it will force refresh the keyset from the mint * @returns keyset */ async getKeys(keysetId?: string, forceRefresh?: boolean): Promise { @@ -238,12 +252,13 @@ class CashuWallet { /** * 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? Deprecated. Use `outputAmounts` instead. Optional preference for splitting proofs into specific amounts. - * @param outputAmounts? optionally specify the output's amounts to keep and to send. - * @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 + * @param {(string|Token)} token - Cashu token, either as string or decoded + * @param options.keysetId? 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 options.outputAmounts? optionally specify the output's amounts to keep and to send. + * @param options.proofsWeHave? optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts + * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param options.pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! + * @param options.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( @@ -274,12 +289,12 @@ class CashuWallet { /** * Receive a single cashu token entry * @param tokenEntry a single entry of a cashu token - * @param preference? Deprecated. Use `outputAmounts` instead. Optional preference for splitting proofs into specific amounts. - * @param outputAmounts? optionally specify the output's amounts to keep. - * @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 + * @param options.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 options.outputAmounts? optionally specify the output's amounts to keep. + * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param options.pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! + * @param options.privkey? will create a signature on the @param tokenEntry secrets if set + * @returns {Promise>} New token entry with newly created proofs, proofs that had errors */ async receiveTokenEntry( tokenEntry: TokenEntry, @@ -316,6 +331,20 @@ class CashuWallet { return proofs; } + /** + * Send proofs of a given amount, by providing at least the required amount of proofs + * @param amount amount to send + * @param proofs array of proofs (accumulated amount of proofs must be >= than amount) + * @param options.outputAmounts? optionally specify the output's amounts to keep and send. + * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param options.proofsWeHave? optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts + * @param options.pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! + * @param options.privkey? will create a signature on the output secrets if set + * @param options.keysetId? 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 options.offline? optionally send proofs offline. + * @param options.includeFees? optionally include fees in the response. + * @returns {SendResponse} + */ async send( amount: number, proofs: Array, @@ -330,7 +359,6 @@ class CashuWallet { includeFees?: boolean; } ): Promise { - console.log(`### SEND: ${amount} | ${sumProofs(proofs)}`); if (sumProofs(proofs) < amount) { throw new Error('Not enough funds available to send'); } @@ -418,6 +446,11 @@ class CashuWallet { }; } + /** + * calculates the fees based on inputs (proofs) + * @param proofs input proofs to calculate fees for + * @returns fee amount + */ getFeesForProofs(proofs: Array): number { const fees = Math.floor( Math.max( @@ -427,19 +460,25 @@ class CashuWallet { 0 ) + 999) / - 1000, + 1000, 0 ) ); return fees; } + /** + * calculates the fees based on inputs for a given keyset + * @param nInputs number of inputs + * @param keysetId keysetId used to lookup `input_fee_ppk` + * @returns fee amount + */ getFeesForKeyset(nInputs: number, keysetId: string): number { const fees = Math.floor( Math.max( (nInputs * (this._keysets.find((k: MintKeyset) => k.id === keysetId)?.input_fee_ppk || 0) + 999) / - 1000, + 1000, 0 ) ); @@ -452,11 +491,13 @@ class CashuWallet { * if both amount and preference are set, but the preference cannot fulfill the amount, then we use the default split * @param amount amount to send while performing the optimal split (least proofs possible). can be set to undefined if preference is set * @param proofs proofs matching that amount - * @param preference Deprecated. Use `outputAmounts` instead. Optional preference for splitting proofs into specific amounts. - * @param outputAmounts? optionally specify the output's amounts to keep and to send. - * @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 + * @param options.outputAmounts? optionally specify the output's amounts to keep and to send. + * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param options.keysetId? 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 options.includeFees? include estimated fees for the receiver to receive the proofs + * @param options.proofsWeHave? optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts + * @param options.pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! + * @param options.privkey? will create a signature on the @param proofs secrets if set * @returns promise of the change- and send-proofs */ async swap( @@ -569,6 +610,7 @@ class CashuWallet { * Regenerates * @param start set starting point for count (first cycle for each keyset should usually be 0) * @param count set number of blinded messages that should be generated + * @param options.keysetId set a custom keysetId to restore from. keysetIds can be loaded with `CashuMint.getKeySets()` * @returns proofs */ async restore( @@ -630,10 +672,10 @@ class CashuWallet { * @param amount amount to request * @param quote ID of mint quote * @param options.keysetId? optionally set keysetId for blank outputs for returned change. - * @param preference? Deprecated. Use `outputAmounts` instead. Optional preference for splitting proofs into specific amounts. - * @param outputAmounts? optionally specify the output's amounts to keep and to send. - * @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 options.preference? Deprecated. Use `outputAmounts` instead. Optional preference for splitting proofs into specific amounts. + * @param options.outputAmounts? optionally specify the output's amounts to keep and to send. + * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect + * @param options.pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! * @returns proofs */ async mintProofs( @@ -833,7 +875,7 @@ class CashuWallet { } /** * returns proofs that are already spent (use for keeping wallet state clean) - * @param proofs (only the 'Y' field is required) + * @param proofs (only the `secret` field is required) * @returns */ async checkProofsSpent(proofs: Array): Promise> { diff --git a/src/utils.ts b/src/utils.ts index 964029bb..a580f200 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -21,13 +21,21 @@ import { sha256 } from '@noble/hashes/sha256'; import { decodeCBOR, encodeCBOR } from './cbor.js'; import { PaymentRequest } from './model/PaymentRequest.js'; -function splitAmount( +/** + * Splits the amount into denominations of the provided @param keyset + * @param value amount to split + * @param keyset keys to look up split amounts + * @param split? optional custom split amounts + * @param order? optional order for split amounts (default: "asc") + * @returns Array of split amounts + * @throws Error if @param split amount is greater than @param value amount + */ +export function splitAmount( value: number, keyset: Keys, split?: Array, order?: "desc" | "asc" ): Array { - const chunks: Array = []; if (split) { if (split.reduce((a: number, b: number) => a + b, 0) > value) { throw new Error( @@ -37,23 +45,38 @@ function splitAmount( )} > ${value}` ); } - chunks.push(...getPreference(value, keyset, split)); + split.forEach((amt: number) => { + if (!hasCorrespondingKey(amt, keyset)) { + throw new Error('Provided amount preferences do not match the amounts of the mint keyset.'); + } + }) value = value - - chunks.reduce((curr: number, acc: number) => { + split.reduce((curr: number, acc: number) => { return curr + acc; }, 0); } + else { + split = []; + } const sortedKeyAmounts = getKeysetAmounts(keyset); sortedKeyAmounts.forEach((amt: number) => { const q = Math.floor(value / amt); - for (let i = 0; i < q; ++i) chunks.push(amt); + for (let i = 0; i < q; ++i) split?.push(amt); value %= amt; }); - return chunks.sort((a, b) => (order === 'desc' ? b - a : a - b)); + return split.sort((a, b) => (order === 'desc' ? b - a : a - b)); } -function getKeepAmounts( +/** + * Creates a list of amounts to keep based on the proofs we have and the proofs we want to reach. + * @param proofsWeHave complete set of proofs stored (from current mint) + * @param amountToKeep amount to keep + * @param keys keys of current keyset + * @param targetCount the target number of proofs to reach + * @returns an array of amounts to keep + */ +export function getKeepAmounts( proofsWeHave: Array, amountToKeep: number, keys: Keys, @@ -83,16 +106,15 @@ function getKeepAmounts( }); } const sortedAmountsWeWant = amountsWeWant.sort((a, b) => a - b); - // console.log(`# getKeepAmounts: amountToKeep: ${amountToKeep}`); - // console.log(`# getKeepAmounts: amountsWeHave: ${amountsWeHave}`); - // console.log(`# getKeepAmounts: amountsWeWant: ${sortedAmountsWeWant}`); return sortedAmountsWeWant; } - -// function isPowerOfTwo(number: number) { -// return number && !(number & (number - 1)); -// } -function getKeysetAmounts(keyset: Keys, order = 'desc'): Array { +/** + * returns the amounts in the keyset sorted by the order specified + * @param keyset to search in + * @param order order to sort the amounts in + * @returns the amounts in the keyset sorted by the order specified + */ +export function getKeysetAmounts(keyset: Keys, order: "asc" | "desc" = 'desc'): Array { if (order == 'desc') { return Object.keys(keyset) .map((k: string) => parseInt(k)) @@ -103,44 +125,59 @@ function getKeysetAmounts(keyset: Keys, order = 'desc'): Array { .sort((a: number, b: number) => a - b); } -function hasCorrespondingKey(amount: number, keyset: Keys) { +/** + * Checks if the provided amount is in the keyset. + * @param amount amount to check + * @param keyset to search in + * @returns true if the amount is in the keyset, false otherwise + */ +export function hasCorrespondingKey(amount: number, keyset: Keys): boolean { return amount in keyset; } -function getPreference(amount: number, keyset: Keys, split: Array): Array { - const chunks: Array = []; - split.forEach((splitAmount: number) => { - if (!hasCorrespondingKey(splitAmount, keyset)) { - throw new Error('Provided amount preferences do not match the amounts of the mint keyset.'); - } - chunks.push(splitAmount); - }); - return chunks; -} - -function bytesToNumber(bytes: Uint8Array): bigint { +/** + * Converts a bytes array to a number. + * @param bytes to convert to number + * @returns number + */ +export function bytesToNumber(bytes: Uint8Array): bigint { return hexToNumber(bytesToHex(bytes)); } -function hexToNumber(hex: string): bigint { +/** + * Converts a hex string to a number. + * @param hex to convert to number + * @returns number + */ +export function hexToNumber(hex: string): bigint { return BigInt(`0x${hex}`); } -//used for json serialization -function bigIntStringify(_key: unknown, value: T) { +/** + * Helper function to stringify a bigint + * @param _key + * @param value to stringify + * @returns stringified bigint + */ +export function bigIntStringify(_key: unknown, value: T): string | T { return typeof value === 'bigint' ? value.toString() : value; } /** * Helper function to encode a v3 cashu token - * @param token - * @returns + * @param token to encode + * @returns encoded token */ -function getEncodedToken(token: Token): string { +export function getEncodedToken(token: Token): string { return TOKEN_PREFIX + TOKEN_VERSION + encodeJsonToBase64(token); } -function getEncodedTokenV4(token: Token): string { +/** + * Helper function to encode a v4 cashu token + * @param token to encode + * @returns encoded token + */ +export function getEncodedTokenV4(token: Token): string { const idMap: { [id: string]: Array } = {}; let mint: string | undefined = undefined; for (let i = 0; i < token.token.length; i++) { @@ -189,7 +226,7 @@ function getEncodedTokenV4(token: Token): string { * @param token an encoded cashu token (cashuAey...) * @returns cashu token object */ -function getDecodedToken(token: string) { +export function getDecodedToken(token: string) { // remove prefixes const uriPrefixes = ['web+cashu://', 'cashu://', 'cashu:', 'cashu']; uriPrefixes.forEach((prefix: string) => { @@ -202,10 +239,11 @@ function getDecodedToken(token: string) { } /** - * @param token - * @returns + * Helper function to decode different versions of cashu tokens into an object + * @param token an encoded cashu token (cashuAey...) + * @returns cashu Token object */ -function handleTokens(token: string): Token { +export function handleTokens(token: string): Token { const version = token.slice(0, 1); const encodedToken = token.slice(1); if (version === 'A') { @@ -248,7 +286,7 @@ export function deriveKeysetId(keys: Keys) { return '00' + hashHex; } -function mergeUInt8Arrays(a1: Uint8Array, a2: Uint8Array): Uint8Array { +export function mergeUInt8Arrays(a1: Uint8Array, a2: Uint8Array): Uint8Array { // sum of individual array lengths const mergedArray = new Uint8Array(a1.length + a2.length); mergedArray.set(a1); @@ -286,18 +324,6 @@ export function sumProofs(proofs: Array) { return proofs.reduce((acc: number, proof: Proof) => acc + proof.amount, 0); } -function decodePaymentRequest(paymentRequest: string) { +export function decodePaymentRequest(paymentRequest: string) { return PaymentRequest.fromEncodedRequest(paymentRequest); } - -export { - bigIntStringify, - bytesToNumber, - getDecodedToken, - getEncodedToken, - getEncodedTokenV4, - hexToNumber, - splitAmount, - getKeepAmounts, - decodePaymentRequest -}; From 3c92c218221bc4d5e1edf72af870196cb59173cf Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 24 Oct 2024 21:30:01 +0900 Subject: [PATCH 61/70] add missing param description --- src/CashuWallet.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 7fb6ec12..5760d3b2 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -73,6 +73,7 @@ class CashuWallet { * @param keys public keys from the mint (will be fetched from mint if not provided) * @param keysets keysets from the mint (will be fetched from mint if not provided) * @param mintInfo mint info from the mint (will be fetched from mint if not provided) + * @param denominationTarget target number proofs per denomination (default: see @constant DEFAULT_DENOMINATION_TARGET) * @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 * @param loadMint if set to true info will be loaded from mint From 3f9d4a8006992201943151e5da6da3fb9147798f Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 24 Oct 2024 21:30:46 +0900 Subject: [PATCH 62/70] mark optional params --- src/CashuWallet.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 5760d3b2..f4631d70 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -69,14 +69,14 @@ class CashuWallet { /** * @param mint Cashu mint instance is used to make api calls - * @param unit optionally set unit (default is 'sat') - * @param keys public keys from the mint (will be fetched from mint if not provided) - * @param keysets keysets from the mint (will be fetched from mint if not provided) - * @param mintInfo mint info from the mint (will be fetched from mint if not provided) - * @param denominationTarget target number proofs per denomination (default: see @constant DEFAULT_DENOMINATION_TARGET) - * @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. + * @param options.unit optionally set unit (default is 'sat') + * @param options.keys public keys from the mint (will be fetched from mint if not provided) + * @param options.keysets keysets from the mint (will be fetched from mint if not provided) + * @param options.mintInfo mint info from the mint (will be fetched from mint if not provided) + * @param options.denominationTarget target number proofs per denomination (default: see @constant DEFAULT_DENOMINATION_TARGET) + * @param options.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 - * @param loadMint if set to true info will be loaded from mint + * @param options.loadMint if set to true info will be loaded from mint */ constructor( mint: CashuMint, From e3e26b017adec94f0dc2e8f6fd30e2b28dff86fb Mon Sep 17 00:00:00 2001 From: gandlaf21 Date: Thu, 24 Oct 2024 21:49:02 +0900 Subject: [PATCH 63/70] format --- migration-2.0.0.md | 9 +++------ src/CashuWallet.ts | 17 ++++++++--------- src/utils.ts | 31 +++++++++++++++---------------- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/migration-2.0.0.md b/migration-2.0.0.md index cde883d3..a4b30c1f 100644 --- a/migration-2.0.0.md +++ b/migration-2.0.0.md @@ -19,7 +19,7 @@ const payRes = await wallet.meltProofs(quote, send); --- -#### Preference for outputs are now passed as a object of simple arrays +#### Preference for outputs are now passed as a object of simple arrays **`AmountPreference`** is not used anymore. @@ -28,18 +28,15 @@ const payRes = await wallet.meltProofs(quote, send); where ```typescript - export type OutputAmounts = { sendAmounts: Array; keepAmounts?: Array; }; - - ``` -#### renamed functions +#### renamed functions - in `SendResponse`, `returnChange` is now called `keep` - `CashuWallet.mintTokens()` is now called `CashuWallet.mintProofs()` - `CashuWallet.meltTokens()` is now called `CashuWallet.meltProofs()` -- `CashuMint.split()` is now called `CashuMint.swap()` \ No newline at end of file +- `CashuMint.split()` is now called `CashuMint.swap()` diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index f4631d70..8d220960 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -41,10 +41,9 @@ import { import { createP2PKsecret, getSignedProofs } from '@cashu/crypto/modules/client/NUT11'; import { type Proof as NUT11Proof } from '@cashu/crypto/modules/common/index'; - /** - * The default number of proofs per denomination to keep in a wallet. -*/ + * The default number of proofs per denomination to keep in a wallet. + */ const DEFAULT_DENOMINATION_TARGET = 3; /** @@ -76,7 +75,7 @@ class CashuWallet { * @param options.denominationTarget target number proofs per denomination (default: see @constant DEFAULT_DENOMINATION_TARGET) * @param options.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 - * @param options.loadMint if set to true info will be loaded from mint + * @param options.loadMint if set to true info will be loaded from mint */ constructor( mint: CashuMint, @@ -256,7 +255,7 @@ class CashuWallet { * @param {(string|Token)} token - Cashu token, either as string or decoded * @param options.keysetId? 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 options.outputAmounts? optionally specify the output's amounts to keep and to send. - * @param options.proofsWeHave? optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts + * @param options.proofsWeHave? optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect * @param options.pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! * @param options.privkey? will create a signature on the @param token secrets if set @@ -338,7 +337,7 @@ class CashuWallet { * @param proofs array of proofs (accumulated amount of proofs must be >= than amount) * @param options.outputAmounts? optionally specify the output's amounts to keep and send. * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect - * @param options.proofsWeHave? optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts + * @param options.proofsWeHave? optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts * @param options.pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! * @param options.privkey? will create a signature on the output secrets if set * @param options.keysetId? override the keysetId derived from the current mintKeys with a custom one. This should be a keyset that was fetched from the `/keysets` endpoint @@ -461,7 +460,7 @@ class CashuWallet { 0 ) + 999) / - 1000, + 1000, 0 ) ); @@ -479,7 +478,7 @@ class CashuWallet { Math.max( (nInputs * (this._keysets.find((k: MintKeyset) => k.id === keysetId)?.input_fee_ppk || 0) + 999) / - 1000, + 1000, 0 ) ); @@ -496,7 +495,7 @@ class CashuWallet { * @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect * @param options.keysetId? 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 options.includeFees? include estimated fees for the receiver to receive the proofs - * @param options.proofsWeHave? optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts + * @param options.proofsWeHave? optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts * @param options.pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! * @param options.privkey? will create a signature on the @param proofs secrets if set * @returns promise of the change- and send-proofs diff --git a/src/utils.ts b/src/utils.ts index a580f200..67e54151 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -25,7 +25,7 @@ import { PaymentRequest } from './model/PaymentRequest.js'; * Splits the amount into denominations of the provided @param keyset * @param value amount to split * @param keyset keys to look up split amounts - * @param split? optional custom split amounts + * @param split? optional custom split amounts * @param order? optional order for split amounts (default: "asc") * @returns Array of split amounts * @throws Error if @param split amount is greater than @param value amount @@ -34,7 +34,7 @@ export function splitAmount( value: number, keyset: Keys, split?: Array, - order?: "desc" | "asc" + order?: 'desc' | 'asc' ): Array { if (split) { if (split.reduce((a: number, b: number) => a + b, 0) > value) { @@ -45,18 +45,17 @@ export function splitAmount( )} > ${value}` ); } - split.forEach((amt: number) => { + split.forEach((amt: number) => { if (!hasCorrespondingKey(amt, keyset)) { throw new Error('Provided amount preferences do not match the amounts of the mint keyset.'); } - }) + }); value = value - split.reduce((curr: number, acc: number) => { return curr + acc; }, 0); - } - else { + } else { split = []; } const sortedKeyAmounts = getKeysetAmounts(keyset); @@ -114,7 +113,7 @@ export function getKeepAmounts( * @param order order to sort the amounts in * @returns the amounts in the keyset sorted by the order specified */ -export function getKeysetAmounts(keyset: Keys, order: "asc" | "desc" = 'desc'): Array { +export function getKeysetAmounts(keyset: Keys, order: 'asc' | 'desc' = 'desc'): Array { if (order == 'desc') { return Object.keys(keyset) .map((k: string) => parseInt(k)) @@ -154,11 +153,11 @@ export function hexToNumber(hex: string): bigint { } /** - * Helper function to stringify a bigint - * @param _key - * @param value to stringify - * @returns stringified bigint - */ + * Helper function to stringify a bigint + * @param _key + * @param value to stringify + * @returns stringified bigint + */ export function bigIntStringify(_key: unknown, value: T): string | T { return typeof value === 'bigint' ? value.toString() : value; } @@ -173,10 +172,10 @@ export function getEncodedToken(token: Token): string { } /** - * Helper function to encode a v4 cashu token - * @param token to encode - * @returns encoded token - */ + * Helper function to encode a v4 cashu token + * @param token to encode + * @returns encoded token + */ export function getEncodedTokenV4(token: Token): string { const idMap: { [id: string]: Array } = {}; let mint: string | undefined = undefined; From dd0af825df967eafdd70a630054dac9354e5af4a Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:16:23 +0200 Subject: [PATCH 64/70] add getKeepAmount tests --- src/CashuWallet.ts | 4 ++-- src/utils.ts | 2 +- test/utils.test.ts | 32 +++++++++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 8d220960..71cdb312 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -73,7 +73,7 @@ class CashuWallet { * @param options.keysets keysets from the mint (will be fetched from mint if not provided) * @param options.mintInfo mint info from the mint (will be fetched from mint if not provided) * @param options.denominationTarget target number proofs per denomination (default: see @constant DEFAULT_DENOMINATION_TARGET) - * @param options.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. + * @param options.mnemonicOrSeed mnemonic phrase or Seed to initial derivation key for this wallet's 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 * @param options.loadMint if set to true info will be loaded from mint */ @@ -195,7 +195,7 @@ class CashuWallet { /** * Get keysets from the mint with the unit of the wallet - * @returns keysets with wallets unit + * @returns keysets with wallet's unit */ async getKeySets(): Promise> { const allKeysets = await this.mint.getKeySets(); diff --git a/src/utils.ts b/src/utils.ts index 67e54151..d7320b69 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -88,7 +88,7 @@ export function getKeepAmounts( const sortedKeyAmounts = getKeysetAmounts(keys, 'asc'); sortedKeyAmounts.forEach((amt) => { const countWeHave = amountsWeHave.filter((a) => a === amt).length; - const countWeWant = Math.floor(targetCount - countWeHave); + const countWeWant = Math.max(targetCount - countWeHave, 0); for (let i = 0; i < countWeWant; ++i) { if (amountsWeWant.reduce((a, b) => a + b, 0) + amt > amountToKeep) { break; diff --git a/test/utils.test.ts b/test/utils.test.ts index 675902f7..8091c064 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,4 +1,4 @@ -import { Token, Keys } from '../src/model/types/index.js'; +import { Token, Keys, Proof } from '../src/model/types/index.js'; import * as utils from '../src/utils.js'; import { PUBKEYS } from './consts.js'; @@ -294,3 +294,33 @@ describe('test v4 encoding', () => { expect(decodedExpectedToken).toEqual(decodedEncodedToken); }); }); + +describe('test output selection', () => { + test('keep amounts', () => { + const amountsWeHave = [1, 2, 4, 4, 4, 8]; + const proofsWeHave = amountsWeHave.map((amount) => { + return { + amount: amount, + id: 'id', + C: 'C' + } as Proof; + }); + const keys = PUBKEYS as Keys; + + // info: getKeepAmounts returns the amounts we need to fill up + // the wallet to a target number of denominations plus an optimal + // split of the remaining amount (to reach the total amount) + + let amountsToKeep = utils.getKeepAmounts(proofsWeHave, 22, keys, 3); + // keeping 22 with a target count of 3, we expect two 1s, two 2s, no 4s, and two 8s, and no extra to reach 22 + expect(amountsToKeep).toEqual([1, 1, 2, 2, 8, 8]); + + // keeping 22 with a target count of 4, we expect three 1s, three 2s, one 4, and one 8 and another 1 to reach 22 + amountsToKeep = utils.getKeepAmounts(proofsWeHave, 22, keys, 4); + expect(amountsToKeep).toEqual([1, 1, 1, 1, 2, 2, 2, 4, 8]); + + // keeping 22 with a target of 2, we expect one 1, one 2, no 4s, one 8, and another 1, 2, 8 to reach 22 + amountsToKeep = utils.getKeepAmounts(proofsWeHave, 22, keys, 2); + expect(amountsToKeep).toEqual([1, 1, 2, 2, 8, 8]); + }); +}); From 21bd131403e1f3c45a592600aa0155c3f705fae1 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:25:43 +0200 Subject: [PATCH 65/70] add example of send and receive --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f12c0e70..c47fd243 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ npm i @cashu/cashu-ts import { CashuMint, CashuWallet, MintQuoteState } from '@cashu/cashu-ts'; const mintUrl = 'http://localhost:3338'; // the mint URL const mint = new CashuMint(mintUrl); -const wallet = new CashuWallet(mint); +const wallet = new CashuWallet(mint, { loadMint: true }); const mintQuote = await wallet.createMintQuote(64); // pay the invoice here before you continue... const mintQuoteChecked = await wallet.checkMintQuote(mintQuote.quote); @@ -98,7 +98,13 @@ const meltResponse = await wallet.meltProofs(meltQuote, proofsToSend); #### Create a token and receive it ```typescript +// we assume that `wallet` already minted `proofs`, as above +const wallet2 = new CashuWallet(mint, { loadMint: true }) // receiving wallet +const { keep, send } = await wallet.send(32, proofs); +const token = getEncodedTokenV4({ token: [{ mint: mintUrl, proofs: send }] }); +console.log(token); +const receiveProofs = await wallet2.receive(token); ``` ## Contribute From d4ef4951ef69b1bd881936f089432a9744b6fa4e Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:26:00 +0200 Subject: [PATCH 66/70] format --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c47fd243..c4f1f428 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ const meltResponse = await wallet.meltProofs(meltQuote, proofsToSend); ```typescript // we assume that `wallet` already minted `proofs`, as above -const wallet2 = new CashuWallet(mint, { loadMint: true }) // receiving wallet +const wallet2 = new CashuWallet(mint, { loadMint: true }); // receiving wallet const { keep, send } = await wallet.send(32, proofs); const token = getEncodedTokenV4({ token: [{ mint: mintUrl, proofs: send }] }); console.log(token); From 08e62ff00a1110264699a62a1428748e290d155e Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 26 Oct 2024 15:36:06 +0200 Subject: [PATCH 67/70] remove loadMint from constructor --- README.md | 6 ++-- src/CashuWallet.ts | 73 ++++++++++++++++++++-------------------- test/integration.test.ts | 12 +++---- test/wallet.test.ts | 5 ++- 4 files changed, 49 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index c4f1f428..f4636028 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ npm i @cashu/cashu-ts import { CashuMint, CashuWallet, MintQuoteState } from '@cashu/cashu-ts'; const mintUrl = 'http://localhost:3338'; // the mint URL const mint = new CashuMint(mintUrl); -const wallet = new CashuWallet(mint, { loadMint: true }); +const wallet = new CashuWallet(mint); const mintQuote = await wallet.createMintQuote(64); // pay the invoice here before you continue... const mintQuoteChecked = await wallet.checkMintQuote(mintQuote.quote); @@ -77,7 +77,7 @@ if (mintQuoteChecked.state == MintQuoteState.PAID) { import { CashuMint, CashuWallet } from '@cashu/cashu-ts'; const mintUrl = 'http://localhost:3338'; // the mint URL const mint = new CashuMint(mintUrl); -const wallet = new CashuWallet(mint, { loadMint: true }); // load the keysets of the mint +const wallet = new CashuWallet(mint); // load the keysets of the mint const invoice = 'lnbc......'; // Lightning invoice to pay const meltQuote = await wallet.createMeltQuote(invoice); @@ -99,7 +99,7 @@ const meltResponse = await wallet.meltProofs(meltQuote, proofsToSend); ```typescript // we assume that `wallet` already minted `proofs`, as above -const wallet2 = new CashuWallet(mint, { loadMint: true }); // receiving wallet +const wallet2 = new CashuWallet(mint); // receiving wallet const { keep, send } = await wallet.send(32, proofs); const token = getEncodedTokenV4({ token: [{ mint: mintUrl, proofs: send }] }); console.log(token); diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 71cdb312..1bb98426 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -75,7 +75,6 @@ class CashuWallet { * @param options.denominationTarget target number proofs per denomination (default: see @constant DEFAULT_DENOMINATION_TARGET) * @param options.mnemonicOrSeed mnemonic phrase or Seed to initial derivation key for this wallet's 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 - * @param options.loadMint if set to true info will be loaded from mint */ constructor( mint: CashuMint, @@ -86,7 +85,6 @@ class CashuWallet { mintInfo?: GetInfoResponse; mnemonicOrSeed?: string | Uint8Array; denominationTarget?: number; - loadMint?: boolean; } ) { this.mint = mint; @@ -103,16 +101,6 @@ class CashuWallet { this._denominationTarget = options.denominationTarget; } - if (options?.loadMint) { - this.loadMint() - .then(() => { - console.log('Mint loaded'); - }) - .catch((e: Error) => { - console.error('Failed to load mint', e); - }); - } - if (!options?.mnemonicOrSeed) { return; } else if (options?.mnemonicOrSeed instanceof Uint8Array) { @@ -184,6 +172,10 @@ class CashuWallet { activeKeysets = hexKeysets; } // end deprecated + + // we only consider keyset IDs that start with "00" + activeKeysets = activeKeysets.filter((k: MintKeyset) => k.id.startsWith('00')); + const activeKeyset = activeKeysets.sort( (a: MintKeyset, b: MintKeyset) => (a.input_fee_ppk ?? 0) - (b.input_fee_ppk ?? 0) )[0]; @@ -226,28 +218,31 @@ class CashuWallet { * @returns keyset */ async getKeys(keysetId?: string, forceRefresh?: boolean): Promise { + if (!(this._keysets.length > 0) || forceRefresh) { + await this.getKeySets(); + } + // no keyset id is chosen, let's choose one if (!keysetId) { - if (!(this._keysets.length > 0) || forceRefresh) { - const allKeysets = await this.mint.getKeySets(); - const keysetToActivate = this.getActiveKeyset(allKeysets.keysets); - keysetId = keysetToActivate.id; - } else { - const localKeyset = this.getActiveKeyset(this._keysets); - keysetId = localKeyset.id; + const localKeyset = this.getActiveKeyset(this._keysets); + keysetId = localKeyset.id; + } + // make sure we have keyset for this id + if (!this._keysets.find((k: MintKeyset) => k.id === keysetId)) { + await this.getKeySets(); + if (!this._keysets.find((k: MintKeyset) => k.id === keysetId)) { + throw new Error(`could not initialize keys. No keyset with id '${keysetId}' found`); } } - if (this._keys.get(keysetId)) { - this.keysetId = keysetId; - return this._keys.get(keysetId) as MintKeys; - } - const allKeysets = await this.mint.getKeys(keysetId); - const keyset = allKeysets.keysets[0]; - if (!keyset) { - throw new Error(`could not initialize keys. No keyset with id '${keysetId}' found`); + + // make sure we have keys for this id + if (!this._keys.get(keysetId)) { + const keys = await this.mint.getKeys(keysetId); + this._keys.set(keysetId, keys.keysets[0]); } - this._keys.set(keysetId, keyset); + + // set and return this.keysetId = keysetId; - return keyset; + return this._keys.get(keysetId) as MintKeys; } /** @@ -276,13 +271,7 @@ class CashuWallet { token = getDecodedToken(token); } const tokenEntries: Array = token.token; - const proofs = await this.receiveTokenEntry(tokenEntries[0], { - keysetId: options?.keysetId, - outputAmounts: options?.outputAmounts, - counter: options?.counter, - pubkey: options?.pubkey, - privkey: options?.privkey - }); + const proofs = await this.receiveTokenEntry(tokenEntries[0], options); return proofs; } @@ -307,10 +296,10 @@ class CashuWallet { } ): Promise> { const proofs: Array = []; + const keys = await this.getKeys(options?.keysetId); const amount = tokenEntry.proofs.reduce((total: number, curr: Proof) => total + curr.amount, 0) - this.getFeesForProofs(tokenEntry.proofs); - const keys = await this.getKeys(options?.keysetId); const { payload, blindedMessages } = this.createSwapPayload( amount, tokenEntry.proofs, @@ -452,6 +441,16 @@ class CashuWallet { * @returns fee amount */ getFeesForProofs(proofs: Array): number { + if (!this._keysets.length) { + throw new Error('Could not calculate fees. No keysets found'); + } + const keysetIds = new Set(proofs.map((p: Proof) => p.id)); + keysetIds.forEach((id: string) => { + if (!this._keysets.find((k: MintKeyset) => k.id === id)) { + throw new Error(`Could not calculate fees. No keyset found with id: ${id}`); + } + }); + const fees = Math.floor( Math.max( (proofs.reduce( diff --git a/test/integration.test.ts b/test/integration.test.ts index b5332da9..8ca42730 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -79,7 +79,7 @@ describe('mint api', () => { }); test('pay local invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit, loadMint: true }); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(100); const tokens = await wallet.mintProofs(100, request.quote); @@ -111,7 +111,7 @@ describe('mint api', () => { }); test('pay external invoice', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit, loadMint: true }); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(3000); const tokens = await wallet.mintProofs(3000, request.quote); @@ -156,7 +156,7 @@ describe('mint api', () => { }); test('test send tokens with change', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit, loadMint: true }); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(100); const tokens = await wallet.mintProofs(100, request.quote); @@ -171,7 +171,7 @@ describe('mint api', () => { }, 10000000); test('receive tokens with previous split', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit, loadMint: true }); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(100); const tokens = await wallet.mintProofs(100, request.quote); @@ -184,7 +184,7 @@ describe('mint api', () => { }); test('receive tokens with previous mint', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit, loadMint: true }); + const wallet = new CashuWallet(mint, { unit }); const request = await wallet.createMintQuote(64); const tokens = await wallet.mintProofs(64, request.quote); const encoded = getEncodedToken({ @@ -195,7 +195,7 @@ describe('mint api', () => { }); test('send and receive p2pk', async () => { const mint = new CashuMint(mintUrl); - const wallet = new CashuWallet(mint, { unit, loadMint: true }); + const wallet = new CashuWallet(mint, { unit }); const privKeyAlice = secp256k1.utils.randomPrivateKey(); const pubKeyAlice = secp256k1.getPublicKey(privKeyAlice); diff --git a/test/wallet.test.ts b/test/wallet.test.ts index f8b5c51a..3d702a49 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -407,6 +407,7 @@ describe('send', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; + await wallet.getKeys(); const result = await wallet.send(4, overpayProofs, { // preference: { sendPreference: [{ amount: 1, count: 4 }] } outputAmounts: { sendAmounts: [1, 1, 1, 1], keepAmounts: [] } @@ -465,6 +466,7 @@ describe('send', () => { C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' } ]; + await wallet.getKeys(); const result = await wallet.send(3, overpayProofs, { outputAmounts: { sendAmounts: [1, 1, 1], keepAmounts: [1] } }); @@ -519,12 +521,13 @@ describe('send', () => { describe('deterministic', () => { test('no seed', async () => { const wallet = new CashuWallet(mint); + await wallet.getKeys(); const result = await wallet .send( 1, [ { - id: 'z32vUtKgNCm1', + id: '009a1f293253e41e', amount: 2, secret: '1f98e6837a434644c9411825d7c6d6e13974b931f8f0652217cea29010674a13', C: '034268c0bd30b945adf578aca2dc0d1e26ef089869aaf9a08ba3a6da40fda1d8be' From 691accb38ab756b83165052ffdf6a83b123a4ac7 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 26 Oct 2024 15:45:48 +0200 Subject: [PATCH 68/70] clean up --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f4636028..1fda2f75 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ import { CashuMint, CashuWallet, MintQuoteState } from '@cashu/cashu-ts'; const mintUrl = 'http://localhost:3338'; // the mint URL const mint = new CashuMint(mintUrl); const wallet = new CashuWallet(mint); +await wallet.loadMint(); // persist wallet.keys and wallet.keysets to avoid calling loadMint() in the future const mintQuote = await wallet.createMintQuote(64); // pay the invoice here before you continue... const mintQuoteChecked = await wallet.checkMintQuote(mintQuote.quote); @@ -99,11 +100,11 @@ const meltResponse = await wallet.meltProofs(meltQuote, proofsToSend); ```typescript // we assume that `wallet` already minted `proofs`, as above -const wallet2 = new CashuWallet(mint); // receiving wallet const { keep, send } = await wallet.send(32, proofs); const token = getEncodedTokenV4({ token: [{ mint: mintUrl, proofs: send }] }); console.log(token); +const wallet2 = new CashuWallet(mint); // receiving wallet const receiveProofs = await wallet2.receive(token); ``` From c0bfb76a55899e59b335c82f564c15b4b96bf56a Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 26 Oct 2024 15:48:01 +0200 Subject: [PATCH 69/70] filter base64 implicitly using keyset ID version prefix --- src/CashuWallet.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 1bb98426..48d06326 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -166,12 +166,6 @@ class CashuWallet { */ getActiveKeyset(keysets: Array): MintKeyset { let activeKeysets = keysets.filter((k: MintKeyset) => k.active); - // begin deprecated: if there are keyset IDs that are not hex strings, we need to filter them out - const hexKeysets = activeKeysets.filter((k: MintKeyset) => /^[0-9a-fA-F]+$/.test(k.id)); - if (hexKeysets.length > 0) { - activeKeysets = hexKeysets; - } - // end deprecated // we only consider keyset IDs that start with "00" activeKeysets = activeKeysets.filter((k: MintKeyset) => k.id.startsWith('00')); From 110756285c9ec45020c571ab040bbac69e3208cb Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 26 Oct 2024 16:26:11 +0200 Subject: [PATCH 70/70] expose selectProofsToSend --- src/CashuWallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 48d06326..b5e5d97e 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -380,7 +380,7 @@ class CashuWallet { return { keep: keepProofsOffline, send: sendProofOffline }; } - private selectProofsToSend( + selectProofsToSend( proofs: Array, amountToSend: number, includeFees?: boolean