From 4368422b5c59cc75863842a0db50d486c09bd613 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 8 Sep 2022 11:36:22 +0300 Subject: [PATCH 001/154] feat: show version Helps when users report issues --- lnbits/extensions/watchonly/templates/watchonly/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index b9ce361c..1a564489 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -148,7 +148,7 @@
- {{SITE_TITLE}} Onchain Wallet (watch-only) Extension + {{SITE_TITLE}} Onchain Wallet (watch-only) Extension (v0.100)
From c432671ab7794decd1547ea9844554b43dc025e4 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 8 Sep 2022 12:47:59 +0300 Subject: [PATCH 002/154] chore: fix typo --- .../watchonly/static/components/wallet-list/wallet-list.js | 4 ++-- lnbits/extensions/watchonly/static/js/index.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js b/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js index f36e0d59..d5ad5e32 100644 --- a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js +++ b/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js @@ -233,7 +233,7 @@ async function walletList(path) { const addressData = mapAddressesData(data) addressData.note = `Shared on ${currentDateTime()}` - const lastAcctiveAddress = + const lastActiveAddress = this.addresses .filter( a => @@ -243,7 +243,7 @@ async function walletList(path) { addressData.gapLimitExceeded = !addressData.isChange && addressData.addressIndex > - lastAcctiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT + lastActiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT const wallet = this.walletAccounts.find(w => w.id === walletId) || {} wallet.address_no = addressData.addressIndex diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index ad1cdc14..61255c50 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -227,7 +227,7 @@ const watchOnly = async () => { newAddr => !this.addresses.find(a => a.address === newAddr.address) ) - const lastAcctiveAddress = + const lastActiveAddress = uniqueAddresses.filter(a => !a.isChange && a.hasActivity).pop() || {} @@ -237,7 +237,7 @@ const watchOnly = async () => { a.gapLimitExceeded = !a.isChange && a.addressIndex > - lastAcctiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT + lastActiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT }) this.addresses.push(...uniqueAddresses) } From 8c87e84b680473606ab59111d94ca1b2a1c11bd8 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 8 Sep 2022 16:37:02 +0300 Subject: [PATCH 003/154] feat: show address details on device --- lnbits/extensions/watchonly/crud.py | 6 ++- lnbits/extensions/watchonly/migrations.py | 7 +++ lnbits/extensions/watchonly/models.py | 2 + .../serial-signer/serial-signer.html | 51 ++++++++++--------- .../components/serial-signer/serial-signer.js | 34 +++++++++---- .../components/wallet-list/wallet-list.html | 35 ++++++++++--- .../components/wallet-list/wallet-list.js | 15 +++++- .../extensions/watchonly/static/js/index.js | 24 +++++++-- lnbits/extensions/watchonly/static/js/map.js | 1 + .../extensions/watchonly/static/js/utils.js | 1 + .../watchonly/templates/watchonly/index.html | 5 +- lnbits/extensions/watchonly/views_api.py | 3 +- 12 files changed, 131 insertions(+), 53 deletions(-) diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py index 21fea6f0..de338b91 100644 --- a/lnbits/extensions/watchonly/crud.py +++ b/lnbits/extensions/watchonly/crud.py @@ -23,9 +23,10 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount: type, address_no, balance, - network + network, + meta ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( wallet_id, @@ -37,6 +38,7 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount: w.address_no, w.balance, w.network, + w.meta, ), ) diff --git a/lnbits/extensions/watchonly/migrations.py b/lnbits/extensions/watchonly/migrations.py index 0c06b738..76f7f951 100644 --- a/lnbits/extensions/watchonly/migrations.py +++ b/lnbits/extensions/watchonly/migrations.py @@ -93,3 +93,10 @@ async def m006_drop_mempool_table(db): Mempool data is now part of `config` """ await db.execute("DROP TABLE watchonly.mempool;") + + +async def m007_add_wallet_meta_data(db): + """ + Add 'meta' for storing various metadata about the wallet + """ + await db.execute("ALTER TABLE watchonly.wallets ADD COLUMN meta TEXT DEFAULT '{}';") diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index 0c08780d..cedaa210 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -9,6 +9,7 @@ class CreateWallet(BaseModel): masterpub: str = Query("") title: str = Query("") network: str = "Mainnet" + meta: str = "{}" class WalletAccount(BaseModel): @@ -21,6 +22,7 @@ class WalletAccount(BaseModel): balance: int type: Optional[str] = "" network: str = "Mainnet" + meta: str = "{}" @classmethod def from_row(cls, row: Row) -> "WalletAccount": diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html index 68b81980..b256ea60 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html @@ -170,6 +170,31 @@ type="password" label="Password" > + + + + + + +

- -
- - - - -
+ Enter new password (8 numbers/letters)
Master Pubkey:
- + +
+
+ +
+
+
+
XPub:
+
+ +
+
+
-
Last Address Index:
diff --git a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js b/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js index d5ad5e32..d8df84e1 100644 --- a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js +++ b/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js @@ -118,9 +118,11 @@ async function walletList(path) { }, createWalletAccount: async function (data) { try { + const meta = {accountPath: this.accountPath} if (this.formDialog.useSerialPort) { const {xpub, fingerprint} = await this.fetchXpubFromHww() if (!xpub) return + meta.xpub = xpub const path = this.accountPath.substring(2) const outputType = this.formDialog.addressType.id if (outputType === 'sh') { @@ -129,6 +131,7 @@ async function walletList(path) { data.masterpub = `${outputType}([${fingerprint}/${path}]${xpub}/{0,1}/*)` } } + data.meta = JSON.stringify(meta) const response = await LNbits.api.request( 'POST', '/watchonly/api/v1/wallet', @@ -247,7 +250,7 @@ async function walletList(path) { const wallet = this.walletAccounts.find(w => w.id === walletId) || {} wallet.address_no = addressData.addressIndex - this.$emit('new-receive-address', addressData) + this.$emit('new-receive-address', {addressData, wallet}) }, showAddAccountDialog: function () { this.formDialog.show = true @@ -283,6 +286,16 @@ async function walletList(path) { const addressType = this.addressTypeOptions.find(t => t.id === value.id) || {} this.accountPath = addressType[`path${this.network}`] + }, + // todo: bad. base.js not present in custom components + copyText: function (text, message, position) { + var notify = this.$q.notify + Quasar.utils.copyToClipboard(text).then(function () { + notify({ + message: message || 'Copied to clipboard!', + position: position || 'bottom' + }) + }) } }, created: async function () { diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index 61255c50..45f2293b 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -172,10 +172,6 @@ const watchOnly = async () => { this.$refs.paymentRef.updateSignedPsbt(psbtBase64) }, - //################### SERIAL PORT ################### - - //################### HARDWARE WALLET ################### - //################### UTXOs ################### scanAllAddresses: async function () { await this.refreshAddresses() @@ -380,6 +376,26 @@ const watchOnly = async () => { showAddressDetails: function (addressData) { this.openQrCodeDialog(addressData) }, + showAddressDetailsWithConfirmation: function ({addressData, wallet}) { + this.showAddressDetails(addressData) + if (this.$refs.serialSigner.isConnected()) { + if (this.$refs.serialSigner.isAuthenticated()) { + if (wallet.meta?.accountPath) { + const branchIndex = addressData.isChange ? 1 : 0 + const path = + wallet.meta.accountPath + + `/${branchIndex}/${addressData.addressIndex}` + this.$refs.serialSigner.hwwShowAddress(path, addressData.address) + } + } else { + this.$q.notify({ + type: 'warning', + message: 'Please login in order to confirm address on device', + timeout: 10000 + }) + } + } + }, initUtxos: function (addresses) { if (!this.fetchedUtxos && addresses.length) { this.fetchedUtxos = true diff --git a/lnbits/extensions/watchonly/static/js/map.js b/lnbits/extensions/watchonly/static/js/map.js index ecc0b316..81093936 100644 --- a/lnbits/extensions/watchonly/static/js/map.js +++ b/lnbits/extensions/watchonly/static/js/map.js @@ -74,6 +74,7 @@ const mapWalletAccount = function (o) { 'YYYY-MM-DD HH:mm' ) : '', + meta: o.meta ? JSON.parse(o.meta) : null, label: o.title, expanded: false }) diff --git a/lnbits/extensions/watchonly/static/js/utils.js b/lnbits/extensions/watchonly/static/js/utils.js index 5e39a37f..c73dd9c0 100644 --- a/lnbits/extensions/watchonly/static/js/utils.js +++ b/lnbits/extensions/watchonly/static/js/utils.js @@ -3,6 +3,7 @@ const PSBT_BASE64_PREFIX = 'cHNidP8' const COMMAND_PING = '/ping' const COMMAND_PASSWORD = '/password' const COMMAND_PASSWORD_CLEAR = '/password-clear' +const COMMAND_ADDRESS = '/address' const COMMAND_SEND_PSBT = '/psbt' const COMMAND_SIGN_PSBT = '/sign' const COMMAND_HELP = '/help' diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index 1a564489..f9ffc298 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -27,7 +27,7 @@ :addresses="addresses" :serial-signer-ref="$refs.serialSigner" @accounts-update="updateAccounts" - @new-receive-address="showAddressDetails" + @new-receive-address="showAddressDetailsWithConfirmation" > @@ -148,7 +148,8 @@
- {{SITE_TITLE}} Onchain Wallet (watch-only) Extension (v0.100) + {{SITE_TITLE}} Onchain Wallet (watch-only) Extension + (v0.100)
diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 1a4b93ed..3bfa4707 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -93,6 +93,7 @@ async def api_wallet_create_or_update( address_no=-1, # so fresh address on empty wallet can get address with index 0 balance=0, network=network["name"], + meta=data.meta, ) wallets = await get_watch_wallets(w.wallet.user, network["name"]) @@ -137,7 +138,7 @@ async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(require_admin await delete_watch_wallet(wallet_id) await delete_addresses_for_wallet(wallet_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT #############################ADDRESSES########################## From d77619279d4b7538c4faa3e418e0836df7ffc7ef Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 9 Sep 2022 16:28:20 +0300 Subject: [PATCH 004/154] chore: code clean-up --- lnbits/extensions/watchonly/views_api.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 3bfa4707..77d28fee 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -269,7 +269,6 @@ async def api_psbt_create( for i, inp in enumerate(inputs_extra): psbt.inputs[i].bip32_derivations = inp["bip32_derivations"] psbt.inputs[i].non_witness_utxo = inp.get("non_witness_utxo", None) - print("### ", inp.get("non_witness_utxo", None)) outputs_extra = [] bip32_derivations = {} @@ -344,11 +343,8 @@ async def api_tx_broadcast( async with httpx.AsyncClient() as client: r = await client.post(endpoint + "/api/tx", data=data.tx_hex) tx_id = r.text - print("### broadcast tx_id: ", tx_id) return tx_id - # return "0f0f0f0f0f0f0f0f0f0f0f00f0f0f0f0f0f0f0f0f0f00f0f0f0f0f0f0.mock.transaction.id" except Exception as e: - print("### broadcast error: ", str(e)) raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) From f14689fb604614c618b591c21d2a7914de16201b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 9 Sep 2022 17:06:36 +0300 Subject: [PATCH 005/154] feat: do not allow taproot inputs to be sent for signing --- .../watchonly/static/components/payment/payment.js | 12 ++++++++++++ .../static/components/utxo-list/utxo-list.html | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/lnbits/extensions/watchonly/static/components/payment/payment.js b/lnbits/extensions/watchonly/static/components/payment/payment.js index a74e5489..f2cccbba 100644 --- a/lnbits/extensions/watchonly/static/components/payment/payment.js +++ b/lnbits/extensions/watchonly/static/components/payment/payment.js @@ -104,6 +104,18 @@ async function payment(path) { }) return } + const p2trUtxo = this.utxos.find( + u => u.selected && u.accountType === 'p2tr' + ) + if (p2trUtxo) { + this.$q.notify({ + type: 'warning', + message: 'Taproot Signing not supported yet!', + caption: 'Please manually deselect the Taproot UTXOs', + timeout: 10000 + }) + return + } if (!this.serialSignerRef.isAuthenticated()) { await this.serialSignerRef.hwwShowPasswordDialog() const authenticated = await this.serialSignerRef.isAuthenticating() diff --git a/lnbits/extensions/watchonly/static/components/utxo-list/utxo-list.html b/lnbits/extensions/watchonly/static/components/utxo-list/utxo-list.html index a55b99e9..fd1d591c 100644 --- a/lnbits/extensions/watchonly/static/components/utxo-list/utxo-list.html +++ b/lnbits/extensions/watchonly/static/components/utxo-list/utxo-list.html @@ -97,6 +97,13 @@ change + + taproot +
From d965457b92bda74f39471f347a9ecf7e6d1b5af7 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 9 Sep 2022 17:47:12 +0300 Subject: [PATCH 006/154] feat: show seed word in browser (for devices with no display) --- .../serial-signer/serial-signer.html | 18 ++++++++++++++++-- .../components/serial-signer/serial-signer.js | 9 ++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html index b256ea60..64848dda 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html @@ -393,9 +393,23 @@
- + - Check word at position {{hww.seedWordPosition}} on display + Check word at position {{hww.seedWordPosition}} on device +
+
+ +
+
+
+
+ +
+
diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js index cc4aaa06..47089f08 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js @@ -38,6 +38,8 @@ async function serialSigner(path) { psbtSentResolve: null, xpubResolve: null, seedWordPosition: 1, + seedWord: null, + showSeedWord: false, showSeedDialog: false, // config: null, @@ -374,6 +376,10 @@ async function serialSigner(path) { }) } }, + closeSeedDialog: function () { + this.hww.seedWord = null + this.hww.showSeedWord = false + }, hwwConfirmNext: async function () { this.hww.confirm.outputIndex += 1 if (this.hww.confirm.outputIndex >= this.tx.outputs.length) { @@ -817,7 +823,8 @@ async function serialSigner(path) { await this.sendCommandSecure(COMMAND_SEED, [this.hww.seedWordPosition]) }, handleShowSeedResponse: function (res = '') { - const args = res.trim().split(' ') + const [pos, word] = res.trim().split(' ') + this.hww.seedWord = `${pos}. ${word}` }, hwwRestore: async function () { try { From a8951f5b48f9c763147ef45383157c4f7f939aee Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 9 Sep 2022 18:40:31 +0300 Subject: [PATCH 007/154] feat: show QR code for descriptor and xpub --- .../components/wallet-list/wallet-list.html | 36 +++++++++++++++++-- .../components/wallet-list/wallet-list.js | 6 ++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.html b/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.html index a7ca6497..b656bdca 100644 --- a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.html +++ b/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.html @@ -116,6 +116,7 @@ >New Receive Address
+
{{getAccountDescription(props.row.type)}}
@@ -124,9 +125,19 @@
Master Pubkey:
-
+
+
+ +
XPub:
-
+
+
+ +
+ + + + + + +
diff --git a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js b/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js index d8df84e1..004f092b 100644 --- a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js +++ b/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js @@ -16,6 +16,8 @@ async function walletList(path) { return { walletAccounts: [], address: {}, + showQrCodeDialog: false, + qrCodeValue: null, formDialog: { show: false, @@ -296,6 +298,10 @@ async function walletList(path) { position: position || 'bottom' }) }) + }, + openQrCodeDialog: function (qrCodeValue) { + this.qrCodeValue = qrCodeValue + this.showQrCodeDialog = true } }, created: async function () { From 3342be0b167de969c6d6f11c0feea5ebfb935370 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 19 Sep 2022 17:11:57 +0300 Subject: [PATCH 008/154] feat: update word position on device button click --- .../watchonly/static/components/serial-signer/serial-signer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js index 47089f08..b06f94ae 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js @@ -825,6 +825,7 @@ async function serialSigner(path) { handleShowSeedResponse: function (res = '') { const [pos, word] = res.trim().split(' ') this.hww.seedWord = `${pos}. ${word}` + this.hww.seedWordPosition = pos }, hwwRestore: async function () { try { From 282ab319893f49ddbaa87620b45b3addcaa867b6 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 19 Sep 2022 17:14:08 +0300 Subject: [PATCH 009/154] chore: version bumb --- lnbits/extensions/watchonly/templates/watchonly/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index f9ffc298..d76b675a 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -149,7 +149,7 @@
{{SITE_TITLE}} Onchain Wallet (watch-only) Extension - (v0.100) + (v0.2)
From 7a243105f9f96e89d9efc80cae4c2428ac0675cf Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 20 Sep 2022 10:50:48 +0300 Subject: [PATCH 010/154] feat: add info message for Console --- .../components/serial-signer/serial-signer.html | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html index 64848dda..b5f8098f 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html @@ -375,7 +375,20 @@ + +
+
+ + Open the browser Developer Console for more Details! + +
+
+ -
Close
From c30ead3d9b6c4d7bcd54388414293b490802f4bd Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 20 Sep 2022 15:49:03 +0300 Subject: [PATCH 011/154] feat: seed input: autocomplete and validate words --- .../components/seed-input/seed-input.html | 80 + .../components/seed-input/seed-input.js | 102 + .../serial-signer/serial-signer.html | 64 +- .../components/serial-signer/serial-signer.js | 5 + .../watchonly/static/js/bip39-word-list.js | 2050 +++++++++++++++++ .../extensions/watchonly/static/js/index.js | 1 + .../watchonly/templates/watchonly/index.html | 4 + 7 files changed, 2279 insertions(+), 27 deletions(-) create mode 100644 lnbits/extensions/watchonly/static/components/seed-input/seed-input.html create mode 100644 lnbits/extensions/watchonly/static/components/seed-input/seed-input.js create mode 100644 lnbits/extensions/watchonly/static/js/bip39-word-list.js diff --git a/lnbits/extensions/watchonly/static/components/seed-input/seed-input.html b/lnbits/extensions/watchonly/static/components/seed-input/seed-input.html new file mode 100644 index 00000000..60cdaaa8 --- /dev/null +++ b/lnbits/extensions/watchonly/static/components/seed-input/seed-input.html @@ -0,0 +1,80 @@ +
+
+
+
Seed Input Done
+
+
+
+
+
Word Count
+
+ +
+
+
+
+
+
Enter word at position: {{actualPosition}}
+
+
+
+
+ Previous +
+
+ +
+ +
+ Next + Done +
+ +
+
+
diff --git a/lnbits/extensions/watchonly/static/components/seed-input/seed-input.js b/lnbits/extensions/watchonly/static/components/seed-input/seed-input.js new file mode 100644 index 00000000..5e415abb --- /dev/null +++ b/lnbits/extensions/watchonly/static/components/seed-input/seed-input.js @@ -0,0 +1,102 @@ +async function seedInput(path) { + const template = await loadTemplateAsync(path) + Vue.component('seed-input', { + name: 'seed-input', + template, + + computed: { + actualPosition: function () { + return this.words[this.currentPosition].position + } + }, + + data: function () { + return { + wordCountOptions: ['12', '15', '18', '21', '24'], + wordCount: 24, + words: [], + currentPosition: 0, + stringOptions: [], + options: [], + currentWord: '', + done: false + } + }, + + methods: { + filterFn(val, update, abort) { + update(() => { + const needle = val.toLocaleLowerCase() + this.options = this.stringOptions + .filter(v => v.toLocaleLowerCase().indexOf(needle) != -1) + .sort((a, b) => { + if (a.startsWith(needle)) { + if (b.startsWith(needle)) { + return a - b + } + return -1 + } else { + if (b.startsWith(needle)) { + return 1 + } + return a - b + } + }) + }) + }, + initWords() { + const words = [] + for (let i = 1; i <= this.wordCount; i++) { + words.push({ + position: i, + value: '' + }) + } + this.currentPosition = 0 + this.words = _.shuffle(words) + }, + setModel(val) { + this.currentWord = val + this.words[this.currentPosition].value = this.currentWord + }, + nextPosition() { + if (this.currentPosition < this.wordCount - 1) { + this.currentPosition++ + } + this.currentWord = this.words[this.currentPosition].value + }, + previousPosition() { + if (this.currentPosition > 0) { + this.currentPosition-- + } + this.currentWord = this.words[this.currentPosition].value + }, + seedInputDone() { + const badWordPositions = this.words + .filter(w => !w.value || !this.stringOptions.includes(w.value)) + .map(w => w.position) + if (badWordPositions.length) { + this.$q.notify({ + timeout: 10000, + type: 'warning', + message: + 'The seed has incorrect words. Please check at these positions: ', + caption: 'Position: ' + badWordPositions.join(', ') + }) + return + } + const mnemonic = this.words + .sort((a, b) => a.position - b.position) + .map(w => w.value) + .join(' ') + this.$emit('on-seed-input-done', mnemonic) + this.done = true + } + }, + + created: async function () { + this.stringOptions = bip39WordList + this.initWords() + } + }) +} diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html index b5f8098f..2ed2d49c 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html @@ -375,18 +375,17 @@
-
- - Open the browser Developer Console for more Details! - -
+ + Open the browser Developer Console for more Details! + +
For test purposes only. Do not enter word list with real funds!!! -


- Enter new word list separated by space - - - +
+
+
+ + + +
+ + +
+ +
Enter new password (8 numbers/letters) { diff --git a/lnbits/extensions/watchonly/static/js/bip39-word-list.js b/lnbits/extensions/watchonly/static/js/bip39-word-list.js new file mode 100644 index 00000000..c0a5eac3 --- /dev/null +++ b/lnbits/extensions/watchonly/static/js/bip39-word-list.js @@ -0,0 +1,2050 @@ +const bip39WordList = Object.freeze([ + 'abandon', + 'ability', + 'able', + 'about', + 'above', + 'absent', + 'absorb', + 'abstract', + 'absurd', + 'abuse', + 'access', + 'accident', + 'account', + 'accuse', + 'achieve', + 'acid', + 'acoustic', + 'acquire', + 'across', + 'act', + 'action', + 'actor', + 'actress', + 'actual', + 'adapt', + 'add', + 'addict', + 'address', + 'adjust', + 'admit', + 'adult', + 'advance', + 'advice', + 'aerobic', + 'affair', + 'afford', + 'afraid', + 'again', + 'age', + 'agent', + 'agree', + 'ahead', + 'aim', + 'air', + 'airport', + 'aisle', + 'alarm', + 'album', + 'alcohol', + 'alert', + 'alien', + 'all', + 'alley', + 'allow', + 'almost', + 'alone', + 'alpha', + 'already', + 'also', + 'alter', + 'always', + 'amateur', + 'amazing', + 'among', + 'amount', + 'amused', + 'analyst', + 'anchor', + 'ancient', + 'anger', + 'angle', + 'angry', + 'animal', + 'ankle', + 'announce', + 'annual', + 'another', + 'answer', + 'antenna', + 'antique', + 'anxiety', + 'any', + 'apart', + 'apology', + 'appear', + 'apple', + 'approve', + 'april', + 'arch', + 'arctic', + 'area', + 'arena', + 'argue', + 'arm', + 'armed', + 'armor', + 'army', + 'around', + 'arrange', + 'arrest', + 'arrive', + 'arrow', + 'art', + 'artefact', + 'artist', + 'artwork', + 'ask', + 'aspect', + 'assault', + 'asset', + 'assist', + 'assume', + 'asthma', + 'athlete', + 'atom', + 'attack', + 'attend', + 'attitude', + 'attract', + 'auction', + 'audit', + 'august', + 'aunt', + 'author', + 'auto', + 'autumn', + 'average', + 'avocado', + 'avoid', + 'awake', + 'aware', + 'away', + 'awesome', + 'awful', + 'awkward', + 'axis', + 'baby', + 'bachelor', + 'bacon', + 'badge', + 'bag', + 'balance', + 'balcony', + 'ball', + 'bamboo', + 'banana', + 'banner', + 'bar', + 'barely', + 'bargain', + 'barrel', + 'base', + 'basic', + 'basket', + 'battle', + 'beach', + 'bean', + 'beauty', + 'because', + 'become', + 'beef', + 'before', + 'begin', + 'behave', + 'behind', + 'believe', + 'below', + 'belt', + 'bench', + 'benefit', + 'best', + 'betray', + 'better', + 'between', + 'beyond', + 'bicycle', + 'bid', + 'bike', + 'bind', + 'biology', + 'bird', + 'birth', + 'bitter', + 'black', + 'blade', + 'blame', + 'blanket', + 'blast', + 'bleak', + 'bless', + 'blind', + 'blood', + 'blossom', + 'blouse', + 'blue', + 'blur', + 'blush', + 'board', + 'boat', + 'body', + 'boil', + 'bomb', + 'bone', + 'bonus', + 'book', + 'boost', + 'border', + 'boring', + 'borrow', + 'boss', + 'bottom', + 'bounce', + 'box', + 'boy', + 'bracket', + 'brain', + 'brand', + 'brass', + 'brave', + 'bread', + 'breeze', + 'brick', + 'bridge', + 'brief', + 'bright', + 'bring', + 'brisk', + 'broccoli', + 'broken', + 'bronze', + 'broom', + 'brother', + 'brown', + 'brush', + 'bubble', + 'buddy', + 'budget', + 'buffalo', + 'build', + 'bulb', + 'bulk', + 'bullet', + 'bundle', + 'bunker', + 'burden', + 'burger', + 'burst', + 'bus', + 'business', + 'busy', + 'butter', + 'buyer', + 'buzz', + 'cabbage', + 'cabin', + 'cable', + 'cactus', + 'cage', + 'cake', + 'call', + 'calm', + 'camera', + 'camp', + 'can', + 'canal', + 'cancel', + 'candy', + 'cannon', + 'canoe', + 'canvas', + 'canyon', + 'capable', + 'capital', + 'captain', + 'car', + 'carbon', + 'card', + 'cargo', + 'carpet', + 'carry', + 'cart', + 'case', + 'cash', + 'casino', + 'castle', + 'casual', + 'cat', + 'catalog', + 'catch', + 'category', + 'cattle', + 'caught', + 'cause', + 'caution', + 'cave', + 'ceiling', + 'celery', + 'cement', + 'census', + 'century', + 'cereal', + 'certain', + 'chair', + 'chalk', + 'champion', + 'change', + 'chaos', + 'chapter', + 'charge', + 'chase', + 'chat', + 'cheap', + 'check', + 'cheese', + 'chef', + 'cherry', + 'chest', + 'chicken', + 'chief', + 'child', + 'chimney', + 'choice', + 'choose', + 'chronic', + 'chuckle', + 'chunk', + 'churn', + 'cigar', + 'cinnamon', + 'circle', + 'citizen', + 'city', + 'civil', + 'claim', + 'clap', + 'clarify', + 'claw', + 'clay', + 'clean', + 'clerk', + 'clever', + 'click', + 'client', + 'cliff', + 'climb', + 'clinic', + 'clip', + 'clock', + 'clog', + 'close', + 'cloth', + 'cloud', + 'clown', + 'club', + 'clump', + 'cluster', + 'clutch', + 'coach', + 'coast', + 'coconut', + 'code', + 'coffee', + 'coil', + 'coin', + 'collect', + 'color', + 'column', + 'combine', + 'come', + 'comfort', + 'comic', + 'common', + 'company', + 'concert', + 'conduct', + 'confirm', + 'congress', + 'connect', + 'consider', + 'control', + 'convince', + 'cook', + 'cool', + 'copper', + 'copy', + 'coral', + 'core', + 'corn', + 'correct', + 'cost', + 'cotton', + 'couch', + 'country', + 'couple', + 'course', + 'cousin', + 'cover', + 'coyote', + 'crack', + 'cradle', + 'craft', + 'cram', + 'crane', + 'crash', + 'crater', + 'crawl', + 'crazy', + 'cream', + 'credit', + 'creek', + 'crew', + 'cricket', + 'crime', + 'crisp', + 'critic', + 'crop', + 'cross', + 'crouch', + 'crowd', + 'crucial', + 'cruel', + 'cruise', + 'crumble', + 'crunch', + 'crush', + 'cry', + 'crystal', + 'cube', + 'culture', + 'cup', + 'cupboard', + 'curious', + 'current', + 'curtain', + 'curve', + 'cushion', + 'custom', + 'cute', + 'cycle', + 'dad', + 'damage', + 'damp', + 'dance', + 'danger', + 'daring', + 'dash', + 'daughter', + 'dawn', + 'day', + 'deal', + 'debate', + 'debris', + 'decade', + 'december', + 'decide', + 'decline', + 'decorate', + 'decrease', + 'deer', + 'defense', + 'define', + 'defy', + 'degree', + 'delay', + 'deliver', + 'demand', + 'demise', + 'denial', + 'dentist', + 'deny', + 'depart', + 'depend', + 'deposit', + 'depth', + 'deputy', + 'derive', + 'describe', + 'desert', + 'design', + 'desk', + 'despair', + 'destroy', + 'detail', + 'detect', + 'develop', + 'device', + 'devote', + 'diagram', + 'dial', + 'diamond', + 'diary', + 'dice', + 'diesel', + 'diet', + 'differ', + 'digital', + 'dignity', + 'dilemma', + 'dinner', + 'dinosaur', + 'direct', + 'dirt', + 'disagree', + 'discover', + 'disease', + 'dish', + 'dismiss', + 'disorder', + 'display', + 'distance', + 'divert', + 'divide', + 'divorce', + 'dizzy', + 'doctor', + 'document', + 'dog', + 'doll', + 'dolphin', + 'domain', + 'donate', + 'donkey', + 'donor', + 'door', + 'dose', + 'double', + 'dove', + 'draft', + 'dragon', + 'drama', + 'drastic', + 'draw', + 'dream', + 'dress', + 'drift', + 'drill', + 'drink', + 'drip', + 'drive', + 'drop', + 'drum', + 'dry', + 'duck', + 'dumb', + 'dune', + 'during', + 'dust', + 'dutch', + 'duty', + 'dwarf', + 'dynamic', + 'eager', + 'eagle', + 'early', + 'earn', + 'earth', + 'easily', + 'east', + 'easy', + 'echo', + 'ecology', + 'economy', + 'edge', + 'edit', + 'educate', + 'effort', + 'egg', + 'eight', + 'either', + 'elbow', + 'elder', + 'electric', + 'elegant', + 'element', + 'elephant', + 'elevator', + 'elite', + 'else', + 'embark', + 'embody', + 'embrace', + 'emerge', + 'emotion', + 'employ', + 'empower', + 'empty', + 'enable', + 'enact', + 'end', + 'endless', + 'endorse', + 'enemy', + 'energy', + 'enforce', + 'engage', + 'engine', + 'enhance', + 'enjoy', + 'enlist', + 'enough', + 'enrich', + 'enroll', + 'ensure', + 'enter', + 'entire', + 'entry', + 'envelope', + 'episode', + 'equal', + 'equip', + 'era', + 'erase', + 'erode', + 'erosion', + 'error', + 'erupt', + 'escape', + 'essay', + 'essence', + 'estate', + 'eternal', + 'ethics', + 'evidence', + 'evil', + 'evoke', + 'evolve', + 'exact', + 'example', + 'excess', + 'exchange', + 'excite', + 'exclude', + 'excuse', + 'execute', + 'exercise', + 'exhaust', + 'exhibit', + 'exile', + 'exist', + 'exit', + 'exotic', + 'expand', + 'expect', + 'expire', + 'explain', + 'expose', + 'express', + 'extend', + 'extra', + 'eye', + 'eyebrow', + 'fabric', + 'face', + 'faculty', + 'fade', + 'faint', + 'faith', + 'fall', + 'false', + 'fame', + 'family', + 'famous', + 'fan', + 'fancy', + 'fantasy', + 'farm', + 'fashion', + 'fat', + 'fatal', + 'father', + 'fatigue', + 'fault', + 'favorite', + 'feature', + 'february', + 'federal', + 'fee', + 'feed', + 'feel', + 'female', + 'fence', + 'festival', + 'fetch', + 'fever', + 'few', + 'fiber', + 'fiction', + 'field', + 'figure', + 'file', + 'film', + 'filter', + 'final', + 'find', + 'fine', + 'finger', + 'finish', + 'fire', + 'firm', + 'first', + 'fiscal', + 'fish', + 'fit', + 'fitness', + 'fix', + 'flag', + 'flame', + 'flash', + 'flat', + 'flavor', + 'flee', + 'flight', + 'flip', + 'float', + 'flock', + 'floor', + 'flower', + 'fluid', + 'flush', + 'fly', + 'foam', + 'focus', + 'fog', + 'foil', + 'fold', + 'follow', + 'food', + 'foot', + 'force', + 'forest', + 'forget', + 'fork', + 'fortune', + 'forum', + 'forward', + 'fossil', + 'foster', + 'found', + 'fox', + 'fragile', + 'frame', + 'frequent', + 'fresh', + 'friend', + 'fringe', + 'frog', + 'front', + 'frost', + 'frown', + 'frozen', + 'fruit', + 'fuel', + 'fun', + 'funny', + 'furnace', + 'fury', + 'future', + 'gadget', + 'gain', + 'galaxy', + 'gallery', + 'game', + 'gap', + 'garage', + 'garbage', + 'garden', + 'garlic', + 'garment', + 'gas', + 'gasp', + 'gate', + 'gather', + 'gauge', + 'gaze', + 'general', + 'genius', + 'genre', + 'gentle', + 'genuine', + 'gesture', + 'ghost', + 'giant', + 'gift', + 'giggle', + 'ginger', + 'giraffe', + 'girl', + 'give', + 'glad', + 'glance', + 'glare', + 'glass', + 'glide', + 'glimpse', + 'globe', + 'gloom', + 'glory', + 'glove', + 'glow', + 'glue', + 'goat', + 'goddess', + 'gold', + 'good', + 'goose', + 'gorilla', + 'gospel', + 'gossip', + 'govern', + 'gown', + 'grab', + 'grace', + 'grain', + 'grant', + 'grape', + 'grass', + 'gravity', + 'great', + 'green', + 'grid', + 'grief', + 'grit', + 'grocery', + 'group', + 'grow', + 'grunt', + 'guard', + 'guess', + 'guide', + 'guilt', + 'guitar', + 'gun', + 'gym', + 'habit', + 'hair', + 'half', + 'hammer', + 'hamster', + 'hand', + 'happy', + 'harbor', + 'hard', + 'harsh', + 'harvest', + 'hat', + 'have', + 'hawk', + 'hazard', + 'head', + 'health', + 'heart', + 'heavy', + 'hedgehog', + 'height', + 'hello', + 'helmet', + 'help', + 'hen', + 'hero', + 'hidden', + 'high', + 'hill', + 'hint', + 'hip', + 'hire', + 'history', + 'hobby', + 'hockey', + 'hold', + 'hole', + 'holiday', + 'hollow', + 'home', + 'honey', + 'hood', + 'hope', + 'horn', + 'horror', + 'horse', + 'hospital', + 'host', + 'hotel', + 'hour', + 'hover', + 'hub', + 'huge', + 'human', + 'humble', + 'humor', + 'hundred', + 'hungry', + 'hunt', + 'hurdle', + 'hurry', + 'hurt', + 'husband', + 'hybrid', + 'ice', + 'icon', + 'idea', + 'identify', + 'idle', + 'ignore', + 'ill', + 'illegal', + 'illness', + 'image', + 'imitate', + 'immense', + 'immune', + 'impact', + 'impose', + 'improve', + 'impulse', + 'inch', + 'include', + 'income', + 'increase', + 'index', + 'indicate', + 'indoor', + 'industry', + 'infant', + 'inflict', + 'inform', + 'inhale', + 'inherit', + 'initial', + 'inject', + 'injury', + 'inmate', + 'inner', + 'innocent', + 'input', + 'inquiry', + 'insane', + 'insect', + 'inside', + 'inspire', + 'install', + 'intact', + 'interest', + 'into', + 'invest', + 'invite', + 'involve', + 'iron', + 'island', + 'isolate', + 'issue', + 'item', + 'ivory', + 'jacket', + 'jaguar', + 'jar', + 'jazz', + 'jealous', + 'jeans', + 'jelly', + 'jewel', + 'job', + 'join', + 'joke', + 'journey', + 'joy', + 'judge', + 'juice', + 'jump', + 'jungle', + 'junior', + 'junk', + 'just', + 'kangaroo', + 'keen', + 'keep', + 'ketchup', + 'key', + 'kick', + 'kid', + 'kidney', + 'kind', + 'kingdom', + 'kiss', + 'kit', + 'kitchen', + 'kite', + 'kitten', + 'kiwi', + 'knee', + 'knife', + 'knock', + 'know', + 'lab', + 'label', + 'labor', + 'ladder', + 'lady', + 'lake', + 'lamp', + 'language', + 'laptop', + 'large', + 'later', + 'latin', + 'laugh', + 'laundry', + 'lava', + 'law', + 'lawn', + 'lawsuit', + 'layer', + 'lazy', + 'leader', + 'leaf', + 'learn', + 'leave', + 'lecture', + 'left', + 'leg', + 'legal', + 'legend', + 'leisure', + 'lemon', + 'lend', + 'length', + 'lens', + 'leopard', + 'lesson', + 'letter', + 'level', + 'liar', + 'liberty', + 'library', + 'license', + 'life', + 'lift', + 'light', + 'like', + 'limb', + 'limit', + 'link', + 'lion', + 'liquid', + 'list', + 'little', + 'live', + 'lizard', + 'load', + 'loan', + 'lobster', + 'local', + 'lock', + 'logic', + 'lonely', + 'long', + 'loop', + 'lottery', + 'loud', + 'lounge', + 'love', + 'loyal', + 'lucky', + 'luggage', + 'lumber', + 'lunar', + 'lunch', + 'luxury', + 'lyrics', + 'machine', + 'mad', + 'magic', + 'magnet', + 'maid', + 'mail', + 'main', + 'major', + 'make', + 'mammal', + 'man', + 'manage', + 'mandate', + 'mango', + 'mansion', + 'manual', + 'maple', + 'marble', + 'march', + 'margin', + 'marine', + 'market', + 'marriage', + 'mask', + 'mass', + 'master', + 'match', + 'material', + 'math', + 'matrix', + 'matter', + 'maximum', + 'maze', + 'meadow', + 'mean', + 'measure', + 'meat', + 'mechanic', + 'medal', + 'media', + 'melody', + 'melt', + 'member', + 'memory', + 'mention', + 'menu', + 'mercy', + 'merge', + 'merit', + 'merry', + 'mesh', + 'message', + 'metal', + 'method', + 'middle', + 'midnight', + 'milk', + 'million', + 'mimic', + 'mind', + 'minimum', + 'minor', + 'minute', + 'miracle', + 'mirror', + 'misery', + 'miss', + 'mistake', + 'mix', + 'mixed', + 'mixture', + 'mobile', + 'model', + 'modify', + 'mom', + 'moment', + 'monitor', + 'monkey', + 'monster', + 'month', + 'moon', + 'moral', + 'more', + 'morning', + 'mosquito', + 'mother', + 'motion', + 'motor', + 'mountain', + 'mouse', + 'move', + 'movie', + 'much', + 'muffin', + 'mule', + 'multiply', + 'muscle', + 'museum', + 'mushroom', + 'music', + 'must', + 'mutual', + 'myself', + 'mystery', + 'myth', + 'naive', + 'name', + 'napkin', + 'narrow', + 'nasty', + 'nation', + 'nature', + 'near', + 'neck', + 'need', + 'negative', + 'neglect', + 'neither', + 'nephew', + 'nerve', + 'nest', + 'net', + 'network', + 'neutral', + 'never', + 'news', + 'next', + 'nice', + 'night', + 'noble', + 'noise', + 'nominee', + 'noodle', + 'normal', + 'north', + 'nose', + 'notable', + 'note', + 'nothing', + 'notice', + 'novel', + 'now', + 'nuclear', + 'number', + 'nurse', + 'nut', + 'oak', + 'obey', + 'object', + 'oblige', + 'obscure', + 'observe', + 'obtain', + 'obvious', + 'occur', + 'ocean', + 'october', + 'odor', + 'off', + 'offer', + 'office', + 'often', + 'oil', + 'okay', + 'old', + 'olive', + 'olympic', + 'omit', + 'once', + 'one', + 'onion', + 'online', + 'only', + 'open', + 'opera', + 'opinion', + 'oppose', + 'option', + 'orange', + 'orbit', + 'orchard', + 'order', + 'ordinary', + 'organ', + 'orient', + 'original', + 'orphan', + 'ostrich', + 'other', + 'outdoor', + 'outer', + 'output', + 'outside', + 'oval', + 'oven', + 'over', + 'own', + 'owner', + 'oxygen', + 'oyster', + 'ozone', + 'pact', + 'paddle', + 'page', + 'pair', + 'palace', + 'palm', + 'panda', + 'panel', + 'panic', + 'panther', + 'paper', + 'parade', + 'parent', + 'park', + 'parrot', + 'party', + 'pass', + 'patch', + 'path', + 'patient', + 'patrol', + 'pattern', + 'pause', + 'pave', + 'payment', + 'peace', + 'peanut', + 'pear', + 'peasant', + 'pelican', + 'pen', + 'penalty', + 'pencil', + 'people', + 'pepper', + 'perfect', + 'permit', + 'person', + 'pet', + 'phone', + 'photo', + 'phrase', + 'physical', + 'piano', + 'picnic', + 'picture', + 'piece', + 'pig', + 'pigeon', + 'pill', + 'pilot', + 'pink', + 'pioneer', + 'pipe', + 'pistol', + 'pitch', + 'pizza', + 'place', + 'planet', + 'plastic', + 'plate', + 'play', + 'please', + 'pledge', + 'pluck', + 'plug', + 'plunge', + 'poem', + 'poet', + 'point', + 'polar', + 'pole', + 'police', + 'pond', + 'pony', + 'pool', + 'popular', + 'portion', + 'position', + 'possible', + 'post', + 'potato', + 'pottery', + 'poverty', + 'powder', + 'power', + 'practice', + 'praise', + 'predict', + 'prefer', + 'prepare', + 'present', + 'pretty', + 'prevent', + 'price', + 'pride', + 'primary', + 'print', + 'priority', + 'prison', + 'private', + 'prize', + 'problem', + 'process', + 'produce', + 'profit', + 'program', + 'project', + 'promote', + 'proof', + 'property', + 'prosper', + 'protect', + 'proud', + 'provide', + 'public', + 'pudding', + 'pull', + 'pulp', + 'pulse', + 'pumpkin', + 'punch', + 'pupil', + 'puppy', + 'purchase', + 'purity', + 'purpose', + 'purse', + 'push', + 'put', + 'puzzle', + 'pyramid', + 'quality', + 'quantum', + 'quarter', + 'question', + 'quick', + 'quit', + 'quiz', + 'quote', + 'rabbit', + 'raccoon', + 'race', + 'rack', + 'radar', + 'radio', + 'rail', + 'rain', + 'raise', + 'rally', + 'ramp', + 'ranch', + 'random', + 'range', + 'rapid', + 'rare', + 'rate', + 'rather', + 'raven', + 'raw', + 'razor', + 'ready', + 'real', + 'reason', + 'rebel', + 'rebuild', + 'recall', + 'receive', + 'recipe', + 'record', + 'recycle', + 'reduce', + 'reflect', + 'reform', + 'refuse', + 'region', + 'regret', + 'regular', + 'reject', + 'relax', + 'release', + 'relief', + 'rely', + 'remain', + 'remember', + 'remind', + 'remove', + 'render', + 'renew', + 'rent', + 'reopen', + 'repair', + 'repeat', + 'replace', + 'report', + 'require', + 'rescue', + 'resemble', + 'resist', + 'resource', + 'response', + 'result', + 'retire', + 'retreat', + 'return', + 'reunion', + 'reveal', + 'review', + 'reward', + 'rhythm', + 'rib', + 'ribbon', + 'rice', + 'rich', + 'ride', + 'ridge', + 'rifle', + 'right', + 'rigid', + 'ring', + 'riot', + 'ripple', + 'risk', + 'ritual', + 'rival', + 'river', + 'road', + 'roast', + 'robot', + 'robust', + 'rocket', + 'romance', + 'roof', + 'rookie', + 'room', + 'rose', + 'rotate', + 'rough', + 'round', + 'route', + 'royal', + 'rubber', + 'rude', + 'rug', + 'rule', + 'run', + 'runway', + 'rural', + 'sad', + 'saddle', + 'sadness', + 'safe', + 'sail', + 'salad', + 'salmon', + 'salon', + 'salt', + 'salute', + 'same', + 'sample', + 'sand', + 'satisfy', + 'satoshi', + 'sauce', + 'sausage', + 'save', + 'say', + 'scale', + 'scan', + 'scare', + 'scatter', + 'scene', + 'scheme', + 'school', + 'science', + 'scissors', + 'scorpion', + 'scout', + 'scrap', + 'screen', + 'script', + 'scrub', + 'sea', + 'search', + 'season', + 'seat', + 'second', + 'secret', + 'section', + 'security', + 'seed', + 'seek', + 'segment', + 'select', + 'sell', + 'seminar', + 'senior', + 'sense', + 'sentence', + 'series', + 'service', + 'session', + 'settle', + 'setup', + 'seven', + 'shadow', + 'shaft', + 'shallow', + 'share', + 'shed', + 'shell', + 'sheriff', + 'shield', + 'shift', + 'shine', + 'ship', + 'shiver', + 'shock', + 'shoe', + 'shoot', + 'shop', + 'short', + 'shoulder', + 'shove', + 'shrimp', + 'shrug', + 'shuffle', + 'shy', + 'sibling', + 'sick', + 'side', + 'siege', + 'sight', + 'sign', + 'silent', + 'silk', + 'silly', + 'silver', + 'similar', + 'simple', + 'since', + 'sing', + 'siren', + 'sister', + 'situate', + 'six', + 'size', + 'skate', + 'sketch', + 'ski', + 'skill', + 'skin', + 'skirt', + 'skull', + 'slab', + 'slam', + 'sleep', + 'slender', + 'slice', + 'slide', + 'slight', + 'slim', + 'slogan', + 'slot', + 'slow', + 'slush', + 'small', + 'smart', + 'smile', + 'smoke', + 'smooth', + 'snack', + 'snake', + 'snap', + 'sniff', + 'snow', + 'soap', + 'soccer', + 'social', + 'sock', + 'soda', + 'soft', + 'solar', + 'soldier', + 'solid', + 'solution', + 'solve', + 'someone', + 'song', + 'soon', + 'sorry', + 'sort', + 'soul', + 'sound', + 'soup', + 'source', + 'south', + 'space', + 'spare', + 'spatial', + 'spawn', + 'speak', + 'special', + 'speed', + 'spell', + 'spend', + 'sphere', + 'spice', + 'spider', + 'spike', + 'spin', + 'spirit', + 'split', + 'spoil', + 'sponsor', + 'spoon', + 'sport', + 'spot', + 'spray', + 'spread', + 'spring', + 'spy', + 'square', + 'squeeze', + 'squirrel', + 'stable', + 'stadium', + 'staff', + 'stage', + 'stairs', + 'stamp', + 'stand', + 'start', + 'state', + 'stay', + 'steak', + 'steel', + 'stem', + 'step', + 'stereo', + 'stick', + 'still', + 'sting', + 'stock', + 'stomach', + 'stone', + 'stool', + 'story', + 'stove', + 'strategy', + 'street', + 'strike', + 'strong', + 'struggle', + 'student', + 'stuff', + 'stumble', + 'style', + 'subject', + 'submit', + 'subway', + 'success', + 'such', + 'sudden', + 'suffer', + 'sugar', + 'suggest', + 'suit', + 'summer', + 'sun', + 'sunny', + 'sunset', + 'super', + 'supply', + 'supreme', + 'sure', + 'surface', + 'surge', + 'surprise', + 'surround', + 'survey', + 'suspect', + 'sustain', + 'swallow', + 'swamp', + 'swap', + 'swarm', + 'swear', + 'sweet', + 'swift', + 'swim', + 'swing', + 'switch', + 'sword', + 'symbol', + 'symptom', + 'syrup', + 'system', + 'table', + 'tackle', + 'tag', + 'tail', + 'talent', + 'talk', + 'tank', + 'tape', + 'target', + 'task', + 'taste', + 'tattoo', + 'taxi', + 'teach', + 'team', + 'tell', + 'ten', + 'tenant', + 'tennis', + 'tent', + 'term', + 'test', + 'text', + 'thank', + 'that', + 'theme', + 'then', + 'theory', + 'there', + 'they', + 'thing', + 'this', + 'thought', + 'three', + 'thrive', + 'throw', + 'thumb', + 'thunder', + 'ticket', + 'tide', + 'tiger', + 'tilt', + 'timber', + 'time', + 'tiny', + 'tip', + 'tired', + 'tissue', + 'title', + 'toast', + 'tobacco', + 'today', + 'toddler', + 'toe', + 'together', + 'toilet', + 'token', + 'tomato', + 'tomorrow', + 'tone', + 'tongue', + 'tonight', + 'tool', + 'tooth', + 'top', + 'topic', + 'topple', + 'torch', + 'tornado', + 'tortoise', + 'toss', + 'total', + 'tourist', + 'toward', + 'tower', + 'town', + 'toy', + 'track', + 'trade', + 'traffic', + 'tragic', + 'train', + 'transfer', + 'trap', + 'trash', + 'travel', + 'tray', + 'treat', + 'tree', + 'trend', + 'trial', + 'tribe', + 'trick', + 'trigger', + 'trim', + 'trip', + 'trophy', + 'trouble', + 'truck', + 'true', + 'truly', + 'trumpet', + 'trust', + 'truth', + 'try', + 'tube', + 'tuition', + 'tumble', + 'tuna', + 'tunnel', + 'turkey', + 'turn', + 'turtle', + 'twelve', + 'twenty', + 'twice', + 'twin', + 'twist', + 'two', + 'type', + 'typical', + 'ugly', + 'umbrella', + 'unable', + 'unaware', + 'uncle', + 'uncover', + 'under', + 'undo', + 'unfair', + 'unfold', + 'unhappy', + 'uniform', + 'unique', + 'unit', + 'universe', + 'unknown', + 'unlock', + 'until', + 'unusual', + 'unveil', + 'update', + 'upgrade', + 'uphold', + 'upon', + 'upper', + 'upset', + 'urban', + 'urge', + 'usage', + 'use', + 'used', + 'useful', + 'useless', + 'usual', + 'utility', + 'vacant', + 'vacuum', + 'vague', + 'valid', + 'valley', + 'valve', + 'van', + 'vanish', + 'vapor', + 'various', + 'vast', + 'vault', + 'vehicle', + 'velvet', + 'vendor', + 'venture', + 'venue', + 'verb', + 'verify', + 'version', + 'very', + 'vessel', + 'veteran', + 'viable', + 'vibrant', + 'vicious', + 'victory', + 'video', + 'view', + 'village', + 'vintage', + 'violin', + 'virtual', + 'virus', + 'visa', + 'visit', + 'visual', + 'vital', + 'vivid', + 'vocal', + 'voice', + 'void', + 'volcano', + 'volume', + 'vote', + 'voyage', + 'wage', + 'wagon', + 'wait', + 'walk', + 'wall', + 'walnut', + 'want', + 'warfare', + 'warm', + 'warrior', + 'wash', + 'wasp', + 'waste', + 'water', + 'wave', + 'way', + 'wealth', + 'weapon', + 'wear', + 'weasel', + 'weather', + 'web', + 'wedding', + 'weekend', + 'weird', + 'welcome', + 'west', + 'wet', + 'whale', + 'what', + 'wheat', + 'wheel', + 'when', + 'where', + 'whip', + 'whisper', + 'wide', + 'width', + 'wife', + 'wild', + 'will', + 'win', + 'window', + 'wine', + 'wing', + 'wink', + 'winner', + 'winter', + 'wire', + 'wisdom', + 'wise', + 'wish', + 'witness', + 'wolf', + 'woman', + 'wonder', + 'wood', + 'wool', + 'word', + 'work', + 'world', + 'worry', + 'worth', + 'wrap', + 'wreck', + 'wrestle', + 'wrist', + 'write', + 'wrong', + 'yard', + 'year', + 'yellow', + 'you', + 'young', + 'youth', + 'zebra', + 'zero', + 'zone', + 'zoo' +]) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index 45f2293b..7e410104 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -7,6 +7,7 @@ const watchOnly = async () => { await history('static/components/history/history.html') await utxoList('static/components/utxo-list/utxo-list.html') await feeRate('static/components/fee-rate/fee-rate.html') + await seedInput('static/components/seed-input/seed-input.html') await sendTo('static/components/send-to/send-to.html') await payment('static/components/payment/payment.html') await serialSigner('static/components/serial-signer/serial-signer.html') diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index d76b675a..263f6d92 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -239,6 +239,8 @@ + + @@ -246,10 +248,12 @@ + + From b0bedd53df582be18836026eac56b21451fdb6e6 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Thu, 22 Sep 2022 11:15:28 +0300 Subject: [PATCH 012/154] Fix lndgrpc preimage (#998) --- lnbits/core/services.py | 4 ++-- lnbits/wallets/lndgrpc.py | 41 ++++++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 10693f4b..961eb7b2 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -223,8 +223,8 @@ async def pay_invoice( logger.debug(f"deleting temporary payment {temp_id}") await delete_wallet_payment(temp_id, wallet_id, conn=conn) raise PaymentFailure( - f"payment failed: {payment.error_message}" - or "payment failed, but backend didn't give us an error message" + f"Payment failed: {payment.error_message}" + or "Payment failed, but backend didn't give us an error message." ) else: logger.warning( diff --git a/lnbits/wallets/lndgrpc.py b/lnbits/wallets/lndgrpc.py index a613ac9f..7f6135ad 100644 --- a/lnbits/wallets/lndgrpc.py +++ b/lnbits/wallets/lndgrpc.py @@ -198,16 +198,29 @@ class LndWallet(Wallet): 3: False, # FAILED } + failure_reasons = { + 0: "No error given.", + 1: "Payment timed out.", + 2: "No route to destination.", + 3: "Error.", + 4: "Incorrect payment details.", + 5: "Insufficient balance.", + } + fee_msat = None preimage = None - checking_id = resp.payment_hash + error_message = None + checking_id = None - if resp.status: # SUCCEEDED + if statuses[resp.status] == True: # SUCCEEDED fee_msat = -resp.htlcs[-1].route.total_fees_msat - preimage = bytes_to_hex(resp.payment_preimage) + preimage = resp.payment_preimage + checking_id = resp.payment_hash + elif statuses[resp.status] == False: + error_message = failure_reasons[resp.failure_reason] return PaymentResponse( - statuses[resp.status], checking_id, fee_msat, preimage, None + statuses[resp.status], checking_id, fee_msat, preimage, error_message ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: @@ -245,23 +258,29 @@ class LndWallet(Wallet): router.TrackPaymentRequest(payment_hash=r_hash) ) - # HTLCAttempt.HTLCStatus: - # https://github.com/lightningnetwork/lnd/blob/master/lnrpc/lightning.proto#L3641 + # # HTLCAttempt.HTLCStatus: + # # https://github.com/lightningnetwork/lnd/blob/master/lnrpc/lightning.proto#L3641 + # htlc_statuses = { + # 0: None, # IN_FLIGHT + # 1: True, # "SUCCEEDED" + # 2: False, # "FAILED" + # } statuses = { - 0: None, # IN_FLIGHT - 1: True, # "SUCCEEDED" - 2: False, # "FAILED" + 0: None, # NON_EXISTENT + 1: None, # IN_FLIGHT + 2: True, # SUCCEEDED + 3: False, # FAILED } try: async for payment in resp: - if statuses[payment.htlcs[-1].status]: + if len(payment.htlcs) and statuses[payment.status]: return PaymentStatus( True, -payment.htlcs[-1].route.total_fees_msat, bytes_to_hex(payment.htlcs[-1].preimage), ) - return PaymentStatus(statuses[payment.htlcs[-1].status]) + return PaymentStatus(statuses[payment.status]) except: # most likely the payment wasn't found return PaymentStatus(None) From e0981ecede4706956e9fb814c6087759f89cfb20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 23 Sep 2022 09:58:19 +0200 Subject: [PATCH 013/154] change python version of lnd wallet (#996) --- .github/workflows/regtest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index 250a66c7..0029eed4 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8] + python-version: [3.9] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From a98c5f9a8548e2b5f977864838dc078a4f1fd325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 23 Sep 2022 10:00:45 +0200 Subject: [PATCH 014/154] fixes issue #977 (#999) * fixes issue #977 * fix prettier formatting, when pre-commit --- lnbits/core/static/js/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index e62c1a6d..76d82ad4 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -675,7 +675,7 @@ new Vue({ // 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 + let columns = structuredClone(this.paymentsTable.columns) columns.unshift({ name: 'pending', align: 'left', From d64feec2a54437d70bf43b8cd9f544bcf143c8c9 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 23 Sep 2022 10:01:14 +0200 Subject: [PATCH 015/154] strip environment variable list items (#993) --- lnbits/settings.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/lnbits/settings.py b/lnbits/settings.py index 25e43eec..3f4e31cc 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -24,18 +24,21 @@ LNBITS_DATA_FOLDER = env.str( ) LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None) -LNBITS_ALLOWED_USERS: List[str] = env.list( - "LNBITS_ALLOWED_USERS", default=[], subcast=str -) -LNBITS_ADMIN_USERS: List[str] = env.list("LNBITS_ADMIN_USERS", default=[], subcast=str) -LNBITS_ADMIN_EXTENSIONS: List[str] = env.list( - "LNBITS_ADMIN_EXTENSIONS", default=[], subcast=str -) -LNBITS_DISABLED_EXTENSIONS: List[str] = env.list( - "LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str -) +LNBITS_ALLOWED_USERS: List[str] = [ + x.strip(" ") for x in env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str) +] +LNBITS_ADMIN_USERS: List[str] = [ + x.strip(" ") for x in env.list("LNBITS_ADMIN_USERS", default=[], subcast=str) +] +LNBITS_ADMIN_EXTENSIONS: List[str] = [ + x.strip(" ") for x in env.list("LNBITS_ADMIN_EXTENSIONS", default=[], subcast=str) +] +LNBITS_DISABLED_EXTENSIONS: List[str] = [ + x.strip(" ") + for x in env.list("LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str) +] -LNBITS_AD_SPACE = env.list("LNBITS_AD_SPACE", default=[]) +LNBITS_AD_SPACE = [x.strip(" ") for x in env.list("LNBITS_AD_SPACE", default=[])] LNBITS_HIDE_API = env.bool("LNBITS_HIDE_API", default=False) LNBITS_SITE_TITLE = env.str("LNBITS_SITE_TITLE", default="LNbits") LNBITS_DENOMINATION = env.str("LNBITS_DENOMINATION", default="sats") @@ -43,11 +46,14 @@ LNBITS_SITE_TAGLINE = env.str( "LNBITS_SITE_TAGLINE", default="free and open-source lightning wallet" ) LNBITS_SITE_DESCRIPTION = env.str("LNBITS_SITE_DESCRIPTION", default="") -LNBITS_THEME_OPTIONS: List[str] = env.list( - "LNBITS_THEME_OPTIONS", - default="classic, flamingo, mint, salvador, monochrome, autumn", - subcast=str, -) +LNBITS_THEME_OPTIONS: List[str] = [ + x.strip(" ") + for x in env.list( + "LNBITS_THEME_OPTIONS", + default="classic, flamingo, mint, salvador, monochrome, autumn", + subcast=str, + ) +] LNBITS_CUSTOM_LOGO = env.str("LNBITS_CUSTOM_LOGO", default="") WALLET = wallet_class() From b47685f8aa481453fc7e113b31812c8994807dfb Mon Sep 17 00:00:00 2001 From: fusion44 Date: Fri, 23 Sep 2022 10:02:23 +0200 Subject: [PATCH 016/154] Updates and fixes (#912) * feat: add support for python 3.10 * fix: move all in-line comments to their own line On some systems the environs package was uable to parse values as it tried read the comments as code... --- .env.example | 13 +++-- poetry.lock | 131 ++++++++++++++++++++++++++++++++----------------- pyproject.toml | 4 +- 3 files changed, 96 insertions(+), 52 deletions(-) diff --git a/.env.example b/.env.example index 97105bc3..93b82325 100644 --- a/.env.example +++ b/.env.example @@ -9,8 +9,11 @@ LNBITS_ADMIN_USERS="" LNBITS_ADMIN_EXTENSIONS="ngrok" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" -LNBITS_AD_SPACE="" # csv ad image filepaths or urls, extensions can choose to honor -LNBITS_HIDE_API=false # Hides wallet api, extensions can choose to honor +# csv ad image filepaths or urls, extensions can choose to honor +LNBITS_AD_SPACE="" + +# Hides wallet api, extensions can choose to honor +LNBITS_HIDE_API=false # Disable extensions for all users, use "all" to disable all extensions LNBITS_DISABLED_EXTENSIONS="amilk" @@ -25,8 +28,10 @@ LNBITS_DATA_FOLDER="./data" LNBITS_FORCE_HTTPS=true LNBITS_SERVICE_FEE="0.0" -LNBITS_RESERVE_FEE_MIN=2000 # value in millisats -LNBITS_RESERVE_FEE_PERCENT=1.0 # value in percent +# value in millisats +LNBITS_RESERVE_FEE_MIN=2000 +# value in percent +LNBITS_RESERVE_FEE_PERCENT=1.0 # Change theme LNBITS_SITE_TITLE="LNbits" diff --git a/poetry.lock b/poetry.lock index 975c62b2..38f08575 100644 --- a/poetry.lock +++ b/poetry.lock @@ -159,7 +159,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.4.2" +version = "6.4.4" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -194,6 +194,14 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "enum34" +version = "1.1.10" +description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "environs" version = "9.3.3" @@ -374,7 +382,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["isort (>=5.1.1)", "black (>=19.10b0)", "sphinx-rtd-theme (>=0.4.3)", "sphinx-autobuild (>=0.7.1)", "Sphinx (>=2.2.1)", "pytest-cov (>=2.7.1)", "pytest (>=4.6.2)", "tox-travis (>=0.12)", "tox (>=3.9.0)", "flake8 (>=3.7.7)", "colorama (>=0.3.4)", "codecov (>=2.0.15)"] +dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] [[package]] name = "markupsafe" @@ -463,6 +471,17 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +[[package]] +name = "pathlib2" +version = "2.3.7.post1" +description = "Object-oriented filesystem paths" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + [[package]] name = "pathspec" version = "0.9.0" @@ -577,13 +596,15 @@ PNG = ["pypng (>=0.0.13)"] [[package]] name = "pyscss" -version = "1.3.7" +version = "1.4.0" description = "pyScss, a Scss compiler for Python" category = "main" optional = false python-versions = "*" [package.dependencies] +enum34 = "*" +pathlib2 = "*" six = "*" [[package]] @@ -897,7 +918,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.9 | ^3.8 | ^3.7" -content-hash = "cadb8f2e46f0c083e91956f4f0f70b53b6c106f1c0b47972b57132dfee357367" +content-hash = "ac8c4117d537aaf8853d35038f2821ea4bc04b29c7971b91fd46329365008b95" [metadata.files] aiofiles = [ @@ -1025,47 +1046,56 @@ colorama = [ {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] coverage = [ - {file = "coverage-6.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e"}, - {file = "coverage-6.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c"}, - {file = "coverage-6.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8"}, - {file = "coverage-6.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39"}, - {file = "coverage-6.4.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0"}, - {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee"}, - {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d"}, - {file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc"}, - {file = "coverage-6.4.2-cp310-cp310-win32.whl", hash = "sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386"}, - {file = "coverage-6.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0"}, - {file = "coverage-6.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46"}, - {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07"}, - {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039"}, - {file = "coverage-6.4.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996"}, - {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f"}, - {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e"}, - {file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083"}, - {file = "coverage-6.4.2-cp37-cp37m-win32.whl", hash = "sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7"}, - {file = "coverage-6.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120"}, - {file = "coverage-6.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452"}, - {file = "coverage-6.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32"}, - {file = "coverage-6.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae"}, - {file = "coverage-6.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8"}, - {file = "coverage-6.4.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1"}, - {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63"}, - {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933"}, - {file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de"}, - {file = "coverage-6.4.2-cp38-cp38-win32.whl", hash = "sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783"}, - {file = "coverage-6.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6"}, - {file = "coverage-6.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f"}, - {file = "coverage-6.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f"}, - {file = "coverage-6.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe"}, - {file = "coverage-6.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29"}, - {file = "coverage-6.4.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55"}, - {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b"}, - {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978"}, - {file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c"}, - {file = "coverage-6.4.2-cp39-cp39-win32.whl", hash = "sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd"}, - {file = "coverage-6.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf"}, - {file = "coverage-6.4.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97"}, - {file = "coverage-6.4.2.tar.gz", hash = "sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe"}, + {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, + {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"}, + {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"}, + {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"}, + {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"}, + {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"}, + {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"}, + {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"}, + {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"}, + {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"}, + {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"}, + {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"}, + {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"}, + {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"}, + {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"}, + {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"}, + {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"}, + {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"}, + {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, + {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, ] ecdsa = [ {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"}, @@ -1074,6 +1104,11 @@ ecdsa = [ embit = [ {file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"}, ] +enum34 = [ + {file = "enum34-1.1.10-py2-none-any.whl", hash = "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53"}, + {file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"}, + {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, +] environs = [ {file = "environs-9.3.3-py2.py3-none-any.whl", hash = "sha256:ee5466156b50fe03aa9fec6e720feea577b5bf515d7f21b2c46608272557ba26"}, {file = "environs-9.3.3.tar.gz", hash = "sha256:72b867ff7b553076cdd90f3ee01ecc1cf854987639c9c459f0ed0d3d44ae490c"}, @@ -1274,6 +1309,10 @@ packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] +pathlib2 = [ + {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, + {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, +] pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, @@ -1397,7 +1436,7 @@ pyqrcode = [ {file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"}, ] pyscss = [ - {file = "pyScss-1.3.7.tar.gz", hash = "sha256:f1df571569021a23941a538eb154405dde80bed35dc1ea7c5f3e18e0144746bf"}, + {file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"}, ] pytest = [ {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, diff --git a/pyproject.toml b/pyproject.toml index 1ae8c1fe..fc4bb198 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ generate-setup-file = false script = "build.py" [tool.poetry.dependencies] -python = "^3.9 | ^3.8 | ^3.7" +python = "^3.10 | ^3.9 | ^3.8 | ^3.7" aiofiles = "0.8.0" asgiref = "3.4.1" attrs = "21.2.0" @@ -39,7 +39,7 @@ pycryptodomex = "3.14.1" pydantic = "1.8.2" pypng = "0.0.21" pyqrcode = "1.2.1" -pyscss = "1.3.7" +pyScss = "1.4.0" python-dotenv = "0.19.0" pyyaml = "5.4.1" represent = "1.6.0.post0" From 8305ebe6e8dd82ee2bab5c7d56090661c7e2660a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 23 Sep 2022 10:02:44 +0200 Subject: [PATCH 017/154] fix scss error on poetry install (#989) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fc4bb198..e95c6a2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ mypy = "^0.971" types-protobuf = "^3.19.22" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0", "pyScss"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] From d8acad42823721edbd0f6aa7741f500e8e280ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 23 Sep 2022 10:35:18 +0200 Subject: [PATCH 018/154] make sure tests not spamming boltz live servers (#1000) * make sure tests not spamming boltz live servers * fixing tests to no run on live servers --- Makefile | 8 +++++ tests/extensions/boltz/test_api.py | 53 +++--------------------------- 2 files changed, 13 insertions(+), 48 deletions(-) diff --git a/Makefile b/Makefile index 6b2fdeb7..4f99f1da 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,10 @@ checkisort: poetry run isort --check-only . test: + BOLTZ_NETWORK="regtest" \ + BOLTZ_URL="http://127.0.0.1:9001" \ + BOLTZ_MEMPOOL_SPACE_URL="http://127.0.0.1:8080" \ + BOLTZ_MEMPOOL_SPACE_URL_WS="ws://127.0.0.1:8080" \ LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \ FAKE_WALLET_SECRET="ToTheMoon1" \ LNBITS_DATA_FOLDER="./tests/data" \ @@ -46,6 +50,10 @@ test-real-wallet: poetry run pytest test-venv: + BOLTZ_NETWORK="regtest" \ + BOLTZ_URL="http://127.0.0.1:9001" \ + BOLTZ_MEMPOOL_SPACE_URL="http://127.0.0.1:8080" \ + BOLTZ_MEMPOOL_SPACE_URL_WS="ws://127.0.0.1:8080" \ LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \ FAKE_WALLET_SECRET="ToTheMoon1" \ LNBITS_DATA_FOLDER="./tests/data" \ diff --git a/tests/extensions/boltz/test_api.py b/tests/extensions/boltz/test_api.py index 20b6e5a4..90ce6ec1 100644 --- a/tests/extensions/boltz/test_api.py +++ b/tests/extensions/boltz/test_api.py @@ -5,18 +5,21 @@ from tests.helpers import is_fake, is_regtest @pytest.mark.asyncio +@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest") async def test_mempool_url(client): response = await client.get("/boltz/api/v1/swap/mempool") assert response.status_code == 200 @pytest.mark.asyncio +@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest") async def test_boltz_config(client): response = await client.get("/boltz/api/v1/swap/boltz") assert response.status_code == 200 @pytest.mark.asyncio +@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest") async def test_endpoints_unauthenticated(client): response = await client.get("/boltz/api/v1/swap?all_wallets=true") assert response.status_code == 401 @@ -33,6 +36,7 @@ async def test_endpoints_unauthenticated(client): @pytest.mark.asyncio +@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest") async def test_endpoints_inkey(client, inkey_headers_to): response = await client.get( "/boltz/api/v1/swap?all_wallets=true", headers=inkey_headers_to @@ -56,6 +60,7 @@ async def test_endpoints_inkey(client, inkey_headers_to): @pytest.mark.asyncio +@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest") async def test_endpoints_adminkey_nocontent(client, adminkey_headers_to): response = await client.post("/boltz/api/v1/swap", headers=adminkey_headers_to) assert response.status_code == 204 @@ -73,54 +78,6 @@ async def test_endpoints_adminkey_nocontent(client, adminkey_headers_to): assert response.status_code == 204 -@pytest.mark.asyncio -@pytest.mark.skipif(is_regtest, reason="this test is only passes with fakewallet") -async def test_endpoints_adminkey_fakewallet(client, from_wallet, adminkey_headers_to): - response = await client.post( - "/boltz/api/v1/swap/check", headers=adminkey_headers_to - ) - assert response.status_code == 200 - swap = { - "wallet": from_wallet.id, - "refund_address": "bcrt1q3cwq33y435h52gq3qqsdtczh38ltlnf69zvypm", - "amount": 50_000, - } - response = await client.post( - "/boltz/api/v1/swap", json=swap, headers=adminkey_headers_to - ) - assert response.status_code == 405 - reverse_swap = { - "wallet": from_wallet.id, - "instant_settlement": True, - "onchain_address": "bcrt1q4vfyszl4p8cuvqh07fyhtxve5fxq8e2ux5gx43", - "amount": 50_000, - } - response = await client.post( - "/boltz/api/v1/swap/reverse", json=reverse_swap, headers=adminkey_headers_to - ) - assert response.status_code == 201 - reverse_swap = response.json() - assert reverse_swap["id"] is not None - response = await client.post( - "/boltz/api/v1/swap/status", - params={"swap_id": reverse_swap["id"]}, - headers=adminkey_headers_to, - ) - assert response.status_code == 200 - response = await client.post( - "/boltz/api/v1/swap/status", - params={"swap_id": "wrong"}, - headers=adminkey_headers_to, - ) - assert response.status_code == 404 - response = await client.post( - "/boltz/api/v1/swap/refund", - params={"swap_id": "wrong"}, - headers=adminkey_headers_to, - ) - assert response.status_code == 404 - - @pytest.mark.asyncio @pytest.mark.skipif(is_fake, reason="this test is only passes with regtest") async def test_endpoints_adminkey_regtest(client, from_wallet, adminkey_headers_to): From e75610a04f4f284eaca24cf716b5b8c60b095ddb Mon Sep 17 00:00:00 2001 From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com> Date: Fri, 23 Sep 2022 15:33:51 +0200 Subject: [PATCH 019/154] API auditor (#1002) * just quick auditor * fix for none balance * handle node down case Co-authored-by: calle <93376500+callebtc@users.noreply.github.com> Co-authored-by: calle <93376500+callebtc@users.noreply.github.com> --- lnbits/core/crud.py | 5 +++++ lnbits/core/views/api.py | 27 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index ecc27a9c..cbed6292 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -177,6 +177,11 @@ async def get_wallet_for_key( return Wallet(**row) +async def get_total_balance(conn: Optional[Connection] = None): + row = await (conn or db).fetchone("SELECT SUM(balance) FROM balances") + return 0 if row[0] is None else row[0] + + # wallet payments # --------------- diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index af453f03..7a2bbbe6 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -2,6 +2,7 @@ import asyncio import binascii import hashlib import json +import time from http import HTTPStatus from io import BytesIO from typing import Dict, List, Optional, Tuple, Union @@ -27,7 +28,7 @@ from lnbits.decorators import ( require_invoice_key, ) from lnbits.helpers import url_for, urlsafe_short_hash -from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE +from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET from lnbits.utils.exchange_rates import ( currencies, fiat_amount_as_satoshis, @@ -39,6 +40,7 @@ from ..crud import ( create_payment, get_payments, get_standalone_payment, + get_total_balance, get_wallet, get_wallet_for_key, save_balance_check, @@ -657,3 +659,26 @@ async def img(request: Request, data): "Expires": "0", }, ) + + +@core_app.get("/api/v1/audit/") +async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)): + if wallet.wallet.user not in LNBITS_ADMIN_USERS: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user" + ) + + total_balance = await get_total_balance() + error_message, node_balance = await WALLET.status() + + if not error_message: + delta = node_balance - total_balance + else: + node_balance, delta = None, None + + return { + "node_balance_msats": node_balance, + "lnbits_balance_msats": total_balance, + "delta_msats": delta, + "timestamp": int(time.time()), + } From f6d12c9d24551ffd9f401e8cb5047d113211183b Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 23 Sep 2022 14:40:20 +0100 Subject: [PATCH 020/154] prevent ios autozoom on inputs (#945) Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com> --- lnbits/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/templates/base.html b/lnbits/templates/base.html index acca92e7..67241bb5 100644 --- a/lnbits/templates/base.html +++ b/lnbits/templates/base.html @@ -12,7 +12,7 @@ From 8e06d779e7068e7446d19136526a971adda89661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 23 Sep 2022 15:56:14 +0200 Subject: [PATCH 021/154] add dependencies for corelightning and lndgrpc (#1003) * add dependencies for corelightning and lndgrpc * added wrong cln library, :facepalm: --- .github/workflows/regtest.yml | 2 - docs/guide/wallets.md | 4 - poetry.lock | 415 +++++++++++++++++++++++++++------- pyproject.toml | 3 + 4 files changed, 335 insertions(+), 89 deletions(-) diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index 0029eed4..6572ccdb 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -65,7 +65,6 @@ jobs: - name: Install dependencies run: | poetry install - poetry add grpcio protobuf - name: Run tests env: PYTHONUNBUFFERED: 1 @@ -106,7 +105,6 @@ jobs: - name: Install dependencies run: | poetry install - poetry add pyln-client - name: Run tests env: PYTHONUNBUFFERED: 1 diff --git a/docs/guide/wallets.md b/docs/guide/wallets.md index 592c29ef..80fb54c0 100644 --- a/docs/guide/wallets.md +++ b/docs/guide/wallets.md @@ -15,8 +15,6 @@ A backend wallet can be configured using the following LNbits environment variab ### CoreLightning -Using this wallet requires the installation of the `pylightning` Python package. - - `LNBITS_BACKEND_WALLET_CLASS`: **CoreLightningWallet** - `CORELIGHTNING_RPC`: /file/path/lightning-rpc @@ -39,8 +37,6 @@ or ### LND (gRPC) -Using this wallet requires the installation of the `grpcio` and `protobuf` Python packages. - - `LNBITS_BACKEND_WALLET_CLASS`: **LndWallet** - `LND_GRPC_ENDPOINT`: ip_address - `LND_GRPC_PORT`: port diff --git a/poetry.lock b/poetry.lock index 38f08575..ea83e25e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -20,8 +20,8 @@ sniffio = ">=1.1" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] -test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] +doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] trio = ["trio (>=0.16)"] [[package]] @@ -36,15 +36,15 @@ python-versions = ">=3.6" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -category = "dev" +name = "asn1crypto" +version = "1.5.1" +description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "*" [[package]] name = "attrs" @@ -55,10 +55,21 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] + +[[package]] +name = "base58" +version = "2.1.1" +description = "Base58 and Base58Check implementation." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", "pytest-cov", "pytest-flake8"] [[package]] name = "bech32" @@ -78,7 +89,7 @@ python-versions = "*" [[package]] name = "black" -version = "22.6.0" +version = "22.8.0" description = "The uncompromising code formatter." category = "dev" optional = false @@ -149,6 +160,18 @@ python-versions = ">=3.6" colorama = {version = "*", markers = "platform_system == \"Windows\""} importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +[[package]] +name = "coincurve" +version = "17.0.0" +description = "Cross-platform Python CFFI bindings for libsecp256k1" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +asn1crypto = "*" +cffi = ">=1.3.0" + [[package]] name = "colorama" version = "0.4.5" @@ -171,6 +194,25 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] +[[package]] +name = "cryptography" +version = "36.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools_rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] + [[package]] name = "ecdsa" version = "0.17.0" @@ -215,10 +257,10 @@ marshmallow = ">=3.0.0" python-dotenv = "*" [package.extras] -dev = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "tox"] +dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"] django = ["dj-database-url", "dj-email-url", "django-cache-url"] lint = ["flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] -tests = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url"] +tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] [[package]] name = "fastapi" @@ -233,10 +275,24 @@ pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1. starlette = "0.19.1" [package.extras] -all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] -dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)", "pre-commit (>=2.17.0,<3.0.0)"] -doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer (>=0.4.1,<0.5.0)", "pyyaml (>=5.3.1,<7.0.0)"] -test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==22.3.0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==4.2.1)", "types-orjson (==3.6.2)", "types-dataclasses (==0.6.5)"] +all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] +dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] +doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"] +test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] + +[[package]] +name = "grpcio" +version = "1.49.1" +description = "HTTP/2-based RPC framework" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +six = ">=1.5.2" + +[package.extras] +protobuf = ["grpcio-tools (>=1.49.1)"] [[package]] name = "h11" @@ -290,8 +346,8 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} sniffio = "*" [package.extras] -brotli = ["brotlicffi", "brotli"] -cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (>=1.0.0,<2.0.0)"] @@ -316,9 +372,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] perf = ["ipython"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy", "pytest-perf (>=0.9.2)"] [[package]] name = "iniconfig" @@ -337,10 +393,10 @@ optional = false python-versions = ">=3.6.1,<4.0" [package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] plugins = ["setuptools"] +requirements_deprecated_finder = ["pip-api", "pipreqs"] [[package]] name = "jinja2" @@ -382,7 +438,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] +dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"] [[package]] name = "markupsafe" @@ -404,9 +460,9 @@ python-versions = ">=3.7" packaging = ">=17.0" [package.extras] -dev = ["pytest", "pytz", "simplejson", "mypy (==0.961)", "flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "pre-commit (>=2.4,<3.0)", "tox"] -docs = ["sphinx (==4.5.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.8)"] -lint = ["mypy (==0.961)", "flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "pre-commit (>=2.4,<3.0)"] +dev = ["flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "mypy (==0.961)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.8)", "sphinx (==4.5.0)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "mypy (==0.961)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -418,7 +474,7 @@ optional = false python-versions = ">=3.6" [package.extras] -build = ["twine", "wheel", "blurb"] +build = ["blurb", "twine", "wheel"] docs = ["sphinx"] test = ["pytest (<5.4)", "pytest-cov"] @@ -484,11 +540,11 @@ six = "*" [[package]] name = "pathspec" -version = "0.9.0" +version = "0.10.1" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.7" [[package]] name = "platformdirs" @@ -499,8 +555,8 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "pluggy" @@ -514,8 +570,16 @@ python-versions = ">=3.6" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] -testing = ["pytest-benchmark", "pytest"] -dev = ["tox", "pre-commit"] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "protobuf" +version = "4.21.6" +description = "" +category = "main" +optional = false +python-versions = ">=3.7" [[package]] name = "psycopg2-binary" @@ -564,6 +628,41 @@ typing-extensions = ">=3.7.4.3" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pyln-bolt7" +version = "1.0.246" +description = "BOLT7" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[[package]] +name = "pyln-client" +version = "0.12.0.post1" +description = "Client library and plugin library for Core Lightning" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +pyln-bolt7 = ">=1.0,<2.0" +pyln-proto = ">=0.11,<0.12" + +[[package]] +name = "pyln-proto" +version = "0.11.1" +description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +base58 = ">=2.1.1,<3.0.0" +bitstring = ">=3.1.9,<4.0.0" +coincurve = ">=17.0.0,<18.0.0" +cryptography = ">=36.0.1,<37.0.0" +PySocks = ">=1.7.1,<2.0.0" + [[package]] name = "pyparsing" version = "3.0.9" @@ -573,7 +672,7 @@ optional = false python-versions = ">=3.6.8" [package.extras] -diagrams = ["railroad-diagrams", "jinja2"] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pypng" @@ -607,16 +706,23 @@ enum34 = "*" pathlib2 = "*" six = "*" +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pytest" -version = "7.1.2" +version = "7.1.3" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -642,7 +748,7 @@ pytest = ">=6.1.0" typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} [package.extras] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] [[package]] name = "pytest-cov" @@ -657,7 +763,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "python-dotenv" @@ -690,7 +796,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" six = ">=1.8.0" [package.extras] -test = ["ipython", "pytest (>=3.0.5)", "mock"] +test = ["ipython", "mock", "pytest (>=3.0.5)"] [[package]] name = "rfc3986" @@ -759,7 +865,7 @@ postgresql = ["psycopg2"] postgresql_pg8000 = ["pg8000 (<1.16.6)"] postgresql_psycopg2binary = ["psycopg2-binary"] postgresql_psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql (<1)", "pymysql"] +pymysql = ["pymysql", "pymysql (<1)"] [[package]] name = "sqlalchemy-aio" @@ -820,7 +926,7 @@ python-versions = ">=3.6" [[package]] name = "types-protobuf" -version = "3.19.22" +version = "3.20.4" description = "Typing stubs for protobuf" category = "dev" optional = false @@ -848,7 +954,7 @@ h11 = ">=0.8" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchfiles (>=0.13)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] +standard = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"] [[package]] name = "uvloop" @@ -859,9 +965,9 @@ optional = false python-versions = ">=3.7" [package.extras] -dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"] -test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] [[package]] name = "watchgod" @@ -912,13 +1018,13 @@ optional = false python-versions = ">=3.6" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] [metadata] lock-version = "1.1" -python-versions = "^3.9 | ^3.8 | ^3.7" -content-hash = "ac8c4117d537aaf8853d35038f2821ea4bc04b29c7971b91fd46329365008b95" +python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" +content-hash = "d0556d4a307864ba04a1e5da517884e523396c98a00ae09d9192c37b1d2c555b" [metadata.files] aiofiles = [ @@ -933,13 +1039,18 @@ asgiref = [ {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, ] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +asn1crypto = [ + {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, + {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, ] attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] +base58 = [ + {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, + {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, +] bech32 = [ {file = "bech32-1.2.0-py3-none-any.whl", hash = "sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981"}, {file = "bech32-1.2.0.tar.gz", hash = "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899"}, @@ -950,29 +1061,29 @@ bitstring = [ {file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"}, ] black = [ - {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"}, - {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"}, - {file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"}, - {file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"}, - {file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"}, - {file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"}, - {file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"}, - {file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"}, - {file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"}, - {file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"}, - {file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"}, - {file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"}, - {file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"}, - {file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"}, - {file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"}, - {file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"}, - {file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"}, - {file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"}, - {file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"}, - {file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"}, - {file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"}, - {file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"}, - {file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"}, + {file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"}, + {file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"}, + {file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"}, + {file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"}, + {file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"}, + {file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"}, + {file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"}, + {file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"}, + {file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"}, + {file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"}, + {file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"}, + {file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"}, + {file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"}, + {file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"}, + {file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"}, + {file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"}, + {file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"}, + {file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"}, + {file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"}, + {file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"}, + {file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"}, + {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"}, + {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, ] cerberus = [ {file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"}, @@ -1041,6 +1152,42 @@ click = [ {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, ] +coincurve = [ + {file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"}, + {file = "coincurve-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25dfa105beba24c8de886f8ed654bb1133866e4e22cfd7ea5ad8438cae6ed924"}, + {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:698efdd53e4fe1bbebaee9b75cbc851be617974c1c60098e9145cb7198ae97fb"}, + {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30dd44d1039f1d237aaa2da6d14a455ca88df3bcb00610b41f3253fdca1be97b"}, + {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154e2eb5711db8c5ef52fcd80935b5a0e751c057bc6ffb215a7bb409aedef03"}, + {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c71caffb97dd3d0c243beb62352669b1e5dafa3a4bccdbb27d36bd82f5e65d20"}, + {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:747215254e51dd4dfbe6dded9235491263da5d88fe372d66541ca16b51ea078f"}, + {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad2f6df39ba1e2b7b14bb984505ffa7d0a0ecdd697e8d7dbd19e04bc245c87ed"}, + {file = "coincurve-17.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0503326963916c85b61d16f611ea0545f03c9e418fa8007c233c815429e381e8"}, + {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1013c1597b65684ae1c3e42497f9ef5a04527fa6136a84a16b34602606428c74"}, + {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4beef321fd6434448aab03a0c245f31c4e77f43b54b82108c0948d29852ac7e"}, + {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f47806527d3184da3e8b146fac92a8ed567bbd225194f4517943d8cdc85f9542"}, + {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51e56373ac79f4ec1cfc5da53d72c55f5e5ac28d848b0849ef5e687ace857888"}, + {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d694ad194bee9e8792e2e75879dc5238d8a184010cde36c5ad518fcfe2cd8f2"}, + {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74cedb3d3a1dc5abe0c9c2396e1b82cc64496babc5b42e007e72e185cb1edad8"}, + {file = "coincurve-17.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:db874c5c1dcb1f3a19379773b5e8cffc777625a7a7a60dd9a67206e31e62e2e9"}, + {file = "coincurve-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:896b01941254f0a218cf331a9bddfe2d43892f7f1ba10d6e372e2eb744a744c2"}, + {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6aec70238dbe7a5d66b5f9438ff45b08eb5e0990d49c32ebb65247c5d5b89d7a"}, + {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24284d17162569df917a640f19d9654ba3b43cf560ced8864f270da903f73a5"}, + {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ea057f777842396d387103c606babeb3a1b4c6126769cc0a12044312fc6c465"}, + {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b88642edf7f281649b0c0b6ffade051945ccceae4b885e40445634877d0b3049"}, + {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a80a207131813b038351c5bdae8f20f5f774bbf53622081f208d040dd2b7528f"}, + {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1ef72574aa423bc33665ef4be859164a478bad24d48442da874ef3dc39a474d"}, + {file = "coincurve-17.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfd4fab857bcd975edc39111cb5f5c104f138dac2e9ace35ea8434d37bcea3be"}, + {file = "coincurve-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73f39579dd651a9fc29da5a8fc0d8153d872bcbc166f876457baced1a1c01501"}, + {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8852dc01af4f0fe941ffd04069f7e4fecdce9b867a016f823a02286a1a1f07b5"}, + {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1bef812da1da202cdd601a256825abcf26d86e8634fac3ec3e615e3bb3ff08c"}, + {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abbefc9ccb170cb255a31df32457c2e43084b9f37589d0694dacc2dea6ddaf7c"}, + {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:abbd9d017a7638dc38a3b9bb4851f8801b7818d4e5ac22e0c75e373b3c1dbff0"}, + {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e2c2e8a1f0b1f8e48049c891af4ae3cad65d115d358bde72f6b8abdbb8a23170"}, + {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c571445b166c714af4f8155e38a894376c16c0431e88963f2fff474a9985d87"}, + {file = "coincurve-17.0.0-py3-none-win32.whl", hash = "sha256:b956b0b2c85e25a7d00099970ff5d8338254b45e46f0a940f4a2379438ce0dde"}, + {file = "coincurve-17.0.0-py3-none-win_amd64.whl", hash = "sha256:630388080da3026e0b0176cc6762eaabecba857ee3fc85767577dea063ea7c6e"}, + {file = "coincurve-17.0.0.tar.gz", hash = "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93"}, +] colorama = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, @@ -1097,6 +1244,28 @@ coverage = [ {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, ] +cryptography = [ + {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"}, + {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86"}, + {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2"}, + {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb"}, + {file = "cryptography-36.0.2-cp36-abi3-win32.whl", hash = "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"}, + {file = "cryptography-36.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3"}, + {file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"}, +] ecdsa = [ {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"}, {file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"}, @@ -1117,6 +1286,53 @@ fastapi = [ {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"}, {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"}, ] +grpcio = [ + {file = "grpcio-1.49.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:fd86040232e805b8e6378b2348c928490ee595b058ce9aaa27ed8e4b0f172b20"}, + {file = "grpcio-1.49.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6fd0c9cede9552bf00f8c5791d257d5bf3790d7057b26c59df08be5e7a1e021d"}, + {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d0d402e158d4e84e49c158cb5204119d55e1baf363ee98d6cb5dce321c3a065d"}, + {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ceec743d42a627e64ea266059a62d214c5a3cdfcd0d7fe2b7a8e4e82527c7"}, + {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2106d9c16527f0a85e2eea6e6b91a74fc99579c60dd810d8690843ea02bc0f5f"}, + {file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:52dd02b7e7868233c571b49bc38ebd347c3bb1ff8907bb0cb74cb5f00c790afc"}, + {file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:120fecba2ec5d14b5a15d11063b39783fda8dc8d24addd83196acb6582cabd9b"}, + {file = "grpcio-1.49.1-cp310-cp310-win32.whl", hash = "sha256:f1a3b88e3c53c1a6e6bed635ec1bbb92201bb6a1f2db186179f7f3f244829788"}, + {file = "grpcio-1.49.1-cp310-cp310-win_amd64.whl", hash = "sha256:a7d0017b92d3850abea87c1bdec6ea41104e71c77bca44c3e17f175c6700af62"}, + {file = "grpcio-1.49.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:9fb17ff8c0d56099ac6ebfa84f670c5a62228d6b5c695cf21c02160c2ac1446b"}, + {file = "grpcio-1.49.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:075f2d06e3db6b48a2157a1bcd52d6cbdca980dd18988fe6afdb41795d51625f"}, + {file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46d93a1b4572b461a227f1db6b8d35a88952db1c47e5fadcf8b8a2f0e1dd9201"}, + {file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc79b2b37d779ac42341ddef40ad5bf0966a64af412c89fc2b062e3ddabb093f"}, + {file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5f8b3a971c7820ea9878f3fd70086240a36aeee15d1b7e9ecbc2743b0e785568"}, + {file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49b301740cf5bc8fed4fee4c877570189ae3951432d79fa8e524b09353659811"}, + {file = "grpcio-1.49.1-cp311-cp311-win32.whl", hash = "sha256:1c66a25afc6c71d357867b341da594a5587db5849b48f4b7d5908d236bb62ede"}, + {file = "grpcio-1.49.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b6c3a95d27846f4145d6967899b3ab25fffc6ae99544415e1adcacef84842d2"}, + {file = "grpcio-1.49.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:1cc400c8a2173d1c042997d98a9563e12d9bb3fb6ad36b7f355bc77c7663b8af"}, + {file = "grpcio-1.49.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:34f736bd4d0deae90015c0e383885b431444fe6b6c591dea288173df20603146"}, + {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:196082b9c89ebf0961dcd77cb114bed8171964c8e3063b9da2fb33536a6938ed"}, + {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c9f89c42749890618cd3c2464e1fbf88446e3d2f67f1e334c8e5db2f3272bbd"}, + {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64419cb8a5b612cdb1550c2fd4acbb7d4fb263556cf4625f25522337e461509e"}, + {file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8a5272061826e6164f96e3255405ef6f73b88fd3e8bef464c7d061af8585ac62"}, + {file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ea9d0172445241ad7cb49577314e39d0af2c5267395b3561d7ced5d70458a9f3"}, + {file = "grpcio-1.49.1-cp37-cp37m-win32.whl", hash = "sha256:2070e87d95991473244c72d96d13596c751cb35558e11f5df5414981e7ed2492"}, + {file = "grpcio-1.49.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fcedcab49baaa9db4a2d240ac81f2d57eb0052b1c6a9501b46b8ae912720fbf"}, + {file = "grpcio-1.49.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:afbb3475cf7f4f7d380c2ca37ee826e51974f3e2665613996a91d6a58583a534"}, + {file = "grpcio-1.49.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a4f9ba141380abde6c3adc1727f21529137a2552002243fa87c41a07e528245c"}, + {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:cf0a1fb18a7204b9c44623dfbd1465b363236ce70c7a4ed30402f9f60d8b743b"}, + {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17bb6fe72784b630728c6cff9c9d10ccc3b6d04e85da6e0a7b27fb1d135fac62"}, + {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18305d5a082d1593b005a895c10041f833b16788e88b02bb81061f5ebcc465df"}, + {file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b6a1b39e59ac5a3067794a0e498911cf2e37e4b19ee9e9977dc5e7051714f13f"}, + {file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e20d59aafc086b1cc68400463bddda6e41d3e5ed30851d1e2e0f6a2e7e342d3"}, + {file = "grpcio-1.49.1-cp38-cp38-win32.whl", hash = "sha256:e1e83233d4680863a421f3ee4a7a9b80d33cd27ee9ed7593bc93f6128302d3f2"}, + {file = "grpcio-1.49.1-cp38-cp38-win_amd64.whl", hash = "sha256:221d42c654d2a41fa31323216279c73ed17d92f533bc140a3390cc1bd78bf63c"}, + {file = "grpcio-1.49.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:fa9e6e61391e99708ac87fc3436f6b7b9c6b845dc4639b406e5e61901e1aacde"}, + {file = "grpcio-1.49.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9b449e966ef518ce9c860d21f8afe0b0f055220d95bc710301752ac1db96dd6a"}, + {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:aa34d2ad9f24e47fa9a3172801c676e4037d862247e39030165fe83821a7aafd"}, + {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5207f4eed1b775d264fcfe379d8541e1c43b878f2b63c0698f8f5c56c40f3d68"}, + {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b24a74651438d45619ac67004638856f76cc13d78b7478f2457754cbcb1c8ad"}, + {file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fe763781669790dc8b9618e7e677c839c87eae6cf28b655ee1fa69ae04eea03f"}, + {file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2f2ff7ba0f8f431f32d4b4bc3a3713426949d3533b08466c4ff1b2b475932ca8"}, + {file = "grpcio-1.49.1-cp39-cp39-win32.whl", hash = "sha256:08ff74aec8ff457a89b97152d36cb811dcc1d17cd5a92a65933524e363327394"}, + {file = "grpcio-1.49.1-cp39-cp39-win_amd64.whl", hash = "sha256:274ffbb39717918c514b35176510ae9be06e1d93121e84d50b350861dcb9a705"}, + {file = "grpcio-1.49.1.tar.gz", hash = "sha256:d4725fc9ec8e8822906ae26bb26f5546891aa7fbc3443de970cc556d43a5c99f"}, +] h11 = [ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, @@ -1314,8 +1530,8 @@ pathlib2 = [ {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, + {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"}, + {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"}, ] platformdirs = [ {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, @@ -1325,6 +1541,22 @@ pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] +protobuf = [ + {file = "protobuf-4.21.6-cp310-abi3-win32.whl", hash = "sha256:49f88d56a9180dbb7f6199c920f5bb5c1dd0172f672983bb281298d57c2ac8eb"}, + {file = "protobuf-4.21.6-cp310-abi3-win_amd64.whl", hash = "sha256:7a6cc8842257265bdfd6b74d088b829e44bcac3cca234c5fdd6052730017b9ea"}, + {file = "protobuf-4.21.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ba596b9ffb85c909fcfe1b1a23136224ed678af3faf9912d3fa483d5f9813c4e"}, + {file = "protobuf-4.21.6-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4143513c766db85b9d7c18dbf8339673c8a290131b2a0fe73855ab20770f72b0"}, + {file = "protobuf-4.21.6-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b6cea204865595a92a7b240e4b65bcaaca3ad5d2ce25d9db3756eba06041138e"}, + {file = "protobuf-4.21.6-cp37-cp37m-win32.whl", hash = "sha256:9666da97129138585b26afcb63ad4887f602e169cafe754a8258541c553b8b5d"}, + {file = "protobuf-4.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:308173d3e5a3528787bb8c93abea81d5a950bdce62840d9760effc84127fb39c"}, + {file = "protobuf-4.21.6-cp38-cp38-win32.whl", hash = "sha256:aa29113ec901281f29d9d27b01193407a98aa9658b8a777b0325e6d97149f5ce"}, + {file = "protobuf-4.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:8f9e60f7d44592c66e7b332b6a7b4b6e8d8b889393c79dbc3a91f815118f8eac"}, + {file = "protobuf-4.21.6-cp39-cp39-win32.whl", hash = "sha256:80e6540381080715fddac12690ee42d087d0d17395f8d0078dfd6f1181e7be4c"}, + {file = "protobuf-4.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:77b355c8604fe285536155286b28b0c4cbc57cf81b08d8357bf34829ea982860"}, + {file = "protobuf-4.21.6-py2.py3-none-any.whl", hash = "sha256:07a0bb9cc6114f16a39c866dc28b6e3d96fa4ffb9cc1033057412547e6e75cb9"}, + {file = "protobuf-4.21.6-py3-none-any.whl", hash = "sha256:c7c864148a237f058c739ae7a05a2b403c0dfa4ce7d1f3e5213f352ad52d57c6"}, + {file = "protobuf-4.21.6.tar.gz", hash = "sha256:6b1040a5661cd5f6e610cbca9cfaa2a17d60e2bb545309bc1b278bb05be44bdd"}, +] psycopg2-binary = [ {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, {file = "psycopg2_binary-2.9.1-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24b0b6688b9f31a911f2361fe818492650795c9e5d3a1bc647acbd7440142a4f"}, @@ -1424,6 +1656,18 @@ pydantic = [ {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, ] +pyln-bolt7 = [ + {file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"}, + {file = "pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5"}, +] +pyln-client = [ + {file = "pyln-client-0.12.0.post1.tar.gz", hash = "sha256:c80338e8e9f435720c0e5f552dc4016fc8fba16d4b79764f881067e0fcd5d5c7"}, + {file = "pyln_client-0.12.0.post1-py3-none-any.whl", hash = "sha256:cfe3404eb88f294015145e668d774dd754b3baec36b44fe773fa354f1e1e48c1"}, +] +pyln-proto = [ + {file = "pyln-proto-0.11.1.tar.gz", hash = "sha256:9bed240f41917c4fd526b767218a77d0fbe69242876eef72c35a856796f922d6"}, + {file = "pyln_proto-0.11.1-py3-none-any.whl", hash = "sha256:27b2e04a81b894f69018279c0ce4aa2e7ccd03b86dd9783f96b9d8d1498c8393"}, +] pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, @@ -1438,9 +1682,14 @@ pyqrcode = [ pyscss = [ {file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"}, ] +pysocks = [ + {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] pytest = [ - {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, - {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, + {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, + {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, ] pytest-asyncio = [ {file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"}, @@ -1612,8 +1861,8 @@ typed-ast = [ {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] types-protobuf = [ - {file = "types-protobuf-3.19.22.tar.gz", hash = "sha256:d2b26861b0cb46a3c8669b0df507b7ef72e487da66d61f9f3576aa76ce028a83"}, - {file = "types_protobuf-3.19.22-py3-none-any.whl", hash = "sha256:d291388678af91bb045fafa864f142dc4ac22f5d4cdca097c7d8d8a32fa9b3ab"}, + {file = "types-protobuf-3.20.4.tar.gz", hash = "sha256:0dad3a5009895c985a56e2837f61902bad9594151265ac0ee907bb16d0b01eb7"}, + {file = "types_protobuf-3.20.4-py3-none-any.whl", hash = "sha256:5082437afe64ce3b31c8db109eae86e02fda11e4d5f9ac59cb8578a8a138aa70"}, ] typing-extensions = [ {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, diff --git a/pyproject.toml b/pyproject.toml index e95c6a2e..7f833aa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,9 @@ zipp = "3.5.0" loguru = "0.5.3" cffi = "1.15.0" websocket-client = "1.3.3" +grpcio = "^1.49.1" +protobuf = "^4.21.6" +pyln-client = "^0.12.0" [tool.poetry.dev-dependencies] isort = "^5.10.1" From dc11205128f2cc9dafbe51d3449852959a340733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loun=C3=A8s=20Ksouri?= Date: Fri, 23 Sep 2022 21:11:54 +0200 Subject: [PATCH 022/154] feat(docker): dynamic port and host using environment variables (#1006) --- Dockerfile | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fed097d2..6259fe7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,23 @@ FROM python:3.9-slim + RUN apt-get clean RUN apt-get update RUN apt-get install -y curl pkg-config build-essential RUN curl -sSL https://install.python-poetry.org | python3 - + ENV PATH="/root/.local/bin:$PATH" + WORKDIR /app + COPY . . + RUN poetry config virtualenvs.create false RUN poetry install --no-dev --no-root RUN poetry run python build.py + +ENV LNBITS_PORT="5000" +ENV LNBITS_HOST="0.0.0.0" + EXPOSE 5000 -CMD ["poetry", "run", "lnbits", "--port", "5000", "--host", "0.0.0.0"] + +CMD ["sh", "-c", "poetry run lnbits --port $LNBITS_PORT --host $LNBITS_HOST"] From 6e405d1fc4073b34d880f6045c5c7ec3f2f7ccf0 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 21 Sep 2022 17:13:05 +0300 Subject: [PATCH 023/154] fix: output address on confirmation based on selected network --- lnbits/extensions/watchonly/models.py | 1 + .../watchonly/static/components/payment/payment.js | 6 ++++-- lnbits/extensions/watchonly/templates/watchonly/index.html | 1 + lnbits/extensions/watchonly/views_api.py | 4 +++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index cedaa210..622f5ec8 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -80,6 +80,7 @@ class CreatePsbt(BaseModel): class ExtractPsbt(BaseModel): psbtBase64 = "" # // todo snake case inputs: List[TransactionInput] + network = "Mainnet" class SignedTransaction(BaseModel): diff --git a/lnbits/extensions/watchonly/static/components/payment/payment.js b/lnbits/extensions/watchonly/static/components/payment/payment.js index f2cccbba..9f38df1d 100644 --- a/lnbits/extensions/watchonly/static/components/payment/payment.js +++ b/lnbits/extensions/watchonly/static/components/payment/payment.js @@ -11,7 +11,8 @@ async function payment(path) { 'mempool-endpoint', 'sats-denominated', 'serial-signer-ref', - 'adminkey' + 'adminkey', + 'network' ], watch: { immediate: true, @@ -279,7 +280,8 @@ async function payment(path) { this.adminkey, { psbtBase64, - inputs: this.tx.inputs + inputs: this.tx.inputs, + network: this.network } ) return data diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index 263f6d92..91577cd2 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -136,6 +136,7 @@ :adminkey="g.user.wallets[0].adminkey" :serial-signer-ref="$refs.serialSigner" :sats-denominated="config.sats_denominated" + :network="config.network" @broadcast-done="handleBroadcastSuccess" > diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 77d28fee..750d46c9 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -4,6 +4,7 @@ from http import HTTPStatus import httpx from embit import finalizer, script from embit.ec import PublicKey +from embit.networks import NETWORKS from embit.psbt import PSBT, DerivationPath from embit.transaction import Transaction, TransactionInput, TransactionOutput from fastapi import Query, Request @@ -295,6 +296,7 @@ async def api_psbt_create( async def api_psbt_extract_tx( data: ExtractPsbt, w: WalletTypeInfo = Depends(require_admin_key) ): + network = NETWORKS["main"] if data.network == "Mainnet" else NETWORKS["test"] res = SignedTransaction() try: psbt = PSBT.from_base64(data.psbtBase64) @@ -316,7 +318,7 @@ async def api_psbt_extract_tx( for out in transaction.vout: tx["outputs"].append( - {"amount": out.value, "address": out.script_pubkey.address()} + {"amount": out.value, "address": out.script_pubkey.address(network)} ) res.tx_json = json.dumps(tx) except Exception as e: From 80a62569f9164c8fffe6ec33a04df38234dd44d1 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 22 Sep 2022 10:41:54 +0300 Subject: [PATCH 024/154] fix: wait for connection to init --- .../components/serial-signer/serial-signer.js | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js index 33d07d94..33261280 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js @@ -15,7 +15,7 @@ async function serialSigner(path) { receivedData: '', config: {}, decryptionKey: null, - sharedSecret: null, // todo: store in secure local storage + sharedSecret: null, hww: { password: null, @@ -57,13 +57,16 @@ async function serialSigner(path) { computed: { pairedDevices: { + cache: false, get: function () { + console.log('### get pairedDevices') return ( JSON.parse(window.localStorage.getItem('lnbits-paired-devices')) || [] ) }, set: function (devices) { + console.log('### set pairedDevices', devices) window.localStorage.setItem( 'lnbits-paired-devices', JSON.stringify(devices) @@ -81,6 +84,7 @@ async function serialSigner(path) { await this.openSerialPort(this.config) }, openSerialPort: async function (config = {baudRate: 9600}) { + console.log('### openSerialPort', config) if (!this.checkSerialPortSupported()) return false if (this.selectedPort) { this.$q.notify({ @@ -109,7 +113,10 @@ async function serialSigner(path) { // Wait for the serial port to open. await this.selectedPort.open(config) + // do not await this.startSerialPortReading() + // wait to init + sleep(1000) const textEncoder = new TextEncoderStream() this.writableStreamClosed = textEncoder.readable.pipeTo( @@ -307,7 +314,11 @@ async function serialSigner(path) { }, hwwPing: async function () { try { + console.log('### hwwPing 1', window.location.host) + // Send an empty ping. The serial port buffer might have some jubk data. Flush it. + await this.sendCommandClearText(COMMAND_PING) await this.sendCommandClearText(COMMAND_PING, [window.location.host]) + console.log('### hwwPing 2') } catch (error) { this.$q.notify({ type: 'warning', @@ -582,7 +593,7 @@ async function serialSigner(path) { hwwCheckPairing: async function () { const iv = window.crypto.getRandomValues(new Uint8Array(16)) const encrypted = await this.encryptMessage( - this.sharedSecret, + this.sharedSecret, // todo: revisit iv, PAIRING_CONTROL_TEXT.length + ' ' + PAIRING_CONTROL_TEXT ) @@ -724,6 +735,7 @@ async function serialSigner(path) { }, hwwHelp: async function () { try { + console.log('### cmd help') await this.sendCommandSecure(COMMAND_HELP) this.$q.notify({ type: 'positive', @@ -746,7 +758,7 @@ async function serialSigner(path) { } catch (error) { this.$q.notify({ type: 'warning', - message: 'Failed to ask for help!', + message: 'Failed to wipe!', caption: `${error}`, timeout: 10000 }) @@ -861,7 +873,13 @@ async function serialSigner(path) { sendCommandSecure: async function (command, attrs = []) { const message = [command].concat(attrs).join(' ') + console.log('### sendCommandSecure', message) const iv = window.crypto.getRandomValues(new Uint8Array(16)) + if (!this.sharedSecret || !this.sharedSecret.length) { + throw new Error( + `Secure connection not estabileshed. Tried to run command: ${command}` + ) + } const encrypted = await this.encryptMessage( this.sharedSecret, iv, @@ -874,6 +892,7 @@ async function serialSigner(path) { }, sendCommandClearText: async function (command, attrs = []) { const message = [command].concat(attrs).join(' ') + console.log('### sendCommandClearText', message) await this.writer.write(message + '\n') }, extractCommand: async function (value) { From d5f78e83d927c61071a709175a95692e06188c49 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 22 Sep 2022 10:42:27 +0300 Subject: [PATCH 025/154] chore: update `created by` --- .../watchonly/templates/watchonly/_api_docs.html | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html index ba52c4fa..e40ca81f 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html +++ b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html @@ -18,9 +18,21 @@ >directly from browser -
Created by, +
Created by Ben Arc, + Tiago Vasconcelos, + motorina0 (using, Date: Thu, 22 Sep 2022 10:58:19 +0300 Subject: [PATCH 026/154] fix: paired devices list --- .../serial-signer/serial-signer.html | 2 +- .../components/serial-signer/serial-signer.js | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html index 2ed2d49c..a95a1906 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html @@ -49,7 +49,7 @@ diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js index 33261280..b27f2fa2 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js @@ -51,7 +51,8 @@ async function serialSigner(path) { }, tx: null, // todo: move to hww - showConsole: false + showConsole: false, + showPairedDevices: true } }, @@ -59,14 +60,12 @@ async function serialSigner(path) { pairedDevices: { cache: false, get: function () { - console.log('### get pairedDevices') return ( JSON.parse(window.localStorage.getItem('lnbits-paired-devices')) || [] ) }, set: function (devices) { - console.log('### set pairedDevices', devices) window.localStorage.setItem( 'lnbits-paired-devices', JSON.stringify(devices) @@ -84,7 +83,6 @@ async function serialSigner(path) { await this.openSerialPort(this.config) }, openSerialPort: async function (config = {baudRate: 9600}) { - console.log('### openSerialPort', config) if (!this.checkSerialPortSupported()) return false if (this.selectedPort) { this.$q.notify({ @@ -314,11 +312,9 @@ async function serialSigner(path) { }, hwwPing: async function () { try { - console.log('### hwwPing 1', window.location.host) // Send an empty ping. The serial port buffer might have some jubk data. Flush it. await this.sendCommandClearText(COMMAND_PING) await this.sendCommandClearText(COMMAND_PING, [window.location.host]) - console.log('### hwwPing 2') } catch (error) { this.$q.notify({ type: 'warning', @@ -735,7 +731,6 @@ async function serialSigner(path) { }, hwwHelp: async function () { try { - console.log('### cmd help') await this.sendCommandSecure(COMMAND_HELP) this.$q.notify({ type: 'positive', @@ -873,7 +868,6 @@ async function serialSigner(path) { sendCommandSecure: async function (command, attrs = []) { const message = [command].concat(attrs).join(' ') - console.log('### sendCommandSecure', message) const iv = window.crypto.getRandomValues(new Uint8Array(16)) if (!this.sharedSecret || !this.sharedSecret.length) { throw new Error( @@ -892,7 +886,6 @@ async function serialSigner(path) { }, sendCommandClearText: async function (command, attrs = []) { const message = [command].concat(attrs).join(' ') - console.log('### sendCommandClearText', message) await this.writer.write(message + '\n') }, extractCommand: async function (value) { @@ -968,6 +961,11 @@ async function serialSigner(path) { devices.splice(deviceIndex, 1) } this.pairedDevices = devices + this.showPairedDevices = false + setTimeout(() => { + // force UI refresh + this.showPairedDevices = true + }) }, addPairedDevice: function (deviceId, sharedSecretHex, config) { const devices = this.pairedDevices @@ -979,6 +977,11 @@ async function serialSigner(path) { config }) this.pairedDevices = devices + this.showPairedDevices = false + setTimeout(() => { + // force UI refresh + this.showPairedDevices = true + }) }, updatePairedDeviceConfig(deviceId, config) { const device = this.getPairedDevice(deviceId) From 649c653836502a9cc29ec468874e2f9d3860a305 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 27 Sep 2022 17:19:32 +0300 Subject: [PATCH 027/154] fix: do not show command data in logs --- .../static/components/serial-signer/serial-signer.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js index b27f2fa2..67268eeb 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js @@ -230,8 +230,9 @@ async function serialSigner(path) { while (true) { const {value, done} = await readStringUntil('\n') if (value) { - this.handleSerialPortResponse(value) - this.updateSerialPortConsole(value) + const {command, commandData} = await this.extractCommand(value) + this.handleSerialPortResponse(command, commandData) + this.updateSerialPortConsole(command) } if (done) return } @@ -245,8 +246,7 @@ async function serialSigner(path) { } } }, - handleSerialPortResponse: async function (value) { - const {command, commandData} = await this.extractCommand(value) + handleSerialPortResponse: async function (command, commandData) { this.logPublicCommandsResponse(command, commandData) switch (command) { @@ -287,7 +287,7 @@ async function serialSigner(path) { ) break default: - console.log(` %c${value}`, 'background: #222; color: red') + console.log(` %c${command}`, 'background: #222; color: red') } }, logPublicCommandsResponse: function (command, commandData) { From f1670c3153d990aea8060b5305bc0861d2712573 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 27 Sep 2022 17:20:33 +0300 Subject: [PATCH 028/154] chore: version bump --- lnbits/extensions/watchonly/templates/watchonly/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index 91577cd2..67f89810 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -150,7 +150,7 @@
{{SITE_TITLE}} Onchain Wallet (watch-only) Extension - (v0.2) + (v0.3)
From a8b90bd5952601577f6aa5b4c9ff8e4b499d361c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 27 Sep 2022 17:35:27 +0300 Subject: [PATCH 029/154] fix: close serial port when re-pairing fails --- .../components/serial-signer/serial-signer.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js index 67268eeb..8d3c1fec 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js @@ -610,10 +610,10 @@ async function serialSigner(path) { } }, handleCheckPairingResponse: async function (res = '') { - const [statusCode, encryptedMessage] = res.split(' ') + const [statusCode, message] = res.split(' ') switch (statusCode) { case '0': - const controlText = await this.decryptData(encryptedMessage) + const controlText = await this.decryptData(message) if (controlText == PAIRING_CONTROL_TEXT) { this.$q.notify({ type: 'positive', @@ -629,6 +629,16 @@ async function serialSigner(path) { }) } break + case '1': + this.closeSerialPort() + this.$q.notify({ + type: 'warning', + message: + 'Re-pairing failed. Remove (forget) device and try again!', + caption: `Error: ${message}`, + timeout: 10000 + }) + break default: // noting to do here yet break From 19b1374d51ffe333c1862e8dbcb8332704f5b8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 17:44:52 +0200 Subject: [PATCH 030/154] fixing tests for new poetry version ^1.2 --- .github/workflows/formatting.yml | 13 ++++++++++++- .github/workflows/migrations.yml | 8 ++++++-- .github/workflows/mypy.yml | 8 ++++++-- .github/workflows/regtest.yml | 24 ++++++++++++++++++------ .github/workflows/tests.yml | 19 ++++++++++++++----- 5 files changed, 56 insertions(+), 16 deletions(-) diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index e106ace3..e3d0fd35 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -9,9 +9,20 @@ on: jobs: checks: runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Install packages run: poetry install - name: Check black diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index 90006d2a..c280ad7d 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -22,14 +22,18 @@ jobs: --health-retries 5 strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Install dependencies run: | poetry install diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 61601731..d80da678 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -7,14 +7,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Install dependencies run: | poetry install diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index 6572ccdb..2d7aae6b 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -7,14 +7,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Setup Regtest run: | docker build -t lnbitsdocker/lnbits-legend . @@ -46,14 +50,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Setup Regtest run: | docker build -t lnbitsdocker/lnbits-legend . @@ -86,14 +94,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Setup Regtest run: | docker build -t lnbitsdocker/lnbits-legend . diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 298d7ff0..5d368fbb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -29,14 +30,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Install dependencies env: VIRTUAL_ENV: ./venv @@ -64,14 +69,18 @@ jobs: --health-retries 5 strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Install dependencies run: | poetry install From 463941ec94cee61814a3f0714f2a76247175a6bc Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 28 Sep 2022 10:33:43 +0300 Subject: [PATCH 031/154] fix: always include change output for fee --- .../extensions/watchonly/static/components/payment/payment.html | 2 +- .../watchonly/static/components/serial-signer/serial-signer.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/watchonly/static/components/payment/payment.html b/lnbits/extensions/watchonly/static/components/payment/payment.html index cde65ca2..6e1d94c7 100644 --- a/lnbits/extensions/watchonly/static/components/payment/payment.html +++ b/lnbits/extensions/watchonly/static/components/payment/payment.html @@ -5,7 +5,7 @@ Date: Thu, 29 Sep 2022 10:31:10 +0100 Subject: [PATCH 032/154] Added Caddy --- docs/guide/installation.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 2b058754..e77e301d 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -292,6 +292,41 @@ Save the file and run the following commands: sudo systemctl enable lnbits.service sudo systemctl start lnbits.service ``` +## Running using Caddy reverse proxy with automatic https + +Point your domain at the IP of the server you're running LNbits on, by making an `A` record. + +Install Caddy on the server +https://caddyserver.com/docs/install#debian-ubuntu-raspbian + +``` +sudo caddy stop +``` +Create a Caddyfile +``` +sudo nano Caddyfile +``` +Assuming your LNbits is running on port `5000`: +``` +yourdomain.com { + handle /api/v1/payments/sse* { + reverse_proxy 0.0.0.0:5000 { + header_up X-Forwarded-Host legend.lnbits.com + transport http { + keepalive off + compression off + } + } + } + reverse_proxy 0.0.0.0:5000 { + header_up X-Forwarded-Host legend.lnbits.com + } +} +``` +Save and exit `CTRL + x` +``` +sudo caddy start +``` ## Running behind an apache2 reverse proxy over https Install apache2 and enable apache2 mods From 55b0814d932bda58545ba25cf95f5cac02477039 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 29 Sep 2022 10:34:03 +0100 Subject: [PATCH 033/154] added intro --- docs/guide/installation.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index e77e301d..21ee307a 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -292,7 +292,9 @@ Save the file and run the following commands: sudo systemctl enable lnbits.service sudo systemctl start lnbits.service ``` -## Running using Caddy reverse proxy with automatic https +## Setting up a Caddy reverse proxy with automatic https + +USe Caddy to easily make your LNbits install accessible over clearnet with a domain and https cert. Point your domain at the IP of the server you're running LNbits on, by making an `A` record. From 7de17485d39e262f81e0dfd0cbc61b22cab3f1dc Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 29 Sep 2022 10:34:53 +0100 Subject: [PATCH 034/154] typo --- docs/guide/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 21ee307a..d79363af 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -294,7 +294,7 @@ sudo systemctl start lnbits.service ``` ## Setting up a Caddy reverse proxy with automatic https -USe Caddy to easily make your LNbits install accessible over clearnet with a domain and https cert. +Use Caddy to make your LNbits install accessible over clearnet with a domain and https cert. Point your domain at the IP of the server you're running LNbits on, by making an `A` record. From 9c575b7d3a46f355ec01dad4b27de70b9be4c440 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 29 Sep 2022 10:37:16 +0100 Subject: [PATCH 035/154] typo --- docs/guide/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index d79363af..7f2aefdc 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -292,7 +292,7 @@ Save the file and run the following commands: sudo systemctl enable lnbits.service sudo systemctl start lnbits.service ``` -## Setting up a Caddy reverse proxy with automatic https +## Reverse proxy with automatic https using Caddy Use Caddy to make your LNbits install accessible over clearnet with a domain and https cert. @@ -308,7 +308,7 @@ Create a Caddyfile ``` sudo nano Caddyfile ``` -Assuming your LNbits is running on port `5000`: +Assuming your LNbits is running on port `5000` add: ``` yourdomain.com { handle /api/v1/payments/sse* { From 07293e7d5ce765807f753c7ef0f0e6723f95f4a4 Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Thu, 29 Sep 2022 10:45:09 +0100 Subject: [PATCH 036/154] Update installation.md --- docs/guide/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 7f2aefdc..87679ed5 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -313,7 +313,7 @@ Assuming your LNbits is running on port `5000` add: yourdomain.com { handle /api/v1/payments/sse* { reverse_proxy 0.0.0.0:5000 { - header_up X-Forwarded-Host legend.lnbits.com + header_up X-Forwarded-Host yourdomain.com transport http { keepalive off compression off @@ -321,7 +321,7 @@ yourdomain.com { } } reverse_proxy 0.0.0.0:5000 { - header_up X-Forwarded-Host legend.lnbits.com + header_up X-Forwarded-Host yourdomain.com } } ``` From 325139066c309d4080ab7c2e94ad01ede63cdba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 29 Sep 2022 20:40:58 +0200 Subject: [PATCH 037/154] fix service-worker.js issue #1009 (#1017) * service-worker.js fix for issue #1009 fixup * remove weird comment --- lnbits/core/static/js/service-worker.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lnbits/core/static/js/service-worker.js b/lnbits/core/static/js/service-worker.js index 041b9f32..98ae497e 100644 --- a/lnbits/core/static/js/service-worker.js +++ b/lnbits/core/static/js/service-worker.js @@ -3,7 +3,11 @@ const CACHE_VERSION = 1 const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-` const getApiKey = request => { - return request.headers.get('X-Api-Key') || 'none' + let api_key = request.headers.get('X-Api-Key') + if (!api_key || api_key == 'undefined') { + api_key = 'no_api_key' + } + return api_key } // on activation we clean up the previously registered service workers @@ -26,8 +30,10 @@ self.addEventListener('activate', evt => // If no response is found, it populates the runtime cache with the response // from the network before returning it to the page. self.addEventListener('fetch', event => { - // Skip cross-origin requests, like those for Google Analytics. if ( + !event.request.url.startsWith( + self.location.origin + '/api/v1/payments/sse' + ) && event.request.url.startsWith(self.location.origin) && event.request.method == 'GET' ) { From 04cfdc2bf3b15bc9d9bdc5b0137d0d729c0ccadf Mon Sep 17 00:00:00 2001 From: Taylor Helsper Date: Thu, 29 Sep 2022 23:06:45 -0500 Subject: [PATCH 038/154] Update index.html --- lnbits/core/templates/core/index.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html index f769b44f..1319fa1f 100644 --- a/lnbits/core/templates/core/index.html +++ b/lnbits/core/templates/core/index.html @@ -171,6 +171,19 @@
+
+
+ + + +
+
+   +
+
From fb58f1ed5e04895582ac79e889a74e2d9a07dd57 Mon Sep 17 00:00:00 2001 From: Taylor Helsper Date: Thu, 29 Sep 2022 23:42:38 -0500 Subject: [PATCH 039/154] Update myNode images --- lnbits/static/images/mynode.png | Bin 3757 -> 13994 bytes lnbits/static/images/mynodel.png | Bin 14352 -> 9272 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/lnbits/static/images/mynode.png b/lnbits/static/images/mynode.png index cf25bc586797b750a64ddba848b2499a0698a5b2..390446b8ee906da9e01142b477a59506269ce5e7 100644 GIT binary patch literal 13994 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 delta 3684 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 diff --git a/lnbits/static/images/mynodel.png b/lnbits/static/images/mynodel.png index b8afb9ffcd0dbe9c3c07cbd1e007b1067d92caa9..344b54b6db53406258f7f1207b78f72b9fb1cbf3 100644 GIT binary patch delta 5190 zcmbPGu)|}5ay{Grs*s41pu}>8f};Gi%$!t(lFEWqh1817GzNx>TW`aow<()*{JE~R ziKVf{;k4uW;!Vvu_O83%R@bIaGB!TxZuiLUUHck02hr9!-{kkt{~~SYb7*B()a$jW zHCgND%9>A;pI$-<#AwRyY*w=n-*URXP?0eeRt54YN+p1xn zxkU8&smQIRcch=MI`N##{rr=ECoU>4T;KTobFtQ8^?R*e$7XWd@6q}A^wEy8IXl0I z2zwuNKALiT`nn_O4xHi-GWXYuS2Z=I)SWx)-uLg(XXV7ZCOb1eIK55%&27J>X1?63 z?>(O+{u^A0yKWva|FKH^vD4X=b)Qen3;yx(aB+R^%{ zl|?&`@kEr&Ynb0)-sjRT8WVBUp_y}?2+!x!;=8WByJPm^}oqiCHvnS~*3!UUZ>b#g2b)>-ire^+05;nu6a!0V7_{EEbgubE9wekumcNim!q*$NC!#R=1Tvz|KSEyxo3 z#ka~yBF?K}vxWe3Q>pOjh2mxMCu+sFhrFF$@Ob@+r48@y*E{I3mc&o^Ew=srf^CZ= z&-_{87LvGY(k$*%{KDHTXS+-PeVP61s`&rpwh43XmiEQIIsn%?BA?J zb-CAcdb}tq<~dm`XmRO9M3-g(~UJI0jrCe?#O(-p%TqrA6C3e@A&4$e&Qk9c3pchYmGYl{AFJoKi$)) zU}rUUHd_#(%lV{;&EnQmhcY&sQ-{U2wP(HCm+REHfboJL$8s6wuqy|)-s)NyTq>l! zFW4fY#43b$cgw|#f7^tXJasU8`e}brZm#i{lWrRm3*HMJYSU!kiPH6EjpeGB(a8TX zi@*NH)#r+j8_#kt>hG91GgvI+ROLB_HG0i)qU@RJ0c_d%Urhh#D%jc`SlSeO`lwrr z)aEP6{I9#%zZAV`ThtR^(Xp;VVEJMZo~NrV7iKPaA;7%N^TRK#whXfj!GtXCqgUTe zRGRW}kL8ZO{VN`izS%>|DhU6P5h~IyfJ0?G!t5 z@U2(%1EGSY3riROD(={0d^U+^_WU(BuNg0R6!`sO^M#kp(`WM9M}M$u*{f!vnzvx_ zoTJ}f{=KEHds@0}vCvxgcWjHKxNh|&)~2@xOxd|uca2E>^a)k2Az7~%eB~`FF>6#) zSh^zN;60mvI<5uv6SJCT-pXhR%1!;s+^&$ra%k($2cK45yAa}fU~5n7p=3949m9zR zR~oEJwC7JaH7%FV&Ze0^I!JsEL+XUB?58I9#Ro>_F+FIR5UY~-R#sO0+~T8(?E%Z= zA{{(G&zbI{qPej1nQO9opYLbO8&>hbT&6yeLKhFar!0{Tv7NC&s{RhU*FxUz<8eik zrb$_CnCSMht=9FxAuXo8`^8)~352pAo}l0HTX}o_fi0V>xWx3g2>;prEpXmR=Xn9L zbzA4l?^gSFQv9F!7w7OTk_*15+xg1rn?1f#$H2g-lIiRm;OXoPtJfG9D(1|cVC!`x zK%{klPjh|7(ufWZhx*%$(#grjS}cx=#{^~88hNs@PnMX((`;R!zH5di%R$v046NDb zyT!zoeLVQICgz>(^S$i4)|{6&?Pbmk6J2-zZTz?OZ+4oC-B@x~tlBZ+S?qc1g&%A7 zB}vx4lvw|4r~UaHk?9LdR-HPuXT^bA;?|S3%-1A+-S%9*Oa4gNl(dOI>OJ?!E}fXT z`StRX7JojPY>({P++BP*JoiD-8BIAe=3_k`D_(dQm-ZFhn7T`Z|3vK090vdh=`Ue(;^nyCu?6ZiNWl3i)zEBCB^|8=`_uY9v}XR*5mY)=bW`%vJX zeBw@i?eiNd?oKfM|2vo0*w6O!dGk76Mw6uh-3Ge@Z!jAT4q7AK4;p-^64E|6nC91qB6nPu~Emkj&iF z;F84L0$U|RGcyxYLn8}wLrZgGV*}&OGvsQRCc7xasF;`;ni!>+8R(j)CK~ISSQwb- zCK@H1>!uhQm|LcrSz20}n@&EWpuq+)eey>IQ86O}BO_e{OI;(&5F=wN6GJOQLtO*Y z&76vfoI)4V!)zEB7?_g0-CY>|xA&jfKe$!D3T^vIy zZoQqAIYs2E=<)yCSAExFlVs=VKGLevpe_v2NYc-BrszK2dpp zJ$_f*-mmw*zuWzNFB1z>ql1C~2TMJa#!elqXHKc*U{es?_blhy7d@E?8VTM;C5P9_ zrIbe79-QFa%I?LkyEiHJd)D2+=7rL6m8o3cC$}Xje07v~P@im_Z)CPisNHXap3#Rz z4rZ)Oy!MXIZ-{)d&h0Droyc0UQ}_Sd`d>~8o7fI>Up_1O>k?C_*27%B)I)pR>VsBJ zjcc3Z&lSJWboW--wY+<>v}^nAuIr|+*=;gU?ZJ{g4IB)Px8y6@#8t1)d=s==XM4a_ zDUV1L6^sk)nO!NwJuCw9hI>h8On%Vl+|V`s{w!;@c{TO2B7xcjnbm(H~_ zUqAB8wl%*9S#gi;X@l3rb^AJwZ}MHa#aRC8jo;5b%U?UMOuZUB>%~{ET_+dvDcpEz zI?piQLz>~3Mb!o+sh2TRHy%Fhe>sW2RA$N_iOj{nF8h6}-zgpIpxqRGWtT$esVh^r z6o<%j?&hDiuF#XOe4FweiF)Y{IX7q7^=&6UTL&+`6kgYP?1#|nqOai#&vlnDtmV;v z`(}w|w4#Mo!1JgAu2ot)*z2>;G&ShI{crOn=h&r*86C@}Y8Kzl;r+JJrU(7pXNWKV|LWJGn%0Dh0+(l^r|VO8vA9p)xnQ}*>POkduO=*Et!({p=ZC#j(cJJY zeY+B}cOAUXvT5fU>&^3*KhAIxX87Q=XIe(LZ1=yuqQ_Hr^M5<)X!J4g`2PNnY;r+u z9;ap8Htx6K{9av_9Ur#3=Oj}{-bRZcqq{NNLb*J=7wxoAtXX>0WN-eH+7%a7cNzNI z)-NszzW6Ssobgxdtt-bD%TBEQ!<%b(V0R_kn~%N|>YkN5eEfQhBd6fYhhGa?^d4@u z=neBbbFxgwD`&%>NwZH*eE+6^VPV{e@eHTz~D_kocz;_5Q_+ z_D){K;2^o=nrk)F$+ESI)i(AS($9CScz@Pn%2sDfudaJ*urHmFkQ?6r&KHLK-M z8ya-}-E;5S?#2mP>6^N^AFYbePo0pyBq98d%$}EG7Z>HfOI*Bhg+piVEtbzBdEdT$ zDSq~u{}jXdzpo8;$i11J&FIkeQRWDzPeB@M&HHV+)CH~x}LqJc6?g8?w^1?S54<` zeSX>b=gB))RS=Y;XRZ(n{lEs0w_Pdczi#F#-t+-vPV{*$({Rlm0#dbTjw zqrPeW#>&L7x7!yjZMpeNH|~4Q&SfVgl1sAuro_*he&DHFo8PC5&9cj`^9ik3=9a~| zZKd~=eGCii&vMIJZvDjKQ6@b7W zyrw+mN7}x1Pxeh(Uq0WvJ7*sg!zTk4#*m9CnYnA&>n9j)-ki{X=Ju&63vAVH&;Dp- zW0<((&!*O7?Ziz#V(X4PzRLDDQvTkxw0TcIYPs#_$O(Nt_dCa_&XUQwm+Ve;|2d^T z*Y)6E`Ss6K_ni47!cY*V_fzAg34=#!<>s|bYoBH)Uz6T8_0QD4o$#rXRO|uDx;RNmkK~nD}XpJ8u=f-+0aV^M0ZKYYr@v``=Rke$t<5^S?4O zXx^RElN4Vre|&m)%)zbC^P2< zE5pWp7CSFU8=sC)JY{HdH}T1Q*XbV~rq294=bp%UZ|Qyg5AQv`-IaFZwER;a2i~)v z=SV#M+tTZ>?dYOi`ChM^cJDG>oy?%H^7#F0a@XEnk$OD;N!jYAt0z_m?Wkrmx9=RSlt#ZE2(JJQ? zJhoqu1&?9%`K zqwbkzNZ*>9g;S;Yyn2u3FS^`Tv-FQupVrO$wy$`i(gbhhCA)87JN{WdxXnuBYTo<< zj2Xu1Vk@^q{qkN}nI1P?EA0gP<%IHjQQuVeSI3%fOzW?=H@eBC^<*w{yOhI+s@rU@ z)n1)H>)kDPLdGPz|3mV$<8cS)78yI;kn4r|cV+59D` z0yg(&+`9U9k5B88(tWZAj!%u1zp;8dlS7I~@8@Wh!_P$Qr+ajkJ+6ssxRm!MZ(l(6 zn?n+1MRU!RGeg`IQs;bIHhaUo_kO8byX^i=JIQcy#TJItno~0~%wK<4THhZ!>Ef%i z#Tyo`D4Q;!o;8>EnZ)}~CX2(WHx&tKe%TaN##b-MTK z(kriiPIZ?n{N((7e*EiNUc3G4-(UatZ~uqiuh*Sf|G#6;lppi#>i^4peEs$C>pwF4 z{@L#16WMqFf9-%oCzy5yxT6*Bdf13-}xq^Poxq16K`#!!YkFIxp zdOtnPJ%4}ftheippRb?(>->Jptr6W`C$G!YW|w{_?zc04l^ONoZedN<7tI&_;`{#p z{d?`itVf=TmHUo~*su2aC4K2h^|ShHkCR8=PY=qf(OtRcP<7Nvp@^41tS*)ZYdl-O z^=IVb`cJR-7e&S!R2uxaxK;l6KW2H|zos^~{#3dD+Vk(T!`I{A@8A0Mw@Raa&%C-h zcK^05`uBI*kDvYTueFV!M`@ioo-)&R;bk^(ZuC?)xes`Mdg}$y`^J(|T z?H`KktM{|--#x#MS+=;&IIz3EN8sg0UdxCX58FQPd%qzvsZZT(-|-!p8{=MoNjl>D zo0;Lkc5d@+CX<%(@t8$;?vbfqqq^sFU->Rss~y`9SSLS7aNP0h-`l#s$NxY1ZZGp9 z?yL`oR+{(vu$o>clhXmuzf3K^IH6#FmY4fo?ZO{{FXHR#S8nX&n->1;9IM}cyY>jx zKS#oX=9~U)y??WQnYGDpD+bj^cUq6OxUkpou56L2>E77tAZz`1<{F!2GT{Z*EkTPm zo|+N7%A{TM!JV3At>5oOJbIpbW3t-%>7s=og3&7Jx?TR&Xg`a^1}O#H3>bsvke@3(U-nq0qV-o=eG zm}T={F*NkOn)zPw`HFM8%Z@Fy_Hpf8vuTB;*cC&o8U7tl+vNBDzPWS%o7t!T=6}`w zchhqDwZHi#=T?^1m%Nkz{Y^a9xbxt*`I#wPcePjf_sHkZ+W47$=HK`A<@Mj2|Fg5- z;{4#LkiTB{d}+e9&^=pc_wG8s?~qmEnc#Y-jbe^wQM2?Xe0uBUn(H6&@L8$z((5&8 zYeTNV0>#n)veVLB5t=rdUPF?@~NK$}kzE^r%my>9H%%z>k63+=p^o1;Y2Ih%Hy0j9R?jxHyj!;Ffk3m9XVk?@nl1%vcyDFT zWu4FVBclAfyR#ps`niYa^d{&>T+4g;dqJ%Bdr7x5mW3M(^0zhrIQzvf(h zbn3F#w%w-dvzJ?(dC+j^LvWeDwx9O`Tlr__uBU3s%1AG-XS?n;Ir6rQdQ9>U$(2Sz z%llOot{5I--e#O@ovQ!pfo1R2cazU&# zm1P>9xmNbrVsF-*oi7fuv>D6pH9sBQy11}S_J(wLz-P```>P6IoIJFzv zLfegAoYX6po)UICr((|3{KI^$Re4S?A0EG^DN-wad#>{9D`IW?*Xowszt#S;_#88kY9()}uz@ z84faC@!G`#Ir9oLd-TJ%rQgrJwtwvv)cmKFiP^}mL^2veod|NVC z*P1b&idtjwP+DWs)`J%ooi_|qQ(M%btgS8k!%{@!RN~#kO38{n8j9TOmKJZVnAbER zPos|MuS@TRU^N3BHM`y$(~h$^s)lWO_I6$GEy-o|KK$W{ex@AKVM(W~pDp;cb!CFS z=vUU?t*>t^*|lXtbPdyLUzud9sLoavjx8drO`2)Xo-8+hQlPPe_ohpmY|DqeqHo+2 zbh0Nk&Qt1$@hfVORA;XD`>{$`CQttB8E4i%7H1Ci%FM98DsjK;_Qt#Z`>f8SEfTZ6 zp)A+Vc-VvYLw(o%RW6-{w&Cju zO9GCSCSG>eTdpiVBES zBem&oj$ZsJ6+DaUR@={r!|&>yL}s1;oZ(ZwbZ(5?)2tGKQ+^8$FwRllyWVKxU$e9| z3p%8%vkqWY3KpU!X&HR5t^{?Ta~?y_OJ*)-k|hpqs< z_lvtM7v?;^QX4qI#!l_(FY5^C*Zg1Sm;OO>hil=dLM#q%}U0bT>*2@+| zE3~bgFKVEAO!gV$g*>OV9!Fv))!2C!+CF37w(S1gO!FhNUnxY0Da4k}Xqy{ss3@X; zGP^gaZ?!G=_40t~drQ1#dH7`}d4FJc;=S#Ef_HAJP4N?t4HnEdl0I{ua@|sxbN<9a z%`!G6;r#b)(oB~w$cl(R>~(o6y(ND0#sl?&MrkacJFjgD@MwCslR-e-EwpZfen#iM z=|*N&K>@c<7z73}bFnPcb+|W?x8&Y^*;%vYvw!+XEjY65VO!t~ZpO!Z8YD$p?To!T zpT+reUsF?X(qVt4%^1Y_LC`h&ah6DPnUPhbRChL);juLb_p^7K9I)E#H&y?a;hsaL z*IAy|_xzT5RPtcWsjL4UmTfit@J4TS>PkIyG7H{BWT$UTUl$$+Cax%-w-?SoC*PN^ zHgir}{&3;KCN;h!we9hWD^VqN2jlvE7VW2#cCI+AaPXGyV%Zp- z$O99ObUiGtS$emas zg^3NKJ`3hsDJ-A#;XwVw7~QnGw-UYw6g(HS*Bo9Wdb}a&i`bmrwq++~?X5{{J8mV^ zSLJgl=Si-(mB8iXLl*N6zGd0*;^3}$LyI>-{#J&U-%PELW;$Br$R6C*ea=6B#ztq! zqhC&LJDHTD`R(GoH@syZ+$(IIWESpuRQ@qqFzA(_&zi=G=Kq$P<~x}N)LXsKm{Y`k z`IuzH(_KdX7agrc6<@abIC3p4H{qVAa57iFAMoIBGXAkkrNvPIgW_k&E@!W}(!lF6rbs~>T@oi>^Flt1u`kjyzk_ ztghLlW62s)GkM#IvS1_kj~@hsxNR)m_@A@Rx|Ut9dTpNPp?{CF7W%x=n8@RnD!0JQ zM`07c&5Yz9lKCgLsxJ`=ilFV>txN@IBs@rhSGq+5Uxz((;X=$)Chdb~{ZMe3^KXak}jZt1UM_33=(9 zoIm?7(%{s4;w$y)2wSe&i)}%Nd7WYB^j12N?X+xlF#vKg{pEeZVnza_n>bB`BiebX`vtHoQK9oe2g zS-G+Fl`9YIo)ot1&%QXfg$CM-u70R`%j0~REq(gUbGKG2#>C&9u$ZIkqs1Yv z{gYUpMt}S(81X=+!9&lhWQ{=m@`p-m*m$h9`OdJ#EZUg;`t%E)i$e0v@A?Z^)ujvP zw%9a#)O%_NaL?J_F5Iy9{4EDwhB<-^yQNM$xo??ky8WK?OWWy7L<*kG1ss=i|B``E=#bolSa1TK-+SiUXdf~I?!cKwa+ z&z+$z&FQJrLS71~nyuP3Bj|1U3GpwCzZbmwS)wKH$-z6<-rQnZzm(A_>1z$|CSMX| zlfKMR#F@;)rc>)C?CG+w{EIL;;C%5?UJFaK8$&~uUz2t@XRh9=_`(@gd zn=b5>{<(Zl!%2zLKMFakrT@*`!FM`G|NN)=q;222Zk^TAdOZzt-_HI9gX_Xhk4q5IBY5F#8Re}_+{{98&Z{;Nl^oz$8 zUu$JAyr`d9xwdPKu*c#LpEXV}Xf>6t`)O0z-f-c;^RO42lO3-qXZB{C`x7|Lw8LYc z;=S^TL5n8tIyLRg!bm7D(tS?hpn!fZy@e2hr)vA3? z>Fz@PUfIe@&i|r!SL*H6X4$|YnHOzt)!wJEnJH;z`W-HoHq|`E1E$}VbiJR6N6G6~ z9AG&5u=FM4p1aRn62Gv1pQV^4!_+Rn>D(=WcI%EcXZq$#UW#jwYHzWvf^aN&snAhE%!9dzGSwiqP4N0Md1snN5>>$dEWiNvXH~O@(MH-Y1;~1b+QW3IL8#JctZJr`|kZ09#5T? z{BVBa^PK9eU^T9kBkMoz6xlHC@z?qF9a74L#(Jq*48AM6RUhw(I?A@KTfaPZH`fgN zU9F9iI1g!=E7{dvzAJaoUisOQVy^rOzD*}C<;~^4#r|1C%y6BhrN@frB>`>Sh9(*E z>$(^&tQ6T9Zhe=z+qC%lV~O|20*~BLx;Ufz@4Po0$M2f2oxY*t>8llD=j43Wy-{>O z#9qHJim^#5eZ?}lK<13O$$e}0ZZ4f{_G$TV72`Yav@#s8u|@8eQ2!Aa(wnzbK=Mq_ z8)x0wRfkhl6aMHfBXfI;xvGo0}c}i?H<8zhfJqrc((vlt2EFQVvsk+?d8L}jQ zOO5u8)Be|&S=0wk&y$^V>$J<#EQYiXu8xa3r|59_sHUf6@h}Q6_{i{O8L#F8Z;eC7 z*P2c|W;MvmU=BO+`I%>m&yF8$dS}j?6_jYK-?2p_@y^TiqY1ulNo7Amx7E&k!8YMA z^M%lZch|NBPD_%VJ3Yt2`)<@Fr8#PkCQ0>$cs6>-F4+|zGVNwUeSD3?ue3|9{#Q%6 zHwWkLWtfrKba~l_G^dTdo-MCR`Lo{(7p%SX{+d=PgXFFdpXJ}YBV;rGHsqaRKdP%U z^|5gJ85z$Hn<_XBj~1lbFP%7hnd#A;8b7OUbIw{ZD|KFZvxI2widU*m5 zyK!l=40FF-)%CNAT&d3GlF#c+ZrPL`Z(Y;L_3EEU!f|fn_a`+)Q)9&THJ0h$J>Je_ za8uxKz}^n&6}t1f96Bzuzdura?Co2DS=Zad8H;zcZFZ7YOJjUrIMF=HiD%L?0ry^y zhB(#>J+0rxHnyx#NOF82<|(L&l?rDygsU>z*3TTU;J&ZNNz@x z`mdwuw=Uij+NpA0@it$bYMZwETk9{|w_f$A=au|>ed#Tq4T)bZnAdc5n~42p<&dg_k1J=biBKEIzsed+9Z>CHx4Yj3akdQtD$Z5bA^ zj5kt;O5W~Te&|)Hg2C2Q4Ti(9+&d4>xncchT4A=7o6*kmZKn^;eX=%oTGUlbrG&$M zeS#Z~i!k{r96bMSWWA#aU>l8tTdDh8(8J<^Jrd?$ClJ2!EHb^Ftp=e92ygX1c9 zFX$+qu$t%T(r-1jSM-dvgZZQ{W_EMPH^zkt=SE#pdnug!=+T9Dw&CeFY~Bj5o%QvL z`;}EYvm$peAN2QMcGl?ogZg#LzZ)pbJ6004FZ5WrNSNAkhY9nJ-8j{361H)cVNl)O z#|deRqmBNi5wY~ND8bv3(mue74{9QF0il@hg|$}YFLD9gLu+`;+s(dtc&Yo6&|*p_$TcP(dHjQA0^hGWbxGaUT`6j&`6UyrmF6npAkx%udK5pf~eaIR46 zV^!-qGj}d(>}ys1CVSbL@N?u;Gq-0q*se-c_#K^vtD4)ZzDq$;nHYIT-kkm$j|4 z@Uc?r%F0zqKa{?BTNMMVUCsit{M=L9*yfy&|M4MeYh>HU74xef-I&{LYgx{={!Qs5 zH!I)2($T?lwpmYH;jy;l^n`=AyB;5yrl}w;td@DlXp-(WE-}}9L#O-|Gh-~W4$n1{ z6~7y9USA?O%a8Hhm2)zIukI$TvVGyn_B!}yzl5iw@t?*v=Tj<6zwZc2_J~R6c(A2U z>2YAmY7xKwh?pNP=M$ozd~)K9{kSOn*xRiAT``~J%DCJgHcu>G5o@)}ou6ZQ&6^jI z*TbeVTb=w{VJop{_5>b*h4cO~O8aQZly3d6_~+i+OKW@UE1M@|y=pzVAm)Tf^8~&> z7ew;E+$ne~M?|F6Q2QB}ca+X*)={K#-4OE+;m$hD8x|@C0`-g05SA>K2 z8!TRVY_9S0`65kakXfeeCYuIoC>- zcg*iNVG?Q0{%&FGo2fQ&3sP8SGQ2Rp9C>7cbWL*l4{=RejQ}eBJp2jgSFe*>}z$RM1-|KLI$g%pBN9%8#e5Am~7|X4f zR+MBnYRp}| z$v|Ig!?&a7*3_TkUH#%7-yi3X%{Tfk=?K|(mM=0f$|?J6GPBKo-ZQi8!m`4=U#oOu z`YewgPCGJ%lT*|-bJ;mTzUrXqO%2svPMJ%M=&?QX#O*boZI<>t zF`PKvbBFTOKJ5U+A-i{gqxOIv1@1vZ5ISYqqXkTdMQ) zXH4!|QTJU@Ti0Ieip_PrKRL_Wm8JCO0i_3*9b7~v{*nt}sqvc0E7_p(NOdY>3zdMh-fW4mt|cbQHt{yu}2-Dxr#>%%5AN!@Wtp^2mexW`DnO_Wz8u)+nEN z<)fSS-YHimyx;wCR$PMbw0#1==uUgub5%C|t*mS=yWRC6g z-#a(nnE(3b8Fe~F&tlFvD>@!3z4K)E#@>2w{jI%kf2B%JvV6VV+kKy}DBt^T4lah^6cAB66FE&8d)We%jl_|zrM%+--#GomBgx9f1cO-)ji7m?ZDpnB~ejT zar51?cb;s2T{-1j=2cp&cU-vs+HUXmjn^}lxBqipJawff!MPo?tgmN=Tnso9*@an|5k zpQ`Hq>Rj@fo(Zd(&ph*Y?B%OBkNmroSvbP&`Gsr0Esr%n`6OcOeA~OCv0G}33*)oT zGuH3z@44d3R&33-;D7Ps=^qczVP9>hx9j_%+SR5!eGi57CkDQp^0KIq>-X79+9?e} z)_Ofb%U^%`SA1*!(z?zzz6*1w_H;>Xvgl3YeU$pkb=$WGlDmxcH*1`Jm^aP+0AqcD z{^3ePCcXyl9oG(TTfWlp*78=HiLZCp_3Iwre_w0zQ?|PmPi{?`^H6BY)3dAWr+lZ2A19yR-1d)Nq(8_-aqZ6bJ683_3Hu`vNry7 zd;L6hLScH*l!H+V{Zg5gRiDMEyq;^_ZCz#EC%pIONng=peyHlbQEM-2L6G zcdwOM|CqeCseiMb=gf+1|99WIub(`9W&exf&(T3IJH9O4zrNJg+V<&*=6Nsn9hkW~ zyZhYy2QuYNwL7h*{+}^xdd0CX28YDT79QFAy=ac*$9&(fukM%nUVL2mt0R)0`)WksRR`kD)FYUEkmAq5e=;qG*0u$HrT)Y3;Z0$AkuYBu1Y%TrxSxIU_ za?RwGQ;J&N-Z|DaLC#z97}K}l`g2BUtWkfpc27v&_j=#l1@|^q>Cyp;o`REcn>&9B0d3|3<&M-?0}&{+xFB#bR`Pb;TVZ47Cskrq>*n!oS^tQOfAut0rInX^R{XBKJgwr-#CrAm zU%WP78!Vy(lO`RpbpH2X&x&%-?;$(qo-4BYy?CNu$R_z4r|ND6X0N&yzO2vN&rH+c zMwW3z|ALdps={BT9X`D0LALySNrl_BexDXQ{Pz2L>yXtqiMOn5i=Vkmt^Lg77`w#o z<07R)rN`!I8d*=R^Uu!+KRWTuaesedg84b2{B;*+Mucwep0nsc>?6Mk zd1=ACUmg6988qf;RR5i%QWThL&A`RHIPcVkJy~LfVjWfX8s~p1+q_I*nB?gB-b2E; z=DaL#`ZP1e-Sr1Jc1Sl`+|TE_%3ilJYwEGhDE!zbR~s5WHk z@p@InyQ5&`^;tP;t2q*1B|bVJ*|SLhXT#x{jQ>0reJpG?65{GDUNMt3mi_fYhrmOEb&wj$$j@R;(x8k>jaj!N--nSIG|LiW? zox^dj7}RGf9X+wUEh?*Kk-mGX`=;HV&YHgYds+U;*DuZ69G7V)JlEP%{kKPx;jjA1 zcE&-6GUk7-33OhxbJvdWrN;BWRGOzZ&;G#mF7Xw^^s_nX{Lz*y7F#~s|IXW^73u;Pcm1C`nL%+$#Sc--%k|&gYJGGjr%an!^mD3*M83+C8M&|L zpPnVJSvqy0sIgk7v{1pS*-T5GNIz3KvR(Ol<2Maw4HY-V*cS&A7N2<|*QZ;vFYyj< z;~A~#>NWyjQZij^zS{lIzO%EfvXQs@+nobb)|f15efd=2^&B0;jM$2)!P>shc3(9* zS*sO$=!be~{k+2RAD?f_SQWPL@uaBCH!rqGz4x)$wB6ZDZu*9K_0u_xru|P4R#sBq zXI=1Itn8C=m&OuX=bu{qA2|1OH?A~YtmE6(`bnj{tt>+1UyNqvMOTQj^W$}!!{Ys~>{D~?tln}= z=6v?X*I_&O&2l+8D-T>qxp*WZdFtn_HTmB@YOUT}cXwvZtibb@CEN^)1dm17&JXGF zyQ+L+)eFBk-j~1TTxY0!ey@I&%%|P}ZS(cEw;sGZz3OrCU8VA=ZhD0~XBYQd`wK7O zvkKJ~GM={Yv-{U`=gWIz=k&1n2bQoK8&z%7F3;4?eYlLNsO}jXx5Fu0Q?IM)+}F&v z=3a@bpQ`!p>@NLBGV0z!8>8=9-&wqJ+N->YCq)?zFRraQexu;1$4{l1>#}DYsei|F zqh#NaTh~@H?-9^={@$$Y;vfC+n@6($dc6D7U6okxdHU7s`tup(fm0^OFFL3H)TX@m z7_-r$1)}c`X0_WEIw=lpj(Y>e*DI51l?EcTehJa5Sj@{huf{ocCWm*Haap7c9a zVRx^ju5{b4y}f)LSJ{mfsk?t#P2VZMXhpQ?t@?s$@4HK9?+7{XZ?kZ>u<1$XnvX{k z%QxRUxZ#()&gvS~rovqfhB+K}^qz~UWv8zAm|Io0{#=5^`Iak3AN}FK|A43VipZj4 ziQis5Sp}w9Eaa+wy~3bk5C; zl+r9tR$bp+Zynu!vNrX7`&R$ys#eP87pFVD)#2p(s~Z{F1;Q zT{-RfmQ|9PMLi!yF3yg7QZ!-jp0l&10&6oS-(PvUyjOY3>#Z80{~y`C5jdb*er( Date: Fri, 30 Sep 2022 12:41:35 +0100 Subject: [PATCH 040/154] add small description in UI (#1023) --- .../extensions/scrub/templates/scrub/index.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lnbits/extensions/scrub/templates/scrub/index.html b/lnbits/extensions/scrub/templates/scrub/index.html index c063c858..5a1cae58 100644 --- a/lnbits/extensions/scrub/templates/scrub/index.html +++ b/lnbits/extensions/scrub/templates/scrub/index.html @@ -68,6 +68,21 @@
{{SITE_TITLE}} Scrub extension
+

+ Automatically forward funds (Scrub) that get paid to the LNbits + wallet, to an LNURLpay or Lightning Address. +
+ More info in Scrub's + readme. +

+

+ Important: wallet will need a float to account for + any fees, before being able to push a payment +

From 4ad3c841528de3efafefe48f667e6800eb7074e3 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Fri, 30 Sep 2022 13:55:46 +0200 Subject: [PATCH 041/154] downgrade pyln from 0.12.0 to 0.11.1 (#1021) --- poetry.lock | 36 ++++++++++++++++++++++++++++-------- pyproject.toml | 2 +- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/poetry.lock b/poetry.lock index ea83e25e..2a57a5c1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -118,6 +118,9 @@ category = "main" optional = false python-versions = ">=2.7" +[package.dependencies] +setuptools = "*" + [[package]] name = "certifi" version = "2021.5.30" @@ -206,7 +209,7 @@ python-versions = ">=3.6" cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] @@ -638,7 +641,7 @@ python-versions = ">=3.7,<4.0" [[package]] name = "pyln-client" -version = "0.12.0.post1" +version = "0.11.1" description = "Client library and plugin library for Core Lightning" category = "main" optional = false @@ -707,7 +710,7 @@ pathlib2 = "*" six = "*" [[package]] -name = "pysocks" +name = "PySocks" version = "1.7.1" description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." category = "main" @@ -823,6 +826,19 @@ python-versions = "*" [package.dependencies] cffi = ">=1.3.0" +[[package]] +name = "setuptools" +version = "65.4.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "shortuuid" version = "1.0.1" @@ -860,7 +876,7 @@ mssql = ["pyodbc"] mssql_pymssql = ["pymssql"] mssql_pyodbc = ["pyodbc"] mysql = ["mysqlclient"] -oracle = ["cx-oracle"] +oracle = ["cx_oracle"] postgresql = ["psycopg2"] postgresql_pg8000 = ["pg8000 (<1.16.6)"] postgresql_psycopg2binary = ["psycopg2-binary"] @@ -1024,7 +1040,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black ( [metadata] lock-version = "1.1" python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" -content-hash = "d0556d4a307864ba04a1e5da517884e523396c98a00ae09d9192c37b1d2c555b" +content-hash = "72e4462285d0bc5e2cb83c88c613726beced959b268bd30b984d8baaeff178ea" [metadata.files] aiofiles = [ @@ -1661,8 +1677,8 @@ pyln-bolt7 = [ {file = "pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5"}, ] pyln-client = [ - {file = "pyln-client-0.12.0.post1.tar.gz", hash = "sha256:c80338e8e9f435720c0e5f552dc4016fc8fba16d4b79764f881067e0fcd5d5c7"}, - {file = "pyln_client-0.12.0.post1-py3-none-any.whl", hash = "sha256:cfe3404eb88f294015145e668d774dd754b3baec36b44fe773fa354f1e1e48c1"}, + {file = "pyln-client-0.11.1.tar.gz", hash = "sha256:f5ea648840b030e2bbcf8c66ee72d25a5817f89854434a28d30e887547138c8e"}, + {file = "pyln_client-0.11.1-py3-none-any.whl", hash = "sha256:497db443406b80c98c0434e2938eb1b2a17e88fd9aa63b018124068198df6141"}, ] pyln-proto = [ {file = "pyln-proto-0.11.1.tar.gz", hash = "sha256:9bed240f41917c4fd526b767218a77d0fbe69242876eef72c35a856796f922d6"}, @@ -1682,7 +1698,7 @@ pyqrcode = [ pyscss = [ {file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"}, ] -pysocks = [ +PySocks = [ {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, @@ -1767,6 +1783,10 @@ secp256k1 = [ {file = "secp256k1-0.14.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c9e7c024ff17e9b9d7c392bb2a917da231d6cb40ab119389ff1f51dca10339a4"}, {file = "secp256k1-0.14.0.tar.gz", hash = "sha256:82c06712d69ef945220c8b53c1a0d424c2ff6a1f64aee609030df79ad8383397"}, ] +setuptools = [ + {file = "setuptools-65.4.0-py3-none-any.whl", hash = "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1"}, + {file = "setuptools-65.4.0.tar.gz", hash = "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9"}, +] shortuuid = [ {file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"}, {file = "shortuuid-1.0.1.tar.gz", hash = "sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f"}, diff --git a/pyproject.toml b/pyproject.toml index 7f833aa5..864500f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ cffi = "1.15.0" websocket-client = "1.3.3" grpcio = "^1.49.1" protobuf = "^4.21.6" -pyln-client = "^0.12.0" +pyln-client = "0.11.1" [tool.poetry.dev-dependencies] isort = "^5.10.1" From 148789a86f61aef3b241497fd6ca668edeb44a70 Mon Sep 17 00:00:00 2001 From: Taylor Helsper Date: Fri, 30 Sep 2022 10:19:17 -0500 Subject: [PATCH 042/154] Fix formatting --- lnbits/core/templates/core/index.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html index 1319fa1f..68a7b7ed 100644 --- a/lnbits/core/templates/core/index.html +++ b/lnbits/core/templates/core/index.html @@ -180,9 +180,7 @@ > -
-   -
+
 
From 41668d6f3288fa75ac47be685e338242e0ec85c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Sat, 1 Oct 2022 11:33:58 +0200 Subject: [PATCH 043/154] add warning for voidwallet for better coloring in logs --- lnbits/wallets/void.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/wallets/void.py b/lnbits/wallets/void.py index 0de387aa..b74eb245 100644 --- a/lnbits/wallets/void.py +++ b/lnbits/wallets/void.py @@ -23,7 +23,7 @@ class VoidWallet(Wallet): raise Unsupported("") async def status(self) -> StatusResponse: - logger.info( + logger.warning( "This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits." ) return StatusResponse(None, 0) From 3aa4d09103ac94b1e3703086f1528da1020f53e3 Mon Sep 17 00:00:00 2001 From: Indra <86242283+VajraOfIndra@users.noreply.github.com> Date: Sun, 2 Oct 2022 16:45:16 +0100 Subject: [PATCH 044/154] Adding missing theme options in conf file Missing bitcoin theme in comment. Missing flamingo theme in list of values for LNBITS_THEME_OPTIONS. --- .env.example | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 93b82325..7d6de35f 100644 --- a/.env.example +++ b/.env.example @@ -37,8 +37,8 @@ LNBITS_RESERVE_FEE_PERCENT=1.0 LNBITS_SITE_TITLE="LNbits" LNBITS_SITE_TAGLINE="free and open-source lightning wallet" LNBITS_SITE_DESCRIPTION="Some description about your service, will display if title is not 'LNbits'" -# Choose from mint, flamingo, freedom, salvador, autumn, monochrome, classic -LNBITS_THEME_OPTIONS="classic, bitcoin, freedom, mint, autumn, monochrome, salvador" +# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic +LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador" # LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg" # Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet @@ -91,4 +91,4 @@ LNBITS_DENOMINATION=sats # EclairWallet ECLAIR_URL=http://127.0.0.1:8283 -ECLAIR_PASS=eclairpw \ No newline at end of file +ECLAIR_PASS=eclairpw From 5a12f4f237a1a292c394a8bfeb3d5d40577afac7 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Tue, 4 Oct 2022 09:51:47 +0200 Subject: [PATCH 045/154] Improved SSE listeners (#865) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * logging listeners * comments * generate privkey upon init * listener queue * remove duplicate check * make format * reuse channel * error handling in sse listener * uuid for listeners * register named invoices * uuid for listeners and listener list * fix poetry lock * setuptools * requirements asyncio timeout * setuptool;s * make format * remove async-timeout * async_timeout readd * try lower setuptools version * try lower lower setuptools version * back to current version + fix, maybe * fix worflows to use poetry 1.2.1 * remove uneeded setuptools from build-system * fix up formatting workflow * debug to trace * more traces * debug logs to trace Co-authored-by: dni ⚡ --- lnbits/app.py | 3 +- lnbits/core/services.py | 2 +- lnbits/core/tasks.py | 40 ++++++++--- lnbits/core/views/api.py | 45 ++++++++----- lnbits/core/views/public_api.py | 4 +- lnbits/extensions/boltcards/tasks.py | 3 +- lnbits/extensions/boltz/boltz.py | 4 +- lnbits/extensions/boltz/mempool.py | 4 +- lnbits/extensions/boltz/tasks.py | 3 +- lnbits/extensions/copilot/tasks.py | 3 +- lnbits/extensions/jukebox/tasks.py | 3 +- lnbits/extensions/livestream/tasks.py | 4 +- lnbits/extensions/lnaddress/tasks.py | 3 +- lnbits/extensions/lnticket/__init__.py | 1 + lnbits/extensions/lnticket/tasks.py | 3 +- lnbits/extensions/lnurlp/tasks.py | 3 +- lnbits/extensions/lnurlpayout/tasks.py | 3 +- lnbits/extensions/satspay/tasks.py | 3 +- lnbits/extensions/scrub/tasks.py | 3 +- lnbits/extensions/splitpayments/tasks.py | 4 +- lnbits/extensions/subdomains/tasks.py | 3 +- lnbits/extensions/tpos/tasks.py | 4 +- lnbits/helpers.py | 23 +++++++ lnbits/tasks.py | 84 +++++++++++++++++------- lnbits/wallets/fake.py | 32 ++++----- poetry.lock | 57 ++++++++++------ pyproject.toml | 3 +- requirements.txt | 2 + 28 files changed, 239 insertions(+), 110 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index f612c32c..51482538 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -34,7 +34,6 @@ from .tasks import ( check_pending_payments, internal_invoice_listener, invoice_listener, - run_deferred_async, webhook_handler, ) @@ -185,7 +184,7 @@ def register_async_tasks(app): loop.create_task(catch_everything_and_restart(invoice_listener)) loop.create_task(catch_everything_and_restart(internal_invoice_listener)) await register_task_listeners() - await run_deferred_async() + # await run_deferred_async() # calle: doesn't do anyting? @app.on_event("shutdown") async def stop_listeners(): diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 961eb7b2..5d993b4c 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -186,9 +186,9 @@ async def pay_invoice( ) # notify receiver asynchronously - from lnbits.tasks import internal_invoice_queue + logger.debug(f"enqueuing internal invoice {internal_checking_id}") await internal_invoice_queue.put(internal_checking_id) else: logger.debug(f"backend: sending payment {temp_id}") diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py index 07b8a893..b57e2625 100644 --- a/lnbits/core/tasks.py +++ b/lnbits/core/tasks.py @@ -1,30 +1,43 @@ import asyncio -from typing import List +from typing import Dict import httpx from loguru import logger -from lnbits.tasks import register_invoice_listener +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import SseListenersDict, register_invoice_listener from . import db from .crud import get_balance_notify from .models import Payment -api_invoice_listeners: List[asyncio.Queue] = [] +api_invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict( + "api_invoice_listeners" +) async def register_task_listeners(): + """ + Registers an invoice listener queue for the core tasks. + Incoming payaments in this queue will eventually trigger the signals sent to all other extensions + and fulfill other core tasks such as dispatching webhooks. + """ invoice_paid_queue = asyncio.Queue(5) - register_invoice_listener(invoice_paid_queue) + # we register invoice_paid_queue to receive all incoming invoices + register_invoice_listener(invoice_paid_queue, "core/tasks.py") + # register a worker that will react to invoices asyncio.create_task(wait_for_paid_invoices(invoice_paid_queue)) async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue): + """ + This worker dispatches events to all extensions, dispatches webhooks and balance notifys. + """ while True: payment = await invoice_paid_queue.get() - logger.debug("received invoice paid event") + logger.trace("received invoice paid event") # send information to sse channel - await dispatch_invoice_listener(payment) + await dispatch_api_invoice_listeners(payment) # dispatch webhook if payment.webhook and not payment.webhook_status: @@ -41,16 +54,23 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue): pass -async def dispatch_invoice_listener(payment: Payment): - for send_channel in api_invoice_listeners: +async def dispatch_api_invoice_listeners(payment: Payment): + """ + Emits events to invoice listener subscribed from the API. + """ + for chan_name, send_channel in api_invoice_listeners.items(): try: + logger.debug(f"sending invoice paid event to {chan_name}") send_channel.put_nowait(payment) except asyncio.QueueFull: - logger.debug("removing sse listener", send_channel) - api_invoice_listeners.remove(send_channel) + logger.error(f"removing sse listener {send_channel}:{chan_name}") + api_invoice_listeners.pop(chan_name) async def dispatch_webhook(payment: Payment): + """ + Dispatches the webhook to the webhook url. + """ async with httpx.AsyncClient() as client: data = payment.dict() try: diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 7a2bbbe6..c07df568 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -3,11 +3,13 @@ import binascii import hashlib import json import time +import uuid from http import HTTPStatus from io import BytesIO from typing import Dict, List, Optional, Tuple, Union from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse +import async_timeout import httpx import pyqrcode from fastapi import Depends, Header, Query, Request @@ -16,7 +18,7 @@ from fastapi.params import Body from loguru import logger from pydantic import BaseModel from pydantic.fields import Field -from sse_starlette.sse import EventSourceResponse +from sse_starlette.sse import EventSourceResponse, ServerSentEvent from starlette.responses import HTMLResponse, StreamingResponse from lnbits import bolt11, lnurl @@ -366,37 +368,48 @@ async def api_payments_pay_lnurl( } -async def subscribe(request: Request, wallet: Wallet): +async def subscribe_wallet_invoices(request: Request, wallet: Wallet): + """ + Subscribe to new invoices for a wallet. Can be wrapped in EventSourceResponse. + Listenes invoming payments for a wallet and yields jsons with payment details. + """ this_wallet_id = wallet.id payment_queue: asyncio.Queue[Payment] = asyncio.Queue(0) - logger.debug("adding sse listener", payment_queue) - api_invoice_listeners.append(payment_queue) + uid = f"{this_wallet_id}_{str(uuid.uuid4())[:8]}" + logger.debug(f"adding sse listener for wallet: {uid}") + api_invoice_listeners[uid] = payment_queue send_queue: asyncio.Queue[Tuple[str, Payment]] = asyncio.Queue(0) async def payment_received() -> None: while True: - payment: Payment = await payment_queue.get() - if payment.wallet_id == this_wallet_id: - logger.debug("payment received", payment) - await send_queue.put(("payment-received", payment)) + try: + async with async_timeout.timeout(1): + payment: Payment = await payment_queue.get() + if payment.wallet_id == this_wallet_id: + logger.debug("sse listener: payment receieved", payment) + await send_queue.put(("payment-received", payment)) + except asyncio.TimeoutError: + pass - asyncio.create_task(payment_received()) + task = asyncio.create_task(payment_received()) try: while True: + if await request.is_disconnected(): + await request.close() + break typ, data = await send_queue.get() - if data: jdata = json.dumps(dict(data.dict(), pending=False)) - # yield dict(id=1, event="this", data="1234") - # await asyncio.sleep(2) yield dict(data=jdata, event=typ) - # yield dict(data=jdata.encode("utf-8"), event=typ.encode("utf-8")) - except asyncio.CancelledError: + except asyncio.CancelledError as e: + logger.debug(f"CancelledError on listener {uid}: {e}") + api_invoice_listeners.pop(uid) + task.cancel() return @@ -405,7 +418,9 @@ async def api_payments_sse( request: Request, wallet: WalletTypeInfo = Depends(get_key_type) ): return EventSourceResponse( - subscribe(request, wallet.wallet), ping=20, media_type="text/event-stream" + subscribe_wallet_invoices(request, wallet.wallet), + ping=20, + media_type="text/event-stream", ) diff --git a/lnbits/core/views/public_api.py b/lnbits/core/views/public_api.py index 2d2cdd66..9b0ebc98 100644 --- a/lnbits/core/views/public_api.py +++ b/lnbits/core/views/public_api.py @@ -46,8 +46,8 @@ async def api_public_payment_longpolling(payment_hash): payment_queue = asyncio.Queue(0) - logger.debug("adding standalone invoice listener", payment_hash, payment_queue) - api_invoice_listeners.append(payment_queue) + logger.debug(f"adding standalone invoice listener for hash: {payment_hash}") + api_invoice_listeners[payment_hash] = payment_queue response = None diff --git a/lnbits/extensions/boltcards/tasks.py b/lnbits/extensions/boltcards/tasks.py index 1b51c98b..c1e99b76 100644 --- a/lnbits/extensions/boltcards/tasks.py +++ b/lnbits/extensions/boltcards/tasks.py @@ -5,6 +5,7 @@ import httpx from lnbits.core import db as core_db from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import create_refund, get_hit @@ -12,7 +13,7 @@ from .crud import create_refund, get_hit async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/boltz/boltz.py b/lnbits/extensions/boltz/boltz.py index 4e5fecd0..ac99d4f4 100644 --- a/lnbits/extensions/boltz/boltz.py +++ b/lnbits/extensions/boltz/boltz.py @@ -34,8 +34,8 @@ from .models import ( from .utils import check_balance, get_timestamp, req_wrap net = NETWORKS[BOLTZ_NETWORK] -logger.debug(f"BOLTZ_URL: {BOLTZ_URL}") -logger.debug(f"Bitcoin Network: {net['name']}") +logger.trace(f"BOLTZ_URL: {BOLTZ_URL}") +logger.trace(f"Bitcoin Network: {net['name']}") async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap: diff --git a/lnbits/extensions/boltz/mempool.py b/lnbits/extensions/boltz/mempool.py index ee305257..a44c0f02 100644 --- a/lnbits/extensions/boltz/mempool.py +++ b/lnbits/extensions/boltz/mempool.py @@ -11,8 +11,8 @@ from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL, BOLTZ_MEMPOOL_SPACE_URL_WS from .utils import req_wrap -logger.debug(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}") -logger.debug(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}") +logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}") +logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}") websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws" diff --git a/lnbits/extensions/boltz/tasks.py b/lnbits/extensions/boltz/tasks.py index d6f72edf..ace94557 100644 --- a/lnbits/extensions/boltz/tasks.py +++ b/lnbits/extensions/boltz/tasks.py @@ -5,6 +5,7 @@ from loguru import logger from lnbits.core.models import Payment from lnbits.core.services import check_transaction_status +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .boltz import ( @@ -127,7 +128,7 @@ async def check_for_pending_swaps(): async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py index f3c5cff8..c59ef4cc 100644 --- a/lnbits/extensions/copilot/tasks.py +++ b/lnbits/extensions/copilot/tasks.py @@ -7,6 +7,7 @@ from starlette.exceptions import HTTPException from lnbits.core import db as core_db from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_copilot @@ -15,7 +16,7 @@ from .views import updater async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/jukebox/tasks.py b/lnbits/extensions/jukebox/tasks.py index 70a2e65d..5614d926 100644 --- a/lnbits/extensions/jukebox/tasks.py +++ b/lnbits/extensions/jukebox/tasks.py @@ -1,6 +1,7 @@ import asyncio from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import update_jukebox_payment @@ -8,7 +9,7 @@ from .crud import update_jukebox_payment async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/livestream/tasks.py b/lnbits/extensions/livestream/tasks.py index 85bdd5e0..626c698c 100644 --- a/lnbits/extensions/livestream/tasks.py +++ b/lnbits/extensions/livestream/tasks.py @@ -6,7 +6,7 @@ from loguru import logger from lnbits.core import db as core_db from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import urlsafe_short_hash +from lnbits.helpers import get_current_extension_name, urlsafe_short_hash from lnbits.tasks import internal_invoice_listener, register_invoice_listener from .crud import get_livestream_by_track, get_producer, get_track @@ -14,7 +14,7 @@ from .crud import get_livestream_by_track, get_producer, get_track async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/lnaddress/tasks.py b/lnbits/extensions/lnaddress/tasks.py index 9abe10c3..0c377eec 100644 --- a/lnbits/extensions/lnaddress/tasks.py +++ b/lnbits/extensions/lnaddress/tasks.py @@ -3,6 +3,7 @@ import asyncio import httpx from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_address, get_domain, set_address_paid, set_address_renewed @@ -10,7 +11,7 @@ from .crud import get_address, get_domain, set_address_paid, set_address_renewed async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/lnticket/__init__.py b/lnbits/extensions/lnticket/__init__.py index 792b1175..cb793f4d 100644 --- a/lnbits/extensions/lnticket/__init__.py +++ b/lnbits/extensions/lnticket/__init__.py @@ -1,4 +1,5 @@ import asyncio +import json from fastapi import APIRouter diff --git a/lnbits/extensions/lnticket/tasks.py b/lnbits/extensions/lnticket/tasks.py index 7e672115..746ebea9 100644 --- a/lnbits/extensions/lnticket/tasks.py +++ b/lnbits/extensions/lnticket/tasks.py @@ -3,6 +3,7 @@ import asyncio from loguru import logger from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_ticket, set_ticket_paid @@ -10,7 +11,7 @@ from .crud import get_ticket, set_ticket_paid async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/lnurlp/tasks.py b/lnbits/extensions/lnurlp/tasks.py index 525d36ce..86f1579a 100644 --- a/lnbits/extensions/lnurlp/tasks.py +++ b/lnbits/extensions/lnurlp/tasks.py @@ -5,6 +5,7 @@ import httpx from lnbits.core import db as core_db from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_pay_link @@ -12,7 +13,7 @@ from .crud import get_pay_link async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/lnurlpayout/tasks.py b/lnbits/extensions/lnurlpayout/tasks.py index b621876c..71f299be 100644 --- a/lnbits/extensions/lnurlpayout/tasks.py +++ b/lnbits/extensions/lnurlpayout/tasks.py @@ -10,6 +10,7 @@ from lnbits.core.crud import get_wallet from lnbits.core.models import Payment from lnbits.core.services import pay_invoice from lnbits.core.views.api import api_payments_decode +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_lnurlpayout_from_wallet @@ -17,7 +18,7 @@ from .crud import get_lnurlpayout_from_wallet async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py index d325405b..46c16bbc 100644 --- a/lnbits/extensions/satspay/tasks.py +++ b/lnbits/extensions/satspay/tasks.py @@ -4,6 +4,7 @@ from loguru import logger from lnbits.core.models import Payment from lnbits.extensions.satspay.crud import check_address_balance, get_charge +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener # from .crud import get_ticket, set_ticket_paid @@ -11,7 +12,7 @@ from lnbits.tasks import register_invoice_listener async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py index 87e1364b..320d34da 100644 --- a/lnbits/extensions/scrub/tasks.py +++ b/lnbits/extensions/scrub/tasks.py @@ -9,6 +9,7 @@ from fastapi import HTTPException from lnbits import bolt11 from lnbits.core.models import Payment from lnbits.core.services import pay_invoice +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_scrub_by_wallet @@ -16,7 +17,7 @@ from .crud import get_scrub_by_wallet async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py index 0948e849..b7cf1750 100644 --- a/lnbits/extensions/splitpayments/tasks.py +++ b/lnbits/extensions/splitpayments/tasks.py @@ -6,7 +6,7 @@ from loguru import logger from lnbits.core import db as core_db from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import urlsafe_short_hash +from lnbits.helpers import get_current_extension_name, urlsafe_short_hash from lnbits.tasks import internal_invoice_queue, register_invoice_listener from .crud import get_targets @@ -14,7 +14,7 @@ from .crud import get_targets async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/subdomains/tasks.py b/lnbits/extensions/subdomains/tasks.py index d8f35161..04ee2dd4 100644 --- a/lnbits/extensions/subdomains/tasks.py +++ b/lnbits/extensions/subdomains/tasks.py @@ -3,6 +3,7 @@ import asyncio import httpx from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .cloudflare import cloudflare_create_subdomain @@ -11,7 +12,7 @@ from .crud import get_domain, set_subdomain_paid async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py index af9663cc..f18d1689 100644 --- a/lnbits/extensions/tpos/tasks.py +++ b/lnbits/extensions/tpos/tasks.py @@ -4,7 +4,7 @@ import json from lnbits.core import db as core_db from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import urlsafe_short_hash +from lnbits.helpers import get_current_extension_name, urlsafe_short_hash from lnbits.tasks import internal_invoice_queue, register_invoice_listener from .crud import get_tpos @@ -12,7 +12,7 @@ from .crud import get_tpos async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/helpers.py b/lnbits/helpers.py index e97fc7bb..e213240c 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -183,3 +183,26 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates: t.env.globals["VENDORED_CSS"] = ["/static/bundle.css"] return t + + +def get_current_extension_name() -> str: + """ + Returns the name of the extension that calls this method. + """ + import inspect + import json + import os + + callee_filepath = inspect.stack()[1].filename + callee_dirname, callee_filename = os.path.split(callee_filepath) + + path = os.path.normpath(callee_dirname) + extension_director_name = path.split(os.sep)[-1] + try: + config_path = os.path.join(callee_dirname, "config.json") + with open(config_path) as json_file: + config = json.load(json_file) + ext_name = config["name"] + except: + ext_name = extension_director_name + return ext_name diff --git a/lnbits/tasks.py b/lnbits/tasks.py index 41287ff2..94e43dcf 100644 --- a/lnbits/tasks.py +++ b/lnbits/tasks.py @@ -1,8 +1,9 @@ import asyncio import time import traceback +import uuid from http import HTTPStatus -from typing import Callable, List +from typing import Callable, Dict, List from fastapi.exceptions import HTTPException from loguru import logger @@ -18,20 +19,6 @@ from lnbits.settings import WALLET from .core import db -deferred_async: List[Callable] = [] - - -def record_async(func: Callable) -> Callable: - def recorder(state): - deferred_async.append(func) - - return recorder - - -async def run_deferred_async(): - for func in deferred_async: - asyncio.create_task(catch_everything_and_restart(func)) - async def catch_everything_and_restart(func): try: @@ -50,18 +37,48 @@ async def send_push_promise(a, b) -> None: pass -invoice_listeners: List[asyncio.Queue] = [] +class SseListenersDict(dict): + """ + A dict of sse listeners. + """ + + def __init__(self, name: str = None): + self.name = name or f"sse_listener_{str(uuid.uuid4())[:8]}" + + def __setitem__(self, key, value): + assert type(key) == str, f"{key} is not a string" + assert type(value) == asyncio.Queue, f"{value} is not an asyncio.Queue" + logger.trace(f"sse: adding listener {key} to {self.name}. len = {len(self)+1}") + return super().__setitem__(key, value) + + def __delitem__(self, key): + logger.trace(f"sse: removing listener from {self.name}. len = {len(self)-1}") + return super().__delitem__(key) + + _RaiseKeyError = object() # singleton for no-default behavior + + def pop(self, key, v=_RaiseKeyError) -> None: + logger.trace(f"sse: removing listener from {self.name}. len = {len(self)-1}") + return super().pop(key) -def register_invoice_listener(send_chan: asyncio.Queue): +invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict("invoice_listeners") + + +def register_invoice_listener(send_chan: asyncio.Queue, name: str = None): """ - A method intended for extensions to call when they want to be notified about - new invoice payments incoming. + A method intended for extensions (and core/tasks.py) to call when they want to be notified about + new invoice payments incoming. Will emit all incoming payments. """ - invoice_listeners.append(send_chan) + name_unique = f"{name or 'no_name'}_{str(uuid.uuid4())[:8]}" + logger.trace(f"sse: registering invoice listener {name_unique}") + invoice_listeners[name_unique] = send_chan async def webhook_handler(): + """ + Returns the webhook_handler for the selected wallet if present. Used by API. + """ handler = getattr(WALLET, "webhook_listener", None) if handler: return await handler() @@ -72,18 +89,36 @@ internal_invoice_queue: asyncio.Queue = asyncio.Queue(0) async def internal_invoice_listener(): + """ + internal_invoice_queue will be filled directly in core/services.py + after the payment was deemed to be settled internally. + + Called by the app startup sequence. + """ while True: checking_id = await internal_invoice_queue.get() + logger.info("> got internal payment notification", checking_id) asyncio.create_task(invoice_callback_dispatcher(checking_id)) async def invoice_listener(): + """ + invoice_listener will collect all invoices that come directly + from the backend wallet. + + Called by the app startup sequence. + """ async for checking_id in WALLET.paid_invoices_stream(): logger.info("> got a payment notification", checking_id) asyncio.create_task(invoice_callback_dispatcher(checking_id)) async def check_pending_payments(): + """ + check_pending_payments is called during startup to check for pending payments with + the backend and also to delete expired invoices. Incoming payments will be + checked only once, outgoing pending payments will be checked regularly. + """ outgoing = True incoming = True @@ -133,9 +168,14 @@ async def perform_balance_checks(): async def invoice_callback_dispatcher(checking_id: str): + """ + Takes incoming payments, sets pending=False, and dispatches them to + invoice_listeners from core and extensions. + """ payment = await get_standalone_payment(checking_id, incoming=True) if payment and payment.is_in: - logger.trace("sending invoice callback for payment", checking_id) + logger.trace(f"sse sending invoice callback for payment {checking_id}") await payment.set_pending(False) - for send_chan in invoice_listeners: + for chan_name, send_chan in invoice_listeners.items(): + logger.trace(f"sse sending to chan: {chan_name}") await send_chan.put(payment) diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py index 8424001b..a07ef4d8 100644 --- a/lnbits/wallets/fake.py +++ b/lnbits/wallets/fake.py @@ -8,9 +8,7 @@ from typing import AsyncGenerator, Dict, Optional from environs import Env # type: ignore from loguru import logger -from lnbits.helpers import urlsafe_short_hash - -from ..bolt11 import decode, encode +from ..bolt11 import Invoice, decode, encode from .base import ( InvoiceResponse, PaymentResponse, @@ -24,6 +22,16 @@ env.read_env() class FakeWallet(Wallet): + queue: asyncio.Queue = asyncio.Queue(0) + secret: str = env.str("FAKE_WALLET_SECTRET", default="ToTheMoon1") + privkey: str = hashlib.pbkdf2_hmac( + "sha256", + secret.encode("utf-8"), + ("FakeWallet").encode("utf-8"), + 2048, + 32, + ).hex() + async def status(self) -> StatusResponse: logger.info( "FakeWallet funding source is for using LNbits as a centralised, stand-alone payment system with brrrrrr." @@ -39,18 +47,12 @@ class FakeWallet(Wallet): ) -> InvoiceResponse: # we set a default secret since FakeWallet is used for internal=True invoices # and the user might not have configured a secret yet - secret = env.str("FAKE_WALLET_SECTRET", default="ToTheMoon1") + data: Dict = { "out": False, "amount": amount, "currency": "bc", - "privkey": hashlib.pbkdf2_hmac( - "sha256", - secret.encode("utf-8"), - ("FakeWallet").encode("utf-8"), - 2048, - 32, - ).hex(), + "privkey": self.privkey, "memo": None, "description_hash": None, "description": "", @@ -86,8 +88,9 @@ class FakeWallet(Wallet): invoice = decode(bolt11) if ( hasattr(invoice, "checking_id") - and invoice.checking_id[6:] == data["privkey"][:6] # type: ignore + and invoice.checking_id[:6] == self.privkey[:6] # type: ignore ): + await self.queue.put(invoice) return PaymentResponse(True, invoice.payment_hash, 0) else: return PaymentResponse( @@ -101,7 +104,6 @@ class FakeWallet(Wallet): return PaymentStatus(None) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: - self.queue: asyncio.Queue = asyncio.Queue(0) while True: - value = await self.queue.get() - yield value + value: Invoice = await self.queue.get() + yield value.payment_hash diff --git a/poetry.lock b/poetry.lock index 2a57a5c1..343fffbf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -46,6 +46,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} + [[package]] name = "attrs" version = "21.2.0" @@ -111,7 +122,7 @@ jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] -name = "cerberus" +name = "Cerberus" version = "1.3.4" description = "Lightweight, extensible schema and data validation tool for Python dictionaries." category = "main" @@ -402,7 +413,7 @@ plugins = ["setuptools"] requirements_deprecated_finder = ["pip-api", "pipreqs"] [[package]] -name = "jinja2" +name = "Jinja2" version = "3.0.1" description = "A very fast and expressive template engine." category = "main" @@ -444,7 +455,7 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"] [[package]] -name = "markupsafe" +name = "MarkupSafe" version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" @@ -648,12 +659,12 @@ optional = false python-versions = ">=3.7,<4.0" [package.dependencies] -pyln-bolt7 = ">=1.0,<2.0" -pyln-proto = ">=0.11,<0.12" +pyln-bolt7 = ">=1.0" +pyln-proto = ">=0.12" [[package]] name = "pyln-proto" -version = "0.11.1" +version = "0.12.0" description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)." category = "main" optional = false @@ -686,7 +697,7 @@ optional = false python-versions = "*" [[package]] -name = "pyqrcode" +name = "PyQRCode" version = "1.2.1" description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output." category = "main" @@ -697,7 +708,7 @@ python-versions = "*" PNG = ["pypng (>=0.0.13)"] [[package]] -name = "pyscss" +name = "pyScss" version = "1.4.0" description = "pyScss, a Scss compiler for Python" category = "main" @@ -780,7 +791,7 @@ python-versions = ">=3.5" cli = ["click (>=5.0)"] [[package]] -name = "pyyaml" +name = "PyYAML" version = "5.4.1" description = "YAML parser and emitter for Python" category = "main" @@ -788,7 +799,7 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] -name = "represent" +name = "Represent" version = "1.6.0.post0" description = "Create __repr__ automatically or declaratively." category = "main" @@ -864,7 +875,7 @@ optional = false python-versions = ">=3.5" [[package]] -name = "sqlalchemy" +name = "SQLAlchemy" version = "1.3.23" description = "Database Abstraction Library" category = "main" @@ -1059,6 +1070,10 @@ asn1crypto = [ {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, ] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, @@ -1101,7 +1116,7 @@ black = [ {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"}, {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, ] -cerberus = [ +Cerberus = [ {file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"}, ] certifi = [ @@ -1413,7 +1428,7 @@ isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] -jinja2 = [ +Jinja2 = [ {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, ] @@ -1425,7 +1440,7 @@ loguru = [ {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, ] -markupsafe = [ +MarkupSafe = [ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, @@ -1681,8 +1696,8 @@ pyln-client = [ {file = "pyln_client-0.11.1-py3-none-any.whl", hash = "sha256:497db443406b80c98c0434e2938eb1b2a17e88fd9aa63b018124068198df6141"}, ] pyln-proto = [ - {file = "pyln-proto-0.11.1.tar.gz", hash = "sha256:9bed240f41917c4fd526b767218a77d0fbe69242876eef72c35a856796f922d6"}, - {file = "pyln_proto-0.11.1-py3-none-any.whl", hash = "sha256:27b2e04a81b894f69018279c0ce4aa2e7ccd03b86dd9783f96b9d8d1498c8393"}, + {file = "pyln-proto-0.12.0.tar.gz", hash = "sha256:3214d99d8385f2135a94937f0dc1da626a33b257e9ebc320841656edaefabbe5"}, + {file = "pyln_proto-0.12.0-py3-none-any.whl", hash = "sha256:dedef5d8e476a9ade5a0b2eb919ccc37e4a57f2a78fdc399f1c5e0de17e41604"}, ] pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, @@ -1691,11 +1706,11 @@ pyparsing = [ pypng = [ {file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"}, ] -pyqrcode = [ +PyQRCode = [ {file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"}, {file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"}, ] -pyscss = [ +pyScss = [ {file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"}, ] PySocks = [ @@ -1719,7 +1734,7 @@ python-dotenv = [ {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"}, {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"}, ] -pyyaml = [ +PyYAML = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, @@ -1750,7 +1765,7 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] -represent = [ +Represent = [ {file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"}, {file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"}, ] @@ -1799,7 +1814,7 @@ sniffio = [ {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, ] -sqlalchemy = [ +SQLAlchemy = [ {file = "SQLAlchemy-1.3.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec"}, {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:29cccc9606750fe10c5d0e8bd847f17a97f3850b8682aef1f56f5d5e1a5a64b1"}, {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:927ce09e49bff3104459e1451ce82983b0a3062437a07d883a4c66f0b344c9b5"}, diff --git a/pyproject.toml b/pyproject.toml index 864500f7..19dac860 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ asgiref = "3.4.1" attrs = "21.2.0" bech32 = "1.2.0" bitstring = "3.1.9" -cerberus = "1.3.4" certifi = "2021.5.30" charset-normalizer = "2.0.6" click = "8.0.1" @@ -62,6 +61,8 @@ cffi = "1.15.0" websocket-client = "1.3.3" grpcio = "^1.49.1" protobuf = "^4.21.6" +Cerberus = "^1.3.4" +async-timeout = "^4.0.2" pyln-client = "0.11.1" [tool.poetry.dev-dependencies] diff --git a/requirements.txt b/requirements.txt index 697ea1d4..eb9a6e5e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -51,3 +51,5 @@ uvloop==0.16.0 watchfiles==0.16.0 websockets==10.3 websocket-client==1.3.3 +async-timeout==4.0.2 +setuptools==65.4.0 \ No newline at end of file From b6755abc8e9911c34b96d3039140b2b64ff5634f Mon Sep 17 00:00:00 2001 From: HODLmeTight <35168804+TrezorHannes@users.noreply.github.com> Date: Wed, 5 Oct 2022 09:16:54 +0200 Subject: [PATCH 046/154] updated poetry install notes - referenced option for python3.10 and newer - poetry install with --only main, instead of --no-dev (deprecated) --- docs/guide/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 87679ed5..5e9fdb28 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -26,8 +26,8 @@ sudo apt install python3.9 python3.9-distutils curl -sSL https://install.python-poetry.org | python3 - export PATH="/home/ubuntu/.local/bin:$PATH" # or whatever is suggested in the poetry install notes printed to terminal -poetry env use python3.9 -poetry install --no-dev +poetry env use python3.9 # you can exchange with python3.10 or newer versions. Identify your version with python3 --version and specify here +poetry install --only main poetry run python build.py mkdir data From bc2068cc87f5d7b867ce0e9f4ccc8fabf8af7732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 6 Oct 2022 09:42:38 +0200 Subject: [PATCH 047/154] fix poetry lock in main --- poetry.lock | 152 ++++++++++++++++++++++++++-------------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/poetry.lock b/poetry.lock index 343fffbf..5b283d75 100644 --- a/poetry.lock +++ b/poetry.lock @@ -196,7 +196,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.4.4" +version = "6.5.0" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -589,7 +589,7 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "4.21.6" +version = "4.21.7" description = "" category = "main" optional = false @@ -659,12 +659,12 @@ optional = false python-versions = ">=3.7,<4.0" [package.dependencies] -pyln-bolt7 = ">=1.0" -pyln-proto = ">=0.12" +pyln-bolt7 = ">=1.0,<2.0" +pyln-proto = ">=0.11,<0.12" [[package]] name = "pyln-proto" -version = "0.12.0" +version = "0.11.1" description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)." category = "main" optional = false @@ -839,14 +839,14 @@ cffi = ">=1.3.0" [[package]] name = "setuptools" -version = "65.4.0" +version = "65.4.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] @@ -1051,7 +1051,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black ( [metadata] lock-version = "1.1" python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" -content-hash = "72e4462285d0bc5e2cb83c88c613726beced959b268bd30b984d8baaeff178ea" +content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42" [metadata.files] aiofiles = [ @@ -1224,56 +1224,56 @@ colorama = [ {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] coverage = [ - {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, - {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"}, - {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"}, - {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"}, - {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"}, - {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"}, - {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"}, - {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"}, - {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"}, - {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"}, - {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"}, - {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"}, - {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"}, - {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"}, - {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"}, - {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"}, - {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"}, - {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"}, - {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, - {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] cryptography = [ {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"}, @@ -1573,20 +1573,20 @@ pluggy = [ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] protobuf = [ - {file = "protobuf-4.21.6-cp310-abi3-win32.whl", hash = "sha256:49f88d56a9180dbb7f6199c920f5bb5c1dd0172f672983bb281298d57c2ac8eb"}, - {file = "protobuf-4.21.6-cp310-abi3-win_amd64.whl", hash = "sha256:7a6cc8842257265bdfd6b74d088b829e44bcac3cca234c5fdd6052730017b9ea"}, - {file = "protobuf-4.21.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ba596b9ffb85c909fcfe1b1a23136224ed678af3faf9912d3fa483d5f9813c4e"}, - {file = "protobuf-4.21.6-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4143513c766db85b9d7c18dbf8339673c8a290131b2a0fe73855ab20770f72b0"}, - {file = "protobuf-4.21.6-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b6cea204865595a92a7b240e4b65bcaaca3ad5d2ce25d9db3756eba06041138e"}, - {file = "protobuf-4.21.6-cp37-cp37m-win32.whl", hash = "sha256:9666da97129138585b26afcb63ad4887f602e169cafe754a8258541c553b8b5d"}, - {file = "protobuf-4.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:308173d3e5a3528787bb8c93abea81d5a950bdce62840d9760effc84127fb39c"}, - {file = "protobuf-4.21.6-cp38-cp38-win32.whl", hash = "sha256:aa29113ec901281f29d9d27b01193407a98aa9658b8a777b0325e6d97149f5ce"}, - {file = "protobuf-4.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:8f9e60f7d44592c66e7b332b6a7b4b6e8d8b889393c79dbc3a91f815118f8eac"}, - {file = "protobuf-4.21.6-cp39-cp39-win32.whl", hash = "sha256:80e6540381080715fddac12690ee42d087d0d17395f8d0078dfd6f1181e7be4c"}, - {file = "protobuf-4.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:77b355c8604fe285536155286b28b0c4cbc57cf81b08d8357bf34829ea982860"}, - {file = "protobuf-4.21.6-py2.py3-none-any.whl", hash = "sha256:07a0bb9cc6114f16a39c866dc28b6e3d96fa4ffb9cc1033057412547e6e75cb9"}, - {file = "protobuf-4.21.6-py3-none-any.whl", hash = "sha256:c7c864148a237f058c739ae7a05a2b403c0dfa4ce7d1f3e5213f352ad52d57c6"}, - {file = "protobuf-4.21.6.tar.gz", hash = "sha256:6b1040a5661cd5f6e610cbca9cfaa2a17d60e2bb545309bc1b278bb05be44bdd"}, + {file = "protobuf-4.21.7-cp310-abi3-win32.whl", hash = "sha256:c7cb105d69a87416bd9023e64324e1c089593e6dae64d2536f06bcbe49cd97d8"}, + {file = "protobuf-4.21.7-cp310-abi3-win_amd64.whl", hash = "sha256:3ec85328a35a16463c6f419dbce3c0fc42b3e904d966f17f48bae39597c7a543"}, + {file = "protobuf-4.21.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:db9056b6a11cb5131036d734bcbf91ef3ef9235d6b681b2fc431cbfe5a7f2e56"}, + {file = "protobuf-4.21.7-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:ca200645d6235ce0df3ccfdff1567acbab35c4db222a97357806e015f85b5744"}, + {file = "protobuf-4.21.7-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b019c79e23a80735cc8a71b95f76a49a262f579d6b84fd20a0b82279f40e2cc1"}, + {file = "protobuf-4.21.7-cp37-cp37m-win32.whl", hash = "sha256:d3f89ccf7182293feba2de2739c8bf34fed1ed7c65a5cf987be00311acac57c1"}, + {file = "protobuf-4.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:a74d96cd960b87b4b712797c741bb3ea3a913f5c2dc4b6cbe9c0f8360b75297d"}, + {file = "protobuf-4.21.7-cp38-cp38-win32.whl", hash = "sha256:8e09d1916386eca1ef1353767b6efcebc0a6859ed7f73cb7fb974feba3184830"}, + {file = "protobuf-4.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:9e355f2a839d9930d83971b9f562395e13493f0e9211520f8913bd11efa53c02"}, + {file = "protobuf-4.21.7-cp39-cp39-win32.whl", hash = "sha256:f370c0a71712f8965023dd5b13277444d3cdfecc96b2c778b0e19acbfd60df6e"}, + {file = "protobuf-4.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:9643684232b6b340b5e63bb69c9b4904cdd39e4303d498d1a92abddc7e895b7f"}, + {file = "protobuf-4.21.7-py2.py3-none-any.whl", hash = "sha256:8066322588d4b499869bf9f665ebe448e793036b552f68c585a9b28f1e393f66"}, + {file = "protobuf-4.21.7-py3-none-any.whl", hash = "sha256:58b81358ec6c0b5d50df761460ae2db58405c063fd415e1101209221a0a810e1"}, + {file = "protobuf-4.21.7.tar.gz", hash = "sha256:71d9dba03ed3432c878a801e2ea51e034b0ea01cf3a4344fb60166cb5f6c8757"}, ] psycopg2-binary = [ {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, @@ -1696,8 +1696,8 @@ pyln-client = [ {file = "pyln_client-0.11.1-py3-none-any.whl", hash = "sha256:497db443406b80c98c0434e2938eb1b2a17e88fd9aa63b018124068198df6141"}, ] pyln-proto = [ - {file = "pyln-proto-0.12.0.tar.gz", hash = "sha256:3214d99d8385f2135a94937f0dc1da626a33b257e9ebc320841656edaefabbe5"}, - {file = "pyln_proto-0.12.0-py3-none-any.whl", hash = "sha256:dedef5d8e476a9ade5a0b2eb919ccc37e4a57f2a78fdc399f1c5e0de17e41604"}, + {file = "pyln-proto-0.11.1.tar.gz", hash = "sha256:9bed240f41917c4fd526b767218a77d0fbe69242876eef72c35a856796f922d6"}, + {file = "pyln_proto-0.11.1-py3-none-any.whl", hash = "sha256:27b2e04a81b894f69018279c0ce4aa2e7ccd03b86dd9783f96b9d8d1498c8393"}, ] pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, @@ -1799,8 +1799,8 @@ secp256k1 = [ {file = "secp256k1-0.14.0.tar.gz", hash = "sha256:82c06712d69ef945220c8b53c1a0d424c2ff6a1f64aee609030df79ad8383397"}, ] setuptools = [ - {file = "setuptools-65.4.0-py3-none-any.whl", hash = "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1"}, - {file = "setuptools-65.4.0.tar.gz", hash = "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9"}, + {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"}, + {file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"}, ] shortuuid = [ {file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"}, From 79319faa2146da17cd22467d9dd4762c42540f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 6 Oct 2022 10:17:21 +0200 Subject: [PATCH 048/154] make workflows only run on pull_request --- .github/workflows/codeql.yml | 2 -- .github/workflows/formatting.yml | 2 -- .github/workflows/mypy.yml | 2 +- .github/workflows/regtest.yml | 2 +- .github/workflows/tests.yml | 2 +- 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 876c8b8a..bbe95983 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,8 +1,6 @@ name: codeql on: - push: - branches: [main, ] pull_request: branches: [main] schedule: diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index e3d0fd35..21c7fb38 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -1,8 +1,6 @@ name: formatting on: - push: - branches: [ main ] pull_request: branches: [ main ] diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index d80da678..96b574d2 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -1,6 +1,6 @@ name: mypy -on: [push, pull_request] +on: [pull_request] jobs: check: diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index 2d7aae6b..f0adb3ac 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -1,6 +1,6 @@ name: regtest -on: [push, pull_request] +on: [pull_request] jobs: LndRestWallet: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5d368fbb..c7b6e44b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,6 @@ name: tests -on: [push, pull_request] +on: [pull_request] jobs: venv-sqlite: From 63e4f7d59c0505654d47b8793738211966b9b776 Mon Sep 17 00:00:00 2001 From: HODLmeTight <35168804+TrezorHannes@users.noreply.github.com> Date: Thu, 6 Oct 2022 14:25:14 +0200 Subject: [PATCH 049/154] changed comment places and small adjustments - #comments in own lines - cleaned up some ominous settings - added --debug option for running the server --- docs/guide/installation.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 5e9fdb28..37104fde 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -18,21 +18,25 @@ If you have problems installing LNbits using these instructions, please have a l git clone https://github.com/lnbits/lnbits-legend.git cd lnbits-legend/ -# for making sure python 3.9 is installed, skip if installed +# for making sure python 3.9 is installed, skip if installed. To check your installed version: python3 --version sudo apt update sudo apt install software-properties-common sudo add-apt-repository ppa:deadsnakes/ppa sudo apt install python3.9 python3.9-distutils curl -sSL https://install.python-poetry.org | python3 - -export PATH="/home/ubuntu/.local/bin:$PATH" # or whatever is suggested in the poetry install notes printed to terminal -poetry env use python3.9 # you can exchange with python3.10 or newer versions. Identify your version with python3 --version and specify here +# Once the above poetry install is completed, use the installation path printed to terminal and replace in the following command +export PATH="/home/user/.local/bin:$PATH" +# Next command, you can exchange with python3.10 or newer versions. +# Identify your version with python3 --version and specify in the next line +# command is only needed when your default python is not ^3.9 or ^3.10 +poetry env use python3.9 poetry install --only main -poetry run python build.py mkdir data cp .env.example .env -nano .env # set funding source +# set funding source amongst other options +nano .env ``` #### Running the server @@ -40,6 +44,7 @@ nano .env # set funding source ```sh poetry run lnbits # To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0' +# add --debug for slightly more verbose output ``` ## Option 2: Nix From 36b60ad318fffa87d8ec55753538525531d76dec Mon Sep 17 00:00:00 2001 From: HODLmeTight <35168804+TrezorHannes@users.noreply.github.com> Date: Thu, 6 Oct 2022 14:50:33 +0200 Subject: [PATCH 050/154] added debug option for .env --- docs/guide/installation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 37104fde..6b95f93b 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -44,7 +44,8 @@ nano .env ```sh poetry run lnbits # To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0' -# add --debug for slightly more verbose output +# adding --debug in the start-up command above to help your troubleshooting and generate a more verbose output +# Note that you have to add the line DEBUG=true in your .env file, too. ``` ## Option 2: Nix From 27bae6a0c6cce731256095465905c92c1547c1e2 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 6 Oct 2022 14:16:21 +0100 Subject: [PATCH 051/154] adds atm check, so withdraw isnt cancelled on false call by wallets --- lnbits/extensions/lnurldevice/lnurl.py | 29 +++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index df0cd4b8..ec7f3b48 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -184,22 +184,27 @@ async def lnurl_callback( raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found." ) - if pr: - if lnurldevicepayment.id != k1: - return {"status": "ERROR", "reason": "Bad K1"} - if lnurldevicepayment.payhash != "payment_hash": - return {"status": "ERROR", "reason": f"Payment already claimed"} + if device.device == "atm": + if not pr: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="No payment request" + ) + else: + if lnurldevicepayment.id != k1: + return {"status": "ERROR", "reason": "Bad K1"} + if lnurldevicepayment.payhash != "payment_hash": + return {"status": "ERROR", "reason": f"Payment already claimed"} lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload ) - await pay_invoice( - wallet_id=device.wallet, - payment_request=pr, - max_sat=lnurldevicepayment.sats / 1000, - extra={"tag": "withdraw"}, - ) - return {"status": "OK"} + await pay_invoice( + wallet_id=device.wallet, + payment_request=pr, + max_sat=lnurldevicepayment.sats / 1000, + extra={"tag": "withdraw"}, + ) + return {"status": "OK"} payment_hash, payment_request = await create_invoice( wallet_id=device.wallet, From 8e8bf08ea587aa9013a966a807600812fee3c780 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 6 Oct 2022 15:21:09 +0100 Subject: [PATCH 052/154] fix issue with splitting to multiple wallets-queue --- lnbits/extensions/splitpayments/tasks.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py index b7cf1750..cfc6c226 100644 --- a/lnbits/extensions/splitpayments/tasks.py +++ b/lnbits/extensions/splitpayments/tasks.py @@ -28,6 +28,10 @@ async def on_invoice_paid(payment: Payment) -> None: # now we make some special internal transfers (from no one to the receiver) targets = await get_targets(payment.wallet_id) + + if not targets: + return + transfers = [ (target.wallet, int(target.percent * payment.amount / 100)) for target in targets @@ -41,9 +45,6 @@ async def on_invoice_paid(payment: Payment) -> None: ) return - if not targets: - return - # mark the original payment with one extra key, "splitted" # (this prevents us from doing this process again and it's informative) # and reduce it by the amount we're going to send to the producer @@ -76,5 +77,5 @@ async def on_invoice_paid(payment: Payment) -> None: ) # manually send this for now - await internal_invoice_queue.put(internal_checking_id) + await internal_invoice_queue.put(internal_checking_id) return From 11ec82e33962462880d08a9dc2ab6e828762edbd Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 6 Oct 2022 15:21:53 +0100 Subject: [PATCH 053/154] floor amount when not whole sat --- lnbits/extensions/scrub/tasks.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py index 320d34da..62adc5e5 100644 --- a/lnbits/extensions/scrub/tasks.py +++ b/lnbits/extensions/scrub/tasks.py @@ -1,6 +1,7 @@ import asyncio import json from http import HTTPStatus +from math import floor from urllib.parse import urlparse import httpx @@ -26,7 +27,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: # (avoid loops) - if "scrubed" == payment.extra.get("tag"): + if payment.extra.get("tag") == "scrubed": # already scrubbed return @@ -42,12 +43,13 @@ async def on_invoice_paid(payment: Payment) -> None: # I REALLY HATE THIS DUPLICATION OF CODE!! CORE/VIEWS/API.PY, LINE 267 domain = urlparse(data["callback"]).netloc - + rounded_amount = floor(payment.amount / 1000) * 1000 + async with httpx.AsyncClient() as client: try: r = await client.get( data["callback"], - params={"amount": payment.amount}, + params={"amount": rounded_amount}, timeout=40, ) if r.is_error: @@ -66,7 +68,8 @@ async def on_invoice_paid(payment: Payment) -> None: ) invoice = bolt11.decode(params["pr"]) - if invoice.amount_msat != payment.amount: + + if invoice.amount_msat != rounded_amount: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail=f"{domain} returned an invalid invoice. Expected {payment.amount} msat, got {invoice.amount_msat}.", From 7316ef7dbb67a9327597e2e97aa7ef54f7a08ac0 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 6 Oct 2022 15:33:20 +0100 Subject: [PATCH 054/154] add disclaimer about whole (integer) sats --- lnbits/extensions/scrub/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lnbits/extensions/scrub/README.md b/lnbits/extensions/scrub/README.md index 680c5e6d..3b8d0b2d 100644 --- a/lnbits/extensions/scrub/README.md +++ b/lnbits/extensions/scrub/README.md @@ -4,6 +4,8 @@ SCRUB is a small but handy extension that allows a user to take advantage of all the functionalities inside **LNbits** and upon a payment received to your LNbits wallet, automatically forward it to your desired wallet via LNURL or LNAddress! +Only whole values, integers, are Scrubbed, amounts will be rounded down (example: 6.3 will be 6)! The decimals, if existing, will be kept in your wallet! + [**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets) ## Usage From 92a756b9bb92078bb937f4a22c9b35b033f44fed Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 6 Oct 2022 15:34:37 +0100 Subject: [PATCH 055/154] make format --- lnbits/extensions/scrub/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py index 62adc5e5..852f3860 100644 --- a/lnbits/extensions/scrub/tasks.py +++ b/lnbits/extensions/scrub/tasks.py @@ -44,7 +44,7 @@ async def on_invoice_paid(payment: Payment) -> None: # I REALLY HATE THIS DUPLICATION OF CODE!! CORE/VIEWS/API.PY, LINE 267 domain = urlparse(data["callback"]).netloc rounded_amount = floor(payment.amount / 1000) * 1000 - + async with httpx.AsyncClient() as client: try: r = await client.get( @@ -68,7 +68,7 @@ async def on_invoice_paid(payment: Payment) -> None: ) invoice = bolt11.decode(params["pr"]) - + if invoice.amount_msat != rounded_amount: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, From 3222ae198d4f4bda826f1a63f861d5e90d0df4ac Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 6 Oct 2022 17:10:15 +0100 Subject: [PATCH 056/154] Adding bitcoinswitch to lnurldevices --- lnbits/extensions/lnurldevice/__init__.py | 7 +++- lnbits/extensions/lnurldevice/lnurl.py | 40 ++++++++++++++++-- lnbits/extensions/lnurldevice/migrations.py | 6 +++ lnbits/extensions/lnurldevice/models.py | 2 + lnbits/extensions/lnurldevice/tasks.py | 39 ++++++++++++++++++ lnbits/extensions/lnurldevice/views.py | 45 +++++++++++++++++++++ 6 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 lnbits/extensions/lnurldevice/tasks.py diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py index 54849c95..dc4456b4 100644 --- a/lnbits/extensions/lnurldevice/__init__.py +++ b/lnbits/extensions/lnurldevice/__init__.py @@ -2,6 +2,7 @@ from fastapi import APIRouter from lnbits.db import Database from lnbits.helpers import template_renderer +from lnbits.tasks import catch_everything_and_restart db = Database("ext_lnurldevice") @@ -11,7 +12,11 @@ lnurldevice_ext: APIRouter = APIRouter(prefix="/lnurldevice", tags=["lnurldevice def lnurldevice_renderer(): return template_renderer(["lnbits/extensions/lnurldevice/templates"]) - +from .tasks import wait_for_paid_invoices from .lnurl import * # noqa from .views import * # noqa from .views_api import * # noqa + +def lnurldevice_start(): + loop = asyncio.get_event_loop() + loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index ec7f3b48..a2bc0dd4 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -102,7 +102,22 @@ async def lnurl_v1_params( if device.device == "atm": if paymentcheck: return {"status": "ERROR", "reason": f"Payment already claimed"} - + if device.device == "switch": + lnurldevicepayment = await create_lnurldevicepayment( + deviceid=device.id, + sats=device.profit, + ) + if not lnurldevicepayment: + return {"status": "ERROR", "reason": "Could not create payment."} + return { + "tag": "payRequest", + "callback": request.url_for( + "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id + ), + "minSendable": device.profit * 1000, + "maxSendable": device.profit * 1000, + "metadata": await device.lnurlpay_metadata(), + } if len(p) % 4 > 0: p += "=" * (4 - (len(p) % 4)) @@ -205,6 +220,27 @@ async def lnurl_callback( extra={"tag": "withdraw"}, ) return {"status": "OK"} + if device.device == "switch": + payment_hash, payment_request = await create_invoice( + wallet_id=device.wallet, + amount=lnurldevicepayment.sats / 1000, + memo=device.title, + unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"), + extra={"tag": "Switch", "id": device.paymentid, "time": device.amount}, + ) + lnurldevicepayment = await update_lnurldevicepayment( + lnurldevicepayment_id=paymentid, payhash=payment_hash + ) + + return { + "pr": payment_request, + "successAction": { + "tag": "url", + "description": "Check the attached link", + "url": request.url_for("lnurldevice.displaypin", paymentid=paymentid), + }, + "routes": [], + } payment_hash, payment_request = await create_invoice( wallet_id=device.wallet, @@ -226,5 +262,3 @@ async def lnurl_callback( }, "routes": [], } - - return resp.dict() diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index c7899282..65913b02 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -79,3 +79,9 @@ async def m002_redux(db): ) except: return + +async def m003_redux(db): + """ + Add 'meta' for storing various metadata about the wallet + """ + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;") \ No newline at end of file diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index fef0aec1..cf617141 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -17,6 +17,7 @@ class createLnurldevice(BaseModel): currency: str device: str profit: float + amount: int class lnurldevices(BaseModel): @@ -27,6 +28,7 @@ class lnurldevices(BaseModel): currency: str device: str profit: float + amount: int timestamp: str def from_row(cls, row: Row) -> "lnurldevices": diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py new file mode 100644 index 00000000..2337c0ad --- /dev/null +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -0,0 +1,39 @@ +import asyncio +import json +from http import HTTPStatus +from urllib.parse import urlparse + +import httpx +from fastapi import HTTPException + +from lnbits import bolt11 +from lnbits.core.models import Payment +from lnbits.core.services import pay_invoice +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import register_invoice_listener + +from .crud import get_lnurldevice +from .views import updater + +async def wait_for_paid_invoices(): + invoice_queue = asyncio.Queue() + register_invoice_listener(invoice_queue, get_current_extension_name()) + + while True: + payment = await invoice_queue.get() + await on_invoice_paid(payment) + + +async def on_invoice_paid(payment: Payment) -> None: + # (avoid loops) + if "switch" == payment.extra.get("tag"): + lnurldevicepayment = await get_lnurldevicepayment(payment.extra.get("id")) + if not lnurldevicepayment: + return + if lnurldevicepayment.payhash == "used": + return + lnurldevicepayment = await update_lnurldevicepayment( + lnurldevicepayment_id=paymentid, payhash="used" + ) + return await updater(lnurldevicepayment.deviceid) + return \ No newline at end of file diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index 3389e17c..4a28a5c1 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -51,3 +51,48 @@ async def displaypin(request: Request, paymentid: str = Query(None)): "lnurldevice/error.html", {"request": request, "pin": "filler", "not_paid": True}, ) + + +##################WEBSOCKET ROUTES######################## + + +class ConnectionManager: + def __init__(self): + self.active_connections: List[WebSocket] = [] + + async def connect(self, websocket: WebSocket, lnurldevice_id: str): + await websocket.accept() + websocket.id = lnurldevice_id + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_personal_message(self, message: str, lnurldevice_id: str): + for connection in self.active_connections: + if connection.id == lnurldevice_id: + await connection.send_text(message) + + async def broadcast(self, message: str): + for connection in self.active_connections: + await connection.send_text(message) + + +manager = ConnectionManager() + + +@lnurldevice_ext.websocket("/ws/{lnurldevice_id}", name="lnurldevice.lnurldevice_by_id") +async def websocket_endpoint(websocket: WebSocket, lnurldevice_id: str): + await manager.connect(websocket, lnurldevice_id) + try: + while True: + data = await websocket.receive_text() + except WebSocketDisconnect: + manager.disconnect(websocket) + + +async def updater(lnurldevice_id): + lnurldevice = await get_lnurldevice(lnurldevice_id) + if not lnurldevice: + return + await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id) \ No newline at end of file From 05c8f02cb7cc714457e885c2c9002f6cc97c3074 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 6 Oct 2022 17:41:06 +0100 Subject: [PATCH 057/154] Added code sample for websocket stuff --- docs/devs/websockets.md | 87 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/devs/websockets.md diff --git a/docs/devs/websockets.md b/docs/devs/websockets.md new file mode 100644 index 00000000..34cf940d --- /dev/null +++ b/docs/devs/websockets.md @@ -0,0 +1,87 @@ +--- +layout: default +parent: For developers +title: Websockets +nav_order: 2 +--- + + +Websockets +================= + +`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from teh copilot extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension): + + +```sh +from fastapi import Request, WebSocket, WebSocketDisconnect + +class ConnectionManager: + def __init__(self): + self.active_connections: List[WebSocket] = [] + + async def connect(self, websocket: WebSocket, extension_id: str): + await websocket.accept() + websocket.id = extension_id + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_personal_message(self, message: str, extension_id: str): + for connection in self.active_connections: + if connection.id == extension_id: + await connection.send_text(message) + + async def broadcast(self, message: str): + for connection in self.active_connections: + await connection.send_text(message) + + +manager = ConnectionManager() + + +@extension_ext.websocket("/ws/{extension_id}", name="extension.websocket_by_id") +async def websocket_endpoint(websocket: WebSocket, extension_id: str): + await manager.connect(websocket, extension_id) + try: + while True: + data = await websocket.receive_text() + except WebSocketDisconnect: + manager.disconnect(websocket) + + +async def updater(extension_id, data): + extension = await get_extension(extension_id) + if not extension: + return + await manager.send_personal_message(f"{data}", extension_id) +``` + +Example vue-js function for listening to the websocket: + +``` +initWs: async function () { + if (location.protocol !== 'http:') { + localUrl = + 'wss://' + + document.domain + + ':' + + location.port + + '/extension/ws/' + + self.extension.id + } else { + localUrl = + 'ws://' + + document.domain + + ':' + + location.port + + '/extension/ws/' + + self.extension.id + } + this.ws = new WebSocket(localUrl) + this.ws.addEventListener('message', async ({data}) => { + const res = JSON.parse(data.toString()) + console.log(res) + }) +}, +``` \ No newline at end of file From 6d55445a1bb941ed2b13b290cb4d8994ef0a52ba Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Thu, 6 Oct 2022 17:44:04 +0100 Subject: [PATCH 058/154] Update websockets.md --- docs/devs/websockets.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/devs/websockets.md b/docs/devs/websockets.md index 34cf940d..0638e4f2 100644 --- a/docs/devs/websockets.md +++ b/docs/devs/websockets.md @@ -9,7 +9,7 @@ nav_order: 2 Websockets ================= -`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from teh copilot extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension): +`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from the `copilot` extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension): ```sh @@ -84,4 +84,4 @@ initWs: async function () { console.log(res) }) }, -``` \ No newline at end of file +``` From b46c06012d1e4e280eb9a8d3459276489a935b62 Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Thu, 6 Oct 2022 17:51:36 +0100 Subject: [PATCH 059/154] Revert "make gh workflows only run on pull_request" --- .github/workflows/codeql.yml | 2 ++ .github/workflows/formatting.yml | 2 ++ .github/workflows/mypy.yml | 2 +- .github/workflows/regtest.yml | 2 +- .github/workflows/tests.yml | 2 +- 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bbe95983..876c8b8a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,6 +1,8 @@ name: codeql on: + push: + branches: [main, ] pull_request: branches: [main] schedule: diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index 21c7fb38..e3d0fd35 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -1,6 +1,8 @@ name: formatting on: + push: + branches: [ main ] pull_request: branches: [ main ] diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 96b574d2..d80da678 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -1,6 +1,6 @@ name: mypy -on: [pull_request] +on: [push, pull_request] jobs: check: diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index f0adb3ac..2d7aae6b 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -1,6 +1,6 @@ name: regtest -on: [pull_request] +on: [push, pull_request] jobs: LndRestWallet: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c7b6e44b..5d368fbb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,6 @@ name: tests -on: [pull_request] +on: [push, pull_request] jobs: venv-sqlite: From ff98b87eca0ab2309ccc1b1be33969aaeb96b035 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 6 Oct 2022 22:19:30 +0100 Subject: [PATCH 060/154] Added a switch --- lnbits/extensions/lnurldevice/__init__.py | 1 + lnbits/extensions/lnurldevice/models.py | 2 +- .../templates/lnurldevice/_api_docs.html | 8 +-- .../templates/lnurldevice/index.html | 66 ++++++++++++++++++- lnbits/extensions/lnurldevice/views.py | 32 ++++++++- 5 files changed, 102 insertions(+), 7 deletions(-) diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py index dc4456b4..5452e356 100644 --- a/lnbits/extensions/lnurldevice/__init__.py +++ b/lnbits/extensions/lnurldevice/__init__.py @@ -1,3 +1,4 @@ +import asyncio from fastapi import APIRouter from lnbits.db import Database diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index cf617141..0714c656 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -36,7 +36,7 @@ class lnurldevices(BaseModel): def lnurl(self, req: Request) -> Lnurl: url = req.url_for( - "lnurldevice.lnurl_response", device_id=self.id, _external=True + "lnurldevice.lnurl_v1_params", device_id=self.id ) return lnurl_encode(url) diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html index 7f9afa27..7497714e 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html @@ -1,10 +1,10 @@

- Register LNURLDevice devices to receive payments in your LNbits wallet.
- Build your own here - https://github.com/arcbtc/bitcoinpos + Such as the LNPoS + https://lnbits.github.io/lnpos
Created by, Ben Arc + LNURLDevice Settings + + + bitcoinSwitch embeddable QRCode + +

LNURLDevice device string
+
{% raw + %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{% endraw + %} Click to copy URL + + Click to copy URL - +
@@ -191,6 +221,7 @@ label="Type of device" > +
+ + +
{ diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index 4a28a5c1..a64de84b 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -1,4 +1,6 @@ from http import HTTPStatus +import pyqrcode +from io import BytesIO from fastapi import Request from fastapi.param_functions import Query @@ -14,6 +16,9 @@ from lnbits.decorators import check_user_exists from . import lnurldevice_ext, lnurldevice_renderer from .crud import get_lnurldevice, get_lnurldevicepayment +from fastapi import Request, WebSocket, WebSocketDisconnect + +from starlette.responses import HTMLResponse, StreamingResponse templates = Jinja2Templates(directory="templates") @@ -52,6 +57,30 @@ async def displaypin(request: Request, paymentid: str = Query(None)): {"request": request, "pin": "filler", "not_paid": True}, ) +@lnurldevice_ext.get("/img/{lnurldevice_id}", response_class=StreamingResponse) +async def img(request: Request, lnurldevice_id): + lnurldevice = await get_lnurldevice(lnurldevice_id) + if not lnurldevice: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist." + ) + qr = pyqrcode.create(lnurldevice.lnurl(request)) + stream = BytesIO() + qr.svg(stream, scale=3) + stream.seek(0) + + async def _generator(stream: BytesIO): + yield stream.getvalue() + + return StreamingResponse( + _generator(stream), + headers={ + "Content-Type": "image/svg+xml", + "Cache-Control": "no-cache, no-store, must-revalidate", + "Pragma": "no-cache", + "Expires": "0", + }, + ) ##################WEBSOCKET ROUTES######################## @@ -95,4 +124,5 @@ async def updater(lnurldevice_id): lnurldevice = await get_lnurldevice(lnurldevice_id) if not lnurldevice: return - await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id) \ No newline at end of file + await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id) + From 3bf38884ed8cb30ef9b5c428fdaea1acbedf1020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 7 Oct 2022 08:30:07 +0200 Subject: [PATCH 061/154] log successful connection to backend with logger.success() --- lnbits/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/app.py b/lnbits/app.py index 51482538..8b9cf798 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -126,7 +126,7 @@ def check_funding_source(app: FastAPI) -> None: logger.info("Retrying connection to backend in 5 seconds...") await asyncio.sleep(5) signal.signal(signal.SIGINT, original_sigint_handler) - logger.info( + logger.success( f"✔️ Backend {WALLET.__class__.__name__} connected and with a balance of {balance} msat." ) From 52dc528a8a7557da685da624e22058e737e49a16 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 7 Oct 2022 11:29:06 +0100 Subject: [PATCH 062/154] Added qrdialogue --- .../templates/lnurldevice/index.html | 61 +++++++++++++++---- lnbits/extensions/lnurldevice/views.py | 19 +----- lnbits/extensions/lnurldevice/views_api.py | 13 +++- 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index 528e5afe..b55c83f7 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -94,18 +94,17 @@ - bitcoinSwitch embeddable QRCode - + v-if="props.row.device == 'switch'" + :disable="protocol == 'http:'" + flat + unelevated + dense + size="xs" + icon="visibility" + :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" + @click="openQrCodeDialog(props.row.id)" + > LNURLs only work over HTTPS view LNURL + + + + + + {% raw %} + +

+ ID: {{ qrCodeDialog.data.id }}
+ +

+ {% endraw %} +
+ Copy LNURL + Close +
+
+
{% endblock %} {% block scripts %} {{ window_vars(user) }} @@ -306,6 +333,7 @@ mixins: [windowMixin], data: function () { return { + protocol: window.location.protocol, location: window.location.hostname, wslocation: window.location.hostname, filter: '', @@ -404,6 +432,14 @@ } }, methods: { + openQrCodeDialog: function (lnurldevice_id) { + var lnurldevice = _.findWhere(this.lnurldeviceLinks, {id: lnurldevice_id}) + console.log(lnurldevice) + this.qrCodeDialog.data = _.clone(lnurldevice) + this.qrCodeDialog.data.url = + window.location.protocol + '//' + window.location.host + this.qrCodeDialog.show = true + }, cancellnurldevice: function (data) { var self = this self.formDialoglnurldevice.show = false @@ -460,6 +496,7 @@ .then(function (response) { if (response.data) { self.lnurldeviceLinks = response.data.map(maplnurldevice) + console.log(response.data) } }) .catch(function (error) { diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index a64de84b..26f581d3 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -64,23 +64,8 @@ async def img(request: Request, lnurldevice_id): raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist." ) - qr = pyqrcode.create(lnurldevice.lnurl(request)) - stream = BytesIO() - qr.svg(stream, scale=3) - stream.seek(0) - - async def _generator(stream: BytesIO): - yield stream.getvalue() - - return StreamingResponse( - _generator(stream), - headers={ - "Content-Type": "image/svg+xml", - "Cache-Control": "no-cache, no-store, must-revalidate", - "Pragma": "no-cache", - "Expires": "0", - }, - ) + return lnurldevice.lnurl(request) + ##################WEBSOCKET ROUTES######################## diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index d152d210..4a4c2b90 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -45,14 +45,21 @@ async def api_lnurldevice_create_or_update( @lnurldevice_ext.get("/api/v1/lnurlpos") -async def api_lnurldevices_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_lnurldevices_retrieve(req: Request, wallet: WalletTypeInfo = Depends(get_key_type)): wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids try: return [ - {**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids) + {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}} + for lnurldevice in await get_lnurldevices(wallet_ids) ] except: - return "" + try: + return [ + {**lnurldevice.dict()} + for lnurldevice in await get_lnurldevices(wallet_ids) + ] + except: + return "" @lnurldevice_ext.get("/api/v1/lnurlpos/{lnurldevice_id}") From 7711387d6472171539493c2f830869edef1e49fd Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 7 Oct 2022 15:23:57 +0300 Subject: [PATCH 063/154] fix: use bigint for amounts in postgres (#1030) --- lnbits/db.py | 6 ++++++ lnbits/extensions/boltcards/migrations.py | 8 ++++---- lnbits/extensions/boltz/migrations.py | 12 ++++++------ lnbits/extensions/invoices/migrations.py | 2 +- lnbits/extensions/lnurldevice/migrations.py | 2 +- lnbits/extensions/lnurlpayout/migrations.py | 4 ++-- lnbits/extensions/streamalerts/migrations.py | 2 +- lnbits/extensions/tipjar/migrations.py | 4 ++-- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lnbits/db.py b/lnbits/db.py index 66981784..f52b0391 100644 --- a/lnbits/db.py +++ b/lnbits/db.py @@ -52,6 +52,12 @@ class Compat: return "" return "" + @property + def big_int(self) -> str: + if self.type in {POSTGRES}: + return "BIGINT" + return "INT" + class Connection(Compat): def __init__(self, conn: AsyncConnection, txn, typ, name, schema): diff --git a/lnbits/extensions/boltcards/migrations.py b/lnbits/extensions/boltcards/migrations.py index 08126013..9609e0c3 100644 --- a/lnbits/extensions/boltcards/migrations.py +++ b/lnbits/extensions/boltcards/migrations.py @@ -29,7 +29,7 @@ async def m001_initial(db): ) await db.execute( - """ + f""" CREATE TABLE boltcards.hits ( id TEXT PRIMARY KEY UNIQUE, card_id TEXT NOT NULL, @@ -38,7 +38,7 @@ async def m001_initial(db): useragent TEXT, old_ctr INT NOT NULL DEFAULT 0, new_ctr INT NOT NULL DEFAULT 0, - amount INT NOT NULL, + amount {db.big_int} NOT NULL, time TIMESTAMP NOT NULL DEFAULT """ + db.timestamp_now + """ @@ -47,11 +47,11 @@ async def m001_initial(db): ) await db.execute( - """ + f""" CREATE TABLE boltcards.refunds ( id TEXT PRIMARY KEY UNIQUE, hit_id TEXT NOT NULL, - refund_amount INT NOT NULL, + refund_amount {db.big_int} NOT NULL, time TIMESTAMP NOT NULL DEFAULT """ + db.timestamp_now + """ diff --git a/lnbits/extensions/boltz/migrations.py b/lnbits/extensions/boltz/migrations.py index e4026dd0..925322ec 100644 --- a/lnbits/extensions/boltz/migrations.py +++ b/lnbits/extensions/boltz/migrations.py @@ -1,16 +1,16 @@ async def m001_initial(db): await db.execute( - """ + f""" CREATE TABLE boltz.submarineswap ( id TEXT PRIMARY KEY, wallet TEXT NOT NULL, payment_hash TEXT NOT NULL, - amount INT NOT NULL, + amount {db.big_int} NOT NULL, status TEXT NOT NULL, boltz_id TEXT NOT NULL, refund_address TEXT NOT NULL, refund_privkey TEXT NOT NULL, - expected_amount INT NOT NULL, + expected_amount {db.big_int} NOT NULL, timeout_block_height INT NOT NULL, address TEXT NOT NULL, bip21 TEXT NOT NULL, @@ -22,12 +22,12 @@ async def m001_initial(db): """ ) await db.execute( - """ + f""" CREATE TABLE boltz.reverse_submarineswap ( id TEXT PRIMARY KEY, wallet TEXT NOT NULL, onchain_address TEXT NOT NULL, - amount INT NOT NULL, + amount {db.big_int} NOT NULL, instant_settlement BOOLEAN NOT NULL, status TEXT NOT NULL, boltz_id TEXT NOT NULL, @@ -37,7 +37,7 @@ async def m001_initial(db): claim_privkey TEXT NOT NULL, lockup_address TEXT NOT NULL, invoice TEXT NOT NULL, - onchain_amount INT NOT NULL, + onchain_amount {db.big_int} NOT NULL, time TIMESTAMP NOT NULL DEFAULT """ + db.timestamp_now + """ diff --git a/lnbits/extensions/invoices/migrations.py b/lnbits/extensions/invoices/migrations.py index c47a954a..74a0fdba 100644 --- a/lnbits/extensions/invoices/migrations.py +++ b/lnbits/extensions/invoices/migrations.py @@ -45,7 +45,7 @@ async def m001_initial_invoices(db): id TEXT PRIMARY KEY, invoice_id TEXT NOT NULL, - amount INT NOT NULL, + amount {db.big_int} NOT NULL, time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index c7899282..da62cb7e 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -29,7 +29,7 @@ async def m001_initial(db): payhash TEXT, payload TEXT NOT NULL, pin INT, - sats INT, + sats {db.big_int}, timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} ); """ diff --git a/lnbits/extensions/lnurlpayout/migrations.py b/lnbits/extensions/lnurlpayout/migrations.py index 6af04791..7a45e495 100644 --- a/lnbits/extensions/lnurlpayout/migrations.py +++ b/lnbits/extensions/lnurlpayout/migrations.py @@ -3,14 +3,14 @@ async def m001_initial(db): Initial lnurlpayouts table. """ await db.execute( - """ + f""" CREATE TABLE lnurlpayout.lnurlpayouts ( id TEXT PRIMARY KEY, title TEXT NOT NULL, wallet TEXT NOT NULL, admin_key TEXT NOT NULL, lnurlpay TEXT NOT NULL, - threshold INT NOT NULL + threshold {db.big_int} NOT NULL ); """ ) diff --git a/lnbits/extensions/streamalerts/migrations.py b/lnbits/extensions/streamalerts/migrations.py index 1b0cea37..7d50e8f1 100644 --- a/lnbits/extensions/streamalerts/migrations.py +++ b/lnbits/extensions/streamalerts/migrations.py @@ -25,7 +25,7 @@ async def m001_initial(db): name TEXT NOT NULL, message TEXT NOT NULL, cur_code TEXT NOT NULL, - sats INT NOT NULL, + sats {db.big_int} NOT NULL, amount FLOAT NOT NULL, service INTEGER NOT NULL, posted BOOLEAN NOT NULL, diff --git a/lnbits/extensions/tipjar/migrations.py b/lnbits/extensions/tipjar/migrations.py index 6b58fbca..d8f6da3f 100644 --- a/lnbits/extensions/tipjar/migrations.py +++ b/lnbits/extensions/tipjar/migrations.py @@ -19,8 +19,8 @@ async def m001_initial(db): wallet TEXT NOT NULL, name TEXT NOT NULL, message TEXT NOT NULL, - sats INT NOT NULL, - tipjar INT NOT NULL, + sats {db.big_int} NOT NULL, + tipjar {db.big_int} NOT NULL, FOREIGN KEY(tipjar) REFERENCES {db.references_schema}TipJars(id) ); """ From 8d4337679f93161966310ccf56d30eb4fd576317 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 7 Oct 2022 13:46:40 +0100 Subject: [PATCH 064/154] Websocket/listener working --- lnbits/extensions/lnurldevice/crud.py | 6 +++-- lnbits/extensions/lnurldevice/lnurl.py | 26 +++++++++++-------- lnbits/extensions/lnurldevice/tasks.py | 8 +++--- .../templates/lnurldevice/index.html | 3 ++- lnbits/extensions/lnurldevice/views_api.py | 9 ++++--- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py index 45166521..4c25e4cb 100644 --- a/lnbits/extensions/lnurldevice/crud.py +++ b/lnbits/extensions/lnurldevice/crud.py @@ -22,9 +22,10 @@ async def create_lnurldevice( wallet, currency, device, - profit + profit, + amount ) - VALUES (?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( lnurldevice_id, @@ -34,6 +35,7 @@ async def create_lnurldevice( data.currency, data.device, data.profit, + data.amount, ), ) return await get_lnurldevice(lnurldevice_id) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index a2bc0dd4..3823005e 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -103,9 +103,19 @@ async def lnurl_v1_params( if paymentcheck: return {"status": "ERROR", "reason": f"Payment already claimed"} if device.device == "switch": + + price_msat = ( + await fiat_amount_as_satoshis(float(device.profit), device.currency) + if device.currency != "sat" + else amount_in_cent + ) * 1000 + lnurldevicepayment = await create_lnurldevicepayment( deviceid=device.id, - sats=device.profit, + payload="bla", + sats=price_msat, + pin=1, + payhash="bla", ) if not lnurldevicepayment: return {"status": "ERROR", "reason": "Could not create payment."} @@ -114,8 +124,8 @@ async def lnurl_v1_params( "callback": request.url_for( "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id ), - "minSendable": device.profit * 1000, - "maxSendable": device.profit * 1000, + "minSendable": price_msat, + "maxSendable": price_msat, "metadata": await device.lnurlpay_metadata(), } if len(p) % 4 > 0: @@ -224,21 +234,15 @@ async def lnurl_callback( payment_hash, payment_request = await create_invoice( wallet_id=device.wallet, amount=lnurldevicepayment.sats / 1000, - memo=device.title, + memo=device.title + "-" + lnurldevicepayment.id, unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"), - extra={"tag": "Switch", "id": device.paymentid, "time": device.amount}, + extra={"tag": "Switch", "id": paymentid, "time": device.amount}, ) lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=payment_hash ) - return { "pr": payment_request, - "successAction": { - "tag": "url", - "description": "Check the attached link", - "url": request.url_for("lnurldevice.displaypin", paymentid=paymentid), - }, "routes": [], } diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 2337c0ad..4bf0d3b9 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -12,8 +12,9 @@ from lnbits.core.services import pay_invoice from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .crud import get_lnurldevice +from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment from .views import updater +from loguru import logger async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() @@ -23,17 +24,16 @@ async def wait_for_paid_invoices(): payment = await invoice_queue.get() await on_invoice_paid(payment) - async def on_invoice_paid(payment: Payment) -> None: # (avoid loops) - if "switch" == payment.extra.get("tag"): + if "Switch" == payment.extra.get("tag"): lnurldevicepayment = await get_lnurldevicepayment(payment.extra.get("id")) if not lnurldevicepayment: return if lnurldevicepayment.payhash == "used": return lnurldevicepayment = await update_lnurldevicepayment( - lnurldevicepayment_id=paymentid, payhash="used" + lnurldevicepayment_id=payment.extra.get("id"), payhash="used" ) return await updater(lnurldevicepayment.deviceid) return \ No newline at end of file diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index b55c83f7..ccb0a6e9 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -240,6 +240,7 @@ fill-mask="0" reverse-fill-mask :step="'0.01'" + value="0.00" > Date: Fri, 7 Oct 2022 23:18:57 +0100 Subject: [PATCH 065/154] Black --- lnbits/extensions/lnurldevice/__init__.py | 2 ++ lnbits/extensions/lnurldevice/lnurl.py | 2 +- lnbits/extensions/lnurldevice/migrations.py | 5 ++++- lnbits/extensions/lnurldevice/models.py | 4 +--- lnbits/extensions/lnurldevice/tasks.py | 10 ++++++---- lnbits/extensions/lnurldevice/views.py | 4 ++-- lnbits/extensions/lnurldevice/views_api.py | 12 +++++++----- 7 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py index 5452e356..d987e6cc 100644 --- a/lnbits/extensions/lnurldevice/__init__.py +++ b/lnbits/extensions/lnurldevice/__init__.py @@ -13,11 +13,13 @@ lnurldevice_ext: APIRouter = APIRouter(prefix="/lnurldevice", tags=["lnurldevice def lnurldevice_renderer(): return template_renderer(["lnbits/extensions/lnurldevice/templates"]) + from .tasks import wait_for_paid_invoices from .lnurl import * # noqa from .views import * # noqa from .views_api import * # noqa + def lnurldevice_start(): loop = asyncio.get_event_loop() loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index 3823005e..79892b78 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -122,7 +122,7 @@ async def lnurl_v1_params( return { "tag": "payRequest", "callback": request.url_for( - "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id + "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id ), "minSendable": price_msat, "maxSendable": price_msat, diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index 65913b02..fa56b3d1 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -80,8 +80,11 @@ async def m002_redux(db): except: return + async def m003_redux(db): """ Add 'meta' for storing various metadata about the wallet """ - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;") \ No newline at end of file + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;" + ) diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index 0714c656..01bcc2ba 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -35,9 +35,7 @@ class lnurldevices(BaseModel): return cls(**dict(row)) def lnurl(self, req: Request) -> Lnurl: - url = req.url_for( - "lnurldevice.lnurl_v1_params", device_id=self.id - ) + url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) return lnurl_encode(url) async def lnurlpay_metadata(self) -> LnurlPayMetadata: diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 4bf0d3b9..2f048138 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -16,6 +16,7 @@ from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepay from .views import updater from loguru import logger + async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() register_invoice_listener(invoice_queue, get_current_extension_name()) @@ -23,7 +24,8 @@ async def wait_for_paid_invoices(): while True: payment = await invoice_queue.get() await on_invoice_paid(payment) - + + async def on_invoice_paid(payment: Payment) -> None: # (avoid loops) if "Switch" == payment.extra.get("tag"): @@ -33,7 +35,7 @@ async def on_invoice_paid(payment: Payment) -> None: if lnurldevicepayment.payhash == "used": return lnurldevicepayment = await update_lnurldevicepayment( - lnurldevicepayment_id=payment.extra.get("id"), payhash="used" - ) + lnurldevicepayment_id=payment.extra.get("id"), payhash="used" + ) return await updater(lnurldevicepayment.deviceid) - return \ No newline at end of file + return diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index 26f581d3..752d2c81 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -57,6 +57,7 @@ async def displaypin(request: Request, paymentid: str = Query(None)): {"request": request, "pin": "filler", "not_paid": True}, ) + @lnurldevice_ext.get("/img/{lnurldevice_id}", response_class=StreamingResponse) async def img(request: Request, lnurldevice_id): lnurldevice = await get_lnurldevice(lnurldevice_id) @@ -65,7 +66,7 @@ async def img(request: Request, lnurldevice_id): status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist." ) return lnurldevice.lnurl(request) - + ##################WEBSOCKET ROUTES######################## @@ -110,4 +111,3 @@ async def updater(lnurldevice_id): if not lnurldevice: return await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id) - diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index 65625cbb..c034f66e 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -46,18 +46,20 @@ async def api_lnurldevice_create_or_update( @lnurldevice_ext.get("/api/v1/lnurlpos") -async def api_lnurldevices_retrieve(req: Request, wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_lnurldevices_retrieve( + req: Request, wallet: WalletTypeInfo = Depends(get_key_type) +): wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids try: return [ - {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}} - for lnurldevice in await get_lnurldevices(wallet_ids) + {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}} + for lnurldevice in await get_lnurldevices(wallet_ids) ] except: try: return [ - {**lnurldevice.dict()} - for lnurldevice in await get_lnurldevices(wallet_ids) + {**lnurldevice.dict()} + for lnurldevice in await get_lnurldevices(wallet_ids) ] except: return "" From d4a5fe27768a8d05417e7ed8d6340b2420291a5f Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 7 Oct 2022 23:48:42 +0100 Subject: [PATCH 066/154] isort --- lnbits/extensions/lnurldevice/tasks.py | 4 ++-- lnbits/extensions/lnurldevice/views.py | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 2f048138..db8d87f0 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -12,9 +12,9 @@ from lnbits.core.services import pay_invoice from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment +from .crud import (get_lnurldevice, get_lnurldevicepayment, + update_lnurldevicepayment) from .views import updater -from loguru import logger async def wait_for_paid_invoices(): diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index 752d2c81..5c6eba24 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -1,13 +1,13 @@ from http import HTTPStatus -import pyqrcode from io import BytesIO -from fastapi import Request +import pyqrcode +from fastapi import Request, WebSocket, WebSocketDisconnect from fastapi.param_functions import Query from fastapi.params import Depends from fastapi.templating import Jinja2Templates from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse +from starlette.responses import HTMLResponse, StreamingResponse from lnbits.core.crud import update_payment_status from lnbits.core.models import User @@ -16,9 +16,6 @@ from lnbits.decorators import check_user_exists from . import lnurldevice_ext, lnurldevice_renderer from .crud import get_lnurldevice, get_lnurldevicepayment -from fastapi import Request, WebSocket, WebSocketDisconnect - -from starlette.responses import HTMLResponse, StreamingResponse templates = Jinja2Templates(directory="templates") From e3bd38fc6b11cbc9c1382fbac5fdfc005a0142db Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 7 Oct 2022 23:51:28 +0100 Subject: [PATCH 067/154] back --- lnbits/extensions/lnurldevice/tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index db8d87f0..c8f3db04 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -12,8 +12,7 @@ from lnbits.core.services import pay_invoice from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .crud import (get_lnurldevice, get_lnurldevicepayment, - update_lnurldevicepayment) +from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment from .views import updater From 34effe3ae5690ad3b68d0a1af34ccc2d9d9b6e21 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 7 Oct 2022 23:57:28 +0100 Subject: [PATCH 068/154] isort --- lnbits/extensions/lnurldevice/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py index d987e6cc..c27c9e17 100644 --- a/lnbits/extensions/lnurldevice/__init__.py +++ b/lnbits/extensions/lnurldevice/__init__.py @@ -2,8 +2,8 @@ import asyncio from fastapi import APIRouter from lnbits.db import Database -from lnbits.helpers import template_renderer from lnbits.tasks import catch_everything_and_restart +from lnbits.helpers import template_renderer db = Database("ext_lnurldevice") From fbc20b3579169b44227cc8ba8e20cc78be1b65f5 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 8 Oct 2022 01:28:46 +0100 Subject: [PATCH 069/154] added credits --- .../templates/lnurldevice/_api_docs.html | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html index 7497714e..b239e981 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html @@ -2,12 +2,21 @@
From 3bf3c91007ae4f589f06176a405061b92878e5ff Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Mon, 10 Oct 2022 16:12:06 +0200 Subject: [PATCH 070/154] Add backend: ln.tips (LightningTipBot) wallet (#921) * add ln.tips * add wallet * revert lnbits * make format * .env.example * listener fix * reconnect fixed * make format * make format --- .env.example | 7 +- lnbits/extensions/lnurldevice/__init__.py | 5 +- .../templates/lnurldevice/_api_docs.html | 22 +-- .../templates/lnurldevice/index.html | 142 ++++++++------- lnbits/wallets/__init__.py | 2 + lnbits/wallets/lntips.py | 170 ++++++++++++++++++ 6 files changed, 263 insertions(+), 85 deletions(-) create mode 100644 lnbits/wallets/lntips.py diff --git a/.env.example b/.env.example index 7d6de35f..987c6ca6 100644 --- a/.env.example +++ b/.env.example @@ -41,7 +41,7 @@ LNBITS_SITE_DESCRIPTION="Some description about your service, will display if ti LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador" # LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg" -# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet +# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet # LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet LNBITS_BACKEND_WALLET_CLASS=VoidWallet # VoidWallet is just a fallback that works without any actual Lightning capabilities, @@ -92,3 +92,8 @@ LNBITS_DENOMINATION=sats # EclairWallet ECLAIR_URL=http://127.0.0.1:8283 ECLAIR_PASS=eclairpw + +# LnTipsWallet +# Enter /api in LightningTipBot to get your key +LNTIPS_API_KEY=LNTIPS_ADMIN_KEY +LNTIPS_API_ENDPOINT=https://ln.tips diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py index c27c9e17..d2010c44 100644 --- a/lnbits/extensions/lnurldevice/__init__.py +++ b/lnbits/extensions/lnurldevice/__init__.py @@ -1,9 +1,10 @@ import asyncio + from fastapi import APIRouter from lnbits.db import Database -from lnbits.tasks import catch_everything_and_restart from lnbits.helpers import template_renderer +from lnbits.tasks import catch_everything_and_restart db = Database("ext_lnurldevice") @@ -14,8 +15,8 @@ def lnurldevice_renderer(): return template_renderer(["lnbits/extensions/lnurldevice/templates"]) -from .tasks import wait_for_paid_invoices from .lnurl import * # noqa +from .tasks import wait_for_paid_invoices from .views import * # noqa from .views_api import * # noqa diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html index b239e981..f93d44d8 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html @@ -3,20 +3,22 @@

For LNURL based Points of Sale, ATMs, and relay devices
Use with:
- LNPoS - https://lnbits.github.io/lnpos + https://lnbits.github.io/lnpos
- bitcoinSwitch - https://github.com/lnbits/bitcoinSwitch + https://github.com/lnbits/bitcoinSwitch
- FOSSA - https://github.com/lnbits/fossa + https://github.com/lnbits/fossa
- Created by, Ben Arc, BC, Vlad StanBen Arc, + BC, + Vlad Stan

diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index ccb0a6e9..028dd94b 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -94,17 +94,19 @@ LNURLs only work over HTTPS view LNURL + v-if="props.row.device == 'switch'" + :disable="protocol == 'http:'" + flat + unelevated + dense + size="xs" + icon="visibility" + :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" + @click="openQrCodeDialog(props.row.id)" + > + LNURLs only work over HTTPS view LNURL
LNURLDevice device string
- {% raw - %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{% endraw - %} Click to copy URL - - {% raw %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{% + endraw %} Click to copy URL + + {% raw - %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}}, - {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw - %} Click to copy URL - -
+ >{% raw + %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}}, + {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw + %} Click to copy URL + +
@@ -229,29 +230,28 @@ label="Profit margin (% added to invoices/deducted from faucets)" >
- - -
+ + +

ID: {{ qrCodeDialog.data.id }}
-

{% endraw %}
@@ -434,13 +433,15 @@ }, methods: { openQrCodeDialog: function (lnurldevice_id) { - var lnurldevice = _.findWhere(this.lnurldeviceLinks, {id: lnurldevice_id}) - console.log(lnurldevice) - this.qrCodeDialog.data = _.clone(lnurldevice) - this.qrCodeDialog.data.url = - window.location.protocol + '//' + window.location.host - this.qrCodeDialog.show = true - }, + var lnurldevice = _.findWhere(this.lnurldeviceLinks, { + id: lnurldevice_id + }) + console.log(lnurldevice) + this.qrCodeDialog.data = _.clone(lnurldevice) + this.qrCodeDialog.data.url = + window.location.protocol + '//' + window.location.host + this.qrCodeDialog.show = true + }, cancellnurldevice: function (data) { var self = this self.formDialoglnurldevice.show = false @@ -617,10 +618,7 @@ '//', window.location.host ].join('') - self.wslocation = [ - 'ws://', - window.location.host - ].join('') + self.wslocation = ['ws://', window.location.host].join('') LNbits.api .request('GET', '/api/v1/currencies') .then(response => { diff --git a/lnbits/wallets/__init__.py b/lnbits/wallets/__init__.py index 41949652..fa533566 100644 --- a/lnbits/wallets/__init__.py +++ b/lnbits/wallets/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa + from .cliche import ClicheWallet from .cln import CoreLightningWallet # legacy .env support from .cln import CoreLightningWallet as CLightningWallet @@ -9,6 +10,7 @@ from .lnbits import LNbitsWallet from .lndgrpc import LndWallet from .lndrest import LndRestWallet from .lnpay import LNPayWallet +from .lntips import LnTipsWallet from .lntxbot import LntxbotWallet from .opennode import OpenNodeWallet from .spark import SparkWallet diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py new file mode 100644 index 00000000..54220c85 --- /dev/null +++ b/lnbits/wallets/lntips.py @@ -0,0 +1,170 @@ +import asyncio +import hashlib +import json +import time +from os import getenv +from typing import AsyncGenerator, Dict, Optional + +import httpx +from loguru import logger + +from .base import ( + InvoiceResponse, + PaymentResponse, + PaymentStatus, + StatusResponse, + Wallet, +) + + +class LnTipsWallet(Wallet): + def __init__(self): + endpoint = getenv("LNTIPS_API_ENDPOINT") + self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint + + key = ( + getenv("LNTIPS_API_KEY") + or getenv("LNTIPS_ADMIN_KEY") + or getenv("LNTIPS_INVOICE_KEY") + ) + self.auth = {"Authorization": f"Basic {key}"} + + async def status(self) -> StatusResponse: + async with httpx.AsyncClient() as client: + r = await client.get( + f"{self.endpoint}/api/v1/balance", headers=self.auth, timeout=40 + ) + try: + data = r.json() + except: + return StatusResponse( + f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0 + ) + + if data.get("error"): + return StatusResponse(data["error"], 0) + + return StatusResponse(None, data["balance"] * 1000) + + async def create_invoice( + self, + amount: int, + memo: Optional[str] = None, + description_hash: Optional[bytes] = None, + unhashed_description: Optional[bytes] = None, + **kwargs, + ) -> InvoiceResponse: + data: Dict = {"amount": amount} + if description_hash: + data["description_hash"] = description_hash.hex() + elif unhashed_description: + data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest() + else: + data["memo"] = memo or "" + + async with httpx.AsyncClient() as client: + r = await client.post( + f"{self.endpoint}/api/v1/createinvoice", + headers=self.auth, + json=data, + timeout=40, + ) + + if r.is_error: + try: + data = r.json() + error_message = data["message"] + except: + error_message = r.text + pass + + return InvoiceResponse(False, None, None, error_message) + + data = r.json() + return InvoiceResponse( + True, data["payment_hash"], data["payment_request"], None + ) + + async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: + async with httpx.AsyncClient() as client: + r = await client.post( + f"{self.endpoint}/api/v1/payinvoice", + headers=self.auth, + json={"pay_req": bolt11}, + timeout=None, + ) + if r.is_error: + return PaymentResponse(False, None, 0, None, r.text) + + if "error" in r.json(): + try: + data = r.json() + error_message = data["error"] + except: + error_message = r.text + pass + return PaymentResponse(False, None, 0, None, error_message) + + data = r.json()["details"] + checking_id = data["payment_hash"] + fee_msat = -data["fee"] + preimage = data["preimage"] + return PaymentResponse(True, checking_id, fee_msat, preimage, None) + + async def get_invoice_status(self, checking_id: str) -> PaymentStatus: + async with httpx.AsyncClient() as client: + r = await client.post( + f"{self.endpoint}/api/v1/invoicestatus/{checking_id}", + headers=self.auth, + ) + + if r.is_error or len(r.text) == 0: + return PaymentStatus(None) + + data = r.json() + return PaymentStatus(data["paid"]) + + async def get_payment_status(self, checking_id: str) -> PaymentStatus: + async with httpx.AsyncClient() as client: + r = await client.post( + url=f"{self.endpoint}/api/v1/paymentstatus/{checking_id}", + headers=self.auth, + ) + + if r.is_error: + return PaymentStatus(None) + data = r.json() + + paid_to_status = {False: None, True: True} + return PaymentStatus(paid_to_status[data.get("paid")]) + + async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + last_connected = None + while True: + url = f"{self.endpoint}/api/v1/invoicestream" + try: + async with httpx.AsyncClient(timeout=None, headers=self.auth) as client: + last_connected = time.time() + async with client.stream("GET", url) as r: + async for line in r.aiter_lines(): + try: + prefix = "data: " + if not line.startswith(prefix): + continue + data = line[len(prefix) :] # sse parsing + inv = json.loads(data) + if not inv.get("payment_hash"): + continue + except: + continue + yield inv["payment_hash"] + except Exception as e: + pass + + # do not sleep if the connection was active for more than 10s + # since the backend is expected to drop the connection after 90s + if last_connected is None or time.time() - last_connected < 10: + logger.error( + f"lost connection to {self.endpoint}/api/v1/invoicestream, retrying in 5 seconds" + ) + await asyncio.sleep(5) From d1302e4868f0fe12cf6bdf7d25e82045981077c2 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 08:52:39 +0200 Subject: [PATCH 071/154] show progress (#987) --- lnbits/core/crud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index cbed6292..bb1ca0c1 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -333,7 +333,7 @@ async def delete_expired_invoices( """ ) logger.debug(f"Checking expiry of {len(rows)} invoices") - for (payment_request,) in rows: + for i, (payment_request,) in enumerate(rows): try: invoice = bolt11.decode(payment_request) except: @@ -343,7 +343,7 @@ async def delete_expired_invoices( if expiration_date > datetime.datetime.utcnow(): continue logger.debug( - f"Deleting expired invoice: {invoice.payment_hash} (expired: {expiration_date})" + f"Deleting expired invoice {i}/{len(rows)}: {invoice.payment_hash} (expired: {expiration_date})" ) await (conn or db).execute( """ From 42704eaf1bcf0a0ec6fc4309ea63f692f0721286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 18 Oct 2022 08:49:27 +0200 Subject: [PATCH 072/154] change boltz logs from warn to debug (#1057) --- lnbits/extensions/boltz/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/boltz/tasks.py b/lnbits/extensions/boltz/tasks.py index ace94557..d1ace04b 100644 --- a/lnbits/extensions/boltz/tasks.py +++ b/lnbits/extensions/boltz/tasks.py @@ -57,7 +57,7 @@ async def check_for_pending_swaps(): swap_status = get_swap_status(swap) # should only happen while development when regtest is reset if swap_status.exists is False: - logger.warning(f"Boltz - swap: {swap.boltz_id} does not exist.") + logger.debug(f"Boltz - swap: {swap.boltz_id} does not exist.") await update_swap_status(swap.id, "failed") continue @@ -73,7 +73,7 @@ async def check_for_pending_swaps(): else: if swap_status.hit_timeout: if not swap_status.has_lockup: - logger.warning( + logger.debug( f"Boltz - swap: {swap.id} hit timeout, but no lockup tx..." ) await update_swap_status(swap.id, "timeout") From 9de38f908bfa603c1cfb98cfd078e0136e815320 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 20 Oct 2022 17:40:23 +0300 Subject: [PATCH 073/154] fix: wallet not found error (#1063) --- lnbits/extensions/satspay/crud.py | 9 ++++++++- lnbits/extensions/satspay/helpers.py | 17 +++++++++++++++++ lnbits/extensions/satspay/models.py | 1 - .../satspay/templates/satspay/display.html | 15 +++------------ lnbits/extensions/satspay/views.py | 5 ++--- lnbits/extensions/satspay/views_api.py | 17 +++-------------- 6 files changed, 33 insertions(+), 31 deletions(-) create mode 100644 lnbits/extensions/satspay/helpers.py diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index 47d7a4a8..23d391b7 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -102,7 +102,7 @@ async def check_address_balance(charge_id: str) -> List[Charges]: charge = await get_charge(charge_id) if not charge.paid: if charge.onchainaddress: - config = await get_config(charge.user) + config = await get_charge_config(charge_id) try: async with httpx.AsyncClient() as client: r = await client.get( @@ -122,3 +122,10 @@ async def check_address_balance(charge_id: str) -> List[Charges]: return await update_charge(charge_id=charge_id, balance=charge.amount) row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,)) return Charges.from_row(row) if row else None + + +async def get_charge_config(charge_id: str): + row = await db.fetchone( + """SELECT "user" FROM satspay.charges WHERE id = ?""", (charge_id,) + ) + return await get_config(row.user) diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py new file mode 100644 index 00000000..2d15b557 --- /dev/null +++ b/lnbits/extensions/satspay/helpers.py @@ -0,0 +1,17 @@ +from .models import Charges + + +def compact_charge(charge: Charges): + return { + "id": charge.id, + "description": charge.description, + "onchainaddress": charge.onchainaddress, + "payment_request": charge.payment_request, + "payment_hash": charge.payment_hash, + "time": charge.time, + "amount": charge.amount, + "balance": charge.balance, + "paid": charge.paid, + "timestamp": charge.timestamp, + "completelink": charge.completelink, # should be secret? + } diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py index e8638d5e..daf63f42 100644 --- a/lnbits/extensions/satspay/models.py +++ b/lnbits/extensions/satspay/models.py @@ -19,7 +19,6 @@ class CreateCharge(BaseModel): class Charges(BaseModel): id: str - user: str description: Optional[str] onchainwallet: Optional[str] onchainaddress: Optional[str] diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html index f34ac509..12288c80 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -328,7 +328,7 @@ ) }, checkBalances: async function () { - if (!this.charge.hasStaleBalance) await this.refreshCharge() + if (this.charge.hasStaleBalance) return try { const {data} = await LNbits.api.request( 'GET', @@ -339,18 +339,9 @@ LNbits.utils.notifyApiError(error) } }, - refreshCharge: async function () { - try { - const {data} = await LNbits.api.request( - 'GET', - `/satspay/api/v1/charge/${this.charge.id}` - ) - this.charge = mapCharge(data, this.charge) - } catch (error) { - LNbits.utils.notifyApiError(error) - } - }, checkPendingOnchain: async function () { + if (!this.charge.onchainaddress) return + const { bitcoin: {addresses: addressesAPI} } = mempoolJS({ diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 69d81dad..b789bf8f 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -9,10 +9,9 @@ from starlette.responses import HTMLResponse from lnbits.core.crud import get_wallet from lnbits.core.models import User from lnbits.decorators import check_user_exists -from lnbits.extensions.watchonly.crud import get_config from . import satspay_ext, satspay_renderer -from .crud import get_charge +from .crud import get_charge, get_charge_config templates = Jinja2Templates(directory="templates") @@ -32,7 +31,7 @@ async def display(request: Request, charge_id: str): status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist." ) wallet = await get_wallet(charge.lnbitswallet) - onchainwallet_config = await get_config(charge.user) + onchainwallet_config = await get_charge_config(charge_id) inkey = wallet.inkey if wallet else None mempool_endpoint = ( onchainwallet_config.mempool_endpoint if onchainwallet_config else None diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index f94b970a..73c87e7c 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -20,6 +20,7 @@ from .crud import ( get_charges, update_charge, ) +from .helpers import compact_charge from .models import CreateCharge #############################CHARGES########################## @@ -123,25 +124,13 @@ async def api_charge_balance(charge_id): try: r = await client.post( charge.webhook, - json={ - "id": charge.id, - "description": charge.description, - "onchainaddress": charge.onchainaddress, - "payment_request": charge.payment_request, - "payment_hash": charge.payment_hash, - "time": charge.time, - "amount": charge.amount, - "balance": charge.balance, - "paid": charge.paid, - "timestamp": charge.timestamp, - "completelink": charge.completelink, - }, + json=compact_charge(charge), timeout=40, ) except AssertionError: charge.webhook = None return { - **charge.dict(), + **compact_charge(charge), **{"time_elapsed": charge.time_elapsed}, **{"time_left": charge.time_left}, **{"paid": charge.paid}, From daf5768d52c39cbcf24279d2c8604a2fefa1a197 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 21 Oct 2022 15:49:17 +0300 Subject: [PATCH 074/154] fix: remove user from model (#1067) --- lnbits/extensions/watchonly/crud.py | 4 ++-- lnbits/extensions/watchonly/models.py | 1 - lnbits/extensions/watchonly/views_api.py | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py index de338b91..c4a1df72 100644 --- a/lnbits/extensions/watchonly/crud.py +++ b/lnbits/extensions/watchonly/crud.py @@ -10,7 +10,7 @@ from .models import Address, Config, WalletAccount ##########################WALLETS#################### -async def create_watch_wallet(w: WalletAccount) -> WalletAccount: +async def create_watch_wallet(user: str, w: WalletAccount) -> WalletAccount: wallet_id = urlsafe_short_hash() await db.execute( """ @@ -30,7 +30,7 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount: """, ( wallet_id, - w.user, + user, w.masterpub, w.fingerprint, w.title, diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index 622f5ec8..d8c278ff 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -14,7 +14,6 @@ class CreateWallet(BaseModel): class WalletAccount(BaseModel): id: str - user: str masterpub: str fingerprint: str title: str diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 750d46c9..9030b9c3 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -86,7 +86,6 @@ async def api_wallet_create_or_update( new_wallet = WalletAccount( id="none", - user=w.wallet.user, masterpub=data.masterpub, fingerprint=descriptor.keys[0].fingerprint.hex(), type=descriptor.scriptpubkey_type(), @@ -115,7 +114,7 @@ async def api_wallet_create_or_update( ) ) - wallet = await create_watch_wallet(new_wallet) + wallet = await create_watch_wallet(w.wallet.user, new_wallet) await api_get_addresses(wallet.id, w) except Exception as e: From 2d61a8854f38a61507289af3520e52ff54ccb1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 21 Oct 2022 17:35:58 +0200 Subject: [PATCH 075/154] improving on split payment handling --- lnbits/extensions/splitpayments/models.py | 2 +- lnbits/extensions/splitpayments/tasks.py | 72 ++++++++--------------- 2 files changed, 24 insertions(+), 50 deletions(-) diff --git a/lnbits/extensions/splitpayments/models.py b/lnbits/extensions/splitpayments/models.py index 4b95ed18..6338d97f 100644 --- a/lnbits/extensions/splitpayments/models.py +++ b/lnbits/extensions/splitpayments/models.py @@ -14,7 +14,7 @@ class Target(BaseModel): class TargetPutList(BaseModel): wallet: str = Query(...) alias: str = Query("") - percent: float = Query(..., ge=0.01) + percent: float = Query(..., ge=0.01, lt=100) class TargetPut(BaseModel): diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py index cfc6c226..53378b20 100644 --- a/lnbits/extensions/splitpayments/tasks.py +++ b/lnbits/extensions/splitpayments/tasks.py @@ -1,13 +1,11 @@ import asyncio -import json from loguru import logger -from lnbits.core import db as core_db -from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import get_current_extension_name, urlsafe_short_hash -from lnbits.tasks import internal_invoice_queue, register_invoice_listener +from lnbits.core.services import create_invoice, pay_invoice +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import register_invoice_listener from .crud import get_targets @@ -22,60 +20,36 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") == "splitpayments" or payment.extra.get("splitted"): - # already splitted, ignore + if payment.extra.get("tag") == "splitpayments": + # already a splitted payment, ignore return - # now we make some special internal transfers (from no one to the receiver) targets = await get_targets(payment.wallet_id) if not targets: return - transfers = [ - (target.wallet, int(target.percent * payment.amount / 100)) - for target in targets - ] - transfers = [(wallet, amount) for wallet, amount in transfers if amount > 0] - amount_left = payment.amount - sum([amount for _, amount in transfers]) + total_percent = sum([target.percent for target in targets]) - if amount_left < 0: - logger.error( - "splitpayments failure: amount_left is negative.", payment.payment_hash - ) + if total_percent > 100: + logger.error("splitpayment failure: total percent adds up to more than 100%") return - # mark the original payment with one extra key, "splitted" - # (this prevents us from doing this process again and it's informative) - # and reduce it by the amount we're going to send to the producer - await core_db.execute( - """ - UPDATE apipayments - SET extra = ?, amount = ? - WHERE hash = ? - AND checking_id NOT LIKE 'internal_%' - """, - ( - json.dumps(dict(**payment.extra, splitted=True)), - amount_left, - payment.payment_hash, - ), - ) - - # perform the internal transfer using the same payment_hash - for wallet, amount in transfers: - internal_checking_id = f"internal_{urlsafe_short_hash()}" - await create_payment( - wallet_id=wallet, - checking_id=internal_checking_id, - payment_request="", - payment_hash=payment.payment_hash, - amount=amount, - memo=payment.memo, - pending=False, + logger.debug(f"performing split payments to {len(targets)} targets") + for target in targets: + amount = int(payment.amount * target.percent / 100) # msats + payment_hash, payment_request = await create_invoice( + wallet_id=target.wallet, + amount=int(amount / 1000), # sats + internal=True, + memo=f"split payment: {target.percent}% for {target.alias or target.wallet}", extra={"tag": "splitpayments"}, ) + logger.debug(f"created split invoice: {payment_hash}") - # manually send this for now - await internal_invoice_queue.put(internal_checking_id) - return + checking_id = await pay_invoice( + payment_request=payment_request, + wallet_id=payment.wallet_id, + extra={"tag": "splitpayments"}, + ) + logger.debug(f"paid split invoice: {checking_id}") From fe879ad9b964766c25f8b59a0323d009d332166d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 21 Oct 2022 18:15:27 +0200 Subject: [PATCH 076/154] add tpos to pr --- lnbits/extensions/tpos/tasks.py | 58 ++++++++++----------------------- 1 file changed, 17 insertions(+), 41 deletions(-) diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py index f18d1689..6369bbc7 100644 --- a/lnbits/extensions/tpos/tasks.py +++ b/lnbits/extensions/tpos/tasks.py @@ -1,11 +1,11 @@ import asyncio -import json -from lnbits.core import db as core_db -from lnbits.core.crud import create_payment +from loguru import logger + from lnbits.core.models import Payment -from lnbits.helpers import get_current_extension_name, urlsafe_short_hash -from lnbits.tasks import internal_invoice_queue, register_invoice_listener +from lnbits.core.services import create_invoice, pay_invoice +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import register_invoice_listener from .crud import get_tpos @@ -20,11 +20,9 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") == "tpos" and payment.extra.get("tipSplitted"): - # already splitted, ignore + if payment.extra.get("tag") != "tpos": return - # now we make some special internal transfers (from no one to the receiver) tpos = await get_tpos(payment.extra.get("tposId")) tipAmount = payment.extra.get("tipAmount") @@ -32,39 +30,17 @@ async def on_invoice_paid(payment: Payment) -> None: # no tip amount return - tipAmount = tipAmount * 1000 - amount = payment.amount - tipAmount - - # mark the original payment with one extra key, "splitted" - # (this prevents us from doing this process again and it's informative) - # and reduce it by the amount we're going to send to the producer - await core_db.execute( - """ - UPDATE apipayments - SET extra = ?, amount = ? - WHERE hash = ? - AND checking_id NOT LIKE 'internal_%' - """, - ( - json.dumps(dict(**payment.extra, tipSplitted=True)), - amount, - payment.payment_hash, - ), - ) - - # perform the internal transfer using the same payment_hash - internal_checking_id = f"internal_{urlsafe_short_hash()}" - await create_payment( + payment_hash, payment_request = await create_invoice( wallet_id=tpos.tip_wallet, - checking_id=internal_checking_id, - payment_request="", - payment_hash=payment.payment_hash, - amount=tipAmount, - memo=f"Tip for {payment.memo}", - pending=False, - extra={"tipSplitted": True}, + amount=int(tipAmount), # sats + internal=True, + memo=f"tpos tip", ) + logger.debug(f"tpos: tip invoice created: {payment_hash}") - # manually send this for now - await internal_invoice_queue.put(internal_checking_id) - return + checking_id = await pay_invoice( + payment_request=payment_request, + wallet_id=payment.wallet_id, + extra={"tag": "tpos"}, + ) + logger.debug(f"tpos: tip invoice paid: {checking_id}") From e1fbb8c3cd112d56bbd147565cc649f007d42287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 21 Oct 2022 18:28:39 +0200 Subject: [PATCH 077/154] fix livestream extension --- lnbits/extensions/livestream/tasks.py | 52 ++++++++------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/lnbits/extensions/livestream/tasks.py b/lnbits/extensions/livestream/tasks.py index 626c698c..d081332f 100644 --- a/lnbits/extensions/livestream/tasks.py +++ b/lnbits/extensions/livestream/tasks.py @@ -4,10 +4,10 @@ import json from loguru import logger from lnbits.core import db as core_db -from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import get_current_extension_name, urlsafe_short_hash -from lnbits.tasks import internal_invoice_listener, register_invoice_listener +from lnbits.core.services import create_invoice, pay_invoice +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import register_invoice_listener from .crud import get_livestream_by_track, get_producer, get_track @@ -44,44 +44,20 @@ async def on_invoice_paid(payment: Payment) -> None: # now we make a special kind of internal transfer amount = int(payment.amount * (100 - ls.fee_pct) / 100) - # mark the original payment with two extra keys, "shared_with" and "received" - # (this prevents us from doing this process again and it's informative) - # and reduce it by the amount we're going to send to the producer - await core_db.execute( - """ - UPDATE apipayments - SET extra = ?, amount = ? - WHERE hash = ? - AND checking_id NOT LIKE 'internal_%' - """, - ( - json.dumps( - dict( - **payment.extra, - shared_with=[producer.name, producer.id], - received=payment.amount, - ) - ), - payment.amount - amount, - payment.payment_hash, - ), - ) - - # perform an internal transfer using the same payment_hash to the producer wallet - internal_checking_id = f"internal_{urlsafe_short_hash()}" - await create_payment( - wallet_id=producer.wallet, - checking_id=internal_checking_id, - payment_request="", - payment_hash=payment.payment_hash, - amount=amount, + payment_hash, payment_request = await create_invoice( + wallet_id=tpos.tip_wallet, + amount=amount, # sats + internal=True, memo=f"Revenue from '{track.name}'.", - pending=False, ) + logger.debug(f"livestream: producer invoice created: {payment_hash}") - # manually send this for now - # await internal_invoice_paid.send(internal_checking_id) - await internal_invoice_listener.put(internal_checking_id) + checking_id = await pay_invoice( + payment_request=payment_request, + wallet_id=payment.wallet_id, + extra={"tag": "livestream"}, + ) + logger.debug(f"livestream: producer invoice paid: {checking_id}") # so the flow is the following: # - we receive, say, 1000 satoshis From c8a33b0b93bb3e04bfb3d4ef1903cd1396fb3068 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 21 Oct 2022 20:06:50 +0100 Subject: [PATCH 078/154] Adding loads on gpios to switch --- lnbits/extensions/lnurldevice/lnurl.py | 5 +++-- lnbits/extensions/lnurldevice/tasks.py | 2 +- lnbits/extensions/lnurldevice/views.py | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index 79892b78..c83db6b8 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -91,6 +91,7 @@ async def lnurl_v1_params( device_id: str = Query(None), p: str = Query(None), atm: str = Query(None), + gpio: str = Query(None), ): device = await get_lnurldevice(device_id) if not device: @@ -114,7 +115,7 @@ async def lnurl_v1_params( deviceid=device.id, payload="bla", sats=price_msat, - pin=1, + pin=gpio, payhash="bla", ) if not lnurldevicepayment: @@ -236,7 +237,7 @@ async def lnurl_callback( amount=lnurldevicepayment.sats / 1000, memo=device.title + "-" + lnurldevicepayment.id, unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"), - extra={"tag": "Switch", "id": paymentid, "time": device.amount}, + extra={"tag": "Switch", "pin": ,"id": paymentid, "time": device.amount}, ) lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=payment_hash diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index c8f3db04..55cbaa98 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -36,5 +36,5 @@ async def on_invoice_paid(payment: Payment) -> None: lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=payment.extra.get("id"), payhash="used" ) - return await updater(lnurldevicepayment.deviceid) + return await updater(lnurldevicepayment.deviceid, lnurldevicepayment.pin) return diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index 5c6eba24..d17db7d7 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -103,8 +103,8 @@ async def websocket_endpoint(websocket: WebSocket, lnurldevice_id: str): manager.disconnect(websocket) -async def updater(lnurldevice_id): +async def updater(lnurldevice_id, lnurldevice_pin): lnurldevice = await get_lnurldevice(lnurldevice_id) if not lnurldevice: return - await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id) + return await manager.send_personal_message(f"pin:{lnurldevice.pin},amount:{lnurldevice.amount}", lnurldevice_id) From 3814989b3a142baaea1d010f7022882b72280f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Pha=CC=A3m-Bachelart?= Date: Mon, 24 Oct 2022 11:46:32 +0200 Subject: [PATCH 079/154] Add cliche wallet (#1071) --- docs/guide/wallets.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/guide/wallets.md b/docs/guide/wallets.md index 80fb54c0..10724f34 100644 --- a/docs/guide/wallets.md +++ b/docs/guide/wallets.md @@ -79,3 +79,8 @@ For the invoice to work you must have a publicly accessible URL in your LNbits. - `LNBITS_BACKEND_WALLET_CLASS`: **OpenNodeWallet** - `OPENNODE_API_ENDPOINT`: https://api.opennode.com/ - `OPENNODE_KEY`: opennodeAdminApiKey + + +### Cliche Wallet + +- `CLICHE_ENDPOINT`: ws://127.0.0.1:12000 From fc1a87f617a60de29552c8196a7f08050bb60ce5 Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Mon, 24 Oct 2022 11:50:52 +0200 Subject: [PATCH 080/154] Remove unused proxy_fix (#1007) --- lnbits/app.py | 1 - lnbits/proxy_fix.py | 95 --------------------------------------------- 2 files changed, 96 deletions(-) delete mode 100644 lnbits/proxy_fix.py diff --git a/lnbits/app.py b/lnbits/app.py index 8b9cf798..075828ef 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -91,7 +91,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: ) app.add_middleware(GZipMiddleware, minimum_size=1000) - # app.add_middleware(ASGIProxyFix) check_funding_source(app) register_assets(app) diff --git a/lnbits/proxy_fix.py b/lnbits/proxy_fix.py deleted file mode 100644 index 897835e0..00000000 --- a/lnbits/proxy_fix.py +++ /dev/null @@ -1,95 +0,0 @@ -from functools import partial -from typing import Callable, List, Optional -from urllib.parse import urlparse -from urllib.request import parse_http_list as _parse_list_header - -from quart import Request -from quart_trio.asgi import TrioASGIHTTPConnection -from werkzeug.datastructures import Headers - - -class ASGIProxyFix(TrioASGIHTTPConnection): - def _create_request_from_scope(self, send: Callable) -> Request: - headers = Headers() - headers["Remote-Addr"] = (self.scope.get("client") or [""])[0] - for name, value in self.scope["headers"]: - headers.add(name.decode("latin1").title(), value.decode("latin1")) - if self.scope["http_version"] < "1.1": - headers.setdefault("Host", self.app.config["SERVER_NAME"] or "") - - path = self.scope["path"] - path = path if path[0] == "/" else urlparse(path).path - - x_proto = self._get_real_value(1, headers.get("X-Forwarded-Proto")) - if x_proto: - self.scope["scheme"] = x_proto - - x_host = self._get_real_value(1, headers.get("X-Forwarded-Host")) - if x_host: - headers["host"] = x_host.lower() - - return self.app.request_class( - self.scope["method"], - self.scope["scheme"], - path, - self.scope["query_string"], - headers, - self.scope.get("root_path", ""), - self.scope["http_version"], - max_content_length=self.app.config["MAX_CONTENT_LENGTH"], - body_timeout=self.app.config["BODY_TIMEOUT"], - send_push_promise=partial(self._send_push_promise, send), - scope=self.scope, - ) - - def _get_real_value(self, trusted: int, value: Optional[str]) -> Optional[str]: - """Get the real value from a list header based on the configured - number of trusted proxies. - :param trusted: Number of values to trust in the header. - :param value: Comma separated list header value to parse. - :return: The real value, or ``None`` if there are fewer values - than the number of trusted proxies. - .. versionchanged:: 1.0 - Renamed from ``_get_trusted_comma``. - .. versionadded:: 0.15 - """ - if not (trusted and value): - return None - - values = self.parse_list_header(value) - if len(values) >= trusted: - return values[-trusted] - - return None - - def parse_list_header(self, value: str) -> List[str]: - result = [] - for item in _parse_list_header(value): - if item[:1] == item[-1:] == '"': - item = self.unquote_header_value(item[1:-1]) - result.append(item) - return result - - def unquote_header_value(self, value: str, is_filename: bool = False) -> str: - r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). - This does not use the real unquoting but what browsers are actually - using for quoting. - .. versionadded:: 0.5 - :param value: the header value to unquote. - :param is_filename: The value represents a filename or path. - """ - if value and value[0] == value[-1] == '"': - # this is not the real unquoting, but fixing this so that the - # RFC is met will result in bugs with internet explorer and - # probably some other browsers as well. IE for example is - # uploading files with "C:\foo\bar.txt" as filename - value = value[1:-1] - - # if this is a filename and the starting characters look like - # a UNC path, then just return the value without quotes. Using the - # replace sequence below on a UNC path has the effect of turning - # the leading double slash into a single slash and then - # _fix_ie_filename() doesn't work correctly. See #458. - if not is_filename or value[:2] != "\\\\": - return value.replace("\\\\", "\\").replace('\\"', '"') - return value From 193d037ad6aa102152a824611c65a96a6ff876df Mon Sep 17 00:00:00 2001 From: Anton Kovalenko Date: Mon, 24 Oct 2022 13:23:11 +0300 Subject: [PATCH 081/154] Allow more than 2-nd level domain in incoming ln-addresses (#914) --- lnbits/core/views/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index c07df568..983d5a26 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -476,7 +476,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type except: # parse internet identifier (user@domain.com) name_domain = code.split("@") - if len(name_domain) == 2 and len(name_domain[1].split(".")) == 2: + if len(name_domain) == 2 and len(name_domain[1].split(".")) >= 2: name, domain = name_domain url = ( ("http://" if domain.endswith(".onion") else "https://") From 09cf654427d8f42baee484a5146a180b1261b1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 24 Oct 2022 12:32:20 +0200 Subject: [PATCH 082/154] Proper Error Handling for qrcode-stream components (#1076) * proper errorhandling for camera * fix javascript for calle --- lnbits/core/static/js/wallet.js | 29 ++++++++++++++++++++++++++ lnbits/core/templates/core/wallet.html | 2 ++ 2 files changed, 31 insertions(+) diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index 76d82ad4..66801313 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -361,6 +361,35 @@ new Vue({ this.receive.status = 'pending' }) }, + onInitQR: async function (promise) { + try { + await promise + } catch (error) { + let mapping = { + NotAllowedError: 'ERROR: you need to grant camera access permission', + NotFoundError: 'ERROR: no camera on this device', + NotSupportedError: + 'ERROR: secure context required (HTTPS, localhost)', + NotReadableError: 'ERROR: is the camera already in use?', + OverconstrainedError: 'ERROR: installed cameras are not suitable', + StreamApiNotSupportedError: + 'ERROR: Stream API is not supported in this browser', + InsecureContextError: + 'ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.' + } + let valid_error = Object.keys(mapping).filter(key => { + return error.name === key + }) + let camera_error = valid_error + ? mapping[valid_error] + : `ERROR: Camera error (${error.name})` + this.parse.camera.show = false + this.$q.notify({ + message: camera_error, + type: 'negative' + }) + } + }, decodeQR: function (res) { this.parse.data.request = res this.decodeRequest() diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index bccdc2b4..4bf6067c 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -653,6 +653,7 @@ @@ -671,6 +672,7 @@
From 09871bbabc27b69886d56770178b19f445b7c48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 24 Oct 2022 16:29:30 +0200 Subject: [PATCH 083/154] fix mypy for extensions (#873) * explicitly exclude all extensions from mypy * fix example extension mypy * fix subdomains extension mypy + 1 type error fixed * fix mypy discordbot * mypy check copilot extensnion * copilot black * add invoices ext to ignore * add boltz and boltcard * copilit id is necessary * was discordbot is ok Co-authored-by: dni --- lnbits/extensions/copilot/crud.py | 8 ++--- lnbits/extensions/copilot/tasks.py | 20 ++++++------ lnbits/extensions/copilot/views.py | 8 +++-- lnbits/extensions/copilot/views_api.py | 9 +++--- lnbits/extensions/discordbot/crud.py | 6 ++-- lnbits/extensions/discordbot/views.py | 4 ++- lnbits/extensions/discordbot/views_api.py | 39 +++++++++++++---------- lnbits/extensions/example/views.py | 5 ++- lnbits/extensions/subdomains/crud.py | 4 +-- lnbits/extensions/subdomains/models.py | 30 ++++++++--------- lnbits/extensions/subdomains/tasks.py | 4 +-- lnbits/extensions/subdomains/views.py | 4 ++- lnbits/extensions/subdomains/views_api.py | 25 +++++++++++---- pyproject.toml | 30 +++++++++++++++-- 14 files changed, 125 insertions(+), 71 deletions(-) diff --git a/lnbits/extensions/copilot/crud.py b/lnbits/extensions/copilot/crud.py index d0da044e..5ecb5cd4 100644 --- a/lnbits/extensions/copilot/crud.py +++ b/lnbits/extensions/copilot/crud.py @@ -10,7 +10,7 @@ from .models import Copilots, CreateCopilotData async def create_copilot( data: CreateCopilotData, inkey: Optional[str] = "" -) -> Copilots: +) -> Optional[Copilots]: copilot_id = urlsafe_short_hash() await db.execute( """ @@ -67,19 +67,19 @@ async def create_copilot( async def update_copilot( - data: CreateCopilotData, copilot_id: Optional[str] = "" + data: CreateCopilotData, copilot_id: str ) -> Optional[Copilots]: q = ", ".join([f"{field[0]} = ?" for field in data]) items = [f"{field[1]}" for field in data] items.append(copilot_id) - await db.execute(f"UPDATE copilot.newer_copilots SET {q} WHERE id = ?", (items)) + await db.execute(f"UPDATE copilot.newer_copilots SET {q} WHERE id = ?", (items,)) row = await db.fetchone( "SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,) ) return Copilots(**row) if row else None -async def get_copilot(copilot_id: str) -> Copilots: +async def get_copilot(copilot_id: str) -> Optional[Copilots]: row = await db.fetchone( "SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,) ) diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py index c59ef4cc..48ad7813 100644 --- a/lnbits/extensions/copilot/tasks.py +++ b/lnbits/extensions/copilot/tasks.py @@ -26,7 +26,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: webhook = None data = None - if payment.extra.get("tag") != "copilot": + if not payment.extra or payment.extra.get("tag") != "copilot": # not an copilot invoice return @@ -71,12 +71,12 @@ async def on_invoice_paid(payment: Payment) -> None: async def mark_webhook_sent(payment: Payment, status: int) -> None: - payment.extra["wh_status"] = status - - await core_db.execute( - """ - UPDATE apipayments SET extra = ? - WHERE hash = ? - """, - (json.dumps(payment.extra), payment.payment_hash), - ) + if payment.extra: + payment.extra["wh_status"] = status + await core_db.execute( + """ + UPDATE apipayments SET extra = ? + WHERE hash = ? + """, + (json.dumps(payment.extra), payment.payment_hash), + ) diff --git a/lnbits/extensions/copilot/views.py b/lnbits/extensions/copilot/views.py index 7ee7f590..b4a2354a 100644 --- a/lnbits/extensions/copilot/views.py +++ b/lnbits/extensions/copilot/views.py @@ -15,7 +15,9 @@ templates = Jinja2Templates(directory="templates") @copilot_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): +async def index( + request: Request, user: User = Depends(check_user_exists) # type: ignore +): return copilot_renderer().TemplateResponse( "copilot/index.html", {"request": request, "user": user.dict()} ) @@ -44,7 +46,7 @@ class ConnectionManager: async def connect(self, websocket: WebSocket, copilot_id: str): await websocket.accept() - websocket.id = copilot_id + websocket.id = copilot_id # type: ignore self.active_connections.append(websocket) def disconnect(self, websocket: WebSocket): @@ -52,7 +54,7 @@ class ConnectionManager: async def send_personal_message(self, message: str, copilot_id: str): for connection in self.active_connections: - if connection.id == copilot_id: + if connection.id == copilot_id: # type: ignore await connection.send_text(message) async def broadcast(self, message: str): diff --git a/lnbits/extensions/copilot/views_api.py b/lnbits/extensions/copilot/views_api.py index 91b0572a..46611a2e 100644 --- a/lnbits/extensions/copilot/views_api.py +++ b/lnbits/extensions/copilot/views_api.py @@ -23,7 +23,7 @@ from .views import updater @copilot_ext.get("/api/v1/copilot") async def api_copilots_retrieve( - req: Request, wallet: WalletTypeInfo = Depends(get_key_type) + req: Request, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): wallet_user = wallet.wallet.user copilots = [copilot.dict() for copilot in await get_copilots(wallet_user)] @@ -37,7 +37,7 @@ async def api_copilots_retrieve( async def api_copilot_retrieve( req: Request, copilot_id: str = Query(None), - wallet: WalletTypeInfo = Depends(get_key_type), + wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore ): copilot = await get_copilot(copilot_id) if not copilot: @@ -54,7 +54,7 @@ async def api_copilot_retrieve( async def api_copilot_create_or_update( data: CreateCopilotData, copilot_id: str = Query(None), - wallet: WalletTypeInfo = Depends(require_admin_key), + wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore ): data.user = wallet.wallet.user data.wallet = wallet.wallet.id @@ -67,7 +67,8 @@ async def api_copilot_create_or_update( @copilot_ext.delete("/api/v1/copilot/{copilot_id}") async def api_copilot_delete( - copilot_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key) + copilot_id: str = Query(None), + wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore ): copilot = await get_copilot(copilot_id) diff --git a/lnbits/extensions/discordbot/crud.py b/lnbits/extensions/discordbot/crud.py index 5661fcb4..629a5c00 100644 --- a/lnbits/extensions/discordbot/crud.py +++ b/lnbits/extensions/discordbot/crud.py @@ -98,21 +98,21 @@ async def get_discordbot_wallet(wallet_id: str) -> Optional[Wallets]: return Wallets(**row) if row else None -async def get_discordbot_wallets(admin_id: str) -> Optional[Wallets]: +async def get_discordbot_wallets(admin_id: str) -> List[Wallets]: rows = await db.fetchall( "SELECT * FROM discordbot.wallets WHERE admin = ?", (admin_id,) ) return [Wallets(**row) for row in rows] -async def get_discordbot_users_wallets(user_id: str) -> Optional[Wallets]: +async def get_discordbot_users_wallets(user_id: str) -> List[Wallets]: rows = await db.fetchall( """SELECT * FROM discordbot.wallets WHERE "user" = ?""", (user_id,) ) return [Wallets(**row) for row in rows] -async def get_discordbot_wallet_transactions(wallet_id: str) -> Optional[Payment]: +async def get_discordbot_wallet_transactions(wallet_id: str) -> List[Payment]: return await get_payments( wallet_id=wallet_id, complete=True, pending=False, outgoing=True, incoming=True ) diff --git a/lnbits/extensions/discordbot/views.py b/lnbits/extensions/discordbot/views.py index a5395e21..ec7d18cc 100644 --- a/lnbits/extensions/discordbot/views.py +++ b/lnbits/extensions/discordbot/views.py @@ -9,7 +9,9 @@ from . import discordbot_ext, discordbot_renderer @discordbot_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): +async def index( + request: Request, user: User = Depends(check_user_exists) # type: ignore +): return discordbot_renderer().TemplateResponse( "discordbot/index.html", {"request": request, "user": user.dict()} ) diff --git a/lnbits/extensions/discordbot/views_api.py b/lnbits/extensions/discordbot/views_api.py index 6f213a89..e6d004db 100644 --- a/lnbits/extensions/discordbot/views_api.py +++ b/lnbits/extensions/discordbot/views_api.py @@ -27,32 +27,37 @@ from .models import CreateUserData, CreateUserWallet @discordbot_ext.get("/api/v1/users", status_code=HTTPStatus.OK) -async def api_discordbot_users(wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_discordbot_users( + wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore +): user_id = wallet.wallet.user return [user.dict() for user in await get_discordbot_users(user_id)] @discordbot_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK) -async def api_discordbot_user(user_id, wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_discordbot_user( + user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore +): user = await get_discordbot_user(user_id) - return user.dict() + if user: + return user.dict() @discordbot_ext.post("/api/v1/users", status_code=HTTPStatus.CREATED) async def api_discordbot_users_create( - data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type) + data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): user = await create_discordbot_user(data) full = user.dict() - full["wallets"] = [ - wallet.dict() for wallet in await get_discordbot_users_wallets(user.id) - ] + wallets = await get_discordbot_users_wallets(user.id) + if wallets: + full["wallets"] = [wallet for wallet in wallets] return full @discordbot_ext.delete("/api/v1/users/{user_id}") async def api_discordbot_users_delete( - user_id, wallet: WalletTypeInfo = Depends(get_key_type) + user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): user = await get_discordbot_user(user_id) if not user: @@ -75,7 +80,7 @@ async def api_discordbot_activate_extension( raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." ) - update_user_extension(user_id=userid, extension=extension, active=active) + await update_user_extension(user_id=userid, extension=extension, active=active) return {"extension": "updated"} @@ -84,7 +89,7 @@ async def api_discordbot_activate_extension( @discordbot_ext.post("/api/v1/wallets") async def api_discordbot_wallets_create( - data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type) + data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): user = await create_discordbot_wallet( user_id=data.user_id, wallet_name=data.wallet_name, admin_id=data.admin_id @@ -93,28 +98,30 @@ async def api_discordbot_wallets_create( @discordbot_ext.get("/api/v1/wallets") -async def api_discordbot_wallets(wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_discordbot_wallets( + wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore +): admin_id = wallet.wallet.user - return [wallet.dict() for wallet in await get_discordbot_wallets(admin_id)] + return await get_discordbot_wallets(admin_id) @discordbot_ext.get("/api/v1/transactions/{wallet_id}") async def api_discordbot_wallet_transactions( - wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) + wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): return await get_discordbot_wallet_transactions(wallet_id) @discordbot_ext.get("/api/v1/wallets/{user_id}") async def api_discordbot_users_wallets( - user_id, wallet: WalletTypeInfo = Depends(get_key_type) + user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): - return [s_wallet.dict() for s_wallet in await get_discordbot_users_wallets(user_id)] + return await get_discordbot_users_wallets(user_id) @discordbot_ext.delete("/api/v1/wallets/{wallet_id}") async def api_discordbot_wallets_delete( - wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) + wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): get_wallet = await get_discordbot_wallet(wallet_id) if not get_wallet: diff --git a/lnbits/extensions/example/views.py b/lnbits/extensions/example/views.py index 252b4726..29b257f4 100644 --- a/lnbits/extensions/example/views.py +++ b/lnbits/extensions/example/views.py @@ -12,7 +12,10 @@ templates = Jinja2Templates(directory="templates") @example_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): +async def index( + request: Request, + user: User = Depends(check_user_exists), # type: ignore +): return example_renderer().TemplateResponse( "example/index.html", {"request": request, "user": user.dict()} ) diff --git a/lnbits/extensions/subdomains/crud.py b/lnbits/extensions/subdomains/crud.py index 207e2d1d..aa358d11 100644 --- a/lnbits/extensions/subdomains/crud.py +++ b/lnbits/extensions/subdomains/crud.py @@ -3,10 +3,10 @@ from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash from . import db -from .models import CreateDomain, Domains, Subdomains +from .models import CreateDomain, CreateSubdomain, Domains, Subdomains -async def create_subdomain(payment_hash, wallet, data: CreateDomain) -> Subdomains: +async def create_subdomain(payment_hash, wallet, data: CreateSubdomain) -> Subdomains: await db.execute( """ INSERT INTO subdomains.subdomain (id, domain, email, subdomain, ip, wallet, sats, duration, paid, record_type) diff --git a/lnbits/extensions/subdomains/models.py b/lnbits/extensions/subdomains/models.py index 17004504..39e17615 100644 --- a/lnbits/extensions/subdomains/models.py +++ b/lnbits/extensions/subdomains/models.py @@ -3,24 +3,24 @@ from pydantic.main import BaseModel class CreateDomain(BaseModel): - wallet: str = Query(...) - domain: str = Query(...) - cf_token: str = Query(...) - cf_zone_id: str = Query(...) - webhook: str = Query("") - description: str = Query(..., min_length=0) - cost: int = Query(..., ge=0) - allowed_record_types: str = Query(...) + wallet: str = Query(...) # type: ignore + domain: str = Query(...) # type: ignore + cf_token: str = Query(...) # type: ignore + cf_zone_id: str = Query(...) # type: ignore + webhook: str = Query("") # type: ignore + description: str = Query(..., min_length=0) # type: ignore + cost: int = Query(..., ge=0) # type: ignore + allowed_record_types: str = Query(...) # type: ignore class CreateSubdomain(BaseModel): - domain: str = Query(...) - subdomain: str = Query(...) - email: str = Query(...) - ip: str = Query(...) - sats: int = Query(..., ge=0) - duration: int = Query(...) - record_type: str = Query(...) + domain: str = Query(...) # type: ignore + subdomain: str = Query(...) # type: ignore + email: str = Query(...) # type: ignore + ip: str = Query(...) # type: ignore + sats: int = Query(..., ge=0) # type: ignore + duration: int = Query(...) # type: ignore + record_type: str = Query(...) # type: ignore class Domains(BaseModel): diff --git a/lnbits/extensions/subdomains/tasks.py b/lnbits/extensions/subdomains/tasks.py index 04ee2dd4..c5a7f47b 100644 --- a/lnbits/extensions/subdomains/tasks.py +++ b/lnbits/extensions/subdomains/tasks.py @@ -20,7 +20,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") != "lnsubdomain": + if not payment.extra or payment.extra.get("tag") != "lnsubdomain": # not an lnurlp invoice return @@ -37,7 +37,7 @@ async def on_invoice_paid(payment: Payment) -> None: ) ### Use webhook to notify about cloudflare registration - if domain.webhook: + if domain and domain.webhook: async with httpx.AsyncClient() as client: try: r = await client.post( diff --git a/lnbits/extensions/subdomains/views.py b/lnbits/extensions/subdomains/views.py index df387ba8..962f850d 100644 --- a/lnbits/extensions/subdomains/views.py +++ b/lnbits/extensions/subdomains/views.py @@ -16,7 +16,9 @@ templates = Jinja2Templates(directory="templates") @subdomains_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): +async def index( + request: Request, user: User = Depends(check_user_exists) # type:ignore +): return subdomains_renderer().TemplateResponse( "subdomains/index.html", {"request": request, "user": user.dict()} ) diff --git a/lnbits/extensions/subdomains/views_api.py b/lnbits/extensions/subdomains/views_api.py index b01e6ffb..34d8e75b 100644 --- a/lnbits/extensions/subdomains/views_api.py +++ b/lnbits/extensions/subdomains/views_api.py @@ -29,12 +29,15 @@ from .crud import ( @subdomains_ext.get("/api/v1/domains") async def api_domains( - g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) + g: WalletTypeInfo = Depends(get_key_type), # type: ignore + all_wallets: bool = Query(False), ): wallet_ids = [g.wallet.id] if all_wallets: - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + if user is not None: + wallet_ids = user.wallet_ids return [domain.dict() for domain in await get_domains(wallet_ids)] @@ -42,7 +45,9 @@ async def api_domains( @subdomains_ext.post("/api/v1/domains") @subdomains_ext.put("/api/v1/domains/{domain_id}") async def api_domain_create( - data: CreateDomain, domain_id=None, g: WalletTypeInfo = Depends(get_key_type) + data: CreateDomain, + domain_id=None, + g: WalletTypeInfo = Depends(get_key_type), # type: ignore ): if domain_id: domain = await get_domain(domain_id) @@ -63,7 +68,9 @@ async def api_domain_create( @subdomains_ext.delete("/api/v1/domains/{domain_id}") -async def api_domain_delete(domain_id, g: WalletTypeInfo = Depends(get_key_type)): +async def api_domain_delete( + domain_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore +): domain = await get_domain(domain_id) if not domain: @@ -82,12 +89,14 @@ async def api_domain_delete(domain_id, g: WalletTypeInfo = Depends(get_key_type) @subdomains_ext.get("/api/v1/subdomains") async def api_subdomains( - all_wallets: bool = Query(False), g: WalletTypeInfo = Depends(get_key_type) + all_wallets: bool = Query(False), g: WalletTypeInfo = Depends(get_key_type) # type: ignore ): wallet_ids = [g.wallet.id] if all_wallets: - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + if user is not None: + wallet_ids = user.wallet_ids return [domain.dict() for domain in await get_subdomains(wallet_ids)] @@ -173,7 +182,9 @@ async def api_subdomain_send_subdomain(payment_hash): @subdomains_ext.delete("/api/v1/subdomains/{subdomain_id}") -async def api_subdomain_delete(subdomain_id, g: WalletTypeInfo = Depends(get_key_type)): +async def api_subdomain_delete( + subdomain_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore +): subdomain = await get_subdomain(subdomain_id) if not subdomain: diff --git a/pyproject.toml b/pyproject.toml index 19dac860..7418de27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,8 +89,34 @@ profile = "black" ignore_missing_imports = "True" files = "lnbits" exclude = """(?x)( - ^lnbits/extensions. - | ^lnbits/wallets/lnd_grpc_files. + ^lnbits/extensions/bleskomat. + | ^lnbits/extensions/boltz. + | ^lnbits/extensions/boltcards. + | ^lnbits/extensions/events. + | ^lnbits/extensions/hivemind. + | ^lnbits/extensions/invoices. + | ^lnbits/extensions/jukebox. + | ^lnbits/extensions/livestream. + | ^lnbits/extensions/lnaddress. + | ^lnbits/extensions/lndhub. + | ^lnbits/extensions/lnticket. + | ^lnbits/extensions/lnurldevice. + | ^lnbits/extensions/lnurlp. + | ^lnbits/extensions/lnurlpayout. + | ^lnbits/extensions/ngrok. + | ^lnbits/extensions/offlineshop. + | ^lnbits/extensions/paywall. + | ^lnbits/extensions/satsdice. + | ^lnbits/extensions/satspay. + | ^lnbits/extensions/scrub. + | ^lnbits/extensions/splitpayments. + | ^lnbits/extensions/streamalerts. + | ^lnbits/extensions/tipjar. + | ^lnbits/extensions/tpos. + | ^lnbits/extensions/usermanager. + | ^lnbits/extensions/watchonly. + | ^lnbits/extensions/withdraw. + | ^lnbits/wallets/lnd_grpc_files. )""" [tool.pytest.ini_options] From 3552443f993d4cdf484912f72024401e767afa03 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 25 Oct 2022 11:10:34 +0100 Subject: [PATCH 084/154] adding migrations --- lnbits/extensions/lnurldevice/migrations.py | 25 +++++++++++++++++++++ lnbits/extensions/lnurldevice/models.py | 14 ++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index 7305cceb..52ecba7e 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -88,3 +88,28 @@ async def m003_redux(db): await db.execute( "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;" ) + +async def m004_redux(db): + """ + Add 'meta' for storing various metadata about the wallet + """ + await db.execute( + """ + ALTER TABLE lnurldevice.lnurldevices ADD COLUMN ( + amount1, + amount2, + amount3, + amount4, + time, + time1, + time2, + time3, + time4, + pin1, + pin2, + pin3, + pin4, + ) + INT DEFAULT 0; + """ + ) \ No newline at end of file diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index 01bcc2ba..a4af4404 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -18,6 +18,20 @@ class createLnurldevice(BaseModel): device: str profit: float amount: int + amount1: int + amount2: int + amount3: int + amount4: int + pin: int + pin1: int + pin2: int + pin3: int + pin4: int + time: int + time1: int + time2: int + time3: int + time4: int class lnurldevices(BaseModel): From e88b18a41746c028b125a96fef4d8c2afd89e584 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 25 Oct 2022 12:09:40 +0100 Subject: [PATCH 085/154] added migrate --- lnbits/extensions/lnurldevice/crud.py | 30 ++- lnbits/extensions/lnurldevice/lnurl.py | 8 +- lnbits/extensions/lnurldevice/migrations.py | 37 ++-- lnbits/extensions/lnurldevice/models.py | 30 ++- lnbits/extensions/lnurldevice/tasks.py | 2 +- .../templates/lnurldevice/index.html | 189 ++++++++++++++++-- lnbits/extensions/lnurldevice/views.py | 4 +- 7 files changed, 241 insertions(+), 59 deletions(-) diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py index 4c25e4cb..f9a13b58 100644 --- a/lnbits/extensions/lnurldevice/crud.py +++ b/lnbits/extensions/lnurldevice/crud.py @@ -23,9 +23,22 @@ async def create_lnurldevice( currency, device, profit, - amount + amount, + pin, + profit1, + amount1, + pin1, + profit2, + amount2, + pin2, + profit3, + amount3, + pin3, + profit4, + amount4, + pin4, ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( lnurldevice_id, @@ -36,6 +49,19 @@ async def create_lnurldevice( data.device, data.profit, data.amount, + data.pin, + data.profit1, + data.amount1, + data.pin1, + data.profit2, + data.amount2, + data.pin2, + data.profit3, + data.amount3, + data.pin3, + data.profit4, + data.amount4, + data.pin4 ), ) return await get_lnurldevice(lnurldevice_id) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index c83db6b8..72749e85 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -92,6 +92,8 @@ async def lnurl_v1_params( p: str = Query(None), atm: str = Query(None), gpio: str = Query(None), + profit: str = Query(None), + amount: str = Query(None), ): device = await get_lnurldevice(device_id) if not device: @@ -106,14 +108,14 @@ async def lnurl_v1_params( if device.device == "switch": price_msat = ( - await fiat_amount_as_satoshis(float(device.profit), device.currency) + await fiat_amount_as_satoshis(float(profit), device.currency) if device.currency != "sat" else amount_in_cent ) * 1000 lnurldevicepayment = await create_lnurldevicepayment( deviceid=device.id, - payload="bla", + payload=amount, sats=price_msat, pin=gpio, payhash="bla", @@ -237,7 +239,7 @@ async def lnurl_callback( amount=lnurldevicepayment.sats / 1000, memo=device.title + "-" + lnurldevicepayment.id, unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"), - extra={"tag": "Switch", "pin": ,"id": paymentid, "time": device.amount}, + extra={"tag": "Switch", "pin": lnurldevicepayment.pin,"amount": int(lnurldevicepayment.payload),"id": paymentid}, ) lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=payment_hash diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index 52ecba7e..ebe45301 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -93,23 +93,20 @@ async def m004_redux(db): """ Add 'meta' for storing various metadata about the wallet """ - await db.execute( - """ - ALTER TABLE lnurldevice.lnurldevices ADD COLUMN ( - amount1, - amount2, - amount3, - amount4, - time, - time1, - time2, - time3, - time4, - pin1, - pin2, - pin3, - pin4, - ) - INT DEFAULT 0; - """ - ) \ No newline at end of file + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin INT DEFAULT 0") + + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit1 FLOAT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount1 INT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin1 INT DEFAULT 0") + + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit2 FLOAT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount2 INT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin2 INT DEFAULT 0") + + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit3 FLOAT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount3 INT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin3 INT DEFAULT 0") + + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit4 FLOAT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount4 INT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin4 INT DEFAULT 0") \ No newline at end of file diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index a4af4404..f4638bcd 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -18,20 +18,19 @@ class createLnurldevice(BaseModel): device: str profit: float amount: int - amount1: int - amount2: int - amount3: int - amount4: int pin: int + profit1: float + amount1: int pin1: int + profit2: float + amount2: int pin2: int + profit3: float + amount3: int pin3: int + profit4: float + amount4: int pin4: int - time: int - time1: int - time2: int - time3: int - time4: int class lnurldevices(BaseModel): @@ -43,6 +42,19 @@ class lnurldevices(BaseModel): device: str profit: float amount: int + pin: int + profit1: float + amount1: int + pin1: int + profit2: float + amount2: int + pin2: int + profit3: float + amount3: int + pin3: int + profit4: float + amount4: int + pin4: int timestamp: str def from_row(cls, row: Row) -> "lnurldevices": diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 55cbaa98..2867577d 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -36,5 +36,5 @@ async def on_invoice_paid(payment: Payment) -> None: lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=payment.extra.get("id"), payhash="used" ) - return await updater(lnurldevicepayment.deviceid, lnurldevicepayment.pin) + return await updater(lnurldevicepayment.deviceid, lnurldevicepayment.pin, lnurldevicepayment.amount) return diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index 028dd94b..ae56c283 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -230,27 +230,167 @@ label="Profit margin (% added to invoices/deducted from faucets)" >
- - +

Switches

+ + + + + + + + + + +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
@@ -338,6 +478,11 @@ wslocation: window.location.hostname, filter: '', currency: 'USD', + switch1: false, + switch2: false, + switch3: false, + switch4: false, + switch5: false, lnurldeviceLinks: [], lnurldeviceLinksObj: [], devices: [ @@ -592,7 +737,7 @@ LNbits.utils.notifyApiError(error) }) }, - clearFormDialoglnurldevice() { + clearFormDialoglnurldevice () { this.formDialoglnurldevice.data = { lnurl_toggle: false, show_message: false, diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index d17db7d7..d5d047af 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -103,8 +103,8 @@ async def websocket_endpoint(websocket: WebSocket, lnurldevice_id: str): manager.disconnect(websocket) -async def updater(lnurldevice_id, lnurldevice_pin): +async def updater(lnurldevice_id, lnurldevice_pin, lnurldevice_amount): lnurldevice = await get_lnurldevice(lnurldevice_id) if not lnurldevice: return - return await manager.send_personal_message(f"pin:{lnurldevice.pin},amount:{lnurldevice.amount}", lnurldevice_id) + return await manager.send_personal_message(f"pin:{lnurldevice_pin},amount:{lnurldevice_amount}", lnurldevice_id) From 7881125c71af913aa26844fb3a97a1e94ffa12db Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 26 Oct 2022 10:54:46 +0100 Subject: [PATCH 086/154] Fixed migrate --- lnbits/extensions/lnurldevice/migrations.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index ebe45301..2ba5b0b3 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -94,19 +94,24 @@ async def m004_redux(db): Add 'meta' for storing various metadata about the wallet """ await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin INT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit FLOAT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit1 FLOAT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount1 INT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin1 INT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit1 FLOAT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit2 FLOAT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount2 INT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin2 INT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit2 FLOAT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit3 FLOAT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount3 INT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin3 INT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit3 FLOAT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit4 FLOAT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount4 INT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin4 INT DEFAULT 0") \ No newline at end of file + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin4 INT DEFAULT 0") + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit4 FLOAT DEFAULT 0") \ No newline at end of file From 1bfa28da19ad5585fdb113a4283e2cd8ad9be021 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 26 Oct 2022 10:58:13 +0100 Subject: [PATCH 087/154] reverted migrate --- lnbits/extensions/lnurldevice/migrations.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index 2ba5b0b3..f29bca99 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -94,24 +94,20 @@ async def m004_redux(db): Add 'meta' for storing various metadata about the wallet """ await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin INT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit FLOAT DEFAULT 0") - + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit1 FLOAT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount1 INT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin1 INT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit1 FLOAT DEFAULT 0") - + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit2 FLOAT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount2 INT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin2 INT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit2 FLOAT DEFAULT 0") - + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit3 FLOAT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount3 INT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin3 INT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit3 FLOAT DEFAULT 0") - + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit4 FLOAT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount4 INT DEFAULT 0") await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin4 INT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit4 FLOAT DEFAULT 0") \ No newline at end of file + \ No newline at end of file From 440c4b696671dc6809e81ee04c2c71236220b221 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Wed, 26 Oct 2022 12:56:15 +0200 Subject: [PATCH 088/154] Update issue templates (#1085) --- .github/ISSUE_TEMPLATE/bug_report.md | 31 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..bfaddbeb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - LNbits version: [e.g. 0.9.2 or commit hash] + - Database [e.g. sqlite, postgres] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..4f49a497 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[Feature request]" +labels: feature request +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 2f1f92f1ee7465281cc57f2dd4e984b2c4d799b6 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Wed, 26 Oct 2022 12:57:53 +0200 Subject: [PATCH 089/154] Update issue templates (#1086) --- .github/ISSUE_TEMPLATE/something-else.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/something-else.md diff --git a/.github/ISSUE_TEMPLATE/something-else.md b/.github/ISSUE_TEMPLATE/something-else.md new file mode 100644 index 00000000..1b3b7355 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/something-else.md @@ -0,0 +1,10 @@ +--- +name: Something else +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + From 1d9be9619f46018e8fce7889d268f63ea915cf43 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:01:02 +0200 Subject: [PATCH 090/154] Update issue templates (#1087) --- .github/ISSUE_TEMPLATE/something-else.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/something-else.md b/.github/ISSUE_TEMPLATE/something-else.md index 1b3b7355..4bd9ec2a 100644 --- a/.github/ISSUE_TEMPLATE/something-else.md +++ b/.github/ISSUE_TEMPLATE/something-else.md @@ -1,6 +1,6 @@ --- name: Something else -about: Describe this issue template's purpose here. +about: Anything else that you need to say title: '' labels: '' assignees: '' From 7d736c3f77a1365a145de07da9cd8cc297091717 Mon Sep 17 00:00:00 2001 From: Lee Salminen Date: Wed, 26 Oct 2022 05:02:35 -0600 Subject: [PATCH 091/154] Add `FORWARDED_ALLOW_IPS` to example fly.io (#1082) This PR adds the needed `FORWARDED_ALLOW_IPS="*"` environment variable to the fly.io installation method, which is needed to work. --- docs/guide/installation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 6b95f93b..11ded8e8 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -155,6 +155,7 @@ kill_timeout = 30 HOST="127.0.0.1" PORT=5000 LNBITS_FORCE_HTTPS=true + FORWARDED_ALLOW_IPS="*" LNBITS_DATA_FOLDER="/data" ${PUT_YOUR_LNBITS_ENV_VARS_HERE} From 5a3524da8377d6706a171d31a9cd7d9aa0757bca Mon Sep 17 00:00:00 2001 From: rolznz <33993199+rolznz@users.noreply.github.com> Date: Wed, 26 Oct 2022 18:05:31 +0700 Subject: [PATCH 092/154] Fix usermanager API docs (#1073) - update incorrect response status codes - update invalid activate extension example --- .../templates/usermanager/_api_docs.html | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html index 886589e6..de477834 100644 --- a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html +++ b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html @@ -38,7 +38,7 @@ >
Body (application/json)
- Returns 201 CREATED (application/json) + Returns 200 OK (application/json)
JSON list of users
Curl example
@@ -57,10 +57,15 @@ /usermanager/api/v1/users/<user_id>
Body (application/json)
+
- Returns 201 CREATED (application/json) + Returns 200 OK (application/json)
- JSON list of users + {"id": <string>, "name": <string>, "admin": + <string>, "email": <string>, "password": <string>} +
Curl example
curl -X GET {{ request.base_url @@ -81,7 +86,7 @@ {"X-Api-Key": <string>}
Body (application/json)
- Returns 201 CREATED (application/json) + Returns 200 OK (application/json)
JSON wallet data
Curl example
@@ -104,7 +109,7 @@ {"X-Api-Key": <string>}
Body (application/json)
- Returns 201 CREATED (application/json) + Returns 200 OK (application/json)
JSON a wallets transactions
Curl example
@@ -254,11 +259,15 @@ {"X-Api-Key": <string>}
Curl example
curl -X POST {{ request.base_url }}usermanager/api/v1/extensions -d - '{"userid": <string>, "extension": <string>, "active": - <integer>}' -H "X-Api-Key: {{ user.wallets[0].inkey }}" -H + >curl -X POST {{ request.base_url }}usermanager/api/v1/extensions?extension=withdraw&userid=user_id&active=true -H "X-Api-Key: {{ user.wallets[0].inkey }}" -H "Content-type: application/json" +
+ Returns 200 OK (application/json) +
+ {"extension": "updated"} From d702e1b6a5bb6fe304f98236d0ddddddad223107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 26 Oct 2022 13:07:34 +0200 Subject: [PATCH 093/154] add FORWARDED_ALLOW_IPS variable to .example.env (#1077) --- .env.example | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 987c6ca6..4edaea97 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,9 @@ HOST=127.0.0.1 PORT=5000 +# uvicorn variable, allow https behind a proxy +# FORWARDED_ALLOW_IPS="*" + DEBUG=false LNBITS_ALLOWED_USERS="" @@ -13,7 +16,7 @@ LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" LNBITS_AD_SPACE="" # Hides wallet api, extensions can choose to honor -LNBITS_HIDE_API=false +LNBITS_HIDE_API=false # Disable extensions for all users, use "all" to disable all extensions LNBITS_DISABLED_EXTENSIONS="amilk" @@ -67,7 +70,7 @@ LNBITS_KEY=LNBITS_ADMIN_KEY LND_REST_ENDPOINT=https://127.0.0.1:8080/ LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" LND_REST_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING" -# To use an AES-encrypted macaroon, set +# To use an AES-encrypted macaroon, set # LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn" # LNPayWallet From 937654e4f73e75b4f4446f1e7d15166fd89517ab Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 26 Oct 2022 13:15:50 +0100 Subject: [PATCH 094/154] Fixed models --- lnbits/extensions/lnurldevice/crud.py | 2 +- lnbits/extensions/lnurldevice/models.py | 24 +-- .../templates/lnurldevice/index.html | 146 +++++++++++++----- 3 files changed, 124 insertions(+), 48 deletions(-) diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py index f9a13b58..818b99fd 100644 --- a/lnbits/extensions/lnurldevice/crud.py +++ b/lnbits/extensions/lnurldevice/crud.py @@ -36,7 +36,7 @@ async def create_lnurldevice( pin3, profit4, amount4, - pin4, + pin4 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index f4638bcd..62917632 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -19,18 +19,18 @@ class createLnurldevice(BaseModel): profit: float amount: int pin: int - profit1: float - amount1: int - pin1: int - profit2: float - amount2: int - pin2: int - profit3: float - amount3: int - pin3: int - profit4: float - amount4: int - pin4: int + profit1: float = 0 + amount1: int = 0 + pin1: int = 0 + profit2: float = 0 + amount2: int = 0 + pin2: int = 0 + profit3: float = 0 + amount3: int = 0 + pin3: int = 0 + profit4: float = 0 + amount4: int = 0 + pin4: int = 0 class lnurldevices(BaseModel): diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index ae56c283..72ea8082 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -105,7 +105,7 @@ @click="openQrCodeDialog(props.row.id)" > LNURLs only work over HTTPS view LNURL view LNURLS + + + + + + + + + + + + +
Mails
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. +
+ + +
Alarms
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. +
+ + +
Movies
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. +
+
+ +
LNURLDevice device string
-

Switches

- - + + - - - - - -
- +
+
+ - +
+ - +
+ +
+
-
- +
+
+ - +
+ - +
+ +
+
-
- +
+
+ - +
+ - +
+ +
+
-
- +
+
+ - +
+ - +
+ +
+
-
- +
+
+ - +
+ - +
+ +
+
-
Date: Wed, 26 Oct 2022 20:19:04 +0100 Subject: [PATCH 095/154] Web socket working --- lnbits/extensions/lnurldevice/lnurl.py | 25 +++++-- lnbits/extensions/lnurldevice/models.py | 29 ++++++-- lnbits/extensions/lnurldevice/tasks.py | 2 +- .../templates/lnurldevice/index.html | 69 +++++-------------- lnbits/extensions/lnurldevice/views_api.py | 8 +-- 5 files changed, 64 insertions(+), 69 deletions(-) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index 72749e85..37a4c57d 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -9,6 +9,7 @@ from embit import bech32, compact from fastapi import Request from fastapi.param_functions import Query from starlette.exceptions import HTTPException +from loguru import logger from lnbits.core.services import create_invoice from lnbits.core.views.api import pay_invoice @@ -113,6 +114,14 @@ async def lnurl_v1_params( else amount_in_cent ) * 1000 + # Check they're not trying to trick the switch! + check = False + for switch in device.switches(request): + if switch[0] == gpio and switch[1] == profit and switch[2] == amount: + check = True + if not check: + return {"status": "ERROR", "reason": f"Switch params wrong"} + lnurldevicepayment = await create_lnurldevicepayment( deviceid=device.id, payload=amount, @@ -129,7 +138,7 @@ async def lnurl_v1_params( ), "minSendable": price_msat, "maxSendable": price_msat, - "metadata": await device.lnurlpay_metadata(), + "metadata": device.lnurlpay_metadata, } if len(p) % 4 > 0: p += "=" * (4 - (len(p) % 4)) @@ -236,11 +245,17 @@ async def lnurl_callback( if device.device == "switch": payment_hash, payment_request = await create_invoice( wallet_id=device.wallet, - amount=lnurldevicepayment.sats / 1000, - memo=device.title + "-" + lnurldevicepayment.id, - unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"), - extra={"tag": "Switch", "pin": lnurldevicepayment.pin,"amount": int(lnurldevicepayment.payload),"id": paymentid}, + amount=int(lnurldevicepayment.sats / 1000), + memo=device.id + " PIN " + str(lnurldevicepayment.pin), + unhashed_description=device.lnurlpay_metadata.encode("utf-8"), + extra={ + "tag": "Switch", + "pin": str(lnurldevicepayment.pin), + "amount": str(lnurldevicepayment.payload), + "id": paymentid + }, ) + lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=payment_hash ) diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index 62917632..ace22ca1 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -1,6 +1,6 @@ import json from sqlite3 import Row -from typing import Optional +from typing import Optional, List from fastapi import Request from lnurl import Lnurl @@ -9,7 +9,7 @@ from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore from lnurl.types import LnurlPayMetadata # type: ignore from pydantic import BaseModel from pydantic.main import BaseModel - +from loguru import logger class createLnurldevice(BaseModel): title: str @@ -60,13 +60,28 @@ class lnurldevices(BaseModel): def from_row(cls, row: Row) -> "lnurldevices": return cls(**dict(row)) - def lnurl(self, req: Request) -> Lnurl: - url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - return lnurl_encode(url) - - async def lnurlpay_metadata(self) -> LnurlPayMetadata: + @property + def lnurlpay_metadata(self) -> LnurlPayMetadata: return LnurlPayMetadata(json.dumps([["text/plain", self.title]])) + def switches(self, req: Request) -> List: + switches = [] + if self.profit > 0: + url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) + switches.append([str(self.pin), str(self.profit), str(self.amount), lnurl_encode(url + "?gpio=" + str(self.pin) + "&profit=" + str(self.profit) + "&amount=" + str(self.amount))]) + if self.profit1 > 0: + url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) + switches.append([str(self.pin1), str(self.profit1), str(self.amount1), lnurl_encode(url + "?gpio=" + str(self.pin1) + "&profit=" + str(self.profit1) + "&amount=" + str(self.amount1))]) + if self.profit2 > 0: + url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) + switches.append([str(self.pin2), str(self.profit2), str(self.amount2), lnurl_encode(url + "?gpio=" + str(self.pin2) + "&profit=" + str(self.profit2) + "&amount=" + str(self.amount2))]) + if self.profit3 > 0: + url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) + switches.append([str(self.pin3), str(self.profit3), str(self.amount3), lnurl_encode(url + "?gpio=" + str(self.pin3) + "&profit=" + str(self.profit3) + "&amount=" + str(self.amount3))]) + if self.profit4 > 0: + url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) + switches.append([str(self.pin4), str(self.profit4), str(self.amount4), lnurl_encode(url + "?gpio=" + str(self.pin4) + "&profit=" + str(self.profit4) + "&amount=" + str(self.amount4))]) + return switches class lnurldevicepayment(BaseModel): id: str diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 2867577d..ca7aabb4 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -36,5 +36,5 @@ async def on_invoice_paid(payment: Payment) -> None: lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=payment.extra.get("id"), payhash="used" ) - return await updater(lnurldevicepayment.deviceid, lnurldevicepayment.pin, lnurldevicepayment.amount) + return await updater(lnurldevicepayment.deviceid, lnurldevicepayment.pin, lnurldevicepayment.payload) return diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index 72ea8082..17ce9f28 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -148,42 +148,6 @@ style="width: 700px; max-width: 80vw" class="q-pa-lg q-pt-xl lnbits__dialog-card" > - - - - - - - - - - - - -
Mails
- Lorem ipsum dolor sit amet consectetur adipisicing elit. -
- - -
Alarms
- Lorem ipsum dolor sit amet consectetur adipisicing elit. -
- - -
Movies
- Lorem ipsum dolor sit amet consectetur adipisicing elit. -
-
- -
LNURLDevice device string
+ - {% raw %} -

- ID: {{ qrCodeDialog.data.id }}
-

- {% endraw %} -
- Copy LNURL +
+
+ + Close
+
@@ -545,11 +508,13 @@ mixins: [windowMixin], data: function () { return { + tab: 'mails', protocol: window.location.protocol, location: window.location.hostname, wslocation: window.location.hostname, filter: '', currency: 'USD', + lnurlValue: '', switches: 0, lnurldeviceLinks: [], lnurldeviceLinksObj: [], @@ -599,12 +564,6 @@ label: 'device', field: 'device' }, - { - name: 'profit', - align: 'left', - label: 'profit', - field: 'profit' - }, { name: 'currency', align: 'left', @@ -653,8 +612,12 @@ this.qrCodeDialog.data = _.clone(lnurldevice) this.qrCodeDialog.data.url = window.location.protocol + '//' + window.location.host + this.lnurlValueFetch(this.qrCodeDialog.data.switches[0][3]) this.qrCodeDialog.show = true }, + lnurlValueFetch: function (lnurl){ + this.lnurlValue = lnurl + }, addSwitch: function () { var self = this self.switches = self.switches + 1 @@ -719,7 +682,9 @@ .then(function (response) { if (response.data) { self.lnurldeviceLinks = response.data.map(maplnurldevice) + console.log("response.data") console.log(response.data) + console.log("response.data") } }) .catch(function (error) { diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index c034f66e..c6766423 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -39,10 +39,10 @@ async def api_lnurldevice_create_or_update( ): if not lnurldevice_id: lnurldevice = await create_lnurldevice(data) - return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}} + return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} else: lnurldevice = await update_lnurldevice(data, lnurldevice_id=lnurldevice_id) - return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}} + return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} @lnurldevice_ext.get("/api/v1/lnurlpos") @@ -52,7 +52,7 @@ async def api_lnurldevices_retrieve( wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids try: return [ - {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}} + {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} for lnurldevice in await get_lnurldevices(wallet_ids) ] except: @@ -78,7 +78,7 @@ async def api_lnurldevice_retrieve( ) if not lnurldevice.lnurl_toggle: return {**lnurldevice.dict()} - return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}} + return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} @lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}") From 98d712fa7966e45a91563be66e3ced5d41b5b9bc Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 27 Oct 2022 00:26:07 +0100 Subject: [PATCH 096/154] Fixed pin bug --- lnbits/extensions/lnurldevice/models.py | 2 +- lnbits/extensions/lnurldevice/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index ace22ca1..29d49d3f 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -18,7 +18,7 @@ class createLnurldevice(BaseModel): device: str profit: float amount: int - pin: int + pin: int = 0 profit1: float = 0 amount1: int = 0 pin1: int = 0 diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index d5d047af..f5fd598c 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -107,4 +107,4 @@ async def updater(lnurldevice_id, lnurldevice_pin, lnurldevice_amount): lnurldevice = await get_lnurldevice(lnurldevice_id) if not lnurldevice: return - return await manager.send_personal_message(f"pin:{lnurldevice_pin},amount:{lnurldevice_amount}", lnurldevice_id) + return await manager.send_personal_message(f"{lnurldevice_pin}-{lnurldevice_amount}", lnurldevice_id) From 5b1df5fffc02337f40b374fc9402900c17797c85 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 27 Oct 2022 00:35:49 +0100 Subject: [PATCH 097/154] fixed some ints --- lnbits/extensions/lnurldevice/lnurl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index 37a4c57d..ce2d1c69 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -200,7 +200,7 @@ async def lnurl_v1_params( ), "minSendable": price_msat * 1000, "maxSendable": price_msat * 1000, - "metadata": await device.lnurlpay_metadata(), + "metadata": device.lnurlpay_metadata, } @@ -266,9 +266,9 @@ async def lnurl_callback( payment_hash, payment_request = await create_invoice( wallet_id=device.wallet, - amount=lnurldevicepayment.sats / 1000, + amount=int(lnurldevicepayment.sats / 1000), memo=device.title, - unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"), + unhashed_description=device.lnurlpay_metadata.encode("utf-8"), extra={"tag": "PoS"}, ) lnurldevicepayment = await update_lnurldevicepayment( From 047d0fc761097d868c494009adde91bb39f86913 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 27 Oct 2022 00:37:15 +0100 Subject: [PATCH 098/154] black --- lnbits/extensions/lnurldevice/crud.py | 2 +- lnbits/extensions/lnurldevice/lnurl.py | 10 +-- lnbits/extensions/lnurldevice/migrations.py | 60 ++++++++++---- lnbits/extensions/lnurldevice/models.py | 87 +++++++++++++++++++-- lnbits/extensions/lnurldevice/tasks.py | 6 +- lnbits/extensions/lnurldevice/views.py | 4 +- 6 files changed, 139 insertions(+), 30 deletions(-) diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py index 818b99fd..e02d23b8 100644 --- a/lnbits/extensions/lnurldevice/crud.py +++ b/lnbits/extensions/lnurldevice/crud.py @@ -61,7 +61,7 @@ async def create_lnurldevice( data.pin3, data.profit4, data.amount4, - data.pin4 + data.pin4, ), ) return await get_lnurldevice(lnurldevice_id) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index ce2d1c69..6a80f74e 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -9,7 +9,7 @@ from embit import bech32, compact from fastapi import Request from fastapi.param_functions import Query from starlette.exceptions import HTTPException -from loguru import logger +from loguru import logger from lnbits.core.services import create_invoice from lnbits.core.views.api import pay_invoice @@ -249,13 +249,13 @@ async def lnurl_callback( memo=device.id + " PIN " + str(lnurldevicepayment.pin), unhashed_description=device.lnurlpay_metadata.encode("utf-8"), extra={ - "tag": "Switch", + "tag": "Switch", "pin": str(lnurldevicepayment.pin), "amount": str(lnurldevicepayment.payload), - "id": paymentid - }, + "id": paymentid, + }, ) - + lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=payment_hash ) diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index f29bca99..1df04075 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -89,25 +89,51 @@ async def m003_redux(db): "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;" ) + async def m004_redux(db): """ Add 'meta' for storing various metadata about the wallet """ - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin INT DEFAULT 0") - - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit1 FLOAT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount1 INT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin1 INT DEFAULT 0") + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin INT DEFAULT 0" + ) - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit2 FLOAT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount2 INT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin2 INT DEFAULT 0") - - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit3 FLOAT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount3 INT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin3 INT DEFAULT 0") - - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit4 FLOAT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount4 INT DEFAULT 0") - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin4 INT DEFAULT 0") - \ No newline at end of file + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit1 FLOAT DEFAULT 0" + ) + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount1 INT DEFAULT 0" + ) + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin1 INT DEFAULT 0" + ) + + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit2 FLOAT DEFAULT 0" + ) + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount2 INT DEFAULT 0" + ) + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin2 INT DEFAULT 0" + ) + + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit3 FLOAT DEFAULT 0" + ) + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount3 INT DEFAULT 0" + ) + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin3 INT DEFAULT 0" + ) + + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit4 FLOAT DEFAULT 0" + ) + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount4 INT DEFAULT 0" + ) + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin4 INT DEFAULT 0" + ) diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index 29d49d3f..1b7c0ab8 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -11,6 +11,7 @@ from pydantic import BaseModel from pydantic.main import BaseModel from loguru import logger + class createLnurldevice(BaseModel): title: str wallet: str @@ -68,21 +69,97 @@ class lnurldevices(BaseModel): switches = [] if self.profit > 0: url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - switches.append([str(self.pin), str(self.profit), str(self.amount), lnurl_encode(url + "?gpio=" + str(self.pin) + "&profit=" + str(self.profit) + "&amount=" + str(self.amount))]) + switches.append( + [ + str(self.pin), + str(self.profit), + str(self.amount), + lnurl_encode( + url + + "?gpio=" + + str(self.pin) + + "&profit=" + + str(self.profit) + + "&amount=" + + str(self.amount) + ), + ] + ) if self.profit1 > 0: url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - switches.append([str(self.pin1), str(self.profit1), str(self.amount1), lnurl_encode(url + "?gpio=" + str(self.pin1) + "&profit=" + str(self.profit1) + "&amount=" + str(self.amount1))]) + switches.append( + [ + str(self.pin1), + str(self.profit1), + str(self.amount1), + lnurl_encode( + url + + "?gpio=" + + str(self.pin1) + + "&profit=" + + str(self.profit1) + + "&amount=" + + str(self.amount1) + ), + ] + ) if self.profit2 > 0: url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - switches.append([str(self.pin2), str(self.profit2), str(self.amount2), lnurl_encode(url + "?gpio=" + str(self.pin2) + "&profit=" + str(self.profit2) + "&amount=" + str(self.amount2))]) + switches.append( + [ + str(self.pin2), + str(self.profit2), + str(self.amount2), + lnurl_encode( + url + + "?gpio=" + + str(self.pin2) + + "&profit=" + + str(self.profit2) + + "&amount=" + + str(self.amount2) + ), + ] + ) if self.profit3 > 0: url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - switches.append([str(self.pin3), str(self.profit3), str(self.amount3), lnurl_encode(url + "?gpio=" + str(self.pin3) + "&profit=" + str(self.profit3) + "&amount=" + str(self.amount3))]) + switches.append( + [ + str(self.pin3), + str(self.profit3), + str(self.amount3), + lnurl_encode( + url + + "?gpio=" + + str(self.pin3) + + "&profit=" + + str(self.profit3) + + "&amount=" + + str(self.amount3) + ), + ] + ) if self.profit4 > 0: url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - switches.append([str(self.pin4), str(self.profit4), str(self.amount4), lnurl_encode(url + "?gpio=" + str(self.pin4) + "&profit=" + str(self.profit4) + "&amount=" + str(self.amount4))]) + switches.append( + [ + str(self.pin4), + str(self.profit4), + str(self.amount4), + lnurl_encode( + url + + "?gpio=" + + str(self.pin4) + + "&profit=" + + str(self.profit4) + + "&amount=" + + str(self.amount4) + ), + ] + ) return switches + class lnurldevicepayment(BaseModel): id: str deviceid: str diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index ca7aabb4..d3248ad5 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -36,5 +36,9 @@ async def on_invoice_paid(payment: Payment) -> None: lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=payment.extra.get("id"), payhash="used" ) - return await updater(lnurldevicepayment.deviceid, lnurldevicepayment.pin, lnurldevicepayment.payload) + return await updater( + lnurldevicepayment.deviceid, + lnurldevicepayment.pin, + lnurldevicepayment.payload, + ) return diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index f5fd598c..f435931b 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -107,4 +107,6 @@ async def updater(lnurldevice_id, lnurldevice_pin, lnurldevice_amount): lnurldevice = await get_lnurldevice(lnurldevice_id) if not lnurldevice: return - return await manager.send_personal_message(f"{lnurldevice_pin}-{lnurldevice_amount}", lnurldevice_id) + return await manager.send_personal_message( + f"{lnurldevice_pin}-{lnurldevice_amount}", lnurldevice_id + ) From 1ab0d0f70132ce5d75f41d4ecb3df37135695a4b Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 27 Oct 2022 01:05:09 +0100 Subject: [PATCH 099/154] isort --- lnbits/extensions/lnurldevice/lnurl.py | 12 ++++-------- lnbits/extensions/lnurldevice/models.py | 4 ++-- lnbits/extensions/lnurldevice/tasks.py | 3 ++- lnbits/extensions/lnurldevice/views_api.py | 9 ++------- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index 6a80f74e..bdbcbdaa 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -8,21 +8,17 @@ from typing import Optional from embit import bech32, compact from fastapi import Request from fastapi.param_functions import Query -from starlette.exceptions import HTTPException from loguru import logger +from starlette.exceptions import HTTPException from lnbits.core.services import create_invoice from lnbits.core.views.api import pay_invoice from lnbits.utils.exchange_rates import fiat_amount_as_satoshis from . import lnurldevice_ext -from .crud import ( - create_lnurldevicepayment, - get_lnurldevice, - get_lnurldevicepayment, - get_lnurlpayload, - update_lnurldevicepayment, -) +from .crud import (create_lnurldevicepayment, get_lnurldevice, + get_lnurldevicepayment, get_lnurlpayload, + update_lnurldevicepayment) def bech32_decode(bech): diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index 1b7c0ab8..c27470b7 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -1,15 +1,15 @@ import json from sqlite3 import Row -from typing import Optional, List +from typing import List, Optional from fastapi import Request from lnurl import Lnurl from lnurl import encode as lnurl_encode # type: ignore from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore from lnurl.types import LnurlPayMetadata # type: ignore +from loguru import logger from pydantic import BaseModel from pydantic.main import BaseModel -from loguru import logger class createLnurldevice(BaseModel): diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index d3248ad5..2c2ac713 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -12,7 +12,8 @@ from lnbits.core.services import pay_invoice from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment +from .crud import (get_lnurldevice, get_lnurldevicepayment, + update_lnurldevicepayment) from .views import updater diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index c6766423..d0ea8659 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -11,13 +11,8 @@ from lnbits.extensions.lnurldevice import lnurldevice_ext from lnbits.utils.exchange_rates import currencies from . import lnurldevice_ext -from .crud import ( - create_lnurldevice, - delete_lnurldevice, - get_lnurldevice, - get_lnurldevices, - update_lnurldevice, -) +from .crud import (create_lnurldevice, delete_lnurldevice, get_lnurldevice, + get_lnurldevices, update_lnurldevice) from .models import createLnurldevice From 8bafb9ab1992deaa7c9fe4c0cf9e01263ec10703 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 27 Oct 2022 01:10:03 +0100 Subject: [PATCH 100/154] black --- lnbits/extensions/lnurldevice/lnurl.py | 10 +++++++--- lnbits/extensions/lnurldevice/tasks.py | 3 +-- lnbits/extensions/lnurldevice/views_api.py | 9 +++++++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index bdbcbdaa..c8f9675e 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -16,9 +16,13 @@ from lnbits.core.views.api import pay_invoice from lnbits.utils.exchange_rates import fiat_amount_as_satoshis from . import lnurldevice_ext -from .crud import (create_lnurldevicepayment, get_lnurldevice, - get_lnurldevicepayment, get_lnurlpayload, - update_lnurldevicepayment) +from .crud import ( + create_lnurldevicepayment, + get_lnurldevice, + get_lnurldevicepayment, + get_lnurlpayload, + update_lnurldevicepayment, +) def bech32_decode(bech): diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 2c2ac713..d3248ad5 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -12,8 +12,7 @@ from lnbits.core.services import pay_invoice from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .crud import (get_lnurldevice, get_lnurldevicepayment, - update_lnurldevicepayment) +from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment from .views import updater diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index d0ea8659..c6766423 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -11,8 +11,13 @@ from lnbits.extensions.lnurldevice import lnurldevice_ext from lnbits.utils.exchange_rates import currencies from . import lnurldevice_ext -from .crud import (create_lnurldevice, delete_lnurldevice, get_lnurldevice, - get_lnurldevices, update_lnurldevice) +from .crud import ( + create_lnurldevice, + delete_lnurldevice, + get_lnurldevice, + get_lnurldevices, + update_lnurldevice, +) from .models import createLnurldevice From 904dea37fc25641f3c283a3850af22474700d751 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 27 Oct 2022 01:16:05 +0100 Subject: [PATCH 101/154] prettier --- .../templates/lnurldevice/index.html | 317 ++++++++++-------- 1 file changed, 168 insertions(+), 149 deletions(-) diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index 17ce9f28..83ff4571 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -230,201 +230,217 @@ label="Profit margin (% added to invoices/deducted from faucets)" >
- - - - + +
-
+
+ ref="setAmount" + filled + dense + v-model.trim="formDialoglnurldevice.data.profit" + class="q-pb-md" + :label="'Amount (' + formDialoglnurldevice.data.currency + ') *'" + :mask="'#.##'" + fill-mask="0" + reverse-fill-mask + :step="'0.01'" + value="0.00" + >
+ filled + dense + v-model.trim="formDialoglnurldevice.data.amount" + type="number" + value="1000" + label="milesecs to turn Switch on for (1sec = 1000ms)" + >
+ filled + dense + v-model.trim="formDialoglnurldevice.data.pin" + type="number" + label="GPIO to turn on" + >
-
+
+ ref="setAmount" + filled + dense + v-model.trim="formDialoglnurldevice.data.profit1" + class="q-pb-md" + :label="'Amount (' + formDialoglnurldevice.data.currency + ') *'" + :mask="'#.##'" + fill-mask="0" + reverse-fill-mask + :step="'0.01'" + value="0.00" + >
+ filled + dense + v-model.trim="formDialoglnurldevice.data.amount1" + type="number" + value="1000" + label="milesecs to turn Switch on for (1sec = 1000ms)" + >
+ filled + dense + v-model.trim="formDialoglnurldevice.data.pin1" + type="number" + label="GPIO to turn on" + >
-
+
+ ref="setAmount" + filled + dense + v-model.trim="formDialoglnurldevice.data.profit2" + class="q-pb-md" + :label="'Amount (' + formDialoglnurldevice.data.currency + ') *'" + :mask="'#.##'" + fill-mask="0" + reverse-fill-mask + :step="'0.01'" + value="0.00" + >
+ filled + dense + v-model.trim="formDialoglnurldevice.data.amount2" + type="number" + value="1000" + label="milesecs to turn Switch on for (1sec = 1000ms)" + >
+ filled + dense + v-model.trim="formDialoglnurldevice.data.pin2" + type="number" + label="GPIO to turn on" + >
-
+
-
+
+ ref="setAmount" + filled + dense + v-model.trim="formDialoglnurldevice.data.profit3" + class="q-pb-md" + :label="'Amount (' + formDialoglnurldevice.data.currency + ') *'" + :mask="'#.##'" + fill-mask="0" + reverse-fill-mask + :step="'0.01'" + value="0.00" + >
+ filled + dense + v-model.trim="formDialoglnurldevice.data.amount3" + type="number" + value="1000" + label="milesecs to turn Switch on for (1sec = 1000ms)" + >
+ filled + dense + v-model.trim="formDialoglnurldevice.data.pin3" + type="number" + label="GPIO to turn on" + >
-
+
-
+
+ ref="setAmount" + filled + dense + v-model.trim="formDialoglnurldevice.data.profit4" + class="q-pb-md" + :label="'Amount (' + formDialoglnurldevice.data.currency + ') *'" + :mask="'#.##'" + fill-mask="0" + reverse-fill-mask + :step="'0.01'" + value="0.00" + >
+ filled + dense + v-model.trim="formDialoglnurldevice.data.amount4" + type="number" + value="1000" + label="milesecs to turn Switch on for (1sec = 1000ms)" + >
+ filled + dense + v-model.trim="formDialoglnurldevice.data.pin4" + type="number" + label="GPIO to turn on" + >
@@ -458,7 +474,6 @@ - Copy LNURL -
+ outline + color="grey" + @click="copyText(lnurlValue, 'LNURL copied to clipboard!')" + >Copy LNURL +
- - + Close
-
@@ -615,7 +634,7 @@ this.lnurlValueFetch(this.qrCodeDialog.data.switches[0][3]) this.qrCodeDialog.show = true }, - lnurlValueFetch: function (lnurl){ + lnurlValueFetch: function (lnurl) { this.lnurlValue = lnurl }, addSwitch: function () { @@ -682,9 +701,9 @@ .then(function (response) { if (response.data) { self.lnurldeviceLinks = response.data.map(maplnurldevice) - console.log("response.data") + console.log('response.data') console.log(response.data) - console.log("response.data") + console.log('response.data') } }) .catch(function (error) { From 7be45d5d912a94b704178298c715676c8f484418 Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Fri, 28 Oct 2022 10:22:59 +0100 Subject: [PATCH 102/154] Update installation.md --- docs/guide/installation.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 11ded8e8..bf40418d 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -48,7 +48,9 @@ poetry run lnbits # Note that you have to add the line DEBUG=true in your .env file, too. ``` -## Option 2: Nix +## Option 2: Nix + +> note: currently not supported while we make some architectural changes on the path to leave beta ```sh git clone https://github.com/lnbits/lnbits-legend.git From b92fedbcc9f22415ed7b34d26a83e1c4f2ce8864 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Fri, 28 Oct 2022 11:26:02 +0200 Subject: [PATCH 103/154] refactor try except block (#1084) --- lnbits/extensions/withdraw/lnurl.py | 55 +++++++++++++++-------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py index 18a99599..660e5b7d 100644 --- a/lnbits/extensions/withdraw/lnurl.py +++ b/lnbits/extensions/withdraw/lnurl.py @@ -78,34 +78,35 @@ async def api_lnurl_callback( return {"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."} usescsv = "" + + for x in range(1, link.uses - link.used): + usecv = link.usescsv.split(",") + usescsv += "," + str(usecv[x]) + usecsvback = usescsv + + found = False + if id_unique_hash is not None: + useslist = link.usescsv.split(",") + for ind, x in enumerate(useslist): + tohash = link.id + link.unique_hash + str(x) + if id_unique_hash == shortuuid.uuid(name=tohash): + found = True + useslist.pop(ind) + usescsv = ",".join(useslist) + if not found: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found." + ) + else: + usescsv = usescsv[1:] + + changesback = { + "open_time": link.wait_time, + "used": link.used, + "usescsv": usecsvback, + } + try: - for x in range(1, link.uses - link.used): - usecv = link.usescsv.split(",") - usescsv += "," + str(usecv[x]) - usecsvback = usescsv - - found = False - if id_unique_hash is not None: - useslist = link.usescsv.split(",") - for ind, x in enumerate(useslist): - tohash = link.id + link.unique_hash + str(x) - if id_unique_hash == shortuuid.uuid(name=tohash): - found = True - useslist.pop(ind) - usescsv = ",".join(useslist) - if not found: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found." - ) - else: - usescsv = usescsv[1:] - - changesback = { - "open_time": link.wait_time, - "used": link.used, - "usescsv": usecsvback, - } - changes = { "open_time": link.wait_time + now, "used": link.used + 1, From 4ee571ca7139ffbfb132522fb29d7d1f9f23ea8a Mon Sep 17 00:00:00 2001 From: ChuckNorrison <2964146+ChuckNorrison@users.noreply.github.com> Date: Mon, 7 Nov 2022 11:30:18 +0100 Subject: [PATCH 104/154] Documentation for postgresql default port (#1106) * Documentation for postgresql default port A port is necessary for postgresql config. Add postgresql default port 5432 in documentation for LNBITS_DATABASE_URL config * Update docs/guide/installation.md Co-authored-by: calle <93376500+callebtc@users.noreply.github.com> Co-authored-by: calle <93376500+callebtc@users.noreply.github.com> --- docs/guide/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index bf40418d..072c4d91 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -220,8 +220,8 @@ You need to edit the `.env` file. ```sh # add the database connection string to .env 'nano .env' LNBITS_DATABASE_URL= -# postgres://:@/ - alter line bellow with your user, password and db name -LNBITS_DATABASE_URL="postgres://postgres:postgres@localhost/lnbits" +# postgres://:@:/ - alter line bellow with your user, password and db name +LNBITS_DATABASE_URL="postgres://postgres:postgres@localhost:5432/lnbits" # save and exit ``` From c3ef4d726038aa81b9298afce682d573f9aa12d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 7 Nov 2022 11:30:44 +0100 Subject: [PATCH 105/154] formatting fixes, prettier (#1108) --- .../lnurldevice/templates/lnurldevice/index.html | 2 +- .../templates/usermanager/_api_docs.html | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index 83ff4571..b0b223ff 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -797,7 +797,7 @@ LNbits.utils.notifyApiError(error) }) }, - clearFormDialoglnurldevice () { + clearFormDialoglnurldevice() { this.formDialoglnurldevice.data = { lnurl_toggle: false, show_message: false, diff --git a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html index de477834..36593d74 100644 --- a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html +++ b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html @@ -57,13 +57,14 @@ /usermanager/api/v1/users/<user_id>
Body (application/json)
- +
Returns 200 OK (application/json)
{"id": <string>, "name": <string>, "admin": - <string>, "email": <string>, "password": <string>}
Curl example
@@ -259,15 +260,15 @@ {"X-Api-Key": <string>}
Curl example
curl -X POST {{ request.base_url }}usermanager/api/v1/extensions?extension=withdraw&userid=user_id&active=true -H "X-Api-Key: {{ user.wallets[0].inkey }}" -H - "Content-type: application/json" + >curl -X POST {{ request.base_url + }}usermanager/api/v1/extensions?extension=withdraw&userid=user_id&active=true + -H "X-Api-Key: {{ user.wallets[0].inkey }}" -H "Content-type: + application/json"
Returns 200 OK (application/json)
- {"extension": "updated"} + {"extension": "updated"} From 0ea225b87e19e2e2b72657f3da0f8095b99d5257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 7 Nov 2022 13:31:42 +0100 Subject: [PATCH 106/154] add api docs button --- lnbits/templates/base.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lnbits/templates/base.html b/lnbits/templates/base.html index 67241bb5..ef270371 100644 --- a/lnbits/templates/base.html +++ b/lnbits/templates/base.html @@ -199,6 +199,18 @@ > + + API DOCS + View LNbits Swagger API docs + Date: Tue, 8 Nov 2022 01:15:54 +0100 Subject: [PATCH 107/154] fix sys is not defined --- tools/conv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/conv.py b/tools/conv.py index 5084660f..cb8f0573 100644 --- a/tools/conv.py +++ b/tools/conv.py @@ -24,7 +24,7 @@ sqfolder = env.str("LNBITS_DATA_FOLDER", default=None) LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None) if LNBITS_DATABASE_URL is None: print("missing LNBITS_DATABASE_URL") - sys.exit(1) + os.sys.exit(1) else: # parse postgres://lnbits:postgres@localhost:5432/lnbits pgdb = LNBITS_DATABASE_URL.split("/")[-1] From 61dd9817b6ed966df90f175520da046c47ee28e6 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 8 Nov 2022 13:47:03 +0000 Subject: [PATCH 108/154] fixed atm --- lnbits/extensions/lnurldevice/lnurl.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index c8f9675e..dd8dcb08 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -105,9 +105,9 @@ async def lnurl_v1_params( paymentcheck = await get_lnurlpayload(p) if device.device == "atm": if paymentcheck: - return {"status": "ERROR", "reason": f"Payment already claimed"} + if paymentcheck.payhash != "payment_hash": + return {"status": "ERROR", "reason": f"Payment already claimed"} if device.device == "switch": - price_msat = ( await fiat_amount_as_satoshis(float(profit), device.currency) if device.currency != "sat" @@ -177,7 +177,7 @@ async def lnurl_v1_params( "callback": request.url_for( "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id ), - "k1": lnurldevicepayment.id, + "k1": p, "minWithdrawable": price_msat * 1000, "maxWithdrawable": price_msat * 1000, "defaultDescription": device.title, @@ -227,14 +227,13 @@ async def lnurl_callback( status_code=HTTPStatus.FORBIDDEN, detail="No payment request" ) else: - if lnurldevicepayment.id != k1: + if lnurldevicepayment.payload != k1: return {"status": "ERROR", "reason": "Bad K1"} if lnurldevicepayment.payhash != "payment_hash": return {"status": "ERROR", "reason": f"Payment already claimed"} lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload ) - await pay_invoice( wallet_id=device.wallet, payment_request=pr, From 0ae11df52f62d670a4759eb72a6e41460e809e9f Mon Sep 17 00:00:00 2001 From: Bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 10 Nov 2022 22:22:17 -0800 Subject: [PATCH 109/154] Docker file fix for missing directory Currently Docker will error and exit because `data` directory is not created. This commit adds automatic `data` directory creation to Dockerfile so that the following docker command will not error. ``` docker run --detach --publish 5000:5000 --name lnbits-legend --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits-legend ``` --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 6259fe7b..af3f3616 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN curl -sSL https://install.python-poetry.org | python3 - ENV PATH="/root/.local/bin:$PATH" WORKDIR /app +RUN mkdir -p /app/lnbits/data COPY . . From db4232f2ced4e7e062e3e77ac14a8f1cb7998c73 Mon Sep 17 00:00:00 2001 From: ChuckNorrison <2964146+ChuckNorrison@users.noreply.github.com> Date: Fri, 11 Nov 2022 12:06:35 +0100 Subject: [PATCH 110/154] Documentation improved in example env LNBITS_ALLOWED_USERS and LNBITS_ADMIN_USERS wants usr string from wallet url List needs to be comma separated if used more than one user --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index 4edaea97..e76296ab 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ PORT=5000 DEBUG=false +# Find "usr" string in wallet url to explicit allow users or set admins (comma separated list) LNBITS_ALLOWED_USERS="" LNBITS_ADMIN_USERS="" # Extensions only admin can access From 029e8baf5310a664cfc809b768b80974bb291563 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 11 Nov 2022 23:46:22 +0000 Subject: [PATCH 111/154] added tasks to make tickets --- lnbits/extensions/events/__init__.py | 10 +++++++ lnbits/extensions/events/tasks.py | 40 +++++++++++++++++++++++++++ lnbits/extensions/events/views_api.py | 10 +++---- 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 lnbits/extensions/events/tasks.py diff --git a/lnbits/extensions/events/__init__.py b/lnbits/extensions/events/__init__.py index d0aa27bc..c64a866b 100644 --- a/lnbits/extensions/events/__init__.py +++ b/lnbits/extensions/events/__init__.py @@ -1,7 +1,11 @@ +import asyncio + from fastapi import APIRouter from lnbits.db import Database from lnbits.helpers import template_renderer +from lnbits.tasks import catch_everything_and_restart + db = Database("ext_events") @@ -13,5 +17,11 @@ def events_renderer(): return template_renderer(["lnbits/extensions/events/templates"]) +from .tasks import wait_for_paid_invoices from .views import * # noqa from .views_api import * # noqa + + +def events_start(): + loop = asyncio.get_event_loop() + loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/events/tasks.py b/lnbits/extensions/events/tasks.py new file mode 100644 index 00000000..1dcca9fa --- /dev/null +++ b/lnbits/extensions/events/tasks.py @@ -0,0 +1,40 @@ +import asyncio +import json +from http import HTTPStatus +from urllib.parse import urlparse + +import httpx +from fastapi import HTTPException + +from lnbits import bolt11 +from lnbits.core.models import Payment +from lnbits.core.services import pay_invoice +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import register_invoice_listener + +from .views_api import api_ticket_send_ticket +from loguru import logger +from lnbits.extensions.events.models import CreateTicket + + +async def wait_for_paid_invoices(): + invoice_queue = asyncio.Queue() + register_invoice_listener(invoice_queue, get_current_extension_name()) + + while True: + payment = await invoice_queue.get() + await on_invoice_paid(payment) + + +async def on_invoice_paid(payment: Payment) -> None: + # (avoid loops) + logger.debug(f"cunt: sdvcsd") + if ( + "events" == payment.extra.get("tag") + and payment.extra.get("name") + and payment.extra.get("email") + ): + CreateTicket.name = str(payment.extra.get("name")) + CreateTicket.email = str(payment.extra.get("email")) + await api_ticket_send_ticket(payment.memo, payment.payment_hash, CreateTicket) + return diff --git a/lnbits/extensions/events/views_api.py b/lnbits/extensions/events/views_api.py index 9cb18f04..08651f5d 100644 --- a/lnbits/extensions/events/views_api.py +++ b/lnbits/extensions/events/views_api.py @@ -10,6 +10,7 @@ from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type from lnbits.extensions.events.models import CreateEvent, CreateTicket +from loguru import logger from . import events_ext from .crud import ( @@ -96,8 +97,8 @@ async def api_tickets( return [ticket.dict() for ticket in await get_tickets(wallet_ids)] -@events_ext.get("/api/v1/tickets/{event_id}") -async def api_ticket_make_ticket(event_id): +@events_ext.get("/api/v1/tickets/{event_id}/{name}/{email}") +async def api_ticket_make_ticket(event_id, name, email): event = await get_event(event_id) if not event: raise HTTPException( @@ -108,11 +109,10 @@ async def api_ticket_make_ticket(event_id): wallet_id=event.wallet, amount=event.price_per_ticket, memo=f"{event_id}", - extra={"tag": "events"}, + extra={"tag": "events", "name": name, "email": email}, ) except Exception as e: raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) - return {"payment_hash": payment_hash, "payment_request": payment_request} @@ -156,7 +156,7 @@ async def api_ticket_delete(ticket_id, wallet: WalletTypeInfo = Depends(get_key_ ) await delete_ticket(ticket_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT # Event Tickets From 9f84e7e408ac69f6e61b425ccfbe060557df5d6b Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 11 Nov 2022 23:59:29 +0000 Subject: [PATCH 112/154] typo --- lnbits/extensions/events/tasks.py | 1 - lnbits/extensions/events/templates/events/display.html | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lnbits/extensions/events/tasks.py b/lnbits/extensions/events/tasks.py index 1dcca9fa..2068b3fa 100644 --- a/lnbits/extensions/events/tasks.py +++ b/lnbits/extensions/events/tasks.py @@ -28,7 +28,6 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: # (avoid loops) - logger.debug(f"cunt: sdvcsd") if ( "events" == payment.extra.get("tag") and payment.extra.get("name") diff --git a/lnbits/extensions/events/templates/events/display.html b/lnbits/extensions/events/templates/events/display.html index 4589c578..9dbb95bc 100644 --- a/lnbits/extensions/events/templates/events/display.html +++ b/lnbits/extensions/events/templates/events/display.html @@ -135,7 +135,7 @@ var self = this axios - .get('/events/api/v1/tickets/' + '{{ event_id }}') + .get('/events/api/v1/tickets/' + '{{ event_id }}' + '/' + self.formDialog.data.name + '/' + self.formDialog.data.email) .then(function (response) { self.paymentReq = response.data.payment_request self.paymentCheck = response.data.payment_hash From f3b720b690c533b4b28793209f5a71fd01b9af6e Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Sat, 12 Nov 2022 17:11:24 +0000 Subject: [PATCH 113/154] Fix usermanager API keys (#1117) * fix wrong API keys on examples * add hability to keep wallet after user delete #344 * make format --- lnbits/extensions/usermanager/crud.py | 9 +++++---- .../templates/usermanager/_api_docs.html | 13 ++++++------- lnbits/extensions/usermanager/views_api.py | 10 ++++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lnbits/extensions/usermanager/crud.py b/lnbits/extensions/usermanager/crud.py index 1ce66d4f..649888a8 100644 --- a/lnbits/extensions/usermanager/crud.py +++ b/lnbits/extensions/usermanager/crud.py @@ -63,10 +63,11 @@ async def get_usermanager_users(user_id: str) -> List[Users]: return [Users(**row) for row in rows] -async def delete_usermanager_user(user_id: str) -> None: - wallets = await get_usermanager_wallets(user_id) - for wallet in wallets: - await delete_wallet(user_id=user_id, wallet_id=wallet.id) +async def delete_usermanager_user(user_id: str, delete_core: bool = True) -> None: + if delete_core: + wallets = await get_usermanager_wallets(user_id) + for wallet in wallets: + await delete_wallet(user_id=user_id, wallet_id=wallet.id) await db.execute("DELETE FROM usermanager.users WHERE id = ?", (user_id,)) await db.execute("""DELETE FROM usermanager.wallets WHERE "user" = ?""", (user_id,)) diff --git a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html index 36593d74..ff3ba85a 100644 --- a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html +++ b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html @@ -44,7 +44,7 @@
Curl example
curl -X GET {{ request.base_url }}usermanager/api/v1/users -H - "X-Api-Key: {{ user.wallets[0].inkey }}" + "X-Api-Key: {{ user.wallets[0].adminkey }}" @@ -81,7 +81,7 @@ GET - /usermanager/api/v1/wallets/<user_id>
Headers
{"X-Api-Key": <string>} @@ -92,9 +92,8 @@ JSON wallet data
Curl example
curl -X GET {{ request.base_url - }}usermanager/api/v1/wallets/<user_id> -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url }}usermanager/api/v1/wallets -H + "X-Api-Key: {{ user.wallets[0].adminkey }}"
@@ -221,7 +220,7 @@ curl -X DELETE {{ request.base_url }}usermanager/api/v1/users/<user_id> -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + user.wallets[0].adminkey }}" @@ -239,7 +238,7 @@ curl -X DELETE {{ request.base_url }}usermanager/api/v1/wallets/<wallet_id> -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + user.wallets[0].adminkey }}" diff --git a/lnbits/extensions/usermanager/views_api.py b/lnbits/extensions/usermanager/views_api.py index 7e7b7653..b1bf8ef8 100644 --- a/lnbits/extensions/usermanager/views_api.py +++ b/lnbits/extensions/usermanager/views_api.py @@ -52,15 +52,17 @@ async def api_usermanager_users_create( @usermanager_ext.delete("/api/v1/users/{user_id}") async def api_usermanager_users_delete( - user_id, wallet: WalletTypeInfo = Depends(require_admin_key) + user_id, + delete_core: bool = Query(True), + wallet: WalletTypeInfo = Depends(require_admin_key), ): user = await get_usermanager_user(user_id) if not user: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." ) - await delete_usermanager_user(user_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + await delete_usermanager_user(user_id, delete_core) + return "", HTTPStatus.NO_CONTENT # Activate Extension @@ -124,4 +126,4 @@ async def api_usermanager_wallets_delete( status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist." ) await delete_usermanager_wallet(wallet_id, get_wallet.user) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT From 91be3c30b60ef76282ca67a4a31dce9dd37aa05e Mon Sep 17 00:00:00 2001 From: ChuckNorrison <2964146+ChuckNorrison@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:13:25 +0100 Subject: [PATCH 114/154] add missing import sys allow to call sys module directly --- tools/conv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/conv.py b/tools/conv.py index cb8f0573..5a535a8b 100644 --- a/tools/conv.py +++ b/tools/conv.py @@ -1,5 +1,5 @@ import argparse -import os +import os, sys import sqlite3 from typing import List @@ -24,7 +24,7 @@ sqfolder = env.str("LNBITS_DATA_FOLDER", default=None) LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None) if LNBITS_DATABASE_URL is None: print("missing LNBITS_DATABASE_URL") - os.sys.exit(1) + sys.exit(1) else: # parse postgres://lnbits:postgres@localhost:5432/lnbits pgdb = LNBITS_DATABASE_URL.split("/")[-1] From 00aa0c6cd9e8c23e2ea658e3220caf5e641c8e5d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 16 Nov 2022 16:32:57 +0200 Subject: [PATCH 115/154] chore: code format --- lnbits/extensions/events/__init__.py | 1 - lnbits/extensions/events/tasks.py | 4 ++-- lnbits/extensions/events/templates/events/display.html | 9 ++++++++- lnbits/extensions/events/views_api.py | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/events/__init__.py b/lnbits/extensions/events/__init__.py index c64a866b..f689aaa6 100644 --- a/lnbits/extensions/events/__init__.py +++ b/lnbits/extensions/events/__init__.py @@ -6,7 +6,6 @@ from lnbits.db import Database from lnbits.helpers import template_renderer from lnbits.tasks import catch_everything_and_restart - db = Database("ext_events") diff --git a/lnbits/extensions/events/tasks.py b/lnbits/extensions/events/tasks.py index 2068b3fa..d29215bf 100644 --- a/lnbits/extensions/events/tasks.py +++ b/lnbits/extensions/events/tasks.py @@ -5,16 +5,16 @@ from urllib.parse import urlparse import httpx from fastapi import HTTPException +from loguru import logger from lnbits import bolt11 from lnbits.core.models import Payment from lnbits.core.services import pay_invoice +from lnbits.extensions.events.models import CreateTicket from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .views_api import api_ticket_send_ticket -from loguru import logger -from lnbits.extensions.events.models import CreateTicket async def wait_for_paid_invoices(): diff --git a/lnbits/extensions/events/templates/events/display.html b/lnbits/extensions/events/templates/events/display.html index 9dbb95bc..e65b4e61 100644 --- a/lnbits/extensions/events/templates/events/display.html +++ b/lnbits/extensions/events/templates/events/display.html @@ -135,7 +135,14 @@ var self = this axios - .get('/events/api/v1/tickets/' + '{{ event_id }}' + '/' + self.formDialog.data.name + '/' + self.formDialog.data.email) + .get( + '/events/api/v1/tickets/' + + '{{ event_id }}' + + '/' + + self.formDialog.data.name + + '/' + + self.formDialog.data.email + ) .then(function (response) { self.paymentReq = response.data.payment_request self.paymentCheck = response.data.payment_hash diff --git a/lnbits/extensions/events/views_api.py b/lnbits/extensions/events/views_api.py index 08651f5d..30e7962e 100644 --- a/lnbits/extensions/events/views_api.py +++ b/lnbits/extensions/events/views_api.py @@ -2,6 +2,7 @@ from http import HTTPStatus from fastapi.param_functions import Query from fastapi.params import Depends +from loguru import logger from starlette.exceptions import HTTPException from starlette.requests import Request @@ -10,7 +11,6 @@ from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type from lnbits.extensions.events.models import CreateEvent, CreateTicket -from loguru import logger from . import events_ext from .crud import ( From 1971b9a24bd53a750ee8520b4714b4068d6f9dda Mon Sep 17 00:00:00 2001 From: ChuckNorrison <2964146+ChuckNorrison@users.noreply.github.com> Date: Wed, 16 Nov 2022 23:06:21 +0100 Subject: [PATCH 116/154] fix integer out of range for apipayments related to #1030, this was missing --- lnbits/core/migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/migrations.py b/lnbits/core/migrations.py index ebecb5e3..d92f384a 100644 --- a/lnbits/core/migrations.py +++ b/lnbits/core/migrations.py @@ -51,7 +51,7 @@ async def m001_initial(db): f""" CREATE TABLE IF NOT EXISTS apipayments ( payhash TEXT NOT NULL, - amount INTEGER NOT NULL, + amount {db.big_int} NOT NULL, fee INTEGER NOT NULL DEFAULT 0, wallet TEXT NOT NULL, pending BOOLEAN NOT NULL, From fcc787ed35b6190da65395eec0f711d180a2c487 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Wed, 16 Nov 2022 15:34:35 -0800 Subject: [PATCH 117/154] update Dockerfile dir to lnbits/data --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index af3f3616..f107f68c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN curl -sSL https://install.python-poetry.org | python3 - ENV PATH="/root/.local/bin:$PATH" WORKDIR /app -RUN mkdir -p /app/lnbits/data +RUN mkdir -p lnbits/data COPY . . From 032df0e50ca0151751ab6675b8c3fb01de9cd56f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 8 Nov 2022 20:01:09 +0200 Subject: [PATCH 118/154] feat: allow decimal fee rate --- .../watchonly/static/components/fee-rate/fee-rate.html | 1 + 1 file changed, 1 insertion(+) diff --git a/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html b/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html index c65ad1c4..0df5bebf 100644 --- a/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html +++ b/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html @@ -6,6 +6,7 @@ filled dense v-model.number="feeRate" + step="any" :rules="[val => !!val || 'Field is required']" type="number" label="sats/vbyte" From 0673245d112b4a781cb956a62c62c231d77b758e Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 8 Nov 2022 10:34:11 +0200 Subject: [PATCH 119/154] chore: update watchonly db --- tests/data/mock_data.zip | Bin 24852 -> 30790 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/data/mock_data.zip b/tests/data/mock_data.zip index d184f94ad6f20b2806bb438d047802f452c0847b..5a52941c74a0ef02265cb3ad046998b113cfa3a0 100644 GIT binary patch delta 18912 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 delta 12346 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 jTQ Date: Wed, 2 Nov 2022 15:30:18 +0000 Subject: [PATCH 120/154] fix raise error on delete request --- lnbits/extensions/bleskomat/views_api.py | 2 +- lnbits/extensions/boltcards/views_api.py | 2 +- lnbits/extensions/discordbot/views_api.py | 4 ++-- lnbits/extensions/events/views_api.py | 2 +- lnbits/extensions/livestream/views_api.py | 8 ++++---- lnbits/extensions/lnaddress/views_api.py | 4 ++-- lnbits/extensions/lnticket/views_api.py | 4 ++-- lnbits/extensions/lnurlpayout/views_api.py | 2 +- lnbits/extensions/offlineshop/views_api.py | 2 +- lnbits/extensions/paywall/views_api.py | 2 +- lnbits/extensions/satspay/views_api.py | 2 +- lnbits/extensions/scrub/views_api.py | 2 +- lnbits/extensions/streamalerts/views_api.py | 4 ++-- lnbits/extensions/subdomains/views_api.py | 4 ++-- lnbits/extensions/tpos/views_api.py | 2 +- 15 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lnbits/extensions/bleskomat/views_api.py b/lnbits/extensions/bleskomat/views_api.py index e29e3fe7..b6e417bb 100644 --- a/lnbits/extensions/bleskomat/views_api.py +++ b/lnbits/extensions/bleskomat/views_api.py @@ -95,4 +95,4 @@ async def api_bleskomat_delete( ) await delete_bleskomat(bleskomat_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT diff --git a/lnbits/extensions/boltcards/views_api.py b/lnbits/extensions/boltcards/views_api.py index 7b8357cf..3dfa6c21 100644 --- a/lnbits/extensions/boltcards/views_api.py +++ b/lnbits/extensions/boltcards/views_api.py @@ -129,7 +129,7 @@ async def api_card_delete(card_id, wallet: WalletTypeInfo = Depends(require_admi raise HTTPException(detail="Not your card.", status_code=HTTPStatus.FORBIDDEN) await delete_card(card_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT @boltcards_ext.get("/api/v1/hits") diff --git a/lnbits/extensions/discordbot/views_api.py b/lnbits/extensions/discordbot/views_api.py index e6d004db..b69c274a 100644 --- a/lnbits/extensions/discordbot/views_api.py +++ b/lnbits/extensions/discordbot/views_api.py @@ -65,7 +65,7 @@ async def api_discordbot_users_delete( status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." ) await delete_discordbot_user(user_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT # Activate Extension @@ -129,4 +129,4 @@ async def api_discordbot_wallets_delete( status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist." ) await delete_discordbot_wallet(wallet_id, get_wallet.user) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT diff --git a/lnbits/extensions/events/views_api.py b/lnbits/extensions/events/views_api.py index 30e7962e..668e7f77 100644 --- a/lnbits/extensions/events/views_api.py +++ b/lnbits/extensions/events/views_api.py @@ -79,7 +79,7 @@ async def api_form_delete(event_id, wallet: WalletTypeInfo = Depends(get_key_typ await delete_event(event_id) await delete_event_tickets(event_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT #########Tickets########## diff --git a/lnbits/extensions/livestream/views_api.py b/lnbits/extensions/livestream/views_api.py index cc173a66..0c169a71 100644 --- a/lnbits/extensions/livestream/views_api.py +++ b/lnbits/extensions/livestream/views_api.py @@ -60,14 +60,14 @@ async def api_update_track(track_id, g: WalletTypeInfo = Depends(get_key_type)): ls = await get_or_create_livestream_by_wallet(g.wallet.id) await update_current_track(ls.id, id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT @livestream_ext.put("/api/v1/livestream/fee/{fee_pct}") async def api_update_fee(fee_pct, g: WalletTypeInfo = Depends(get_key_type)): ls = await get_or_create_livestream_by_wallet(g.wallet.id) await update_livestream_fee(ls.id, int(fee_pct)) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT @livestream_ext.post("/api/v1/livestream/tracks") @@ -93,8 +93,8 @@ async def api_add_track( return -@livestream_ext.route("/api/v1/livestream/tracks/{track_id}") +@livestream_ext.delete("/api/v1/livestream/tracks/{track_id}") async def api_delete_track(track_id, g: WalletTypeInfo = Depends(get_key_type)): ls = await get_or_create_livestream_by_wallet(g.wallet.id) await delete_track_from_livestream(ls.id, track_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT diff --git a/lnbits/extensions/lnaddress/views_api.py b/lnbits/extensions/lnaddress/views_api.py index 8f403a38..46ef6b99 100644 --- a/lnbits/extensions/lnaddress/views_api.py +++ b/lnbits/extensions/lnaddress/views_api.py @@ -93,7 +93,7 @@ async def api_domain_delete(domain_id, g: WalletTypeInfo = Depends(get_key_type) raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your domain") await delete_domain(domain_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT # ADDRESSES @@ -253,4 +253,4 @@ async def api_address_delete(address_id, g: WalletTypeInfo = Depends(get_key_typ ) await delete_address(address_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT diff --git a/lnbits/extensions/lnticket/views_api.py b/lnbits/extensions/lnticket/views_api.py index 7c9eb52c..cf6145b3 100644 --- a/lnbits/extensions/lnticket/views_api.py +++ b/lnbits/extensions/lnticket/views_api.py @@ -78,7 +78,7 @@ async def api_form_delete(form_id, wallet: WalletTypeInfo = Depends(get_key_type await delete_form(form_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT #########tickets########## @@ -160,4 +160,4 @@ async def api_ticket_delete(ticket_id, wallet: WalletTypeInfo = Depends(get_key_ raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your ticket.") await delete_ticket(ticket_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT diff --git a/lnbits/extensions/lnurlpayout/views_api.py b/lnbits/extensions/lnurlpayout/views_api.py index 67562909..324eb5dd 100644 --- a/lnbits/extensions/lnurlpayout/views_api.py +++ b/lnbits/extensions/lnurlpayout/views_api.py @@ -80,7 +80,7 @@ async def api_lnurlpayout_delete( ) await delete_lnurlpayout(lnurlpayout_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT @lnurlpayout_ext.get("/api/v1/lnurlpayouts/{lnurlpayout_id}", status_code=HTTPStatus.OK) diff --git a/lnbits/extensions/offlineshop/views_api.py b/lnbits/extensions/offlineshop/views_api.py index 1bd045dd..71583b9e 100644 --- a/lnbits/extensions/offlineshop/views_api.py +++ b/lnbits/extensions/offlineshop/views_api.py @@ -93,7 +93,7 @@ async def api_add_or_update_item( async def api_delete_item(item_id, wallet: WalletTypeInfo = Depends(get_key_type)): shop = await get_or_create_shop_by_wallet(wallet.wallet.id) await delete_item_from_shop(shop.id, item_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT class CreateMethodData(BaseModel): diff --git a/lnbits/extensions/paywall/views_api.py b/lnbits/extensions/paywall/views_api.py index d2d49853..3c1cd8fc 100644 --- a/lnbits/extensions/paywall/views_api.py +++ b/lnbits/extensions/paywall/views_api.py @@ -49,7 +49,7 @@ async def api_paywall_delete( ) await delete_paywall(paywall_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT @paywall_ext.post("/api/v1/paywalls/invoice/{paywall_id}") diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index 73c87e7c..e1b87c41 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -94,7 +94,7 @@ async def api_charge_delete(charge_id, wallet: WalletTypeInfo = Depends(get_key_ ) await delete_charge(charge_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT #############################BALANCE########################## diff --git a/lnbits/extensions/scrub/views_api.py b/lnbits/extensions/scrub/views_api.py index 3714a304..cc55c15d 100644 --- a/lnbits/extensions/scrub/views_api.py +++ b/lnbits/extensions/scrub/views_api.py @@ -109,4 +109,4 @@ async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(require_admi ) await delete_scrub_link(link_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT diff --git a/lnbits/extensions/streamalerts/views_api.py b/lnbits/extensions/streamalerts/views_api.py index bb2998ee..058f5126 100644 --- a/lnbits/extensions/streamalerts/views_api.py +++ b/lnbits/extensions/streamalerts/views_api.py @@ -245,7 +245,7 @@ async def api_delete_donation(donation_id, g: WalletTypeInfo = Depends(get_key_t detail="Not authorized to delete this donation!", ) await delete_donation(donation_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT @streamalerts_ext.delete("/api/v1/services/{service_id}") @@ -262,4 +262,4 @@ async def api_delete_service(service_id, g: WalletTypeInfo = Depends(get_key_typ detail="Not authorized to delete this service!", ) await delete_service(service_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT diff --git a/lnbits/extensions/subdomains/views_api.py b/lnbits/extensions/subdomains/views_api.py index 34d8e75b..b30daabd 100644 --- a/lnbits/extensions/subdomains/views_api.py +++ b/lnbits/extensions/subdomains/views_api.py @@ -81,7 +81,7 @@ async def api_domain_delete( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your domain.") await delete_domain(domain_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT #########subdomains########## @@ -198,4 +198,4 @@ async def api_subdomain_delete( ) await delete_subdomain(subdomain_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index b7f14b98..80501491 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -51,7 +51,7 @@ async def api_tpos_delete( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your TPoS.") await delete_tpos(tpos_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + return "", HTTPStatus.NO_CONTENT @tpos_ext.post("/api/v1/tposs/{tpos_id}/invoices", status_code=HTTPStatus.CREATED) From 0b883d02b3f2cd6a302d60ea09c60641306b1911 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 8 Nov 2022 13:16:08 +0000 Subject: [PATCH 121/154] get payment from db --- lnbits/extensions/tpos/crud.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lnbits/extensions/tpos/crud.py b/lnbits/extensions/tpos/crud.py index 94e2c006..04b5eaa9 100644 --- a/lnbits/extensions/tpos/crud.py +++ b/lnbits/extensions/tpos/crud.py @@ -1,5 +1,6 @@ from typing import List, Optional, Union +from lnbits.core.models import Payment from lnbits.helpers import urlsafe_short_hash from . import db @@ -47,3 +48,12 @@ async def get_tposs(wallet_ids: Union[str, List[str]]) -> List[TPoS]: async def delete_tpos(tpos_id: str) -> None: await db.execute("DELETE FROM tpos.tposs WHERE id = ?", (tpos_id,)) + + +async def get_tpos_payments(tpos_id: str, limit: int = 5): + + rows = await db.fetchall( + f"SELECT * FROM apipayments WHERE extra LIKE '%tposId%' AND extra LIKE '%{tpos_id}%' ORDER BY time DESC LIMIT {limit}" + ) + + return [Payment.from_row(row) for row in rows] From 8d8a309258dcd560d2277a8d0e7c4d1cf1394515 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 8 Nov 2022 13:16:27 +0000 Subject: [PATCH 122/154] api endpoint to get last payments --- lnbits/extensions/tpos/views_api.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 80501491..9167b5a1 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -13,7 +13,7 @@ from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from . import tpos_ext -from .crud import create_tpos, delete_tpos, get_tpos, get_tposs +from .crud import create_tpos, delete_tpos, get_tpos, get_tpos_payments, get_tposs from .models import CreateTposData, PayLnurlWData @@ -81,6 +81,22 @@ async def api_tpos_create_invoice( return {"payment_hash": payment_hash, "payment_request": payment_request} +@tpos_ext.get("/api/v1/tposs/{tpos_id}/invoices") +async def api_tpos_get_latest_invoices(tpos_id: str = None): + payments = await get_tpos_payments(tpos_id) + + return [ + { + "checking_id": payment.checking_id, + "amount": payment.amount, + "time": payment.time, + "bolt11": payment.bolt11, + "pending": payment.pending, + } + for payment in payments + ] + + @tpos_ext.post( "/api/v1/tposs/{tpos_id}/invoices/{payment_request}/pay", status_code=HTTPStatus.OK ) From 9f7b4c1370a93eb51a419951b2db0d570f572eb2 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 8 Nov 2022 13:16:47 +0000 Subject: [PATCH 123/154] add button and list --- .../extensions/tpos/templates/tpos/tpos.html | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index ad04b3be..36d76cf7 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -148,6 +148,9 @@
+ + + + + + + + + + + + + {%raw%} + + {{payment.amount / 1000}} sats + {{payment.checking_id}} + + + {{payment.dateFrom}} + + + {%endraw%} + + + + {% endblock %} {% block styles %} @@ -296,6 +322,10 @@ tipAmount: 0.0, hasNFC: false, nfcTagReading: false, + lastPaymentsDialog: { + show: false, + data: [] + }, invoiceDialog: { show: false, data: null, @@ -520,6 +550,25 @@ self.exchangeRate = response.data.data['BTC' + self.currency][self.currency] }) + }, + getLastPayments(){ + return axios + .get(`/tpos/api/v1/tposs/${this.tposId}/invoices`) + .then(res => { + if(res.data && res.data.length){ + let last = [...res.data] + //last.length = Math.min(last.length, 5) + this.lastPaymentsDialog.data = last.map(obj => { + obj.dateFrom = moment(obj.time * 1000).fromNow() + return obj + }) + } + }) + .catch(e => console.error(e)) + }, + showLastPayments(){ + this.getLastPayments() + this.lastPaymentsDialog.show = true } }, created: function () { From 8e85651c4fbefdec0c389af029a419c7ad3966c5 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 8 Nov 2022 13:18:10 +0000 Subject: [PATCH 124/154] make format --- .../extensions/tpos/templates/tpos/tpos.html | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 36d76cf7..32ed0b88 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -149,7 +149,12 @@
- + {{payment.amount / 1000}} sats - {{payment.checking_id}} + {{payment.checking_id}} {{payment.dateFrom}} - + {%endraw%} - + @@ -551,11 +561,11 @@ response.data.data['BTC' + self.currency][self.currency] }) }, - getLastPayments(){ + getLastPayments() { return axios .get(`/tpos/api/v1/tposs/${this.tposId}/invoices`) .then(res => { - if(res.data && res.data.length){ + if (res.data && res.data.length) { let last = [...res.data] //last.length = Math.min(last.length, 5) this.lastPaymentsDialog.data = last.map(obj => { @@ -566,7 +576,7 @@ }) .catch(e => console.error(e)) }, - showLastPayments(){ + showLastPayments() { this.getLastPayments() this.lastPaymentsDialog.show = true } From a2092675cdceaf974bce48fcee1257107c51361b Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 8 Nov 2022 15:18:07 +0000 Subject: [PATCH 125/154] added some UI sugar, removed pending tx --- lnbits/extensions/tpos/crud.py | 8 +++++++- lnbits/extensions/tpos/templates/tpos/tpos.html | 11 +++++------ lnbits/extensions/tpos/views_api.py | 1 - 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/tpos/crud.py b/lnbits/extensions/tpos/crud.py index 04b5eaa9..c403eb5a 100644 --- a/lnbits/extensions/tpos/crud.py +++ b/lnbits/extensions/tpos/crud.py @@ -53,7 +53,13 @@ async def delete_tpos(tpos_id: str) -> None: async def get_tpos_payments(tpos_id: str, limit: int = 5): rows = await db.fetchall( - f"SELECT * FROM apipayments WHERE extra LIKE '%tposId%' AND extra LIKE '%{tpos_id}%' ORDER BY time DESC LIMIT {limit}" + f""" + SELECT * FROM apipayments + WHERE pending = 'false' + AND extra LIKE '%tposId%' + AND extra LIKE '%{tpos_id}%' + ORDER BY time DESC LIMIT {limit} + """ ) return [Payment.from_row(row) for row in rows] diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 32ed0b88..8fe61452 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -275,17 +275,16 @@ {%raw%} - {{payment.amount / 1000}} sats + {{payment.amount / 1000}} sats {{payment.checking_id}}Hash: {{payment.checking_id.slice(0, 30)}}... {{payment.dateFrom}} - + {%endraw%} diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 9167b5a1..206c1956 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -90,7 +90,6 @@ async def api_tpos_get_latest_invoices(tpos_id: str = None): "checking_id": payment.checking_id, "amount": payment.amount, "time": payment.time, - "bolt11": payment.bolt11, "pending": payment.pending, } for payment in payments From e667d2dc190edfb94736374bf90643b1553f8bc0 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 17 Nov 2022 14:59:03 +0200 Subject: [PATCH 126/154] chore: code format --- tools/conv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/conv.py b/tools/conv.py index 5a535a8b..541a0b75 100644 --- a/tools/conv.py +++ b/tools/conv.py @@ -1,6 +1,7 @@ import argparse -import os, sys +import os import sqlite3 +import sys from typing import List import psycopg2 From 4549190e8688a58f874706c41ee2ddf223586d79 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 17 Nov 2022 12:59:40 +0000 Subject: [PATCH 127/154] abstract get latest payments for extensions --- lnbits/core/crud.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index bb1ca0c1..881d1001 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -229,6 +229,24 @@ async def get_wallet_payment( return Payment.from_row(row) if row else None +async def get_latest_payments_by_extension(ext_name: str, ext_id: str, limit: int = 5): + rows = await db.fetchall( + f""" + SELECT * FROM apipayments + WHERE pending = 'false' + AND extra LIKE ? + AND extra LIKE ? + ORDER BY time DESC LIMIT {limit} + """, + ( + f"%{ext_name}%", + f"%{ext_id}%", + ), + ) + + return rows + + async def get_payments( *, wallet_id: Optional[str] = None, From facc7bbf5e3e7ffb70da6490f695dafeb5428d9f Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 17 Nov 2022 13:00:17 +0000 Subject: [PATCH 128/154] remove core db action from extension --- lnbits/extensions/tpos/crud.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lnbits/extensions/tpos/crud.py b/lnbits/extensions/tpos/crud.py index c403eb5a..94e2c006 100644 --- a/lnbits/extensions/tpos/crud.py +++ b/lnbits/extensions/tpos/crud.py @@ -1,6 +1,5 @@ from typing import List, Optional, Union -from lnbits.core.models import Payment from lnbits.helpers import urlsafe_short_hash from . import db @@ -48,18 +47,3 @@ async def get_tposs(wallet_ids: Union[str, List[str]]) -> List[TPoS]: async def delete_tpos(tpos_id: str) -> None: await db.execute("DELETE FROM tpos.tposs WHERE id = ?", (tpos_id,)) - - -async def get_tpos_payments(tpos_id: str, limit: int = 5): - - rows = await db.fetchall( - f""" - SELECT * FROM apipayments - WHERE pending = 'false' - AND extra LIKE '%tposId%' - AND extra LIKE '%{tpos_id}%' - ORDER BY time DESC LIMIT {limit} - """ - ) - - return [Payment.from_row(row) for row in rows] From 121331fa0b0d884b705eb89ce8160b530c3375c6 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 17 Nov 2022 13:00:53 +0000 Subject: [PATCH 129/154] refactor get latest payments --- lnbits/extensions/tpos/views_api.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 206c1956..e8dec2b4 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -7,13 +7,14 @@ from lnurl import decode as decode_lnurl from loguru import logger from starlette.exceptions import HTTPException -from lnbits.core.crud import get_user +from lnbits.core.crud import get_user, get_latest_payments_by_extension from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key +from lnbits.core.models import Payment from . import tpos_ext -from .crud import create_tpos, delete_tpos, get_tpos, get_tpos_payments, get_tposs +from .crud import create_tpos, delete_tpos, get_tpos, get_tposs from .models import CreateTposData, PayLnurlWData @@ -83,7 +84,12 @@ async def api_tpos_create_invoice( @tpos_ext.get("/api/v1/tposs/{tpos_id}/invoices") async def api_tpos_get_latest_invoices(tpos_id: str = None): - payments = await get_tpos_payments(tpos_id) + payments = [ + Payment.from_row(row) + for row in await get_latest_payments_by_extension( + ext_name="tpos", ext_id=tpos_id + ) + ] return [ { From e93d1b322be5b71567a8e6ca4ed8cd5eadbea74a Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 17 Nov 2022 13:02:32 +0000 Subject: [PATCH 130/154] make format --- lnbits/extensions/tpos/views_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index e8dec2b4..204548b1 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -7,11 +7,11 @@ from lnurl import decode as decode_lnurl from loguru import logger from starlette.exceptions import HTTPException -from lnbits.core.crud import get_user, get_latest_payments_by_extension +from lnbits.core.crud import get_latest_payments_by_extension, get_user +from lnbits.core.models import Payment from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.core.models import Payment from . import tpos_ext from .crud import create_tpos, delete_tpos, get_tpos, get_tposs From 9f03d25a8c7563ea5241c5e40db14cb6c4fe0a59 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 28 Oct 2022 11:27:11 +0100 Subject: [PATCH 131/154] fix issue with export to CSV --- lnbits/extensions/withdraw/static/js/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/withdraw/static/js/index.js b/lnbits/extensions/withdraw/static/js/index.js index 943e9024..c8d24f10 100644 --- a/lnbits/extensions/withdraw/static/js/index.js +++ b/lnbits/extensions/withdraw/static/js/index.js @@ -290,8 +290,8 @@ new Vue({ }) } }, - exportCSV: function () { - LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls) + exportCSV() { + LNbits.utils.exportCSV(this.withdrawLinksTable.columns, this.withdrawLinks) } }, created: function () { From bd85e5dbe37ef1ee818ca333764c982e528633f5 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 17 Nov 2022 15:05:12 +0200 Subject: [PATCH 132/154] feat: set custom file name to the exported `CSV` --- lnbits/extensions/withdraw/static/js/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/withdraw/static/js/index.js b/lnbits/extensions/withdraw/static/js/index.js index c8d24f10..a3eaa593 100644 --- a/lnbits/extensions/withdraw/static/js/index.js +++ b/lnbits/extensions/withdraw/static/js/index.js @@ -291,7 +291,11 @@ new Vue({ } }, exportCSV() { - LNbits.utils.exportCSV(this.withdrawLinksTable.columns, this.withdrawLinks) + LNbits.utils.exportCSV( + this.withdrawLinksTable.columns, + this.withdrawLinks, + 'withdraw-links' + ) } }, created: function () { From 8370acc1f79b7b362850de8ed043746c386258f0 Mon Sep 17 00:00:00 2001 From: cryptoteun Date: Thu, 1 Sep 2022 21:59:38 +0200 Subject: [PATCH 133/154] added warning to backup keys --- lnbits/extensions/boltcards/templates/boltcards/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html index 55cc1e5e..b95a52bf 100644 --- a/lnbits/extensions/boltcards/templates/boltcards/index.html +++ b/lnbits/extensions/boltcards/templates/boltcards/index.html @@ -380,7 +380,10 @@ Lock key: {{ qrCodeDialog.data.k0 }}
Meta key: {{ qrCodeDialog.data.k1 }}
File key: {{ qrCodeDialog.data.k2 }}
+
+ Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!

+
Date: Thu, 17 Nov 2022 15:48:34 +0200 Subject: [PATCH 134/154] chore: code format --- lnbits/extensions/boltcards/templates/boltcards/index.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html index b95a52bf..6398c20e 100644 --- a/lnbits/extensions/boltcards/templates/boltcards/index.html +++ b/lnbits/extensions/boltcards/templates/boltcards/index.html @@ -380,8 +380,9 @@ Lock key: {{ qrCodeDialog.data.k0 }}
Meta key: {{ qrCodeDialog.data.k1 }}
File key: {{ qrCodeDialog.data.k2 }}
-
- Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!
+
+ Always backup all keys that you're trying to write on the card. Without + them you may not be able to change them in the future!


From 2eaf5e5668501c2bfb348d9af34c2b03f3e1a12f Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 17 Nov 2022 15:33:20 +0000 Subject: [PATCH 135/154] remove commented out code --- lnbits/extensions/tpos/templates/tpos/tpos.html | 1 - 1 file changed, 1 deletion(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 8fe61452..12e18bec 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -566,7 +566,6 @@ .then(res => { if (res.data && res.data.length) { let last = [...res.data] - //last.length = Math.min(last.length, 5) this.lastPaymentsDialog.data = last.map(obj => { obj.dateFrom = moment(obj.time * 1000).fromNow() return obj From ebeecdecca4bd15fff506e6fd383b90bfdf879d9 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 18 Nov 2022 10:22:11 +0000 Subject: [PATCH 136/154] add "No paid invoices" if... no paid invoices --- lnbits/extensions/tpos/templates/tpos/tpos.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 12e18bec..6bc6a626 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -272,6 +272,13 @@ + + + No paid invoices + + {%raw%} From 28e62f3333f5ea651bae4d52923d59e4f793399d Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 18 Nov 2022 10:27:23 +0000 Subject: [PATCH 137/154] make format --- lnbits/extensions/tpos/templates/tpos/tpos.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 6bc6a626..c66238f7 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -274,9 +274,7 @@ - No paid invoices + No paid invoices From 79fc0b9be4785f7a8a3f403c785e9029338a4544 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 18 Nov 2022 10:56:14 +0000 Subject: [PATCH 138/154] wrap db call in try except --- lnbits/extensions/tpos/views_api.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 204548b1..fe63a247 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -84,12 +84,16 @@ async def api_tpos_create_invoice( @tpos_ext.get("/api/v1/tposs/{tpos_id}/invoices") async def api_tpos_get_latest_invoices(tpos_id: str = None): - payments = [ - Payment.from_row(row) - for row in await get_latest_payments_by_extension( - ext_name="tpos", ext_id=tpos_id - ) - ] + try: + payments = [ + Payment.from_row(row) + for row in await get_latest_payments_by_extension( + ext_name="tpos", ext_id=tpos_id + ) + ] + + except Exception as e: + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) return [ { From b0b14464835a9b460407d1c8fe4d142b43bbf3a2 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 27 Oct 2022 19:14:26 +0100 Subject: [PATCH 139/154] fixed display amount --- .../extensions/tpos/templates/tpos/tpos.html | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index c66238f7..9e462aba 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -360,15 +360,15 @@ computed: { amount: function () { if (!this.stack.length) return 0.0 - return (Number(this.stack.join('')) / 100).toFixed(2) + return Number(this.stack.join('') / 100) }, famount: function () { - return LNbits.utils.formatCurrency(this.amount, this.currency) + return LNbits.utils.formatCurrency(this.amount.toFixed(2), this.currency) }, sat: function () { if (!this.exchangeRate) return 0 return Math.ceil( - ((this.amount - this.tipAmount) / this.exchangeRate) * 100000000 + (this.amount / this.exchangeRate) * 100000000 ) }, tipAmountSat: function () { @@ -392,26 +392,25 @@ processTipSelection: function (selectedTipOption) { this.tipDialog.show = false - if (selectedTipOption) { - const tipAmount = parseFloat( - parseFloat((selectedTipOption / 100) * this.amount) - ) - const subtotal = parseFloat(this.amount) - const grandTotal = parseFloat((tipAmount + subtotal).toFixed(2)) - const totalString = grandTotal.toFixed(2).toString() + if(!selectedTipOption) return this.showInvoice() - this.stack = [] - for (var i = 0; i < totalString.length; i++) { - const char = totalString[i] + const tipAmount = (selectedTipOption / 100) * this.amount + const subtotal = this.amount + const grandTotal = (tipAmount + subtotal) + const totalString = grandTotal.toFixed(2) - if (char !== '.') { - this.stack.push(char) - } + this.stack = [] + for (var i = 0; i < totalString.length; i++) { + const char = totalString[i] + + if (char !== '.') { + this.stack.push(char) } - - this.tipAmount = tipAmount } + + this.tipAmount = tipAmount + this.showInvoice() }, submitForm: function () { From 476f915fd28a41b2676025d7b1834d850ca0c914 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 27 Oct 2022 19:16:40 +0100 Subject: [PATCH 140/154] format --- lnbits/extensions/tpos/templates/tpos/tpos.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 9e462aba..929af4d7 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -363,13 +363,14 @@ return Number(this.stack.join('') / 100) }, famount: function () { - return LNbits.utils.formatCurrency(this.amount.toFixed(2), this.currency) + return LNbits.utils.formatCurrency( + this.amount.toFixed(2), + this.currency + ) }, sat: function () { if (!this.exchangeRate) return 0 - return Math.ceil( - (this.amount / this.exchangeRate) * 100000000 - ) + return Math.ceil((this.amount / this.exchangeRate) * 100000000) }, tipAmountSat: function () { if (!this.exchangeRate) return 0 @@ -392,11 +393,11 @@ processTipSelection: function (selectedTipOption) { this.tipDialog.show = false - if(!selectedTipOption) return this.showInvoice() + if (!selectedTipOption) return this.showInvoice() const tipAmount = (selectedTipOption / 100) * this.amount const subtotal = this.amount - const grandTotal = (tipAmount + subtotal) + const grandTotal = tipAmount + subtotal const totalString = grandTotal.toFixed(2) this.stack = [] @@ -408,7 +409,6 @@ } } - this.tipAmount = tipAmount this.showInvoice() From 9ecf6f273b092f33573c9f541e616852be330e2f Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Nov 2022 11:42:30 +0200 Subject: [PATCH 141/154] fix: total amount for invoice --- lnbits/extensions/tpos/templates/tpos/tpos.html | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 929af4d7..0dd105b9 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -395,22 +395,7 @@ if (!selectedTipOption) return this.showInvoice() - const tipAmount = (selectedTipOption / 100) * this.amount - const subtotal = this.amount - const grandTotal = tipAmount + subtotal - const totalString = grandTotal.toFixed(2) - - this.stack = [] - for (var i = 0; i < totalString.length; i++) { - const char = totalString[i] - - if (char !== '.') { - this.stack.push(char) - } - } - - this.tipAmount = tipAmount - + this.tipAmount = (selectedTipOption / 100) * this.amount this.showInvoice() }, submitForm: function () { From 0039a48e730565c308d2e5bef02e97de823207bc Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Nov 2022 12:02:51 +0200 Subject: [PATCH 142/154] fix: total amount (amount + tip) --- .../extensions/tpos/templates/tpos/tpos.html | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 0dd105b9..0f9620a9 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -13,7 +13,7 @@
-

{% raw %}{{ famount }}{% endraw %}

+

{% raw %}{{ amountFormatted }}{% endraw %}

{% raw %}{{ fsat }}{% endraw %} sat
@@ -173,12 +173,14 @@ >
-

{% raw %}{{ famount }}{% endraw %}

+

+ {% raw %}{{ amountWithTipFormatted }}{% endraw %} +

{% raw %}{{ fsat }} sat ( + {{ tipAmountSat }} tip)( + {{ tipAmountFormatted }} tip) {% endraw %}
@@ -362,12 +364,18 @@ if (!this.stack.length) return 0.0 return Number(this.stack.join('') / 100) }, - famount: function () { + amountFormatted: function () { return LNbits.utils.formatCurrency( this.amount.toFixed(2), this.currency ) }, + amountWithTipFormatted: function () { + return LNbits.utils.formatCurrency( + (this.amount + this.tipAmount).toFixed(2), + this.currency + ) + }, sat: function () { if (!this.exchangeRate) return 0 return Math.ceil((this.amount / this.exchangeRate) * 100000000) @@ -376,6 +384,9 @@ if (!this.exchangeRate) return 0 return Math.ceil((this.tipAmount / this.exchangeRate) * 100000000) }, + tipAmountFormatted: function () { + return LNbits.utils.formatSat(this.tipAmountSat) + }, fsat: function () { return LNbits.utils.formatSat(this.sat) } @@ -393,7 +404,10 @@ processTipSelection: function (selectedTipOption) { this.tipDialog.show = false - if (!selectedTipOption) return this.showInvoice() + if (!selectedTipOption) { + this.tipAmount = 0.0 + return this.showInvoice() + } this.tipAmount = (selectedTipOption / 100) * this.amount this.showInvoice() From 96046b8d0cd459ca7bf54cd13b3b74398c737f94 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Fri, 14 Oct 2022 21:59:28 +0100 Subject: [PATCH 143/154] add default tip by rounding to value --- .../extensions/tpos/templates/tpos/tpos.html | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 0f9620a9..4b1b0ad3 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -17,6 +17,7 @@
{% raw %}{{ fsat }}{% endraw %} sat
+

{% raw %}{{ parseFloat(roundToSugestion) }}{% endraw %}

@@ -214,14 +215,37 @@ style="padding: 10px; margin: 3px" unelevated @click="processTipSelection(tip)" - size="xl" + size="lg" :outline="!($q.dark.isActive)" rounded color="primary" - v-for="tip in this.tip_options" + v-for="tip in tip_options.filter(f => f != 'Round')" :key="tip" >{% raw %}{{ tip }}{% endraw %}% + + + +

No, thanks

@@ -336,6 +360,7 @@ exchangeRate: null, stack: [], tipAmount: 0.0, + tipRounding: null, hasNFC: false, nfcTagReading: false, lastPaymentsDialog: { @@ -356,7 +381,8 @@ }, complete: { show: false - } + }, + rounding: false } }, computed: { @@ -389,9 +415,36 @@ }, fsat: function () { return LNbits.utils.formatSat(this.sat) + }, + isRoundValid(){ + return this.tipRounding > this.amount + }, + roundToSugestion(){ + //let toNext = 1 + switch(true){ + case this.amount > 50: + toNext = 10 + break + case this.amount > 6: + toNext = 5 + break + case this.amount > 2.5: + toNext = 1 + break + default: + toNext = 0.5 + break + } + + return Math.ceil(this.amount/toNext)*toNext } }, methods: { + setRounding(){ + this.rounding = true + this.tipRounding = this.roundToSugestion + this.$nextTick(() => this.$refs.inputRounding.focus()) + }, closeInvoiceDialog: function () { this.stack = [] this.tipAmount = 0.0 @@ -589,6 +642,10 @@ '{{ tpos.tip_options | tojson }}' == 'null' ? null : JSON.parse('{{ tpos.tip_options }}') + + if('{{ tpos.tip_wallet }}') { + this.tip_options.push("Round") + } setInterval(function () { getRates() }, 120000) From 9430aa28c7cb68c014076aaace4cf30827e08a4c Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Mon, 17 Oct 2022 11:40:30 +0100 Subject: [PATCH 144/154] add rounding tip option --- .../extensions/tpos/templates/tpos/index.html | 8 +++- .../extensions/tpos/templates/tpos/tpos.html | 47 ++++++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/index.html b/lnbits/extensions/tpos/templates/tpos/index.html index 3c4fa24f..1aa75fcf 100644 --- a/lnbits/extensions/tpos/templates/tpos/index.html +++ b/lnbits/extensions/tpos/templates/tpos/index.html @@ -139,8 +139,12 @@ input-debounce="0" new-value-mode="add-unique" label="Tip % Options (hit enter to add values)" - >Hit enter to add values + >Hit enter to add values + +
{% raw %}{{ fsat }}{% endraw %} sat -

{% raw %}{{ parseFloat(roundToSugestion) }}{% endraw %}

@@ -232,9 +231,9 @@ rounded color="primary" label="Round" - >
+ >
- - -
-
+ No, thanks Close
@@ -416,12 +415,12 @@ fsat: function () { return LNbits.utils.formatSat(this.sat) }, - isRoundValid(){ + isRoundValid() { return this.tipRounding > this.amount }, - roundToSugestion(){ + roundToSugestion() { //let toNext = 1 - switch(true){ + switch (true) { case this.amount > 50: toNext = 10 break @@ -435,16 +434,28 @@ toNext = 0.5 break } - - return Math.ceil(this.amount/toNext)*toNext + + return Math.ceil(this.amount / toNext) * toNext } }, methods: { - setRounding(){ + setRounding() { this.rounding = true this.tipRounding = this.roundToSugestion this.$nextTick(() => this.$refs.inputRounding.focus()) }, + calculatePercent() { + let change = ((this.tipRounding - this.amount) / this.amount) * 100 + if (change < 0) { + this.$q.notify({ + type: 'warning', + message: 'Value must be greater than initial amount' + }) + this.tipRounding = this.roundToSugestion + return + } + this.processTipSelection(change) + }, closeInvoiceDialog: function () { this.stack = [] this.tipAmount = 0.0 @@ -643,8 +654,8 @@ ? null : JSON.parse('{{ tpos.tip_options }}') - if('{{ tpos.tip_wallet }}') { - this.tip_options.push("Round") + if ('{{ tpos.tip_wallet }}') { + this.tip_options.push('Round') } setInterval(function () { getRates() From f4c0c92655ac0373d8e98d36313134f6fd85c8f8 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 24 Oct 2022 14:09:40 +0200 Subject: [PATCH 145/154] strings --- lnbits/extensions/tpos/templates/tpos/tpos.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 6a74d95b..f432901a 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -230,7 +230,7 @@ :outline="!($q.dark.isActive)" rounded color="primary" - label="Round" + label="Custom" >
@@ -449,7 +449,7 @@ if (change < 0) { this.$q.notify({ type: 'warning', - message: 'Value must be greater than initial amount' + message: 'Amount with tip must be greater than initial amount.' }) this.tipRounding = this.roundToSugestion return From 12b69a7f826a178ba2ee7d30141266295d4cf1f3 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 17 Nov 2022 15:13:21 +0000 Subject: [PATCH 146/154] fix reseting rounding and better UX --- .../extensions/tpos/templates/tpos/tpos.html | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index f432901a..3be6d4a5 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -230,21 +230,25 @@ :outline="!($q.dark.isActive)" rounded color="primary" - label="Custom" + label="Round to" > - + + + + Ok - - +
Date: Thu, 17 Nov 2022 15:14:53 +0000 Subject: [PATCH 147/154] format --- lnbits/extensions/tpos/templates/tpos/tpos.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 3be6d4a5..c0adfca4 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -245,9 +245,13 @@ --> - Ok + Ok
From edf76ad7cc7d34165c5034fdab573ee7041ab2fb Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 17 Nov 2022 15:32:20 +0000 Subject: [PATCH 148/154] add input number --- lnbits/extensions/tpos/templates/tpos/tpos.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index c0adfca4..6c56b8fb 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -238,6 +238,7 @@ ref="inputRounding" v-model.number="tipRounding" :placeholder="roundToSugestion" + type="number" hint="Total amount including tip" :prefix="currency" > @@ -673,4 +674,18 @@ } }) + {% endblock %} From d64e960ef67990b143ed951a0a9d5920091f0eb7 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 18 Nov 2022 13:58:56 +0200 Subject: [PATCH 149/154] chore: code clean-up and formatting --- lnbits/extensions/tpos/templates/tpos/tpos.html | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index 6c56b8fb..c3886234 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -242,9 +242,6 @@ hint="Total amount including tip" :prefix="currency" > - this.amount }, roundToSugestion() { - //let toNext = 1 switch (true) { case this.amount > 50: toNext = 10 @@ -675,17 +671,15 @@ }) {% endblock %} From b698dac52c281ca0188e6a6edbf41785ed020877 Mon Sep 17 00:00:00 2001 From: Thomas Verstreken <111072251+ThomasFarstrike@users.noreply.github.com> Date: Sat, 19 Nov 2022 21:10:12 +0100 Subject: [PATCH 150/154] Fix typo's (#1136) Costumer to Customer, costumer to customer --- lnbits/extensions/offlineshop/README.md | 12 ++++++------ lnbits/extensions/satspay/README.md | 2 +- lnbits/extensions/tpos/README.md | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/offlineshop/README.md b/lnbits/extensions/offlineshop/README.md index 79bbe41d..25ead5df 100644 --- a/lnbits/extensions/offlineshop/README.md +++ b/lnbits/extensions/offlineshop/README.md @@ -6,9 +6,9 @@ LNBits Offline Shop allows for merchants to receive Bitcoin payments while offline and without any electronic device. -Merchant will create items and associate a QR code ([a LNURLp](https://github.com/lnbits/lnbits/blob/master/lnbits/extensions/lnurlp/README.md)) with a price. He can then print the QR codes and display them on their shop. When a costumer chooses an item, scans the QR code, gets the description and price. After payment, the costumer gets a confirmation code that the merchant can validate to be sure the payment was successful. +Merchant will create items and associate a QR code ([a LNURLp](https://github.com/lnbits/lnbits/blob/master/lnbits/extensions/lnurlp/README.md)) with a price. He can then print the QR codes and display them on their shop. When a customer chooses an item, scans the QR code, gets the description and price. After payment, the customer gets a confirmation code that the merchant can validate to be sure the payment was successful. -Costumers must use an LNURL pay capable wallet. +Customers must use an LNURL pay capable wallet. [**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets) @@ -18,18 +18,18 @@ Costumers must use an LNURL pay capable wallet. ![offline shop back office](https://i.imgur.com/Ei7cxj9.png) 2. Begin by creating an item, click "ADD NEW ITEM" - set the item name and a small description - - you can set an optional, preferably square image, that will show up on the costumer wallet - _depending on wallet_ - - set the item price, if you choose a fiat currency the bitcoin conversion will happen at the time costumer scans to pay\ + - you can set an optional, preferably square image, that will show up on the customer wallet - _depending on wallet_ + - set the item price, if you choose a fiat currency the bitcoin conversion will happen at the time customer scans to pay\ ![add new item](https://i.imgur.com/pkZqRgj.png) 3. After creating some products, click on "PRINT QR CODES"\ ![print qr codes](https://i.imgur.com/2GAiSTe.png) 4. You'll see a QR code for each product in your LNBits Offline Shop with a title and price ready for printing\ ![qr codes sheet](https://i.imgur.com/faEqOcd.png) 5. Place the printed QR codes on your shop, or at the fair stall, or have them as a menu style laminated sheet -6. Choose what type of confirmation do you want costumers to report to merchant after a successful payment\ +6. Choose what type of confirmation do you want customers to report to merchant after a successful payment\ ![wordlist](https://i.imgur.com/9aM6NUL.png) - - Wordlist is the default option: after a successful payment the costumer will receive a word from this list, **sequentially**. Starting in _albatross_ as costumers pay for the items they will get the next word in the list until _zebra_, then it starts at the top again. The list can be changed, for example if you think A-Z is a big list to track, you can use _apple_, _banana_, _coconut_\ + - Wordlist is the default option: after a successful payment the customer will receive a word from this list, **sequentially**. Starting in _albatross_ as customers pay for the items they will get the next word in the list until _zebra_, then it starts at the top again. The list can be changed, for example if you think A-Z is a big list to track, you can use _apple_, _banana_, _coconut_\ ![totp authenticator](https://i.imgur.com/MrJXFxz.png) - TOTP (time-based one time password) can be used instead. If you use Google Authenticator just scan the presented QR with the app and after a successful payment the user will get the password that you can check with GA\ ![disable confirmations](https://i.imgur.com/2OFs4yi.png) diff --git a/lnbits/extensions/satspay/README.md b/lnbits/extensions/satspay/README.md index d52547ae..7a12feb3 100644 --- a/lnbits/extensions/satspay/README.md +++ b/lnbits/extensions/satspay/README.md @@ -18,7 +18,7 @@ Easilly create invoices that support Lightning Network and on-chain BTC payment. ![charge form](https://i.imgur.com/F10yRiW.png) 3. The charge will appear on the _Charges_ section\ ![charges](https://i.imgur.com/zqHpVxc.png) -4. Your costumer/payee will get the payment page +4. Your customer/payee will get the payment page - they can choose to pay on LN\ ![offchain payment](https://i.imgur.com/4191SMV.png) - or pay on chain\ diff --git a/lnbits/extensions/tpos/README.md b/lnbits/extensions/tpos/README.md index 04e049e3..c7e3481d 100644 --- a/lnbits/extensions/tpos/README.md +++ b/lnbits/extensions/tpos/README.md @@ -11,5 +11,5 @@ An easy, fast and secure way to accept Bitcoin, over Lightning Network, at your ![create](https://imgur.com/8jNj8Zq.jpg) 3. Open TPOS on the browser\ ![open](https://imgur.com/LZuoWzb.jpg) -4. Present invoice QR to costumer\ +4. Present invoice QR to customer\ ![pay](https://imgur.com/tOwxn77.jpg) From 22bb67a249a42953054d6141948994820cbe00c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Sat, 19 Nov 2022 21:12:33 +0100 Subject: [PATCH 151/154] lnurl withdraw, better errorhandling for callback endpoint (#1107) --- lnbits/extensions/withdraw/lnurl.py | 37 ++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py index 660e5b7d..5737e54f 100644 --- a/lnbits/extensions/withdraw/lnurl.py +++ b/lnbits/extensions/withdraw/lnurl.py @@ -9,7 +9,7 @@ from fastapi import HTTPException from fastapi.param_functions import Query from loguru import logger from starlette.requests import Request -from starlette.responses import HTMLResponse # type: ignore +from starlette.responses import HTMLResponse from lnbits.core.services import pay_invoice @@ -51,10 +51,24 @@ async def api_lnurl_response(request: Request, unique_hash): # CALLBACK -@withdraw_ext.get("/api/v1/lnurl/cb/{unique_hash}", name="withdraw.api_lnurl_callback") +@withdraw_ext.get( + "/api/v1/lnurl/cb/{unique_hash}", + name="withdraw.api_lnurl_callback", + summary="lnurl withdraw callback", + description=""" + This enpoints allows you to put unique_hash, k1 + and a payment_request to get your payment_request paid. + """, + response_description="JSON with status", + responses={ + 200: {"description": "status: OK"}, + 400: {"description": "k1 is wrong or link open time or withdraw not working."}, + 404: {"description": "withdraw link not found."}, + 405: {"description": "withdraw link is spent."}, + }, +) async def api_lnurl_callback( unique_hash, - request: Request, k1: str = Query(...), pr: str = Query(...), id_unique_hash=None, @@ -63,19 +77,22 @@ async def api_lnurl_callback( now = int(datetime.now().timestamp()) if not link: raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found" + status_code=HTTPStatus.NOT_FOUND, detail="withdraw not found." ) if link.is_spent: raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent." + status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail="withdraw is spent." ) if link.k1 != k1: - raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Bad request.") + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="k1 is wrong.") if now < link.open_time: - return {"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."} + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=f"wait link open_time {link.open_time - now} seconds.", + ) usescsv = "" @@ -95,7 +112,7 @@ async def api_lnurl_callback( usescsv = ",".join(useslist) if not found: raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found." + status_code=HTTPStatus.NOT_FOUND, detail="withdraw not found." ) else: usescsv = usescsv[1:] @@ -144,7 +161,9 @@ async def api_lnurl_callback( except Exception as e: await update_withdraw_link(link.id, **changesback) logger.error(traceback.format_exc()) - return {"status": "ERROR", "reason": "Link not working"} + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail=f"withdraw not working. {str(e)}" + ) # FOR LNURLs WHICH ARE UNIQUE From 0096ce8ad1c75df3a3dcca91e480d963987781dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Sat, 19 Nov 2022 21:41:37 +0100 Subject: [PATCH 152/154] let poetry run lnbits recognize forwarded_ips for https (#1109) --- lnbits/server.py | 17 +++++++++++++---- lnbits/settings.py | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lnbits/server.py b/lnbits/server.py index e9849851..7aaaa964 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -1,9 +1,7 @@ -import time - import click import uvicorn -from lnbits.settings import HOST, PORT +from lnbits.settings import FORWARDED_ALLOW_IPS, HOST, PORT @click.command( @@ -14,10 +12,20 @@ from lnbits.settings import HOST, PORT ) @click.option("--port", default=PORT, help="Port to listen on") @click.option("--host", default=HOST, help="Host to run LNBits on") +@click.option( + "--forwarded-allow-ips", default=FORWARDED_ALLOW_IPS, help="Allowed proxy servers" +) @click.option("--ssl-keyfile", default=None, help="Path to SSL keyfile") @click.option("--ssl-certfile", default=None, help="Path to SSL certificate") @click.pass_context -def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str): +def main( + ctx, + port: int, + host: str, + forwarded_allow_ips: str, + ssl_keyfile: str, + ssl_certfile: str, +): """Launched with `poetry run lnbits` at root level""" # this beautiful beast parses all command line arguments and passes them to the uvicorn server d = dict() @@ -37,6 +45,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str): "lnbits.__main__:app", port=port, host=host, + forwarded_allow_ips=forwarded_allow_ips, ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, **d diff --git a/lnbits/settings.py b/lnbits/settings.py index 3f4e31cc..73b0d6c9 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -18,6 +18,8 @@ DEBUG = env.bool("DEBUG", default=False) HOST = env.str("HOST", default="127.0.0.1") PORT = env.int("PORT", default=5000) +FORWARDED_ALLOW_IPS = env.str("FORWARDED_ALLOW_IPS", default="127.0.0.1") + LNBITS_PATH = path.dirname(path.realpath(__file__)) LNBITS_DATA_FOLDER = env.str( "LNBITS_DATA_FOLDER", default=path.join(LNBITS_PATH, "data") From 604e52c45dc14b4a088c98580cfd492268494f5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Sat, 19 Nov 2022 21:42:10 +0100 Subject: [PATCH 153/154] more mypy extension fixes (#1096) * mypy fix jukebox * mypy fix ngrok * mypy fix satsdice * remove exts from mypy exclude --- lnbits/extensions/jukebox/crud.py | 16 ++--- lnbits/extensions/jukebox/models.py | 29 ++++---- lnbits/extensions/jukebox/tasks.py | 9 +-- lnbits/extensions/jukebox/views.py | 7 +- lnbits/extensions/jukebox/views_api.py | 93 +++++++++++-------------- lnbits/extensions/ngrok/views.py | 5 +- lnbits/extensions/satsdice/crud.py | 47 ++++++------- lnbits/extensions/satsdice/lnurl.py | 37 +++++----- lnbits/extensions/satsdice/models.py | 7 +- lnbits/extensions/satsdice/views.py | 36 ++++++---- lnbits/extensions/satsdice/views_api.py | 24 ++++--- pyproject.toml | 3 - 12 files changed, 157 insertions(+), 156 deletions(-) diff --git a/lnbits/extensions/jukebox/crud.py b/lnbits/extensions/jukebox/crud.py index d160daee..9d6548b6 100644 --- a/lnbits/extensions/jukebox/crud.py +++ b/lnbits/extensions/jukebox/crud.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash @@ -6,11 +6,9 @@ from . import db from .models import CreateJukeboxPayment, CreateJukeLinkData, Jukebox, JukeboxPayment -async def create_jukebox( - data: CreateJukeLinkData, inkey: Optional[str] = "" -) -> Jukebox: +async def create_jukebox(data: CreateJukeLinkData) -> Jukebox: juke_id = urlsafe_short_hash() - result = await db.execute( + await db.execute( """ INSERT INTO jukebox.jukebox (id, "user", title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price, profit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) @@ -36,13 +34,13 @@ async def create_jukebox( async def update_jukebox( - data: CreateJukeLinkData, juke_id: Optional[str] = "" + data: Union[CreateJukeLinkData, Jukebox], juke_id: str = "" ) -> Optional[Jukebox]: q = ", ".join([f"{field[0]} = ?" for field in data]) items = [f"{field[1]}" for field in data] items.append(juke_id) q = q.replace("user", '"user"', 1) # hack to make user be "user"! - await db.execute(f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (items)) + await db.execute(f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (items,)) row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,)) return Jukebox(**row) if row else None @@ -72,7 +70,7 @@ async def delete_jukebox(juke_id: str): """ DELETE FROM jukebox.jukebox WHERE id = ? """, - (juke_id), + (juke_id,), ) @@ -80,7 +78,7 @@ async def delete_jukebox(juke_id: str): async def create_jukebox_payment(data: CreateJukeboxPayment) -> JukeboxPayment: - result = await db.execute( + await db.execute( """ INSERT INTO jukebox.jukebox_payment (payment_hash, juke_id, song_id, paid) VALUES (?, ?, ?, ?) diff --git a/lnbits/extensions/jukebox/models.py b/lnbits/extensions/jukebox/models.py index 90984b03..70cf6523 100644 --- a/lnbits/extensions/jukebox/models.py +++ b/lnbits/extensions/jukebox/models.py @@ -1,6 +1,3 @@ -from sqlite3 import Row -from typing import NamedTuple, Optional - from fastapi.param_functions import Query from pydantic import BaseModel from pydantic.main import BaseModel @@ -20,19 +17,19 @@ class CreateJukeLinkData(BaseModel): class Jukebox(BaseModel): - id: Optional[str] - user: Optional[str] - title: Optional[str] - wallet: Optional[str] - inkey: Optional[str] - sp_user: Optional[str] - sp_secret: Optional[str] - sp_access_token: Optional[str] - sp_refresh_token: Optional[str] - sp_device: Optional[str] - sp_playlists: Optional[str] - price: Optional[int] - profit: Optional[int] + id: str + user: str + title: str + wallet: str + inkey: str + sp_user: str + sp_secret: str + sp_access_token: str + sp_refresh_token: str + sp_device: str + sp_playlists: str + price: int + profit: int class JukeboxPayment(BaseModel): diff --git a/lnbits/extensions/jukebox/tasks.py b/lnbits/extensions/jukebox/tasks.py index 5614d926..8a68fd27 100644 --- a/lnbits/extensions/jukebox/tasks.py +++ b/lnbits/extensions/jukebox/tasks.py @@ -17,7 +17,8 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") != "jukebox": - # not a jukebox invoice - return - await update_jukebox_payment(payment.payment_hash, paid=True) + if payment.extra: + if payment.extra.get("tag") != "jukebox": + # not a jukebox invoice + return + await update_jukebox_payment(payment.payment_hash, paid=True) diff --git a/lnbits/extensions/jukebox/views.py b/lnbits/extensions/jukebox/views.py index 56774394..28359a9a 100644 --- a/lnbits/extensions/jukebox/views.py +++ b/lnbits/extensions/jukebox/views.py @@ -17,7 +17,9 @@ templates = Jinja2Templates(directory="templates") @jukebox_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): +async def index( + request: Request, user: User = Depends(check_user_exists) # type: ignore +): return jukebox_renderer().TemplateResponse( "jukebox/index.html", {"request": request, "user": user.dict()} ) @@ -31,6 +33,7 @@ async def connect_to_jukebox(request: Request, juke_id): status_code=HTTPStatus.NOT_FOUND, detail="Jukebox does not exist." ) devices = await api_get_jukebox_device_check(juke_id) + deviceConnected = False for device in devices["devices"]: if device["id"] == jukebox.sp_device.split("-")[1]: deviceConnected = True @@ -48,5 +51,5 @@ async def connect_to_jukebox(request: Request, juke_id): else: return jukebox_renderer().TemplateResponse( "jukebox/error.html", - {"request": request, "jukebox": jukebox.jukebox(req=request)}, + {"request": request, "jukebox": jukebox.dict()}, ) diff --git a/lnbits/extensions/jukebox/views_api.py b/lnbits/extensions/jukebox/views_api.py index 1f3723a7..5cf1a83b 100644 --- a/lnbits/extensions/jukebox/views_api.py +++ b/lnbits/extensions/jukebox/views_api.py @@ -3,7 +3,6 @@ import json from http import HTTPStatus import httpx -from fastapi import Request from fastapi.param_functions import Query from fastapi.params import Depends from starlette.exceptions import HTTPException @@ -29,9 +28,7 @@ from .models import CreateJukeboxPayment, CreateJukeLinkData @jukebox_ext.get("/api/v1/jukebox") async def api_get_jukeboxs( - req: Request, - wallet: WalletTypeInfo = Depends(require_admin_key), - all_wallets: bool = Query(False), + wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore ): wallet_user = wallet.wallet.user @@ -53,54 +50,52 @@ async def api_check_credentials_callbac( access_token: str = Query(None), refresh_token: str = Query(None), ): - sp_code = "" - sp_access_token = "" - sp_refresh_token = "" - try: - jukebox = await get_jukebox(juke_id) - except: + jukebox = await get_jukebox(juke_id) + if not jukebox: raise HTTPException(detail="No Jukebox", status_code=HTTPStatus.FORBIDDEN) if code: jukebox.sp_access_token = code - jukebox = await update_jukebox(jukebox, juke_id=juke_id) + await update_jukebox(jukebox, juke_id=juke_id) if access_token: jukebox.sp_access_token = access_token jukebox.sp_refresh_token = refresh_token - jukebox = await update_jukebox(jukebox, juke_id=juke_id) + await update_jukebox(jukebox, juke_id=juke_id) return "

Success!

You can close this window

" -@jukebox_ext.get("/api/v1/jukebox/{juke_id}") -async def api_check_credentials_check( - juke_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key) -): +@jukebox_ext.get("/api/v1/jukebox/{juke_id}", dependencies=[Depends(require_admin_key)]) +async def api_check_credentials_check(juke_id: str = Query(None)): jukebox = await get_jukebox(juke_id) return jukebox -@jukebox_ext.post("/api/v1/jukebox", status_code=HTTPStatus.CREATED) +@jukebox_ext.post( + "/api/v1/jukebox", + status_code=HTTPStatus.CREATED, + dependencies=[Depends(require_admin_key)], +) @jukebox_ext.put("/api/v1/jukebox/{juke_id}", status_code=HTTPStatus.OK) async def api_create_update_jukebox( - data: CreateJukeLinkData, - juke_id: str = Query(None), - wallet: WalletTypeInfo = Depends(require_admin_key), + data: CreateJukeLinkData, juke_id: str = Query(None) ): if juke_id: jukebox = await update_jukebox(data, juke_id=juke_id) else: - jukebox = await create_jukebox(data, inkey=wallet.wallet.inkey) + jukebox = await create_jukebox(data) return jukebox -@jukebox_ext.delete("/api/v1/jukebox/{juke_id}") +@jukebox_ext.delete( + "/api/v1/jukebox/{juke_id}", dependencies=[Depends(require_admin_key)] +) async def api_delete_item( - juke_id=None, wallet: WalletTypeInfo = Depends(require_admin_key) + juke_id: str = Query(None), ): await delete_jukebox(juke_id) - try: - return [{**jukebox} for jukebox in await get_jukeboxs(wallet.wallet.user)] - except: - raise HTTPException(status_code=HTTPStatus.NO_CONTENT, detail="No Jukebox") + # try: + # return [{**jukebox} for jukebox in await get_jukeboxs(wallet.wallet.user)] + # except: + # raise HTTPException(status_code=HTTPStatus.NO_CONTENT, detail="No Jukebox") ################JUKEBOX ENDPOINTS################## @@ -114,9 +109,8 @@ async def api_get_jukebox_song( sp_playlist: str = Query(None), retry: bool = Query(False), ): - try: - jukebox = await get_jukebox(juke_id) - except: + jukebox = await get_jukebox(juke_id) + if not jukebox: raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No Jukeboxes") tracks = [] async with httpx.AsyncClient() as client: @@ -152,14 +146,13 @@ async def api_get_jukebox_song( } ) except: - something = None + pass return [track for track in tracks] -async def api_get_token(juke_id=None): - try: - jukebox = await get_jukebox(juke_id) - except: +async def api_get_token(juke_id): + jukebox = await get_jukebox(juke_id) + if not jukebox: raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No Jukeboxes") async with httpx.AsyncClient() as client: @@ -187,7 +180,7 @@ async def api_get_token(juke_id=None): jukebox.sp_access_token = r.json()["access_token"] await update_jukebox(jukebox, juke_id=juke_id) except: - something = None + pass return True @@ -198,9 +191,8 @@ async def api_get_token(juke_id=None): async def api_get_jukebox_device_check( juke_id: str = Query(None), retry: bool = Query(False) ): - try: - jukebox = await get_jukebox(juke_id) - except: + jukebox = await get_jukebox(juke_id) + if not jukebox: raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No Jukeboxes") async with httpx.AsyncClient() as client: rDevice = await client.get( @@ -221,7 +213,7 @@ async def api_get_jukebox_device_check( status_code=HTTPStatus.FORBIDDEN, detail="Failed to get auth" ) else: - return api_get_jukebox_device_check(juke_id, retry=True) + return await api_get_jukebox_device_check(juke_id, retry=True) else: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="No device connected" @@ -233,10 +225,8 @@ async def api_get_jukebox_device_check( @jukebox_ext.get("/api/v1/jukebox/jb/invoice/{juke_id}/{song_id}") async def api_get_jukebox_invoice(juke_id, song_id): - try: - jukebox = await get_jukebox(juke_id) - - except: + jukebox = await get_jukebox(juke_id) + if not jukebox: raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No jukebox") try: @@ -266,8 +256,7 @@ async def api_get_jukebox_invoice(juke_id, song_id): invoice=invoice[1], payment_hash=payment_hash, juke_id=juke_id, song_id=song_id ) jukebox_payment = await create_jukebox_payment(data) - - return data + return jukebox_payment @jukebox_ext.get("/api/v1/jukebox/jb/checkinvoice/{pay_hash}/{juke_id}") @@ -296,13 +285,12 @@ async def api_get_jukebox_invoice_paid( pay_hash: str = Query(None), retry: bool = Query(False), ): - try: - jukebox = await get_jukebox(juke_id) - except: + jukebox = await get_jukebox(juke_id) + if not jukebox: raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No jukebox") await api_get_jukebox_invoice_check(pay_hash, juke_id) jukebox_payment = await get_jukebox_payment(pay_hash) - if jukebox_payment.paid: + if jukebox_payment and jukebox_payment.paid: async with httpx.AsyncClient() as client: r = await client.get( "https://api.spotify.com/v1/me/player/currently-playing?market=ES", @@ -407,9 +395,8 @@ async def api_get_jukebox_invoice_paid( async def api_get_jukebox_currently( retry: bool = Query(False), juke_id: str = Query(None) ): - try: - jukebox = await get_jukebox(juke_id) - except: + jukebox = await get_jukebox(juke_id) + if not jukebox: raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No jukebox") async with httpx.AsyncClient() as client: try: diff --git a/lnbits/extensions/ngrok/views.py b/lnbits/extensions/ngrok/views.py index 1a34fd51..16c3708a 100644 --- a/lnbits/extensions/ngrok/views.py +++ b/lnbits/extensions/ngrok/views.py @@ -1,3 +1,4 @@ +# type: ignore from os import getenv from fastapi import Request @@ -34,7 +35,9 @@ ngrok_tunnel = ngrok.connect(port) @ngrok_ext.get("/") -async def index(request: Request, user: User = Depends(check_user_exists)): +async def index( + request: Request, user: User = Depends(check_user_exists) # type: ignore +): return ngrok_renderer().TemplateResponse( "ngrok/index.html", {"request": request, "ngrok": string5, "user": user.dict()} ) diff --git a/lnbits/extensions/satsdice/crud.py b/lnbits/extensions/satsdice/crud.py index 7da5a1f1..6aeaf31f 100644 --- a/lnbits/extensions/satsdice/crud.py +++ b/lnbits/extensions/satsdice/crud.py @@ -8,7 +8,6 @@ from .models import ( CreateSatsDiceLink, CreateSatsDicePayment, CreateSatsDiceWithdraw, - HashCheck, satsdiceLink, satsdicePayment, satsdiceWithdraw, @@ -76,7 +75,7 @@ async def get_satsdice_pays(wallet_ids: Union[str, List[str]]) -> List[satsdiceL return [satsdiceLink(**row) for row in rows] -async def update_satsdice_pay(link_id: int, **kwargs) -> Optional[satsdiceLink]: +async def update_satsdice_pay(link_id: str, **kwargs) -> satsdiceLink: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) await db.execute( f"UPDATE satsdice.satsdice_pay SET {q} WHERE id = ?", @@ -85,10 +84,10 @@ async def update_satsdice_pay(link_id: int, **kwargs) -> Optional[satsdiceLink]: row = await db.fetchone( "SELECT * FROM satsdice.satsdice_pay WHERE id = ?", (link_id,) ) - return satsdiceLink(**row) if row else None + return satsdiceLink(**row) -async def increment_satsdice_pay(link_id: int, **kwargs) -> Optional[satsdiceLink]: +async def increment_satsdice_pay(link_id: str, **kwargs) -> Optional[satsdiceLink]: q = ", ".join([f"{field[0]} = {field[0]} + ?" for field in kwargs.items()]) await db.execute( f"UPDATE satsdice.satsdice_pay SET {q} WHERE id = ?", @@ -100,7 +99,7 @@ async def increment_satsdice_pay(link_id: int, **kwargs) -> Optional[satsdiceLin return satsdiceLink(**row) if row else None -async def delete_satsdice_pay(link_id: int) -> None: +async def delete_satsdice_pay(link_id: str) -> None: await db.execute("DELETE FROM satsdice.satsdice_pay WHERE id = ?", (link_id,)) @@ -119,9 +118,15 @@ async def create_satsdice_payment(data: CreateSatsDicePayment) -> satsdicePaymen ) VALUES (?, ?, ?, ?, ?) """, - (data["payment_hash"], data["satsdice_pay"], data["value"], False, False), + ( + data.payment_hash, + data.satsdice_pay, + data.value, + False, + False, + ), ) - payment = await get_satsdice_payment(data["payment_hash"]) + payment = await get_satsdice_payment(data.payment_hash) assert payment, "Newly created withdraw couldn't be retrieved" return payment @@ -134,9 +139,7 @@ async def get_satsdice_payment(payment_hash: str) -> Optional[satsdicePayment]: return satsdicePayment(**row) if row else None -async def update_satsdice_payment( - payment_hash: int, **kwargs -) -> Optional[satsdicePayment]: +async def update_satsdice_payment(payment_hash: str, **kwargs) -> satsdicePayment: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) await db.execute( @@ -147,7 +150,7 @@ async def update_satsdice_payment( "SELECT * FROM satsdice.satsdice_payment WHERE payment_hash = ?", (payment_hash,), ) - return satsdicePayment(**row) if row else None + return satsdicePayment(**row) ##################SATSDICE WITHDRAW LINKS @@ -168,16 +171,16 @@ async def create_satsdice_withdraw(data: CreateSatsDiceWithdraw) -> satsdiceWith VALUES (?, ?, ?, ?, ?, ?, ?) """, ( - data["payment_hash"], - data["satsdice_pay"], - data["value"], + data.payment_hash, + data.satsdice_pay, + data.value, urlsafe_short_hash(), urlsafe_short_hash(), int(datetime.now().timestamp()), - data["used"], + data.used, ), ) - withdraw = await get_satsdice_withdraw(data["payment_hash"], 0) + withdraw = await get_satsdice_withdraw(data.payment_hash, 0) assert withdraw, "Newly created withdraw couldn't be retrieved" return withdraw @@ -247,7 +250,7 @@ async def delete_satsdice_withdraw(withdraw_id: str) -> None: ) -async def create_withdraw_hash_check(the_hash: str, lnurl_id: str) -> HashCheck: +async def create_withdraw_hash_check(the_hash: str, lnurl_id: str): await db.execute( """ INSERT INTO satsdice.hash_checkw ( @@ -262,19 +265,15 @@ async def create_withdraw_hash_check(the_hash: str, lnurl_id: str) -> HashCheck: return hashCheck -async def get_withdraw_hash_checkw(the_hash: str, lnurl_id: str) -> Optional[HashCheck]: +async def get_withdraw_hash_checkw(the_hash: str, lnurl_id: str): rowid = await db.fetchone( "SELECT * FROM satsdice.hash_checkw WHERE id = ?", (the_hash,) ) rowlnurl = await db.fetchone( "SELECT * FROM satsdice.hash_checkw WHERE lnurl_id = ?", (lnurl_id,) ) - if not rowlnurl: + if not rowlnurl or not rowid: await create_withdraw_hash_check(the_hash, lnurl_id) return {"lnurl": True, "hash": False} else: - if not rowid: - await create_withdraw_hash_check(the_hash, lnurl_id) - return {"lnurl": True, "hash": False} - else: - return {"lnurl": True, "hash": True} + return {"lnurl": True, "hash": True} diff --git a/lnbits/extensions/satsdice/lnurl.py b/lnbits/extensions/satsdice/lnurl.py index caafc3a4..a9b3cf08 100644 --- a/lnbits/extensions/satsdice/lnurl.py +++ b/lnbits/extensions/satsdice/lnurl.py @@ -1,4 +1,3 @@ -import hashlib import json import math from http import HTTPStatus @@ -83,15 +82,18 @@ async def api_lnurlp_callback( success_action = link.success_action(payment_hash=payment_hash, req=req) - data: CreateSatsDicePayment = { - "satsdice_pay": link.id, - "value": amount_received / 1000, - "payment_hash": payment_hash, - } + data = CreateSatsDicePayment( + satsdice_pay=link.id, + value=amount_received / 1000, + payment_hash=payment_hash, + ) await create_satsdice_payment(data) - payResponse = {"pr": payment_request, "successAction": success_action, "routes": []} - + payResponse: dict = { + "pr": payment_request, + "successAction": success_action, + "routes": [], + } return json.dumps(payResponse) @@ -133,9 +135,7 @@ async def api_lnurlw_response(req: Request, unique_hash: str = Query(None)): name="satsdice.api_lnurlw_callback", ) async def api_lnurlw_callback( - req: Request, unique_hash: str = Query(None), - k1: str = Query(None), pr: str = Query(None), ): @@ -146,12 +146,13 @@ async def api_lnurlw_callback( return {"status": "ERROR", "reason": "spent"} paylink = await get_satsdice_pay(link.satsdice_pay) - await update_satsdice_withdraw(link.id, used=1) - await pay_invoice( - wallet_id=paylink.wallet, - payment_request=pr, - max_sat=link.value, - extra={"tag": "withdraw"}, - ) + if paylink: + await update_satsdice_withdraw(link.id, used=1) + await pay_invoice( + wallet_id=paylink.wallet, + payment_request=pr, + max_sat=link.value, + extra={"tag": "withdraw"}, + ) - return {"status": "OK"} + return {"status": "OK"} diff --git a/lnbits/extensions/satsdice/models.py b/lnbits/extensions/satsdice/models.py index fd9af74f..2537f8d7 100644 --- a/lnbits/extensions/satsdice/models.py +++ b/lnbits/extensions/satsdice/models.py @@ -4,7 +4,7 @@ from typing import Dict, Optional from fastapi import Request from fastapi.param_functions import Query -from lnurl import Lnurl, LnurlWithdrawResponse +from lnurl import Lnurl from lnurl import encode as lnurl_encode # type: ignore from lnurl.types import LnurlPayMetadata # type: ignore from pydantic import BaseModel @@ -80,8 +80,7 @@ class satsdiceWithdraw(BaseModel): def is_spent(self) -> bool: return self.used >= 1 - @property - def lnurl_response(self, req: Request) -> LnurlWithdrawResponse: + def lnurl_response(self, req: Request): url = req.url_for("satsdice.api_lnurlw_callback", unique_hash=self.unique_hash) withdrawResponse = { "tag": "withdrawRequest", @@ -99,7 +98,7 @@ class HashCheck(BaseModel): lnurl_id: str @classmethod - def from_row(cls, row: Row) -> "Hash": + def from_row(cls, row: Row): return cls(**dict(row)) diff --git a/lnbits/extensions/satsdice/views.py b/lnbits/extensions/satsdice/views.py index 72e24867..d2b5e601 100644 --- a/lnbits/extensions/satsdice/views.py +++ b/lnbits/extensions/satsdice/views.py @@ -1,6 +1,8 @@ import random from http import HTTPStatus +from io import BytesIO +import pyqrcode from fastapi import Request from fastapi.param_functions import Query from fastapi.params import Depends @@ -20,13 +22,15 @@ from .crud import ( get_satsdice_withdraw, update_satsdice_payment, ) -from .models import CreateSatsDiceWithdraw, satsdiceLink +from .models import CreateSatsDiceWithdraw templates = Jinja2Templates(directory="templates") @satsdice_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): +async def index( + request: Request, user: User = Depends(check_user_exists) # type: ignore +): return satsdice_renderer().TemplateResponse( "satsdice/index.html", {"request": request, "user": user.dict()} ) @@ -67,7 +71,7 @@ async def displaywin( ) withdrawLink = await get_satsdice_withdraw(payment_hash) payment = await get_satsdice_payment(payment_hash) - if payment.lost: + if not payment or payment.lost: return satsdice_renderer().TemplateResponse( "satsdice/error.html", {"request": request, "link": satsdicelink.id, "paid": False, "lost": True}, @@ -96,13 +100,18 @@ async def displaywin( ) await update_satsdice_payment(payment_hash, paid=1) paylink = await get_satsdice_payment(payment_hash) + if not paylink: + return satsdice_renderer().TemplateResponse( + "satsdice/error.html", + {"request": request, "link": satsdicelink.id, "paid": False, "lost": True}, + ) - data: CreateSatsDiceWithdraw = { - "satsdice_pay": satsdicelink.id, - "value": paylink.value * satsdicelink.multiplier, - "payment_hash": payment_hash, - "used": 0, - } + data = CreateSatsDiceWithdraw( + satsdice_pay=satsdicelink.id, + value=paylink.value * satsdicelink.multiplier, + payment_hash=payment_hash, + used=0, + ) withdrawLink = await create_satsdice_withdraw(data) return satsdice_renderer().TemplateResponse( @@ -121,9 +130,12 @@ async def displaywin( @satsdice_ext.get("/img/{link_id}", response_class=HTMLResponse) async def img(link_id): - link = await get_satsdice_pay(link_id) or abort( - HTTPStatus.NOT_FOUND, "satsdice link does not exist." - ) + link = await get_satsdice_pay(link_id) + if not link: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="satsdice link does not exist." + ) + qr = pyqrcode.create(link.lnurl) stream = BytesIO() qr.svg(stream, scale=3) diff --git a/lnbits/extensions/satsdice/views_api.py b/lnbits/extensions/satsdice/views_api.py index bccaa5ff..d33b76b8 100644 --- a/lnbits/extensions/satsdice/views_api.py +++ b/lnbits/extensions/satsdice/views_api.py @@ -15,9 +15,10 @@ from .crud import ( delete_satsdice_pay, get_satsdice_pay, get_satsdice_pays, + get_withdraw_hash_checkw, update_satsdice_pay, ) -from .models import CreateSatsDiceLink, CreateSatsDiceWithdraws, satsdiceLink +from .models import CreateSatsDiceLink ################LNURL pay @@ -25,13 +26,15 @@ from .models import CreateSatsDiceLink, CreateSatsDiceWithdraws, satsdiceLink @satsdice_ext.get("/api/v1/links") async def api_links( request: Request, - wallet: WalletTypeInfo = Depends(get_key_type), + wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore all_wallets: bool = Query(False), ): wallet_ids = [wallet.wallet.id] if all_wallets: - wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids + user = await get_user(wallet.wallet.user) + if user: + wallet_ids = user.wallet_ids try: links = await get_satsdice_pays(wallet_ids) @@ -46,7 +49,7 @@ async def api_links( @satsdice_ext.get("/api/v1/links/{link_id}") async def api_link_retrieve( - link_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) + link_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): link = await get_satsdice_pay(link_id) @@ -67,7 +70,7 @@ async def api_link_retrieve( @satsdice_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) async def api_link_create_or_update( data: CreateSatsDiceLink, - wallet: WalletTypeInfo = Depends(get_key_type), + wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore link_id: str = Query(None), ): if data.min_bet > data.max_bet: @@ -95,10 +98,10 @@ async def api_link_create_or_update( @satsdice_ext.delete("/api/v1/links/{link_id}") async def api_link_delete( - wallet: WalletTypeInfo = Depends(get_key_type), link_id: str = Query(None) + wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore + link_id: str = Query(None), ): link = await get_satsdice_pay(link_id) - if not link: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist." @@ -117,11 +120,12 @@ async def api_link_delete( ##########LNURL withdraw -@satsdice_ext.get("/api/v1/withdraws/{the_hash}/{lnurl_id}") +@satsdice_ext.get( + "/api/v1/withdraws/{the_hash}/{lnurl_id}", dependencies=[Depends(get_key_type)] +) async def api_withdraw_hash_retrieve( - wallet: WalletTypeInfo = Depends(get_key_type), lnurl_id: str = Query(None), the_hash: str = Query(None), ): - hashCheck = await get_withdraw_hash_check(the_hash, lnurl_id) + hashCheck = await get_withdraw_hash_checkw(the_hash, lnurl_id) return hashCheck diff --git a/pyproject.toml b/pyproject.toml index 7418de27..e66073c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,6 @@ exclude = """(?x)( | ^lnbits/extensions/events. | ^lnbits/extensions/hivemind. | ^lnbits/extensions/invoices. - | ^lnbits/extensions/jukebox. | ^lnbits/extensions/livestream. | ^lnbits/extensions/lnaddress. | ^lnbits/extensions/lndhub. @@ -103,10 +102,8 @@ exclude = """(?x)( | ^lnbits/extensions/lnurldevice. | ^lnbits/extensions/lnurlp. | ^lnbits/extensions/lnurlpayout. - | ^lnbits/extensions/ngrok. | ^lnbits/extensions/offlineshop. | ^lnbits/extensions/paywall. - | ^lnbits/extensions/satsdice. | ^lnbits/extensions/satspay. | ^lnbits/extensions/scrub. | ^lnbits/extensions/splitpayments. From 41aa0743507b5ef62517e48afefa6c0c25e37fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Sat, 19 Nov 2022 21:44:21 +0100 Subject: [PATCH 154/154] Add webhook_api_key and webhook_custom_data to lnurlp webhook (#1065) * add webhook_api_key and webhook_custom_data to lnurlp * formatting * add json validation to lnurlp custom_data * motorina0 improvements --- lnbits/extensions/lnurlp/crud.py | 6 +++++- lnbits/extensions/lnurlp/migrations.py | 8 ++++++++ lnbits/extensions/lnurlp/models.py | 4 ++++ lnbits/extensions/lnurlp/tasks.py | 15 ++++++++++----- .../lnurlp/templates/lnurlp/index.html | 18 ++++++++++++++++++ lnbits/extensions/lnurlp/views_api.py | 19 +++++++++++++++++++ 6 files changed, 64 insertions(+), 6 deletions(-) diff --git a/lnbits/extensions/lnurlp/crud.py b/lnbits/extensions/lnurlp/crud.py index 9cb01fde..d02ae80e 100644 --- a/lnbits/extensions/lnurlp/crud.py +++ b/lnbits/extensions/lnurlp/crud.py @@ -21,13 +21,15 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: served_meta, served_pr, webhook_url, + webhook_headers, + webhook_body, success_text, success_url, comment_chars, currency, fiat_base_multiplier ) - VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?) {returning} """, ( @@ -36,6 +38,8 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: data.min, data.max, data.webhook_url, + data.webhook_headers, + data.webhook_body, data.success_text, data.success_url, data.comment_chars, diff --git a/lnbits/extensions/lnurlp/migrations.py b/lnbits/extensions/lnurlp/migrations.py index 81dd62f8..5258471d 100644 --- a/lnbits/extensions/lnurlp/migrations.py +++ b/lnbits/extensions/lnurlp/migrations.py @@ -60,3 +60,11 @@ async def m004_fiat_base_multiplier(db): await db.execute( "ALTER TABLE lnurlp.pay_links ADD COLUMN fiat_base_multiplier INTEGER DEFAULT 1;" ) + + +async def m005_webhook_headers_and_body(db): + """ + Add headers and body to webhooks + """ + await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_headers TEXT;") + await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_body TEXT;") diff --git a/lnbits/extensions/lnurlp/models.py b/lnbits/extensions/lnurlp/models.py index 4bd438a4..2cb4d0ab 100644 --- a/lnbits/extensions/lnurlp/models.py +++ b/lnbits/extensions/lnurlp/models.py @@ -18,6 +18,8 @@ class CreatePayLinkData(BaseModel): currency: str = Query(None) comment_chars: int = Query(0, ge=0, lt=800) webhook_url: str = Query(None) + webhook_headers: str = Query(None) + webhook_body: str = Query(None) success_text: str = Query(None) success_url: str = Query(None) fiat_base_multiplier: int = Query(100, ge=1) @@ -31,6 +33,8 @@ class PayLink(BaseModel): served_meta: int served_pr: int webhook_url: Optional[str] + webhook_headers: Optional[str] + webhook_body: Optional[str] success_text: Optional[str] success_url: Optional[str] currency: Optional[str] diff --git a/lnbits/extensions/lnurlp/tasks.py b/lnbits/extensions/lnurlp/tasks.py index 86f1579a..23f312cb 100644 --- a/lnbits/extensions/lnurlp/tasks.py +++ b/lnbits/extensions/lnurlp/tasks.py @@ -33,17 +33,22 @@ async def on_invoice_paid(payment: Payment) -> None: if pay_link and pay_link.webhook_url: async with httpx.AsyncClient() as client: try: - r = await client.post( - pay_link.webhook_url, - json={ + kwargs = { + "json": { "payment_hash": payment.payment_hash, "payment_request": payment.bolt11, "amount": payment.amount, "comment": payment.extra.get("comment"), "lnurlp": pay_link.id, }, - timeout=40, - ) + "timeout": 40, + } + if pay_link.webhook_body: + kwargs["json"]["body"] = json.loads(pay_link.webhook_body) + if pay_link.webhook_headers: + kwargs["headers"] = json.loads(pay_link.webhook_headers) + + r = await client.post(pay_link.webhook_url, **kwargs) await mark_webhook_sent(payment, r.status_code) except (httpx.ConnectError, httpx.RequestError): await mark_webhook_sent(payment, -1) diff --git a/lnbits/extensions/lnurlp/templates/lnurlp/index.html b/lnbits/extensions/lnurlp/templates/lnurlp/index.html index de90f5af..eb594cec 100644 --- a/lnbits/extensions/lnurlp/templates/lnurlp/index.html +++ b/lnbits/extensions/lnurlp/templates/lnurlp/index.html @@ -213,6 +213,24 @@ label="Webhook URL (optional)" hint="A URL to be called whenever this link receives a payment." > + +

For LNURL based Points of Sale, ATMs, and relay devices
- Such as the LNPoS + Use with:
+ LNPoS https://lnbits.github.io/lnpos
+ bitcoinSwitch + https://github.com/lnbits/bitcoinSwitch
+ FOSSA + https://github.com/lnbits/fossa
- Created by, Ben ArcBen Arc, BC, Vlad Stan