From 66d7d7969f4b9b74cff690c0455d3e783fc76d07 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 5 Nov 2022 17:58:07 +0100 Subject: [PATCH] refactor wallet js --- lnbits/extensions/cashu/static/js/wallet.js | 1104 +++++++++++++++++ .../cashu/templates/cashu/wallet.html | 1102 +--------------- 2 files changed, 1105 insertions(+), 1101 deletions(-) create mode 100644 lnbits/extensions/cashu/static/js/wallet.js diff --git a/lnbits/extensions/cashu/static/js/wallet.js b/lnbits/extensions/cashu/static/js/wallet.js new file mode 100644 index 000000000..832b075d6 --- /dev/null +++ b/lnbits/extensions/cashu/static/js/wallet.js @@ -0,0 +1,1104 @@ +var currentDateStr = function () { + return Quasar.utils.date.formatDate(new Date(), 'YYYY-MM-DD HH:mm') +} +var mapMint = function (obj) { + obj.date = Quasar.utils.date.formatDate( + new Date(obj.time * 1000), + 'YYYY-MM-DD HH:mm' + ) + obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount) + obj.cashu = ['/cashu/', obj.id].join('') + return obj +} + +Vue.component(VueQrcode.name, VueQrcode) + +new Vue({ + el: '#vue', + mixins: [windowMixin], + data: function () { + return { + tickershort: '', + name: '', + + mintId: '', + mintName: '', + keys: '', + invoicesCashu: [], + invoiceData: { + amount: 0, + memo: '', + bolt11: '', + hash: '' + }, + invoiceCheckListener: () => {}, + payInvoiceData: { + // invoice: '', + bolt11: '', + // camera: { + // show: false, + // camera: 'auto' + // } + show: false, + invoice: null, + lnurlpay: null, + lnurlauth: null, + data: { + request: '', + amount: 0, + comment: '' + }, + paymentChecker: null, + camera: { + show: false, + camera: 'auto' + } + }, + sendData: { + amount: 0, + memo: '', + tokens: '', + tokensBase64: '' + }, + receiveData: { + tokensBase64: '' + }, + showInvoiceDetails: false, + showPayInvoice: false, + showSendTokens: false, + showReceiveTokens: false, + promises: [], + tokens: [], + tab: 'tokens', + + receive: { + show: false, + status: 'pending', + paymentReq: null, + paymentHash: null, + minMax: [0, 2100000000000000], + lnurl: null, + units: ['sat'], + unit: 'sat', + data: { + amount: null, + memo: '' + } + }, + parse: { + show: false, + invoice: null, + lnurlpay: null, + lnurlauth: null, + data: { + request: '', + amount: 0, + comment: '' + }, + paymentChecker: null, + camera: { + show: false, + camera: 'auto' + } + }, + payments: [], + invoicesTable: { + columns: [ + { + name: 'status', + align: 'left', + label: '', + field: 'status' + }, + { + name: 'amount', + align: 'left', + label: 'Amount', + field: 'amount' + }, + { + name: 'memo', + align: 'left', + label: 'Memo', + field: 'memo', + sortable: true + }, + { + name: 'date', + align: 'left', + label: 'Date', + field: 'date', + sortable: true + }, + { + name: 'hash', + align: 'right', + label: 'Hash', + field: 'hash', + sortable: true + } + ], + pagination: { + sortBy: 'date', + descending: true, + rowsPerPage: 5 + }, + filter: null + }, + + tokensTable: { + columns: [ + { + name: 'value', + align: 'left', + label: 'Value ({{LNBITS_DENOMINATION}})', + field: 'value', + sortable: true + }, + { + name: 'count', + align: 'left', + label: 'Count', + field: 'count', + sortable: true + }, + { + name: 'sum', + align: 'left', + label: 'Sum ({{LNBITS_DENOMINATION}})', + field: 'sum', + sortable: true + } + // { + // name: 'memo', + // align: 'left', + // label: 'Memo', + // field: 'memo', + // sortable: true + // } + ], + pagination: { + rowsPerPage: 5 + }, + filter: null + }, + + paymentsChart: { + show: false + }, + disclaimerDialog: { + show: false, + location: window.location + }, + + credit: 0, + newName: '' + } + }, + computed: { + formattedBalance: function () { + return this.balance / 100 + }, + + canPay: function () { + if (!this.payInvoiceData.invoice) return false + return this.payInvoiceData.invoice.sat <= this.balance + }, + pendingPaymentsExist: function () { + return this.payments.findIndex(payment => payment.pending) !== -1 + }, + + balance: function () { + return this.proofs + .map(t => t) + .flat() + .reduce((sum, el) => (sum += el.amount), 0) + } + }, + filters: { + msatoshiFormat: function (value) { + return LNbits.utils.formatSat(value / 1000) + } + }, + methods: { + getBalance: function () { + return this.proofs + .map(t => t) + .flat() + .reduce((sum, el) => (sum += el.amount), 0) + }, + getTokenList: function () { + const x = this.proofs + .map(t => t.amount) + .reduce((acc, amount) => { + acc[amount] = acc[amount] + amount || 1 + return acc + }, {}) + return Object.keys(x).map(k => ({ + value: k, + count: x[k], + sum: k * x[k] + })) + }, + + paymentTableRowKey: function (row) { + return row.payment_hash + row.amount + }, + closeCamera: function () { + this.payInvoiceData.camera.show = false + }, + showCamera: function () { + this.payInvoiceData.camera.show = true + }, + showChart: function () { + this.paymentsChart.show = true + this.$nextTick(() => { + generateChart(this.$refs.canvas, this.payments) + }) + }, + focusInput(el) { + this.$nextTick(() => this.$refs[el].focus()) + }, + showReceiveDialog: function () { + this.receive.show = true + this.receive.status = 'pending' + this.receive.paymentReq = null + this.receive.paymentHash = null + this.receive.data.amount = null + this.receive.data.memo = null + this.receive.unit = 'sat' + this.receive.paymentChecker = null + this.receive.minMax = [0, 2100000000000000] + this.receive.lnurl = null + this.focusInput('setAmount') + }, + showParseDialog: function () { + this.payInvoiceData.show = true + this.payInvoiceData.invoice = null + this.payInvoiceData.lnurlpay = null + this.payInvoiceData.lnurlauth = null + this.payInvoiceData.data.request = '' + this.payInvoiceData.data.comment = '' + this.payInvoiceData.data.paymentChecker = null + this.payInvoiceData.camera.show = false + this.focusInput('pasteInput') + }, + showDisclaimerDialog: function () { + this.disclaimerDialog.show = true + }, + + closeReceiveDialog: function () { + setTimeout(() => { + clearInterval(this.receive.paymentChecker) + }, 10000) + }, + closeParseDialog: function () { + setTimeout(() => { + clearInterval(this.payInvoiceData.paymentChecker) + }, 10000) + }, + onPaymentReceived: function (paymentHash) { + this.fetchPayments() + this.fetchBalance() + + if (this.receive.paymentHash === paymentHash) { + this.receive.show = false + this.receive.paymentHash = null + clearInterval(this.receive.paymentChecker) + } + }, + createInvoice: function () { + this.receive.status = 'loading' + if (LNBITS_DENOMINATION != 'sats') { + this.receive.data.amount = this.receive.data.amount * 100 + } + LNbits.api + .createInvoice( + this.receive.data.amount, + this.receive.data.memo, + this.receive.unit, + this.receive.lnurl && this.receive.lnurl.callback + ) + .then(response => { + this.receive.status = 'success' + this.receive.paymentReq = response.data.payment_request + this.receive.paymentHash = response.data.payment_hash + + if (response.data.lnurl_response !== null) { + if (response.data.lnurl_response === false) { + response.data.lnurl_response = `Unable to connect` + } + + if (typeof response.data.lnurl_response === 'string') { + // failure + this.$q.notify({ + timeout: 5000, + type: 'warning', + message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`, + caption: response.data.lnurl_response + }) + return + } else if (response.data.lnurl_response === true) { + // success + this.$q.notify({ + timeout: 5000, + message: `Invoice sent to ${this.receive.lnurl.domain}!`, + spinner: true + }) + } + } + + clearInterval(this.receive.paymentChecker) + setTimeout(() => { + clearInterval(this.receive.paymentChecker) + }, 40000) + }) + .catch(err => { + LNbits.utils.notifyApiError(err) + this.receive.status = 'pending' + }) + }, + decodeQR: function (res) { + this.payInvoiceData.data.request = res + this.decodeRequest() + this.payInvoiceData.camera.show = false + }, + decodeRequest: function () { + this.payInvoiceData.show = true + let req = this.payInvoiceData.data.request.toLowerCase() + if ( + this.payInvoiceData.data.request.toLowerCase().startsWith('lightning:') + ) { + this.payInvoiceData.data.request = this.payInvoiceData.data.request.slice( + 10 + ) + } else if ( + this.payInvoiceData.data.request.toLowerCase().startsWith('lnurl:') + ) { + this.payInvoiceData.data.request = this.payInvoiceData.data.request.slice( + 6 + ) + } else if (req.indexOf('lightning=lnurl1') !== -1) { + this.payInvoiceData.data.request = this.payInvoiceData.data.request + .split('lightning=')[1] + .split('&')[0] + } + + if ( + this.payInvoiceData.data.request.toLowerCase().startsWith('lnurl1') || + this.payInvoiceData.data.request.match(/[\w.+-~_]+@[\w.+-~_]/) + ) { + return + } + + let invoice + try { + invoice = decode(this.payInvoiceData.data.request) + } catch (error) { + this.$q.notify({ + timeout: 3000, + type: 'warning', + message: error + '.', + caption: '400 BAD REQUEST' + }) + this.payInvoiceData.show = false + throw error + return + } + + let cleanInvoice = { + msat: invoice.human_readable_part.amount, + sat: invoice.human_readable_part.amount / 1000, + fsat: LNbits.utils.formatSat(invoice.human_readable_part.amount / 1000) + } + + _.each(invoice.data.tags, tag => { + if (_.isObject(tag) && _.has(tag, 'description')) { + if (tag.description === 'payment_hash') { + cleanInvoice.hash = tag.value + } else if (tag.description === 'description') { + cleanInvoice.description = tag.value + } else if (tag.description === 'expiry') { + var expireDate = new Date( + (invoice.data.time_stamp + tag.value) * 1000 + ) + cleanInvoice.expireDate = Quasar.utils.date.formatDate( + expireDate, + 'YYYY-MM-DDTHH:mm:ss.SSSZ' + ) + cleanInvoice.expired = false // TODO + } + } + }) + + this.payInvoiceData.invoice = Object.freeze(cleanInvoice) + }, + payInvoice: function () { + let dismissPaymentMsg = this.$q.notify({ + timeout: 0, + message: 'Processing payment...' + }) + }, + payLnurl: function () { + let dismissPaymentMsg = this.$q.notify({ + timeout: 0, + message: 'Processing payment...' + }) + }, + authLnurl: function () { + let dismissAuthMsg = this.$q.notify({ + timeout: 10, + message: 'Performing authentication...' + }) + }, + + deleteWallet: function (walletId, user) { + LNbits.utils + .confirmDialog('Are you sure you want to delete this wallet?') + .onOk(() => { + LNbits.href.deleteWallet(walletId, user) + }) + }, + fetchPayments: function () { + return + }, + fetchBalance: function () {}, + exportCSV: function () { + // status is important for export but it is not in paymentsTable + // because it is manually added with payment detail link and icons + // and would cause duplication in the list + let columns = this.paymentsTable.columns + columns.unshift({ + name: 'pending', + align: 'left', + label: 'Pending', + field: 'pending' + }) + LNbits.utils.exportCSV(columns, this.payments) + }, + + /////////////////////////////////// WALLET /////////////////////////////////// + showInvoicesDialog: async function () { + console.log('##### showInvoicesDialog') + this.invoiceData.amount = 0 + this.invoiceData.bolt11 = '' + this.invoiceData.hash = '' + this.invoiceData.memo = '' + this.showInvoiceDetails = true + }, + + showInvoiceDialog: function (data) { + console.log('##### showInvoiceDialog') + this.invoiceData = _.clone(data) + this.showInvoiceDetails = true + }, + + showPayInvoiceDialog: function () { + console.log('### showPayInvoiceDialog') + this.payInvoiceData.invoice = '' + this.payInvoiceData.data.request = '' + this.showPayInvoice = true + this.payInvoiceData.camera.show = false + }, + + showSendTokensDialog: function () { + this.sendData.tokens = '' + this.sendData.tokensBase64 = '' + this.sendData.amount = 0 + this.sendData.memo = '' + this.showSendTokens = true + }, + + showReceiveTokensDialog: function () { + this.receiveData.tokensBase64 = '' + this.showReceiveTokens = true + }, + + //////////////////////// MINT ////////////////////////////////////////// + requestMintButton: async function () { + await this.requestMint() + console.log('this is your invoice BEFORE') + console.log(this.invoiceData) + this.invoiceCheckListener = setInterval(async () => { + try { + console.log('this is your invoice AFTER') + console.log(this.invoiceData) + await this.recheckInvoice(this.invoiceData.hash, false) + clearInterval(this.invoiceCheckListener) + this.invoiceData.bolt11 = '' + this.showInvoiceDetails = false + navigator.vibrate(200) + this.$q.notify({ + timeout: 5000, + type: 'positive', + message: 'Payment received' + }) + } catch (error) { + console.log('not paid yet') + } + }, 3000) + }, + + requestMint: async function () { + // gets an invoice from the mint to get new tokens + try { + const {data} = await LNbits.api.request( + 'GET', + `/cashu/api/v1/${this.mintId}/mint?amount=${this.invoiceData.amount}` + ) + console.log('### data', data) + + this.invoiceData.bolt11 = data.pr + this.invoiceData.hash = data.hash + this.invoicesCashu.push({ + ..._.clone(this.invoiceData), + date: currentDateStr(), + status: 'pending' + }) + this.storeinvoicesCashu() + this.tab = 'invoices' + return data + } catch (error) { + console.error(error) + LNbits.utils.notifyApiError(error) + throw error + } + }, + mintApi: async function (amounts, payment_hash, verbose = true) { + console.log('### promises', payment_hash) + try { + let secrets = await this.generateSecrets(amounts) + let {blindedMessages, rs} = await this.constructOutputs( + amounts, + secrets + ) + const promises = await LNbits.api.request( + 'POST', + `/cashu/api/v1/${this.mintId}/mint?payment_hash=${payment_hash}`, + '', + { + blinded_messages: blindedMessages + } + ) + console.log('### promises data', promises.data) + let proofs = await this.constructProofs(promises.data, secrets, rs) + return proofs + } catch (error) { + console.error(error) + if (verbose) { + LNbits.utils.notifyApiError(error) + } + throw error + } + }, + mint: async function (amount, payment_hash, verbose = true) { + try { + const split = splitAmount(amount) + const proofs = await this.mintApi(split, payment_hash, verbose) + if (!proofs.length) { + throw 'could not mint' + } + this.proofs = this.proofs.concat(proofs) + this.storeProofs() + await this.setInvoicePaid(payment_hash) + return proofs + } catch (error) { + console.error(error) + if (verbose) { + LNbits.utils.notifyApiError(error) + } + throw error + } + }, + setInvoicePaid: async function (payment_hash) { + const invoice = this.invoicesCashu.find(i => i.hash === payment_hash) + invoice.status = 'paid' + this.storeinvoicesCashu() + }, + recheckInvoice: async function (payment_hash, verbose = true) { + console.log('### recheckInvoice.hash', payment_hash) + const invoice = this.invoicesCashu.find(i => i.hash === payment_hash) + try { + proofs = await this.mint(invoice.amount, invoice.hash, verbose) + return proofs + } catch (error) { + console.log('Invoice still pending') + throw error + } + }, + + generateSecrets: async function (amounts) { + const secrets = [] + for (let i = 0; i < amounts.length; i++) { + const secret = nobleSecp256k1.utils.randomBytes(32) + secrets.push(secret) + } + return secrets + }, + + constructOutputs: async function (amounts, secrets) { + const blindedMessages = [] + const rs = [] + for (let i = 0; i < amounts.length; i++) { + const {B_, r} = await step1Alice(secrets[i]) + blindedMessages.push({amount: amounts[i], B_: B_}) + rs.push(r) + } + return { + blindedMessages, + rs + } + }, + + constructProofs: function (promises, secrets, rs) { + const proofs = [] + for (let i = 0; i < promises.length; i++) { + const encodedSecret = uint8ToBase64.encode(secrets[i]) + let {id, amount, C, secret} = this.promiseToProof( + promises[i].id, + promises[i].amount, + promises[i]['C_'], + encodedSecret, + rs[i] + ) + proofs.push({id, amount, C, secret}) + } + return proofs + }, + + promiseToProof: function (id, amount, C_hex, secret, r) { + const C_ = nobleSecp256k1.Point.fromHex(C_hex) + const A = this.keys[amount] + const C = step3Alice( + C_, + nobleSecp256k1.utils.hexToBytes(r), + nobleSecp256k1.Point.fromHex(A) + ) + return { + id, + amount, + C: C.toHex(true), + secret + } + }, + + sumProofs: function (proofs) { + return proofs.reduce((s, t) => (s += t.amount), 0) + }, + splitToSend: async function (proofs, amount, invlalidate = false) { + // splits proofs so the user can keep firstProofs, send scndProofs + try { + const spendableProofs = proofs.filter(p => !p.reserved) + if (this.sumProofs(spendableProofs) < amount) { + throw new Error('balance too low.') + } + let {fristProofs, scndProofs} = await this.split( + spendableProofs, + amount + ) + + // set scndProofs in this.proofs as reserved + const usedSecrets = proofs.map(p => p.secret) + for (let i = 0; i < this.proofs.length; i++) { + if (usedSecrets.includes(this.proofs[i].secret)) { + this.proofs[i].reserved = true + } + } + if (invlalidate) { + // delete tokens from db + this.proofs = fristProofs + // add new fristProofs, scndProofs to this.proofs + this.storeProofs() + } + + return {fristProofs, scndProofs} + } catch (error) { + console.error(error) + LNbits.utils.notifyApiError(error) + throw error + } + }, + + split: async function (proofs, amount) { + try { + if (proofs.length == 0) { + throw new Error('no proofs provided.') + } + let {fristProofs, scndProofs} = await this.splitApi(proofs, amount) + // delete proofs from this.proofs + const usedSecrets = proofs.map(p => p.secret) + this.proofs = this.proofs.filter(p => !usedSecrets.includes(p.secret)) + // add new fristProofs, scndProofs to this.proofs + this.proofs = this.proofs.concat(fristProofs).concat(scndProofs) + this.storeProofs() + return {fristProofs, scndProofs} + } catch (error) { + console.error(error) + LNbits.utils.notifyApiError(error) + throw error + } + }, + splitApi: async function (proofs, amount) { + try { + const total = this.sumProofs(proofs) + const frst_amount = total - amount + const scnd_amount = amount + const frst_amounts = splitAmount(frst_amount) + const scnd_amounts = splitAmount(scnd_amount) + const amounts = _.clone(frst_amounts) + amounts.push(...scnd_amounts) + let secrets = await this.generateSecrets(amounts) + if (secrets.length != amounts.length) { + throw new Error('number of secrets does not match number of outputs.') + } + let {blindedMessages, rs} = await this.constructOutputs( + amounts, + secrets + ) + const payload = { + amount, + proofs, + outputs: { + blinded_messages: blindedMessages + } + } + + console.log('payload', JSON.stringify(payload)) + + const {data} = await LNbits.api.request( + 'POST', + `/cashu/api/v1/${this.mintId}/split`, + '', + payload + ) + const frst_rs = rs.slice(0, frst_amounts.length) + const frst_secrets = secrets.slice(0, frst_amounts.length) + const scnd_rs = rs.slice(frst_amounts.length) + const scnd_secrets = secrets.slice(frst_amounts.length) + const fristProofs = this.constructProofs( + data.fst, + frst_secrets, + frst_rs + ) + const scndProofs = this.constructProofs(data.snd, scnd_secrets, scnd_rs) + + return {fristProofs, scndProofs} + } catch (error) { + console.error(error) + LNbits.utils.notifyApiError(error) + throw error + } + }, + + redeem: async function () { + this.showReceiveTokens = false + console.log('### receive tokens', this.receiveData.tokensBase64) + try { + if (this.receiveData.tokensBase64.length == 0) { + throw new Error('no tokens provided.') + } + const tokensJson = atob(this.receiveData.tokensBase64) + const proofs = JSON.parse(tokensJson) + const amount = proofs.reduce((s, t) => (s += t.amount), 0) + let {fristProofs, scndProofs} = await this.split(proofs, amount) + // HACK: we need to do this so the balance updates + this.proofs = this.proofs.concat([]) + navigator.vibrate(200) + this.$q.notify({ + timeout: 5000, + type: 'positive', + message: 'Tokens received' + }) + } catch (error) { + console.error(error) + LNbits.utils.notifyApiError(error) + throw error + } + // } + }, + + sendTokens: async function () { + // keep firstProofs, send scndProofs + let {fristProofs, scndProofs} = await this.splitToSend( + this.proofs, + this.sendData.amount, + true + ) + this.sendData.tokens = '' + this.sendData.tokensBase64 = '' + this.sendData.tokens = scndProofs + console.log('### this.sendData.tokens', this.sendData.tokens) + this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens)) + navigator.vibrate(200) + }, + checkFees: async function (payment_request) { + const payload = { + pr: payment_request + } + console.log('#### payload', JSON.stringify(payload)) + try { + const {data} = await LNbits.api.request( + 'POST', + `/cashu/api/v1/${this.mintId}/checkfees`, + '', + payload + ) + console.log('#### checkFees', payment_request, data.fee) + return data.fee + } catch (error) { + console.error(error) + LNbits.utils.notifyApiError(error) + throw error + } + }, + melt: async function () { + // todo: get fees from server and add to inputs + console.log('#### pay lightning') + const amount_invoice = this.payInvoiceData.invoice.sat + const amount = + amount_invoice + + (await this.checkFees(this.payInvoiceData.data.request)) + console.log( + '#### amount invoice', + amount_invoice, + 'amount with fees', + amount + ) + // if (amount > balance()) { + // LNbits.utils.notifyApiError('Balance too low') + // return + // } + let {fristProofs, scndProofs} = await this.splitToSend( + this.proofs, + amount + ) + const payload = { + proofs: scndProofs.flat(), + amount, + invoice: this.payInvoiceData.data.request + } + console.log('#### payload', JSON.stringify(payload)) + try { + const {data} = await LNbits.api.request( + 'POST', + `/cashu/api/v1/${this.mintId}/melt`, + '', + payload + ) + navigator.vibrate(200) + this.$q.notify({ + timeout: 5000, + type: 'positive', + message: 'Invoice paid' + }) + // delete tokens from db + this.proofs = fristProofs + // add new fristProofs, scndProofs to this.proofs + this.storeProofs() + console.log({ + amount: -amount, + bolt11: this.payInvoiceData.data.request, + hash: this.payInvoiceData.data.hash, + memo: this.payInvoiceData.data.memo + }) + this.invoicesCashu.push({ + amount: -amount, + bolt11: this.payInvoiceData.data.request, + hash: this.payInvoiceData.data.hash, + memo: this.payInvoiceData.data.memo, + date: currentDateStr(), + status: 'paid' + }) + this.storeinvoicesCashu() + this.tab = 'invoices' + + this.payInvoiceData.invoice = false + this.payInvoiceData.show = false + } catch (error) { + console.error(error) + LNbits.utils.notifyApiError(error) + throw error + } + }, + + recheckPendingInvoices: async function () { + for (const invoice of this.invoicesCashu) { + if (invoice.status === 'pending' && invoice.sat > 0) { + this.recheckInvoice(invoice.hash, false) + } + } + }, + + fetchMintKeys: async function () { + const {data} = await LNbits.api.request( + 'GET', + `/cashu/api/v1/${this.mintId}/keys` + ) + this.keys = data + localStorage.setItem( + this.mintKey(this.mintId, 'keys'), + JSON.stringify(data) + ) + }, + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + findTokenForAmount: function (amount) { + for (const token of this.proofs) { + const index = token.promises?.findIndex(p => p.amount === amount) + if (index >= 0) { + return { + promise: token.promises[index], + secret: token.secrets[index], + r: token.rs[index] + } + } + } + }, + + checkInvoice: function () { + console.log('#### checkInvoice') + try { + const invoice = decode(this.payInvoiceData.data.request) + + const cleanInvoice = { + msat: invoice.human_readable_part.amount, + sat: invoice.human_readable_part.amount / 1000, + fsat: LNbits.utils.formatSat( + invoice.human_readable_part.amount / 1000 + ) + } + + _.each(invoice.data.tags, tag => { + if (_.isObject(tag) && _.has(tag, 'description')) { + if (tag.description === 'payment_hash') { + cleanInvoice.hash = tag.value + } else if (tag.description === 'description') { + cleanInvoice.description = tag.value + } else if (tag.description === 'expiry') { + var expireDate = new Date( + (invoice.data.time_stamp + tag.value) * 1000 + ) + cleanInvoice.expireDate = Quasar.utils.date.formatDate( + expireDate, + 'YYYY-MM-DDTHH:mm:ss.SSSZ' + ) + cleanInvoice.expired = false // TODO + } + } + + this.payInvoiceData.invoice = cleanInvoice + }) + + console.log( + '#### this.payInvoiceData.invoice', + this.payInvoiceData.invoice + ) + } catch (error) { + this.$q.notify({ + timeout: 5000, + type: 'warning', + message: 'Could not decode invoice', + caption: error + '' + }) + throw error + } + }, + + storeinvoicesCashu: function () { + localStorage.setItem( + this.mintKey(this.mintId, 'invoicesCashu'), + JSON.stringify(this.invoicesCashu) + ) + }, + storeProofs: function () { + localStorage.setItem( + this.mintKey(this.mintId, 'proofs'), + JSON.stringify(this.proofs, bigIntStringify) + ) + }, + + mintKey: function (mintId, key) { + // returns a key for the local storage + // depending on the current mint + return 'cashu.' + mintId + '.' + key + } + }, + watch: { + payments: function () { + this.balance() + } + }, + + created: function () { + let params = new URL(document.location).searchParams + + // get mint + if (params.get('mint_id')) { + this.mintId = params.get('mint_id') + this.$q.localStorage.set('cashu.mint', params.get('mint_id')) + } else if (this.$q.localStorage.getItem('cashu.mint')) { + this.mintId = this.$q.localStorage.getItem('cashu.mint') + } else { + this.$q.notify({ + color: 'red', + message: 'No mint set!' + }) + } + + // get name + if (params.get('mint_name')) { + this.mintName = params.get('mint_name') + this.$q.localStorage.set( + this.mintKey(this.mintId, 'mintName'), + this.mintName + ) + } else if (this.$q.localStorage.getItem('cashu.name')) { + this.mintName = this.$q.localStorage.getItem('cashu.name') + } + + // get ticker + if ( + !params.get('tsh') && + !this.$q.localStorage.getItem(this.mintKey(this.mintId, 'tickershort')) + ) { + this.$q.localStorage.set(this.mintKey(this.mintId, 'tickershort'), 'sats') + this.tickershort = 'sats' + } else if (params.get('tsh')) { + this.$q.localStorage.set( + this.mintKey(this.mintId, 'tickershort'), + params.get('tsh') + ) + this.tickershort = params.get('tsh') + } else if ( + this.$q.localStorage.getItem(this.mintKey(this.mintId, 'tickershort')) + ) { + this.tickershort = this.$q.localStorage.getItem( + this.mintKey(this.mintId, 'tickershort') + ) + } + + const keysJson = localStorage.getItem(this.mintKey(this.mintId, 'keys')) + if (!keysJson) { + this.fetchMintKeys() + } else { + this.keys = JSON.parse(keysJson) + } + + this.invoicesCashu = JSON.parse( + localStorage.getItem(this.mintKey(this.mintId, 'invoicesCashu')) || '[]' + ) + this.proofs = JSON.parse( + localStorage.getItem(this.mintKey(this.mintId, 'proofs')) || '[]' + ) + console.log('### invoicesCashu', this.invoicesCashu) + console.table('### tokens', this.proofs) + console.log('#### this.mintId', this.mintId) + console.log('#### this.mintName', this.mintName) + + this.recheckPendingInvoices() + } +}) diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html index 0dc5bcf10..7ec994372 100644 --- a/lnbits/extensions/cashu/templates/cashu/wallet.html +++ b/lnbits/extensions/cashu/templates/cashu/wallet.html @@ -625,1105 +625,5 @@ - + {% endblock %}