diff --git a/.gitignore b/.gitignore
index 23ee9c3..0748bfb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,4 @@ venv
database.sqlite3
database.sqlite3*
+.pyre*
diff --git a/LNbits/__init__.py b/LNbits/__init__.py
index fcb0e5f..64f52e1 100644
--- a/LNbits/__init__.py
+++ b/LNbits/__init__.py
@@ -7,17 +7,12 @@ from flask import Flask, jsonify, render_template, request, redirect, url_for
from . import bolt11
from .db import Database
-from .settings import DATABASE_PATH, LNBITS_PATH, WALLET, DEFAULT_USER_WALLET_NAME
+from .helpers import megajson
+from .settings import LNBITS_PATH, WALLET, DEFAULT_USER_WALLET_NAME
app = Flask(__name__)
-
-
-def db_connect(db_path=DATABASE_PATH):
- import sqlite3
-
- con = sqlite3.connect(db_path)
- return con
+app.jinja_env.filters["megajson"] = megajson
@app.before_first_request
@@ -50,7 +45,7 @@ def deletewallet():
(thewal, theid),
)
- next_wallet = db.fetchone("SELECT hash FROM wallets WHERE user = ?", (theid,))
+ next_wallet = db.fetchone("SELECT id FROM wallets WHERE user = ?", (theid,))
if next_wallet:
return redirect(url_for("wallet", usr=theid, wal=next_wallet[0]))
@@ -164,16 +159,14 @@ def wallet():
(SELECT balance/1000 FROM balances WHERE wallet = wallets.id),
0
) AS balance,
- name,
- adminkey,
- inkey
+ *
FROM wallets
WHERE user = ? AND id = ?
""",
(usr, wallet_id),
)
- transactions = []
+ transactions = db.fetchall("SELECT * FROM apipayments WHERE wallet = ?", (wallet_id,))
return render_template(
"wallet.html", user_wallets=user_wallets, wallet=wallet, user=usr, transactions=transactions,
diff --git a/LNbits/data/schema.sql b/LNbits/data/schema.sql
index 7b9f3a4..af1eca9 100644
--- a/LNbits/data/schema.sql
+++ b/LNbits/data/schema.sql
@@ -18,7 +18,8 @@ CREATE TABLE IF NOT EXISTS apipayments (
fee integer NOT NULL DEFAULT 0,
wallet text NOT NULL,
pending boolean NOT NULL,
- memo text
+ memo text,
+ time timestamp NOT NULL DEFAULT (strftime('%s', 'now'))
);
CREATE VIEW IF NOT EXISTS balances AS
diff --git a/LNbits/helpers.py b/LNbits/helpers.py
index e69de29..30e1925 100644
--- a/LNbits/helpers.py
+++ b/LNbits/helpers.py
@@ -0,0 +1,16 @@
+import json
+import sqlite3
+
+
+class MegaEncoder(json.JSONEncoder):
+ def default(self, o):
+ if type(o) == sqlite3.Row:
+ val = {}
+ for k in o.keys():
+ val[k] = o[k]
+ return val
+ return o
+
+
+def megajson(o):
+ return json.dumps(o, cls=MegaEncoder)
diff --git a/LNbits/static/app.js b/LNbits/static/app.js
new file mode 100644
index 0000000..3d12314
--- /dev/null
+++ b/LNbits/static/app.js
@@ -0,0 +1,349 @@
+/** @format */
+
+const user = window.user
+const user_wallets = window.user_wallets
+const wallet = window.wallet
+const transactions = window.transactions
+
+var thehash = ''
+var theinvoice = ''
+var outamount = ''
+var outmemo = ''
+
+// API CALLS
+
+function postAjax(url, data, thekey, success) {
+ var params =
+ typeof data == 'string'
+ ? data
+ : Object.keys(data)
+ .map(function(k) {
+ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k])
+ })
+ .join('&')
+ var xhr = window.XMLHttpRequest
+ ? new XMLHttpRequest()
+ : new ActiveXObject('Microsoft.XMLHTTP')
+ xhr.open('POST', url)
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState > 3 && xhr.status == 200) {
+ success(xhr.responseText)
+ }
+ }
+ xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey)
+ xhr.setRequestHeader('Content-Type', 'application/json')
+ xhr.send(params)
+ return xhr
+}
+
+function getAjax(url, thekey, success) {
+ var xhr = window.XMLHttpRequest
+ ? new XMLHttpRequest()
+ : new ActiveXObject('Microsoft.XMLHTTP')
+ xhr.open('GET', url, true)
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState > 3 && xhr.status == 200) {
+ success(xhr.responseText)
+ }
+ }
+ xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey)
+ xhr.setRequestHeader('Content-Type', 'application/json')
+ xhr.send()
+ return xhr
+}
+
+function sendfundsinput() {
+ document.getElementById('sendfunds').innerHTML =
+ "
" +
+ "
'
+ document.getElementById('receive').innerHTML = ''
+}
+
+function sendfundspaste() {
+ invoice = document.getElementById('pasteinvoice').value
+ theinvoice = decode(invoice)
+ outmemo = theinvoice.data.tags[1].value
+ outamount = Number(theinvoice.human_readable_part.amount) / 1000
+ if (outamount > Number(wallet.balance)) {
+ document.getElementById('sendfunds').innerHTML =
+ "" +
+ "
Not enough funds!
" +
+ "" +
+ ' '
+ } else {
+ document.getElementById('sendfunds').innerHTML =
+ "" +
+ '
Invoice detailsAmount: ' +
+ outamount +
+ '
Memo: ' +
+ outmemo +
+ '
' +
+ "" +
+ invoice +
+ '
' +
+ "" +
+ "" +
+ ' '
+ }
+}
+
+function receive() {
+ document.getElementById('receive').innerHTML =
+ "
'
+ document.getElementById('sendfunds').innerHTML = ''
+}
+
+function received() {
+ memo = document.getElementById('memo').value
+ amount = document.getElementById('amount').value
+ postAjax(
+ '/v1/invoices',
+ JSON.stringify({value: amount, memo: memo}),
+ wallet.inkey,
+ function(data) {
+ theinvoice = JSON.parse(data).pay_req
+ thehash = JSON.parse(data).payment_hash
+ document.getElementById('QRCODE').innerHTML =
+ "'
+
+ new QRCode(document.getElementById('qrcode'), {
+ text: theinvoice,
+ width: 300,
+ height: 300,
+ colorDark: '#000000',
+ colorLight: '#ffffff',
+ correctLevel: QRCode.CorrectLevel.M
+ })
+ getAjax('/v1/invoice/' + thehash, wallet.inkey, function(datab) {
+ console.log(JSON.parse(datab).PAID)
+ if (JSON.parse(datab).PAID == 'TRUE') {
+ window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user
+ }
+ })
+ }
+ )
+}
+
+function cancelsend() {
+ window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user
+}
+
+function sendfunds(invoice) {
+ var url = '/v1/channels/transactions'
+ postAjax(
+ url,
+ JSON.stringify({payment_request: invoice}),
+ wallet.adminkey,
+ function(data) {
+ thehash = JSON.parse(data).payment_hash
+ console.log(JSON.parse(data))
+ if (JSON.parse(data).PAID == 'TRUE') {
+ window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user
+ }
+ }
+ )
+}
+
+function scanQRsend() {
+ document.getElementById('sendfunds').innerHTML =
+ "
" +
+ "
🎥 Unable to access video stream (please make sure you have a webcam enabled)
" +
+ "
"
+ var video = document.createElement('video')
+ var canvasElement = document.getElementById('canvas')
+ var canvas = canvasElement.getContext('2d')
+ var loadingMessage = document.getElementById('loadingMessage')
+ var outputContainer = document.getElementById('output')
+ var outputMessage = document.getElementById('outputMessage')
+ var outputData = document.getElementById('outputData')
+ function drawLine(begin, end, color) {
+ canvas.beginPath()
+ canvas.moveTo(begin.x, begin.y)
+ canvas.lineTo(end.x, end.y)
+ canvas.lineWidth = 4
+ canvas.strokeStyle = color
+ canvas.stroke()
+ }
+ // Use facingMode: environment to attemt to get the front camera on phones
+ navigator.mediaDevices
+ .getUserMedia({video: {facingMode: 'environment'}})
+ .then(function(stream) {
+ video.srcObject = stream
+ video.setAttribute('playsinline', true) // required to tell iOS safari we don't want fullscreen
+ video.play()
+ requestAnimationFrame(tick)
+ })
+ function tick() {
+ loadingMessage.innerText = '⌛ Loading video...'
+ if (video.readyState === video.HAVE_ENOUGH_DATA) {
+ loadingMessage.hidden = true
+ canvasElement.hidden = false
+ outputContainer.hidden = false
+ canvasElement.height = video.videoHeight
+ canvasElement.width = video.videoWidth
+ canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height)
+ var imageData = canvas.getImageData(
+ 0,
+ 0,
+ canvasElement.width,
+ canvasElement.height
+ )
+ var code = jsQR(imageData.data, imageData.width, imageData.height, {
+ inversionAttempts: 'dontInvert'
+ })
+ if (code) {
+ drawLine(
+ code.location.topLeftCorner,
+ code.location.topRightCorner,
+ '#FF3B58'
+ )
+ drawLine(
+ code.location.topRightCorner,
+ code.location.bottomRightCorner,
+ '#FF3B58'
+ )
+ drawLine(
+ code.location.bottomRightCorner,
+ code.location.bottomLeftCorner,
+ '#FF3B58'
+ )
+ drawLine(
+ code.location.bottomLeftCorner,
+ code.location.topLeftCorner,
+ '#FF3B58'
+ )
+ outputMessage.hidden = true
+ outputData.parentElement.hidden = false
+ outputData.innerText = JSON.stringify(code.data)
+ theinvoice = decode(code.data)
+ outmemo = theinvoice.data.tags[1].value
+ outamount = Number(theinvoice.human_readable_part.amount) / 1000
+ if (outamount > Number(wallet.balance)) {
+ document.getElementById('sendfunds').innerHTML =
+ "" +
+ "
Not enough funds!
" +
+ "" +
+ ' '
+ } else {
+ document.getElementById('sendfunds').innerHTML =
+ "" +
+ '
Invoice detailsAmount: ' +
+ outamount +
+ '
Memo: ' +
+ outmemo +
+ '
' +
+ "" +
+ JSON.stringify(code.data) +
+ '
' +
+ "" +
+ "" +
+ ' '
+ }
+ } else {
+ outputMessage.hidden = false
+ outputData.parentElement.hidden = true
+ }
+ }
+ requestAnimationFrame(tick)
+ }
+}
+
+function deletewallet() {
+ var url = 'deletewallet?wal=' + wallet.id + '&usr=' + user
+ window.location.href = url
+}
+
+function sidebarmake() {
+ document.getElementById('sidebarmake').innerHTML =
+ "" +
+ "" +
+ "" +
+ '
'
+}
+
+function newwallet() {
+ walname = document.getElementById('walname').value
+ window.location.href = 'wallet?usr=' + user + '&nme=' + walname
+}
+
+function drawChart(transactions) {
+ var linechart = []
+ var transactionsHTML = ''
+ var balance = 0
+
+ for (var i = 0; i < transactions.length; i++) {
+ var tx = transactions[i]
+ var datime = convertTimestamp(tx.time)
+
+ // make the transactions table
+ transactionsHTML +=
+ "" +
+ tx.memo +
+ ' | ' +
+ datime +
+ ' | ' +
+ parseFloat(tx.amount / 1000) +
+ ' |
'
+
+ // make the line chart
+ balance += parseInt(tx.amount / 1000)
+ linechart.push({y: datime, balance: balance})
+ }
+
+ document.getElementById('transactions').innerHTML = transactionsHTML
+
+ if (linechart[0] != '') {
+ document.getElementById('satschart').innerHTML =
+ ""
+ }
+
+ console.log(linechart)
+ var line = new Morris.Line({
+ element: 'line-chart',
+ resize: true,
+ data: linechart,
+ xkey: 'y',
+ ykeys: ['balance'],
+ labels: ['balance'],
+ lineColors: ['#3c8dbc'],
+ hideHover: 'auto'
+ })
+}
+
+function convertTimestamp(timestamp) {
+ var d = new Date(timestamp * 1000),
+ yyyy = d.getFullYear(),
+ mm = ('0' + (d.getMonth() + 1)).slice(-2),
+ dd = ('0' + d.getDate()).slice(-2),
+ hh = d.getHours(),
+ h = hh,
+ min = ('0' + d.getMinutes()).slice(-2),
+ ampm = 'AM',
+ time
+ time = yyyy + '-' + mm + '-' + dd + ' ' + h + ':' + min
+ return time
+}
+
+if (transactions.length) {
+ drawChart(transactions)
+}
diff --git a/LNbits/templates/base.html b/LNbits/templates/base.html
index be4d940..93c3089 100644
--- a/LNbits/templates/base.html
+++ b/LNbits/templates/base.html
@@ -296,4 +296,9 @@
>