diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html index 88af72ca..6d86ff35 100644 --- a/lnbits/extensions/cashu/templates/cashu/wallet.html +++ b/lnbits/extensions/cashu/templates/cashu/wallet.html @@ -72,13 +72,19 @@ > - + + + + + + + + {{props.row.amount}} - +
{{props.row.date}}
@@ -173,10 +182,68 @@ {% endraw %}
+ + - History + + {% raw %} + + {% endraw %} + + +
@@ -653,6 +720,7 @@ mintName: '', keys: '', invoicesCashu: [], + historyTokens: [], invoiceData: { amount: 0, memo: '', @@ -736,21 +804,23 @@ name: 'status', align: 'left', label: '', - field: 'status' + field: 'status', + sortable: true }, { name: 'amount', align: 'left', label: 'Amount', - field: 'amount' - }, - { - name: 'memo', - align: 'left', - label: 'Memo', - field: 'memo', + field: 'amount', sortable: true }, + // { + // name: 'memo', + // align: 'left', + // label: 'Memo', + // field: 'memo', + // sortable: true + // }, { name: 'date', align: 'left', @@ -763,7 +833,7 @@ align: 'right', label: 'Hash', field: 'hash', - sortable: true + sortable: false } ], pagination: { @@ -811,6 +881,43 @@ filter: null }, + historyTable: { + columns: [ + { + name: 'status', + align: 'left', + label: '', + field: 'status', + sortable: true + }, + { + name: 'amount', + align: 'left', + label: 'Value ({{LNBITS_DENOMINATION}})', + field: 'amount', + sortable: true + }, + { + name: 'date', + align: 'left', + label: 'Date', + field: 'date', + sortable: true + }, + { + name: 'token', + align: 'left', + label: 'Token', + field: 'token', + sortable: false + } + ], + pagination: { + rowsPerPage: 5 + }, + filter: null + }, + paymentsChart: { show: false }, @@ -1147,15 +1254,90 @@ }, //////////////////////// MINT ////////////////////////////////////////// + + 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) + }, + + + //////////// API /////////// + requestMintButton: async function () { await this.requestMint() - console.log('this is your invoice BEFORE') - console.log(this.invoiceData) + console.log("#### request mint", this.invoiceData) + let nInterval = 0 this.invoiceCheckListener = setInterval(async () => { try { - console.log('this is your invoice AFTER') + nInterval += 1 + + // exit loop after 5m + if (nInterval > 100) { + console.log("### stopping invoice check worker") + clearInterval(this.invoiceCheckListener) + } + console.log('### setInterval', nInterval) console.log(this.invoiceData) + + // this will throw an error if the invoice is pending await this.recheckInvoice(this.invoiceData.hash, false) + + // only without error (invoice paid) will we reach here + console.log("### stopping invoice check worker") clearInterval(this.invoiceCheckListener) this.invoiceData.bolt11 = '' this.showInvoiceDetails = false @@ -1242,81 +1424,6 @@ 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 { @@ -1435,12 +1542,21 @@ if (this.receiveData.tokensBase64.length == 0) { throw new Error('no tokens provided.') } - const tokensJson = atob(this.receiveData.tokensBase64) - const proofs = JSON.parse(tokensJson) + const tokenJson = atob(this.receiveData.tokensBase64) + const proofs = JSON.parse(tokenJson) 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([]) + + this.historyTokens.push({ + status: 'paid', + amount: amount, + date: currentDateStr(), + token: this.receiveData.tokensBase64 + }) + this.storehistoryTokens() + navigator.vibrate(200) this.$q.notify({ timeout: 5000, @@ -1467,6 +1583,15 @@ this.sendData.tokens = scndProofs console.log('### this.sendData.tokens', this.sendData.tokens) this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens)) + + this.historyTokens.push({ + status: 'pending', + amount: -this.sendData.amount, + date: currentDateStr(), + token: this.sendData.tokensBase64 + }) + this.storehistoryTokens() + navigator.vibrate(200) }, checkFees: async function (payment_request) { @@ -1559,7 +1684,22 @@ 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 + } + }, recheckPendingInvoices: async function () { for (const invoice of this.invoicesCashu) { if (invoice.status === 'pending' && invoice.sat > 0) { @@ -1567,7 +1707,53 @@ } } }, - + setTokenPaid: async function (token) { + const invoice = this.historyTokens.find(i => i.token === token) + invoice.status = 'paid' + this.storehistoryTokens() + }, + checkTokenSpendable: async function(token) { + const tokenJson = atob(token) + const proofs = JSON.parse(tokenJson) + const payload = { + proofs: proofs.flat(), + } + console.log('#### payload', JSON.stringify(payload)) + try { + const {data} = await LNbits.api.request( + 'POST', + `/cashu/api/v1/${this.mintId}/check`, + '', + payload + ) + // iterate through response of form {0: true, 1: false, ...} + let paid = false + for (const [key, spendable] of Object.entries(data)) { + if (!spendable){ + this.setTokenPaid(token) + paid = true + } + } + if (paid){ + navigator.vibrate(200) + this.$q.notify({ + timeout: 5000, + type: 'positive', + message: 'Token sent' + }) + } else { + this.$q.notify({ + timeout: 5000, + color: 'gray', + message: 'Token still pending' + }) + } + } catch (error) { + console.error(error) + LNbits.utils.notifyApiError(error) + throw error + } + }, fetchMintKeys: async function () { const {data} = await LNbits.api.request( 'GET', @@ -1577,6 +1763,7 @@ localStorage.setItem(this.mintKey(this.mintId, 'keys'), JSON.stringify(data)) }, + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1650,6 +1837,12 @@ JSON.stringify(this.invoicesCashu) ) }, + storehistoryTokens: function () { + localStorage.setItem( + this.mintKey(this.mintId, 'historyTokens'), + JSON.stringify(this.historyTokens) + ) + }, storeProofs: function () { localStorage.setItem( this.mintKey(this.mintId, 'proofs'), @@ -1718,6 +1911,11 @@ this.invoicesCashu = JSON.parse( localStorage.getItem(this.mintKey(this.mintId, 'invoicesCashu')) || '[]' ) + + this.historyTokens = JSON.parse( + localStorage.getItem(this.mintKey(this.mintId, 'historyTokens')) || '[]' + ) + this.proofs = JSON.parse(localStorage.getItem(this.mintKey(this.mintId, 'proofs')) || '[]') console.log('### invoicesCashu', this.invoicesCashu) console.table('### tokens', this.proofs)