From 859e546fe1fc9093bea1cfbdb166ea2a3ec604ed Mon Sep 17 00:00:00 2001 From: Erik Brakke Date: Wed, 3 May 2023 17:10:11 -0600 Subject: [PATCH 1/7] decode token with cashu-ts --- package-lock.json | 136 +++++++++++++++++++++++++++++++++++++--------- package.json | 3 +- src/js/token.js | 81 +-------------------------- 3 files changed, 115 insertions(+), 105 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53a73b1b..c5b2fb1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@capacitor/clipboard": "^4.1.0", + "@cashu/cashu-ts": "^0.8.0-rc.0", "@chenfengyuan/vue-qrcode": "^2.0.0", "@quasar/extras": "^1.0.0", "@vueuse/core": "^10.0.2", @@ -16,6 +17,7 @@ "core-js": "^3.6.5", "light-bolt11-decoder": "^3.0.0", "pinia": "^2.0.35", + "qrcode": "^1.5.1", "qrcode-reader-vue3": "^3.1.2", "quasar": "^2.6.0", "underscore": "^1.13.6", @@ -41,7 +43,7 @@ "workbox-webpack-plugin": "^6.5.4" }, "engines": { - "node": ">= 12.22.1", + "node": ">=16.11.0", "npm": ">= 6.13.4", "yarn": ">= 1.21.1" } @@ -2086,6 +2088,40 @@ "tslib": "^2.1.0" } }, + "node_modules/@cashu/cashu-ts": { + "version": "0.8.0-rc.0", + "resolved": "https://registry.npmjs.org/@cashu/cashu-ts/-/cashu-ts-0.8.0-rc.0.tgz", + "integrity": "sha512-ujl3wpSbEFleQQcF++FhC2hrzA3ElN+KGTIvQXcVP9eD4V+QmP9BGtFdwFHEUQll59I7tSvrJIFpPGMUnVAu4g==", + "dependencies": { + "@gandlaf21/bolt11-decode": "^3.0.6", + "@noble/curves": "^1.0.0", + "axios": "^1.2.1", + "buffer": "^6.0.3" + } + }, + "node_modules/@cashu/cashu-ts/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/@chenfengyuan/vue-qrcode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@chenfengyuan/vue-qrcode/-/vue-qrcode-2.0.0.tgz", @@ -2320,6 +2356,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@gandlaf21/bolt11-decode": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@gandlaf21/bolt11-decode/-/bolt11-decode-3.0.6.tgz", + "integrity": "sha512-KUcAK2b9or8J47hzNTM2A+xdU0jCGIL4oC4TDyUlRYMfS5dBVOh4ywg9r3TZD8C/eVx7r14Hp4F79CSDjyCWTQ==", + "dependencies": { + "bech32": "^1.1.2", + "bn.js": "^4.11.8", + "buffer": "^6.0.3" + } + }, + "node_modules/@gandlaf21/bolt11-decode/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -3254,6 +3323,31 @@ "eslint-scope": "5.1.1" } }, + "node_modules/@noble/curves": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz", + "integrity": "sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "1.3.0" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz", + "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -6004,7 +6098,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -6026,6 +6119,11 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -6061,6 +6159,11 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -6692,7 +6795,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -7530,7 +7632,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7735,8 +7836,7 @@ "node_modules/dijkstrajs": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz", - "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==", - "peer": true + "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==" }, "node_modules/dir-compare": { "version": "3.3.0", @@ -8462,8 +8562,7 @@ "node_modules/encode-utf8": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", - "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==", - "peer": true + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -11588,7 +11687,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -16557,7 +16655,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "peer": true, "engines": { "node": ">=10.13.0" } @@ -17356,7 +17453,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.1.tgz", "integrity": "sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==", - "peer": true, "dependencies": { "dijkstrajs": "^1.0.1", "encode-utf8": "^1.0.3", @@ -17870,8 +17966,7 @@ "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "peer": true + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "node_modules/requires-port": { "version": "1.0.0", @@ -18577,8 +18672,7 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "peer": true + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/set-value": { "version": "2.0.1", @@ -21446,8 +21540,7 @@ "node_modules/which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "peer": true + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" }, "node_modules/which-typed-array": { "version": "1.1.9", @@ -21880,8 +21973,7 @@ "node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "peer": true + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "node_modules/yallist": { "version": "3.1.1", @@ -21902,7 +21994,6 @@ "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "peer": true, "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -21924,7 +22015,6 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "peer": true, "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -21937,7 +22027,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -21950,7 +22039,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -21962,7 +22050,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -21977,7 +22064,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, diff --git a/package.json b/package.json index f17abc60..716bf09d 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,15 @@ }, "dependencies": { "@capacitor/clipboard": "^4.1.0", + "@cashu/cashu-ts": "^0.8.0-rc.0", "@chenfengyuan/vue-qrcode": "^2.0.0", "@quasar/extras": "^1.0.0", "@vueuse/core": "^10.0.2", "axios": "^1.3.3", "core-js": "^3.6.5", "light-bolt11-decoder": "^3.0.0", - "qrcode": "^1.5.1", "pinia": "^2.0.35", + "qrcode": "^1.5.1", "qrcode-reader-vue3": "^3.1.2", "quasar": "^2.6.0", "underscore": "^1.13.6", diff --git a/src/js/token.js b/src/js/token.js index 5462836a..674d2a60 100644 --- a/src/js/token.js +++ b/src/js/token.js @@ -1,3 +1,4 @@ +import { getDecodedToken } from "@cashu/cashu-ts"; /** * Functions related to cashu tokens * @typedef {{C: string, amount: number, id: number, secret: number}} Proof @@ -13,11 +14,7 @@ export default { decode, getProofs, getMint }; */ function decode(encoded_token) { if (!encoded_token || encoded_token === "") return ""; - encoded_token = cropPrefixes(encoded_token); - if (isV2Token(encoded_token)) return parseV2Token(encoded_token); - if (isV3Token(encoded_token)) return parseV3Token(encoded_token); - - throw new Error("Unknown token format"); + return getDecodedToken(encoded_token); } /** @@ -49,77 +46,3 @@ function getMint(decoded_token) { return ""; } } - -/** - * @param {string} token - * @returns {boolean} - */ -function isV2Token(token) { - return token.startsWith("eyJwcm9"); -} - -/** - * - * @param {string} token - * @returns {boolean} - */ -function isV3Token(token) { - return token.startsWith("cashuA"); -} - -/** - * @param {string} encoded_token - * @returns {{token: Token[]}} - */ -function parseV3Token(encoded_token) { - let prefix = "cashuA"; - let token_parsed = encoded_token.slice(prefix.length); - let tokenJson = getTokenJSON(token_parsed); - if ( - !(tokenJson.token.length > 0) || - !(tokenJson.token[0].proofs.length > 0) - ) { - throw new Error("No proofs in encoded token"); - } - return tokenJson; -} - -/** - * @param {string} encoded_token - * @returns {{token: Token[]}} - */ -function parseV2Token(encoded_token) { - let tokenV2 = getTokenJSON(encoded_token); - let newToken = { - token: [ - { - proofs: tokenV2.proofs, - mint: tokenV2.mints[0].url, - }, - ], - }; - return newToken; -} - -/** - * @param {string} token - * @returns {{token: Token[]}} - */ -function getTokenJSON(token) { - return JSON.parse(atob(token)); -} - -/** - * - * @param {string} token - * @returns {string} - */ -function cropPrefixes(token) { - let uriPrefixes = ["web+cashu://", "cashu://", "cashu:"]; - uriPrefixes.forEach((prefix) => { - if (token.startsWith(prefix)) { - token = token.slice(prefix.length); - } - }); - return token; -} From 044692be748fa1d290aa4e5288ce616183e6368d Mon Sep 17 00:00:00 2001 From: Erik Brakke Date: Wed, 3 May 2023 19:18:32 -0600 Subject: [PATCH 2/7] replace axios calls to mint with cashu-ts mint --- src/pages/WalletPage.vue | 39 +++++++++++---------------------------- src/stores/mints.js | 7 ++++++- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue index bbb705b9..f7018dc7 100644 --- a/src/pages/WalletPage.vue +++ b/src/pages/WalletPage.vue @@ -977,6 +977,7 @@ export default { "keys", "mints", "proofs", + "activeMint", ]), ...mapWritableState(useMintsStore, ["showAddMintDialog"]), ...mapWritableState(useWorkersStore, [ @@ -1514,9 +1515,7 @@ export default { this.invoiceData.amount = amount; } try { - const { data } = await axios.get( - `${this.activeMintUrl}/mint?amount=${this.invoiceData.amount}` - ); + const data = await this.activeMint.requestMint(this.invoiceData.amount); this.assertMintError(data); console.log("### data", data); @@ -1553,18 +1552,13 @@ export default { let secrets = await this.generateSecrets(amounts); let { outputs, rs } = await this.constructOutputs(amounts, secrets); const keys = this.keys; // fix keys for constructProofs - const promises = await axios.post( - `${this.activeMintUrl}/mint?payment_hash=${payment_hash}`, - { - outputs, - } - ); - this.assertMintError(promises.data, false); - if (promises.data.promises == null) { + const data = await this.activeMint.mint({ outputs }, payment_hash); + this.assertMintError(data, false); + if (data.promises == null) { return {}; } let proofs = await this.constructProofs( - promises.data.promises, + data.promises, secrets, rs, keys @@ -1663,10 +1657,8 @@ export default { outputs, }; const keys = this.keys; // fix keys for constructProofs - const { data } = await axios.post( - `${this.activeMintUrl}/split`, - payload - ); + const data = await this.activeMint.split(payload); + this.assertMintError(data); const frst_rs = rs.slice(0, frst_amounts.length); const frst_secrets = secrets.slice(0, frst_amounts.length); @@ -1874,10 +1866,7 @@ export default { outputs, }; const keys = this.keys; // fix keys for constructProofs - const { data } = await axios.post( - `${this.activeMintUrl}/melt`, - payload - ); + const data = await this.activeMint.melt(payload); this.assertMintError(data); if (data.paid != true) { throw new Error("Invoice not paid."); @@ -1948,10 +1937,7 @@ export default { }), }; try { - const { data } = await axios.post( - `${this.activeMintUrl}/check`, - payload - ); + const data = await this.activeMint.check(payload); this.assertMintError(data); // delete proofs from database if it is spent let spentProofs = proofs.filter((p, pidx) => !data.spendable[pidx]); @@ -1983,10 +1969,7 @@ export default { pr: payment_request, }; try { - const { data } = await axios.post( - `${this.activeMintUrl}/checkfees`, - payload - ); + const data = await this.activeMint.checkFees(payload); this.assertMintError(data); console.log("#### checkFees", payment_request, data.fee); return data.fee; diff --git a/src/stores/mints.js b/src/stores/mints.js index 1f62362c..223d1cee 100644 --- a/src/stores/mints.js +++ b/src/stores/mints.js @@ -3,6 +3,7 @@ import { useLocalStorage } from "@vueuse/core"; import { useWorkersStore } from "./workers"; import { axios } from "boot/axios"; import { notifyApiError, notifyError, notifySuccess } from "src/js/notify"; +import { CashuMint } from "@cashu/cashu-ts"; export const useMintsStore = defineStore("mints", { state: () => { @@ -17,7 +18,11 @@ export const useMintsStore = defineStore("mints", { showAddMintDialog: false, }; }, - getters: {}, + getters: { + activeMint({ activeMintUrl }) { + return new CashuMint(activeMintUrl); + }, + }, actions: { setShowAddMintDialog(show) { this.showAddMintDialog = show; From 70995635127177173ef7c0c37ed7bb624bd17919 Mon Sep 17 00:00:00 2001 From: Erik Brakke Date: Wed, 3 May 2023 19:20:30 -0600 Subject: [PATCH 3/7] update mint store --- src/stores/mints.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/stores/mints.js b/src/stores/mints.js index 223d1cee..0e883e8f 100644 --- a/src/stores/mints.js +++ b/src/stores/mints.js @@ -1,7 +1,6 @@ import { defineStore } from "pinia"; import { useLocalStorage } from "@vueuse/core"; import { useWorkersStore } from "./workers"; -import { axios } from "boot/axios"; import { notifyApiError, notifyError, notifySuccess } from "src/js/notify"; import { CashuMint } from "@cashu/cashu-ts"; @@ -120,9 +119,7 @@ export const useMintsStore = defineStore("mints", { // later, it calles fetchMintKeysets which overwrites this.keysets try { console.log("### GET", `${this.activeMintUrl}/keys`); - const { data } = await axios.get(`${this.activeMintUrl}/keys`, { - timeout: 6000, - }); + const data = await this.activeMint.getKeys(); const keys = data; this.assertMintError(keys); this.keys = keys; @@ -147,9 +144,7 @@ export const useMintsStore = defineStore("mints", { fetchMintKeysets: async function () { // attention: this function overwrites this.keysets try { - const { data } = await axios.get(`${this.activeMintUrl}/keysets`, { - timeout: 6000, - }); + const data = await this.activeMint.getKeySets(); this.assertMintError(data); this.keysets = data.keysets; From 4696d4b596a8d306b164ee1e16862f5bd697b7e6 Mon Sep 17 00:00:00 2001 From: Erik Brakke Date: Thu, 4 May 2023 06:05:54 -0600 Subject: [PATCH 4/7] move invoice data to wallet store --- src/pages/WalletPage.vue | 51 +++++----------------------------------- src/stores/wallet.js | 37 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 45 deletions(-) create mode 100644 src/stores/wallet.js diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue index f7018dc7..4f7f1dd4 100644 --- a/src/pages/WalletPage.vue +++ b/src/pages/WalletPage.vue @@ -815,6 +815,7 @@ import { mapActions, mapState, mapWritableState } from "pinia"; import { useMintsStore } from "src/stores/mints"; import { useWorkersStore } from "src/stores/workers"; import { useTokensStore } from "src/stores/tokens"; +import { useWalletStore } from "src/stores/wallet"; var currentDateStr = function () { return date.formatDate(new Date(), "YYYY-MM-DD HH:mm:ss"); @@ -840,37 +841,11 @@ export default { mintId: "", mintName: "", deferredPWAInstallPrompt: null, - invoiceHistory: [], - invoiceData: { - amount: 0, - memo: "", - bolt11: "", - hash: "", - }, camera: { data: null, show: false, camera: "auto", }, - //invoiceCheckListener: () => {}, - payInvoiceData: { - blocking: false, - bolt11: "", - show: false, - invoice: null, - lnurlpay: null, - lnurlauth: null, - data: { - request: "", - amount: 0, - comment: "", - }, - paymentChecker: null, - camera: { - show: false, - camera: "auto", - }, - }, sendData: { amount: 0, memo: "", @@ -979,6 +954,11 @@ export default { "proofs", "activeMint", ]), + ...mapState(useWalletStore, [ + "invoiceHistory", + "invoiceData", + "payInvoiceData", + ]), ...mapWritableState(useMintsStore, ["showAddMintDialog"]), ...mapWritableState(useWorkersStore, [ "invoiceCheckListener", @@ -1529,7 +1509,6 @@ export default { mint: this.activeMintUrl, }) ); - this.storeInvoiceHistory(); return data; } catch (error) { console.error(error); @@ -1909,7 +1888,6 @@ export default { status: "paid", mint: this.activeMintUrl, }); - this.storeInvoiceHistory(); this.payInvoiceData.invoice = false; this.payInvoiceData.show = false; @@ -1986,7 +1964,6 @@ export default { setInvoicePaid: async function (payment_hash) { const invoice = this.invoiceHistory.find((i) => i.hash === payment_hash); invoice.status = "paid"; - this.storeInvoiceHistory(); }, checkInvoice: async function (payment_hash, verbose = true) { console.log("### checkInvoice.hash", payment_hash); @@ -2240,18 +2217,6 @@ export default { downloadLink.click(); }, - storeInvoiceHistory: function () { - localStorage.setItem( - "cashu.invoiceHistory", - JSON.stringify(this.invoiceHistory) - ); - }, - /*storeProofs: function () { - localStorage.setItem( - "cashu.proofs", - JSON.stringify(this.proofs, bigIntStringify) - ); - },*/ migrationLocalstorage: async function () { // migration from old db to multimint for (var key in localStorage) { @@ -2353,10 +2318,6 @@ export default { this.mintName = params.get("mint_name"); } - this.invoiceHistory = JSON.parse( - localStorage.getItem("cashu.invoiceHistory") || "[]" - ); - // run migrations await this.migrationLocalstorage(); diff --git a/src/stores/wallet.js b/src/stores/wallet.js new file mode 100644 index 00000000..4a06e414 --- /dev/null +++ b/src/stores/wallet.js @@ -0,0 +1,37 @@ +import { defineStore } from "pinia"; +import { useMintsStore } from "./mints"; +import { useLocalStorage } from "@vueuse/core"; + +export const useWalletStore = defineStore("wallet", { + state: () => { + return { + invoiceHistory: useLocalStorage("cashu.invoiceHistory", []), + invoiceData: { + amount: 0, + memo: "", + bolt11: "", + hash: "", + }, + payInvoiceData: { + blocking: false, + bolt11: "", + show: false, + invoice: null, + lnurlpay: null, + lnurlauth: null, + data: { + request: "", + amount: 0, + comment: "", + }, + }, + }; + }, + getters: { + wallet() { + const mints = useMintsStore(); + const wallet = new CashuWallet(mints.keys, mints.activeMint); + return wallet; + }, + }, +}); From e253283bab3602296de12230d2c0a636c6f98908 Mon Sep 17 00:00:00 2001 From: Erik Brakke Date: Thu, 4 May 2023 06:38:12 -0600 Subject: [PATCH 5/7] move requestMint to wallet store --- src/js/utils.js | 7 ++++++- src/pages/WalletPage.vue | 35 +---------------------------------- src/stores/wallet.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/js/utils.js b/src/js/utils.js index 6d4d5a2f..bb2f34fa 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -1,4 +1,5 @@ import * as nobleSecp256k1 from "./noble-secp256k1"; +import { date } from "quasar"; function splitAmount(value) { const chunks = []; @@ -24,4 +25,8 @@ function hexToNumber(hex) { return BigInt(`0x${hex}`); } -export { splitAmount, bytesToNumber, bigIntStringify }; +function currentDateStr() { + return date.formatDate(new Date(), "YYYY-MM-DD HH:mm:ss"); +} + +export { splitAmount, bytesToNumber, bigIntStringify, currentDateStr }; diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue index 4f7f1dd4..dc7cd848 100644 --- a/src/pages/WalletPage.vue +++ b/src/pages/WalletPage.vue @@ -1014,6 +1014,7 @@ export default { "addPendingToken", "setTokenPaid", ]), + ...mapActions(useWalletStore, ["requestMint"]), // TOKEN METHODS decodeToken: function (encoded_token) { return token.decode(encoded_token); @@ -1487,40 +1488,6 @@ export default { // /mint - requestMint: async function (amount = null) { - /* - gets an invoice from the mint to get new tokens - */ - if (amount != null) { - this.invoiceData.amount = amount; - } - try { - const data = await this.activeMint.requestMint(this.invoiceData.amount); - this.assertMintError(data); - console.log("### data", data); - - this.invoiceData.bolt11 = data.pr; - this.invoiceData.hash = data.hash; - this.invoiceHistory.push( - // extend dictionary - Object.assign({}, this.invoiceData, { - date: currentDateStr(), - status: "pending", - mint: this.activeMintUrl, - }) - ); - return data; - } catch (error) { - console.error(error); - try { - this.notifyApiError(error); - } catch {} - throw error; - } - }, - - // /mint - mintApi: async function (amounts, payment_hash, verbose = true) { /* asks the mint to check whether the invoice with payment_hash has been paid diff --git a/src/stores/wallet.js b/src/stores/wallet.js index 4a06e414..1ea61287 100644 --- a/src/stores/wallet.js +++ b/src/stores/wallet.js @@ -1,4 +1,6 @@ import { defineStore } from "pinia"; +import { currentDateStr } from "src/js/utils"; +import { notifyApiError } from "src/js/notify"; import { useMintsStore } from "./mints"; import { useLocalStorage } from "@vueuse/core"; @@ -34,4 +36,31 @@ export const useWalletStore = defineStore("wallet", { return wallet; }, }, + actions: { + requestMint: async function (amount = null) { + const mints = useMintsStore(); + if (amount != null) { + this.invoiceData.amount = amount; + } + try { + const data = await mints.activeMint.requestMint( + this.invoiceData.amount + ); + this.invoiceData.bolt11 = data.pr; + this.invoiceData.hash = data.hash; + this.invoiceHistory.push( + // extend dictionary + Object.assign({}, this.invoiceData, { + date: currentDateStr(), + status: "pending", + mint: this.activeMintUrl, + }) + ); + return data; + } catch (error) { + console.error(error); + notifyApiError(error); + } + }, + }, }); From 74ed3889c1a68774409e625ba5481d4049e6adfd Mon Sep 17 00:00:00 2001 From: Erik Brakke Date: Thu, 4 May 2023 07:03:55 -0600 Subject: [PATCH 6/7] notify support axios errors --- src/js/notify.js | 28 ++++++++++++++++++---------- src/pages/WalletPage.vue | 6 +----- src/stores/wallet.js | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/js/notify.js b/src/js/notify.js index d80f8a82..38486fa5 100644 --- a/src/js/notify.js +++ b/src/js/notify.js @@ -1,4 +1,5 @@ -import { Notify } from 'quasar' +import { Notify } from "quasar"; +import axios from "axios"; function notifyApiError(error) { let types = { @@ -6,11 +7,14 @@ function notifyApiError(error) { 401: "warning", 500: "negative", }; + if (axios.isAxiosError(error)) { + notifyAxiosError(error); + return; + } Notify.create({ timeout: 5000, type: types[error.response.status] || "warning", - message: - error.response.data.message || error.response.data.detail || null, + message: error.response.data.message || error.response.data.detail || null, caption: [error.response.status, " ", error.response.statusText] .join("") @@ -19,6 +23,16 @@ function notifyApiError(error) { }); } +function notifyAxiosError(error) { + Notify.create({ + timeout: 5000, + type: "warning", + message: error.message, + caption: error.code, + icon: null, + }); +} + async function notifySuccess(message, position = "top") { Notify.create({ timeout: 5000, @@ -96,10 +110,4 @@ async function notify( }); } -export { - notifyApiError, - notifySuccess, - notifyError, - notifyWarning, - notify, -} +export { notifyApiError, notifySuccess, notifyError, notifyWarning, notify }; diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue index dc7cd848..c4aeda3e 100644 --- a/src/pages/WalletPage.vue +++ b/src/pages/WalletPage.vue @@ -1014,7 +1014,7 @@ export default { "addPendingToken", "setTokenPaid", ]), - ...mapActions(useWalletStore, ["requestMint"]), + ...mapActions(useWalletStore, ["requestMint", "setInvoicePaid"]), // TOKEN METHODS decodeToken: function (encoded_token) { return token.decode(encoded_token); @@ -1928,10 +1928,6 @@ export default { }, ////////////// UI HELPERS ////////////// - setInvoicePaid: async function (payment_hash) { - const invoice = this.invoiceHistory.find((i) => i.hash === payment_hash); - invoice.status = "paid"; - }, checkInvoice: async function (payment_hash, verbose = true) { console.log("### checkInvoice.hash", payment_hash); const invoice = this.invoiceHistory.find((i) => i.hash === payment_hash); diff --git a/src/stores/wallet.js b/src/stores/wallet.js index 1ea61287..c04fd01f 100644 --- a/src/stores/wallet.js +++ b/src/stores/wallet.js @@ -37,6 +37,13 @@ export const useWalletStore = defineStore("wallet", { }, }, actions: { + /** + * Ask the mint to generate an invoice for the given amount + * Upon paying the request, the mint will credit the wallet with + * cashu tokens + * @param {number | null} amount + * @returns + */ requestMint: async function (amount = null) { const mints = useMintsStore(); if (amount != null) { @@ -62,5 +69,13 @@ export const useWalletStore = defineStore("wallet", { notifyApiError(error); } }, + /** + * Sets an invoice status to paid + * @param {string} payment_hash + */ + setInvoicePaid(payment_hash) { + const invoice = this.invoiceHistory.find((i) => i.hash === payment_hash); + invoice.status = "paid"; + }, }, }); From 763d7531c72237a723ddf02488eafeb8b5cba5b7 Mon Sep 17 00:00:00 2001 From: Erik Brakke Date: Wed, 10 May 2023 16:54:35 -0600 Subject: [PATCH 7/7] axios timeout for cashu-ts --- package-lock.json | 16 ++++++++-------- package.json | 2 +- quasar.config.js | 2 +- src/boot/cashu.js | 11 +++++++++++ src/js/notify.js | 8 ++++++-- 5 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 src/boot/cashu.js diff --git a/package-lock.json b/package-lock.json index 4eefce2b..98f2dfc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "dependencies": { "@capacitor/clipboard": "^4.1.0", - "@cashu/cashu-ts": "^0.8.0-rc.0", + "@cashu/cashu-ts": "^0.8.0-rc.3", "@chenfengyuan/vue-qrcode": "^2.0.0", "@noble/hashes": "^1.3.0", "@noble/secp256k1": "^1.7.1", @@ -1677,13 +1677,13 @@ } }, "node_modules/@cashu/cashu-ts": { - "version": "0.8.0-rc.0", - "resolved": "https://registry.npmjs.org/@cashu/cashu-ts/-/cashu-ts-0.8.0-rc.0.tgz", - "integrity": "sha512-ujl3wpSbEFleQQcF++FhC2hrzA3ElN+KGTIvQXcVP9eD4V+QmP9BGtFdwFHEUQll59I7tSvrJIFpPGMUnVAu4g==", + "version": "0.8.0-rc.3", + "resolved": "https://registry.npmjs.org/@cashu/cashu-ts/-/cashu-ts-0.8.0-rc.3.tgz", + "integrity": "sha512-O0BaUFXkKypgNH9vllPh19Sk9+2bU9NzqfF6nQgJP8ZgFDYd9p1Pefn8TQv/8rltfXuMIIOv/qZgrCvJuZE6rQ==", "dependencies": { "@gandlaf21/bolt11-decode": "^3.0.6", "@noble/curves": "^1.0.0", - "axios": "^1.2.1", + "axios": "^1.4.0", "buffer": "^6.0.3" } }, @@ -3926,9 +3926,9 @@ } }, "node_modules/axios": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.3.tgz", - "integrity": "sha512-eYq77dYIFS77AQlhzEL937yUBSepBfPIe8FcgEDN35vMNZKMrs81pgnyrQpwfy4NF4b4XWX1Zgx7yX+25w8QJA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", diff --git a/package.json b/package.json index 563d8988..e4e950ba 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ }, "dependencies": { "@capacitor/clipboard": "^4.1.0", - "@cashu/cashu-ts": "^0.8.0-rc.0", + "@cashu/cashu-ts": "^0.8.0-rc.3", "@chenfengyuan/vue-qrcode": "^2.0.0", "@noble/hashes": "^1.3.0", "@noble/secp256k1": "^1.7.1", diff --git a/quasar.config.js b/quasar.config.js index 042e1f60..29634167 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -27,7 +27,7 @@ module.exports = configure(function (/* ctx */) { // app boot file (/src/boot) // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli/boot-files - boot: ["base", "global-components"], + boot: ["base", "global-components", "cashu"], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css css: ["app.scss", "base.scss"], diff --git a/src/boot/cashu.js b/src/boot/cashu.js new file mode 100644 index 00000000..b3aeb675 --- /dev/null +++ b/src/boot/cashu.js @@ -0,0 +1,11 @@ +/** + * Configures the Cashu-ts library axios client + */ +import { setupAxios } from "@cashu/cashu-ts"; + +export default () => { + setupAxios({ + // Default timeout for any interaction using the cashu-ts library to interact with a mint + timeout: 15 * 1000, // 15 seconds + }); +}; diff --git a/src/js/notify.js b/src/js/notify.js index 38486fa5..e53d03fd 100644 --- a/src/js/notify.js +++ b/src/js/notify.js @@ -1,5 +1,5 @@ import { Notify } from "quasar"; -import axios from "axios"; +import axios, { AxiosError } from "axios"; function notifyApiError(error) { let types = { @@ -23,10 +23,14 @@ function notifyApiError(error) { }); } +/** + * Cashu-TS will return axios errors when certain calls fail, so we should handle those + * @param {AxiosError} error + */ function notifyAxiosError(error) { Notify.create({ timeout: 5000, - type: "warning", + type: types[error.status] || "warning", message: error.message, caption: error.code, icon: null,