Revert "Improve configuration form for readability and UX"

This commit is contained in:
Arc 2025-09-11 05:15:23 +01:00 • committed by GitHub
commit 93ba3ba628
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 512 additions and 552 deletions

View file

@ -1,7 +1,7 @@
on: on:
push: push:
tags: tags:
- 'v[0-9]+.[0-9]+.[0-9]+' - "v[0-9]+.[0-9]+.[0-9]+"
jobs: jobs:
release: release:
@ -22,7 +22,7 @@ jobs:
with: with:
token: ${{ secrets.EXT_GITHUB }} token: ${{ secrets.EXT_GITHUB }}
repository: lnbits/lnbits-extensions repository: lnbits/lnbits-extensions
path: './lnbits-extensions' path: "./lnbits-extensions"
- name: setup git user - name: setup git user
run: | run: |
@ -31,12 +31,12 @@ jobs:
- name: Create pull request in extensions repo - name: Create pull request in extensions repo
env: env:
GH_TOKEN: ${{ secrets.EXT_GITHUB }} GH_TOKEN: ${{ secrets.EXT_GITHUB }}
repo_name: '${{ github.event.repository.name }}' repo_name: "${{ github.event.repository.name }}"
tag: '${{ github.ref_name }}' tag: "${{ github.ref_name }}"
branch: 'update-${{ github.event.repository.name }}-${{ github.ref_name }}' branch: "update-${{ github.event.repository.name }}-${{ github.ref_name }}"
title: '[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}' title: "[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}"
body: 'https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}' body: "https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}"
archive: 'https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip' archive: "https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip"
run: | run: |
cd lnbits-extensions cd lnbits-extensions
git checkout -b $branch git checkout -b $branch

View file

@ -1,7 +1,7 @@
on: on:
push: push:
tags: tags:
- 'v[0-9]+.[0-9]+.[0-9]+' - "v[0-9]+.[0-9]+.[0-9]+"
jobs: jobs:
release: release:
@ -22,7 +22,7 @@ jobs:
with: with:
token: ${{ secrets.EXT_GITHUB }} token: ${{ secrets.EXT_GITHUB }}
repository: lnbits/lnbits-extensions repository: lnbits/lnbits-extensions
path: './lnbits-extensions' path: "./lnbits-extensions"
- name: setup git user - name: setup git user
run: | run: |
@ -31,12 +31,12 @@ jobs:
- name: Create pull request in extensions repo - name: Create pull request in extensions repo
env: env:
GH_TOKEN: ${{ secrets.EXT_GITHUB }} GH_TOKEN: ${{ secrets.EXT_GITHUB }}
repo_name: '${{ github.event.repository.name }}' repo_name: "${{ github.event.repository.name }}"
tag: '${{ github.ref_name }}' tag: "${{ github.ref_name }}"
branch: 'update-${{ github.event.repository.name }}-${{ github.ref_name }}' branch: "update-${{ github.event.repository.name }}-${{ github.ref_name }}"
title: '[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}' title: "[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}"
body: 'https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}' body: "https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}"
archive: 'https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip' archive: "https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip"
run: | run: |
cd lnbits-extensions cd lnbits-extensions
git checkout -b $branch git checkout -b $branch

View file

@ -15,7 +15,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: '3.9' python-version: "3.9"
- name: Install dependencies - name: Install dependencies
run: | run: |

View file

@ -45,7 +45,7 @@ def nwcprovider_start():
__all__ = [ __all__ = [
"db", "db",
"nwcprovider_ext", "nwcprovider_ext",
"nwcprovider_start",
"nwcprovider_static_files", "nwcprovider_static_files",
"nwcprovider_start",
"nwcprovider_stop", "nwcprovider_stop",
] ]

View file

@ -94,7 +94,6 @@ async def m004_default_config2(db):
{"value": ""}, {"value": ""},
) )
async def m005_key_last_used(db): async def m005_key_last_used(db):
""" """
Add last_used to keys Add last_used to keys

View file

@ -4,8 +4,7 @@ import hashlib
import json import json
import random import random
import time import time
from collections.abc import Awaitable from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Union
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import secp256k1 import secp256k1
import websockets.client as websockets import websockets.client as websockets

View file

@ -1,81 +1,87 @@
window.app = Vue.createApp({ window.app = Vue.createApp({
el: '#vue', el: "#vue",
mixins: [windowMixin], mixins: [windowMixin],
delimiters: ['${', '}'], delimiters: ["${", "}"],
data: function () { data: function () {
return { return {
config: {}, entries: [],
columns: [ columns: [
{ {
name: 'key', name: "key",
required: true, required: true,
label: 'Key', label: "Key",
align: 'left', align: "left",
field: row => row.key, field: (row) => row.key,
sortable: true sortable: true,
}, },
{ {
name: 'value', name: "value",
required: true, required: true,
label: 'Value', label: "Value",
align: 'left', align: "left",
field: row => row.value, field: (row) => row.value,
sortable: true sortable: true,
} },
] ],
} };
}, },
methods: { methods: {
fetchConfig() { fetchConfig() {
this.config = {} this.entries = [];
LNbits.api LNbits.api
.request( .request(
'GET', "GET",
'/nwcprovider/api/v1/config', "/nwcprovider/api/v1/config",
this.g.user.wallets[0].adminkey this.g.user.wallets[0].adminkey,
) )
.then(response => { .then((response) => {
this.config = response.data const newEntries = [];
console.log('Config fetched:', this.config) for (const [key, value] of Object.entries(response.data)) {
newEntries.push({
key: key,
value: value,
});
}
this.entries.length = 0;
this.entries.push(...newEntries);
}) })
.catch(function (error) { .catch(function (error) {
console.error('Error fetching config:', error) console.error("Error fetching config:", error);
}) });
}, },
async saveConfig() { async saveConfig() {
const data = {} const data = {};
for (const [key, value] of Object.entries(this.config)) { for (const entry of this.entries) {
data[key] = value data[entry.key] = entry.value;
} }
console.log('Saving config:', data)
try { try {
const response = await LNbits.api.request( const response = await LNbits.api.request(
'POST', "POST",
'/nwcprovider/api/v1/config', "/nwcprovider/api/v1/config",
this.g.user.wallets[0].adminkey, this.g.user.wallets[0].adminkey,
data data,
) );
Quasar.Notify.create({ Quasar.Notify.create({
type: 'positive', type: "positive",
message: 'Config saved!' message: "Config saved!",
}) });
Quasar.Notify.create({ Quasar.Notify.create({
type: 'warning', type: "warning",
message: message:
'You need to restart the server for the changes to take effect!' "You need to restart the server for the changes to take effect!",
}) });
} catch (error) { } catch (error) {
Quasar.Notify.create({ Quasar.Notify.create({
type: 'negative', type: "negative",
message: 'Error saving config: ' + String(error) message: "Error saving config: " + String(error),
}) });
console.error('Error saving config:', error) console.error("Error saving config:", error);
}
} }
}, },
},
created: function () { created: function () {
this.fetchConfig() this.fetchConfig();
} },
}) });

View file

@ -1,7 +1,7 @@
window.app = Vue.createApp({ window.app = Vue.createApp({
el: '#vue', el: "#vue",
mixins: [windowMixin], mixins: [windowMixin],
delimiters: ['${', '}'], delimiters: ["${", "}"],
data: function () { data: function () {
return { return {
selectedWallet: null, selectedWallet: null,
@ -10,244 +10,244 @@ window.app = Vue.createApp({
nwcsTable: { nwcsTable: {
columns: [ columns: [
{ {
name: 'description', name: "description",
align: 'left', align: "left",
label: 'Description', label: "Description",
field: 'description' field: "description",
}, },
{name: 'status', align: 'left', label: 'Status', field: 'status'}, { name: "status", align: "left", label: "Status", field: "status" },
{ {
name: 'last_used', name: "last_used",
align: 'left', align: "left",
label: 'Last used', label: "Last used",
field: 'last_used' field: "last_used",
}, },
{ {
name: 'created_at', name: "created_at",
align: 'left', align: "left",
label: 'Created', label: "Created",
field: 'created_at' field: "created_at",
}, },
{ {
name: 'expires_at', name: "expires_at",
align: 'left', align: "left",
label: 'Expires', label: "Expires",
field: 'expires_at' field: "expires_at",
} },
], ],
pagination: { pagination: {
rowsPerPage: 10 rowsPerPage: 10,
} },
}, },
connectDialog: { connectDialog: {
show: false, show: false,
data: {} data: {},
}, },
pairingDialog: { pairingDialog: {
show: false, show: false,
data: { data: {
pairingUrl: '' pairingUrl: "",
} },
}, },
pairingQrDialog: { pairingQrDialog: {
show: false, show: false,
data: { data: {
pairingUrl: '' pairingUrl: "",
} },
}, },
connectionInfoDialog: { connectionInfoDialog: {
show: false, show: false,
data: {} data: {},
} },
} };
}, },
methods: { methods: {
showConnectDialog() { showConnectDialog() {
const wallet = this.getWallet() const wallet = this.getWallet();
if (!wallet) { if (!wallet) {
Quasar.Notify.create({ Quasar.Notify.create({
type: 'negative', type: "negative",
message: 'Please select a wallet first' message: "Please select a wallet first",
}) });
return return;
} else { } else {
this.connectDialog.show = true this.connectDialog.show = true;
} }
}, },
openConnectionInfoDialog(data) { openConnectionInfoDialog(data) {
this.connectionInfoDialog.data = data this.connectionInfoDialog.data = data;
this.connectionInfoDialog.show = true this.connectionInfoDialog.show = true;
}, },
closeConnectionInfoDialog() { closeConnectionInfoDialog() {
this.connectionInfoDialog.show = false this.connectionInfoDialog.show = false;
}, },
openPairingUrl() { openPairingUrl() {
const url = this.pairingDialog.data.pairingUrl const url = this.pairingDialog.data.pairingUrl;
if (url) window.open(url, '_blank') if (url) window.open(url, "_blank");
}, },
go(url) { go(url) {
window.open(url, '_blank') window.open(url, "_blank");
}, },
async copyPairingUrl() { async copyPairingUrl() {
const url = this.pairingDialog.data.pairingUrl const url = this.pairingDialog.data.pairingUrl;
if (url) { if (url) {
try { try {
await navigator.clipboard.writeText(url) await navigator.clipboard.writeText(url);
Quasar.Notify.create({ Quasar.Notify.create({
type: 'positive', type: "positive",
message: 'URL copied to clipboard' message: "URL copied to clipboard",
}) });
} catch (err) { } catch (err) {
Quasar.Notify.create({ Quasar.Notify.create({
type: 'negative', type: "negative",
message: 'Failed to copy URL.' message: "Failed to copy URL.",
}) });
} }
} }
}, },
showPairingQR() { showPairingQR() {
this.pairingQrDialog.data.pairingUrl = this.pairingDialog.data.pairingUrl this.pairingQrDialog.data.pairingUrl = this.pairingDialog.data.pairingUrl;
this.pairingQrDialog.show = true this.pairingQrDialog.show = true;
}, },
closePairingQrDialog() { closePairingQrDialog() {
this.pairingQrDialog.show = false this.pairingQrDialog.show = false;
}, },
loadConnectDialogData() { loadConnectDialogData() {
this.connectDialog.data = { this.connectDialog.data = {
description: '', description: "",
expires_at: Date.now() + 1000 * 60 * 60 * 24 * 7, expires_at: Date.now() + 1000 * 60 * 60 * 24 * 7,
neverExpires: true, neverExpires: true,
permissions: [], permissions: [],
budgets: [] budgets: [],
} };
for (const permission of this.nodePermissions) { for (const permission of this.nodePermissions) {
this.connectDialog.data.permissions.push({ this.connectDialog.data.permissions.push({
key: permission.key, key: permission.key,
name: permission.name, name: permission.name,
value: permission.value value: permission.value,
}) });
} }
}, },
deleteBudget(index) { deleteBudget(index) {
this.connectDialog.data.budgets.splice(index, 1) this.connectDialog.data.budgets.splice(index, 1);
}, },
addBudget() { addBudget() {
this.connectDialog.data.budgets.push({ this.connectDialog.data.budgets.push({
budget_sats: 1000, budget_sats: 1000,
used_budget_sats: 0, used_budget_sats: 0,
created_at: new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000, created_at: new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000,
expiration: 'never' expiration: "never",
}) });
}, },
closeConnectDialog() { closeConnectDialog() {
this.connectDialog.show = false this.connectDialog.show = false;
this.loadConnectDialogData() this.loadConnectDialogData();
}, },
getWallet: function () { getWallet: function () {
let wallet = undefined let wallet = undefined;
for (let i = 0; i < this.g.user.wallets.length; i++) { for (let i = 0; i < this.g.user.wallets.length; i++) {
if (this.g.user.wallets[i].id == this.selectedWallet) { if (this.g.user.wallets[i].id == this.selectedWallet) {
wallet = this.g.user.wallets[i] wallet = this.g.user.wallets[i];
break break;
} }
} }
return wallet return wallet;
}, },
async generateKeyPair() { async generateKeyPair() {
while (!window.NobleSecp256k1) { while (!window.NobleSecp256k1) {
await new Promise(resolve => setTimeout(resolve, 1)) await new Promise((resolve) => setTimeout(resolve, 1));
} }
const privKeyBytes = window.NobleSecp256k1.utils.randomPrivateKey() const privKeyBytes = window.NobleSecp256k1.utils.randomPrivateKey();
const pubKeyBytes = window.NobleSecp256k1.getPublicKey(privKeyBytes) const pubKeyBytes = window.NobleSecp256k1.getPublicKey(privKeyBytes);
const out = { const out = {
privKeyBytes: privKeyBytes, privKeyBytes: privKeyBytes,
pubKeyBytes: pubKeyBytes, pubKeyBytes: pubKeyBytes,
privKey: window.NobleSecp256k1.etc.bytesToHex(privKeyBytes), privKey: window.NobleSecp256k1.etc.bytesToHex(privKeyBytes),
pubKey: window.NobleSecp256k1.etc.bytesToHex(pubKeyBytes.slice(1)) pubKey: window.NobleSecp256k1.etc.bytesToHex(pubKeyBytes.slice(1)),
} };
return out return out;
}, },
deleteNWC: async function (pubkey) { deleteNWC: async function (pubkey) {
Quasar.Dialog.create({ Quasar.Dialog.create({
title: 'Confirm Deletion', title: "Confirm Deletion",
message: 'Are you sure you want to delete this connection?', message: "Are you sure you want to delete this connection?",
cancel: true, cancel: true,
persistent: true persistent: true,
}) })
.onOk(async () => { .onOk(async () => {
try { try {
const wallet = this.getWallet() const wallet = this.getWallet();
const response = await LNbits.api.request( const response = await LNbits.api.request(
'DELETE', "DELETE",
`/nwcprovider/api/v1/nwc/${pubkey}`, `/nwcprovider/api/v1/nwc/${pubkey}`,
wallet.adminkey wallet.adminkey,
) );
this.loadNwcs() this.loadNwcs();
Quasar.Notify.create({ Quasar.Notify.create({
type: 'positive', type: "positive",
message: 'Deleted successfully' message: "Deleted successfully",
}) });
} catch (error) { } catch (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error);
} }
}) })
.onCancel(() => { .onCancel(() => {
// User canceled the operation // User canceled the operation
}) });
}, },
loadNwcs: async function () { loadNwcs: async function () {
const wallet = this.getWallet() const wallet = this.getWallet();
if (!wallet) { if (!wallet) {
this.nwcs = [] this.nwcs = [];
return return;
} }
try { try {
const response = await LNbits.api.request( const response = await LNbits.api.request(
'GET', "GET",
'/nwcprovider/api/v1/nwc?include_expired=true&calculate_spent_budget=true', "/nwcprovider/api/v1/nwc?include_expired=true&calculate_spent_budget=true",
wallet.adminkey wallet.adminkey,
) );
this.nwcs = response.data this.nwcs = response.data;
} catch (error) { } catch (error) {
this.nwcs = [] this.nwcs = [];
} }
try { try {
const response = await LNbits.api.request( const response = await LNbits.api.request(
'GET', "GET",
'/nwcprovider/api/v1/permissions', "/nwcprovider/api/v1/permissions",
wallet.adminkey wallet.adminkey,
) );
const permissions = [] const permissions = [];
for (const [key, value] of Object.entries(response.data)) { for (const [key, value] of Object.entries(response.data)) {
permissions.push({ permissions.push({
key: key, key: key,
name: value.name, name: value.name,
value: value.default value: value.default,
}) });
} }
this.nodePermissions = permissions this.nodePermissions = permissions;
} catch (error) { } catch (error) {
Lnbits.utils.notifyApiError(error) Lnbits.utils.notifyApiError(error);
} }
this.loadConnectDialogData() this.loadConnectDialogData();
const newTableEntries = [] const newTableEntries = [];
for (const nwc of this.nwcs) { for (const nwc of this.nwcs) {
const t = Quasar.date.formatDate( const t = Quasar.date.formatDate(
new Date(nwc.data.created_at * 1000), new Date(nwc.data.created_at * 1000),
'YYYY-MM-DD HH:mm' "YYYY-MM-DD HH:mm",
) );
const e = const e =
nwc.data.expires_at > 0 nwc.data.expires_at > 0
? Quasar.date.formatDate( ? Quasar.date.formatDate(
new Date(nwc.data.expires_at * 1000), new Date(nwc.data.expires_at * 1000),
'YYYY-MM-DD HH:mm' "YYYY-MM-DD HH:mm",
) )
: 'Never' : "Never";
const l = Quasar.date.formatDate( const l = Quasar.date.formatDate(
new Date(nwc.data.last_used * 1000), new Date(nwc.data.last_used * 1000),
'YYYY-MM-DD HH:mm' "YYYY-MM-DD HH:mm",
) );
const nwcTableEntry = { const nwcTableEntry = {
description: nwc.data.description, description: nwc.data.description,
created_at: t, created_at: t,
@ -256,130 +256,131 @@ window.app = Vue.createApp({
pubkey: nwc.data.pubkey, pubkey: nwc.data.pubkey,
permissions: nwc.data.permissions, permissions: nwc.data.permissions,
budgets: [], budgets: [],
status: 'Active' status: "Active",
} };
if ( if (
nwc.data.expires_at > 0 && nwc.data.expires_at > 0 &&
nwc.data.expires_at < new Date().getTime() / 1000 nwc.data.expires_at < new Date().getTime() / 1000
) { ) {
nwcTableEntry.status = 'Expired' nwcTableEntry.status = "Expired";
} }
for (const budget of nwc.budgets) { for (const budget of nwc.budgets) {
const createdAt = Quasar.date.formatDate( const createdAt = Quasar.date.formatDate(
new Date(budget.created_at * 1000), new Date(budget.created_at * 1000),
'YYYY-MM-DD HH:mm' "YYYY-MM-DD HH:mm",
) );
let refreshWindow = budget.refresh_window let refreshWindow = budget.refresh_window;
if (refreshWindow <= 0) { if (refreshWindow <= 0) {
refreshWindow = 'Never' refreshWindow = "Never";
} else if (refreshWindow == 60 * 60 * 24) { } else if (refreshWindow == 60 * 60 * 24) {
refreshWindow = 'Daily' refreshWindow = "Daily";
} else if (refreshWindow == 60 * 60 * 24 * 7) { } else if (refreshWindow == 60 * 60 * 24 * 7) {
refreshWindow = 'Weekly' refreshWindow = "Weekly";
} else if (refreshWindow == 60 * 60 * 24 * 30) { } else if (refreshWindow == 60 * 60 * 24 * 30) {
refreshWindow = 'Monthly' refreshWindow = "Monthly";
} else if (refreshWindow == 60 * 60 * 24 * 365) { } else if (refreshWindow == 60 * 60 * 24 * 365) {
refreshWindow = 'Yearly' refreshWindow = "Yearly";
} }
nwcTableEntry.budgets.push({ nwcTableEntry.budgets.push({
budget_sats: budget.budget_msats / 1000, budget_sats: budget.budget_msats / 1000,
used_budget_sats: budget.used_budget_msats / 1000, used_budget_sats: budget.used_budget_msats / 1000,
created_at: createdAt, created_at: createdAt,
refresh_window: refreshWindow refresh_window: refreshWindow,
}) });
} }
newTableEntries.push(nwcTableEntry) newTableEntries.push(nwcTableEntry);
} }
this.nwcEntries = newTableEntries this.nwcEntries = newTableEntries;
}, },
closePairingDialog() { closePairingDialog() {
this.pairingDialog.show = false this.pairingDialog.show = false;
}, },
async showPairingDialog(secret) { async showPairingDialog(secret) {
let response = await LNbits.api.request( let response = await LNbits.api.request(
'GET', "GET",
'/nwcprovider/api/v1/pairing/{SECRET}' "/nwcprovider/api/v1/pairing/{SECRET}",
) );
response = response.data response = response.data;
response = response.replace('{SECRET}', secret) response = response.replace("{SECRET}", secret);
this.pairingDialog.data.pairingUrl = response this.pairingDialog.data.pairingUrl = response;
this.pairingDialog.show = true this.pairingDialog.show = true;
}, },
async confirmConnectDialog() { async confirmConnectDialog() {
const keyPair = await this.generateKeyPair() const keyPair = await this.generateKeyPair();
// timestamp // timestamp
let expires_at = 0 let expires_at = 0;
if (!this.connectDialog.data.neverExpires) { if (!this.connectDialog.data.neverExpires) {
expires_at = expires_at =
new Date(this.connectDialog.data.expires_at).getTime() / 1000 new Date(this.connectDialog.data.expires_at).getTime() / 1000;
} }
const data = { const data = {
permissions: [], permissions: [],
description: this.connectDialog.data.description, description: this.connectDialog.data.description,
expires_at: expires_at, expires_at: expires_at,
budgets: [] budgets: [],
} };
for (const permission of this.connectDialog.data.permissions) { for (const permission of this.connectDialog.data.permissions) {
if (permission.value) data.permissions.push(permission.key) if (permission.value) data.permissions.push(permission.key);
} }
for (const budget of this.connectDialog.data.budgets) { for (const budget of this.connectDialog.data.budgets) {
const budget_msats = budget.budget_sats * 1000 const budget_msats = budget.budget_sats * 1000;
let refresh_window = 0 let refresh_window = 0;
switch (budget.expiry) { switch (budget.expiry) {
case 'Daily': case "Daily":
refresh_window = 60 * 60 * 24 refresh_window = 60 * 60 * 24;
break break;
case 'Weekly': case "Weekly":
refresh_window = 60 * 60 * 24 * 7 refresh_window = 60 * 60 * 24 * 7;
break break;
case 'Monthly': case "Monthly":
refresh_window = 60 * 60 * 24 * 30 refresh_window = 60 * 60 * 24 * 30;
break break;
case 'Yearly': case "Yearly":
refresh_window = 60 * 60 * 24 * 365 refresh_window = 60 * 60 * 24 * 365;
break break;
case 'Never': case "Never":
refresh_window = 0 refresh_window = 0;
break break;
} }
data.budgets.push({ data.budgets.push({
budget_msats: budget_msats, budget_msats: budget_msats,
refresh_window: refresh_window, refresh_window: refresh_window,
created_at: new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000 created_at:
}) new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000,
});
} }
const wallet = this.getWallet() const wallet = this.getWallet();
try { try {
const response = await LNbits.api.request( const response = await LNbits.api.request(
'PUT', "PUT",
'/nwcprovider/api/v1/nwc/' + keyPair.pubKey, "/nwcprovider/api/v1/nwc/" + keyPair.pubKey,
wallet.adminkey, wallet.adminkey,
data data,
) );
this.closeConnectDialog() this.closeConnectDialog();
if ( if (
!response.data || !response.data ||
!response.data.data || !response.data.data ||
!response.data.data.pubkey !response.data.data.pubkey
) { ) {
LNbits.utils.notifyApiError('Error creating nwc pairing') LNbits.utils.notifyApiError("Error creating nwc pairing");
return return;
} }
this.showPairingDialog(keyPair.privKey) this.showPairingDialog(keyPair.privKey);
} catch (error) { } catch (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error);
}
this.loadNwcs()
} }
this.loadNwcs();
},
}, },
created: function () { created: function () {
this.loadNwcs() this.loadNwcs();
}, },
watch: { watch: {
selectedWallet(newValue, oldValue) { selectedWallet(newValue, oldValue) {
this.loadNwcs() this.loadNwcs();
} },
} },
}) });

View file

@ -6,60 +6,60 @@ const B256 = 2n ** 256n,
Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n, Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n,
CURVE = { p: P, n: N, a: 0n, b: 7n, Gx, Gy }, CURVE = { p: P, n: N, a: 0n, b: 7n, Gx, Gy },
fLen = 32, fLen = 32,
crv = t => mod(mod(t * t) * t + CURVE.b), crv = (t) => mod(mod(t * t) * t + CURVE.b),
err = (t = '') => { err = (t = "") => {
throw new Error(t) throw new Error(t);
}, },
big = t => 'bigint' == typeof t, big = (t) => "bigint" == typeof t,
str = t => 'string' == typeof t, str = (t) => "string" == typeof t,
fe = t => big(t) && 0n < t && t < P, fe = (t) => big(t) && 0n < t && t < P,
ge = t => big(t) && 0n < t && t < N, ge = (t) => big(t) && 0n < t && t < N,
isu8 = t => isu8 = (t) =>
t instanceof Uint8Array || t instanceof Uint8Array ||
(null != t && 'object' == typeof t && 'Uint8Array' === t.constructor.name), (null != t && "object" == typeof t && "Uint8Array" === t.constructor.name),
au8 = (t, n) => au8 = (t, n) =>
!isu8(t) || ('number' == typeof n && n > 0 && t.length !== n) !isu8(t) || ("number" == typeof n && n > 0 && t.length !== n)
? err('Uint8Array expected') ? err("Uint8Array expected")
: t, : t,
u8n = t => new Uint8Array(t), u8n = (t) => new Uint8Array(t),
toU8 = (t, n) => au8(str(t) ? h2b(t) : u8n(au8(t)), n), toU8 = (t, n) => au8(str(t) ? h2b(t) : u8n(au8(t)), n),
mod = (t, n = P) => { mod = (t, n = P) => {
let e = t % n let e = t % n;
return e >= 0n ? e : n + e return e >= 0n ? e : n + e;
}, },
isPoint = t => (t instanceof Point ? t : err('Point expected')) isPoint = (t) => (t instanceof Point ? t : err("Point expected"));
class Point { class Point {
constructor(t, n, e) { constructor(t, n, e) {
;((this.px = t), (this.py = n), (this.pz = e)) (this.px = t), (this.py = n), (this.pz = e);
} }
static fromAffine(t) { static fromAffine(t) {
return 0n === t.x && 0n === t.y ? Point.ZERO : new Point(t.x, t.y, 1n) return 0n === t.x && 0n === t.y ? Point.ZERO : new Point(t.x, t.y, 1n);
} }
static fromHex(t) { static fromHex(t) {
let n let n;
const e = (t = toU8(t))[0], const e = (t = toU8(t))[0],
r = t.subarray(1), r = t.subarray(1),
o = slcNum(r, 0, 32), o = slcNum(r, 0, 32),
i = t.length i = t.length;
if (33 === i && [2, 3].includes(e)) { if (33 === i && [2, 3].includes(e)) {
fe(o) || err('Point hex invalid: x not FE') fe(o) || err("Point hex invalid: x not FE");
let t = sqrt(crv(o)) let t = sqrt(crv(o));
;(!(1 & ~e) !== (1n === (1n & t)) && (t = mod(-t)), !(1 & ~e) !== (1n === (1n & t)) && (t = mod(-t)),
(n = new Point(o, t, 1n))) (n = new Point(o, t, 1n));
} }
return ( return (
65 === i && 4 === e && (n = new Point(o, slcNum(r, 32, 64), 1n)), 65 === i && 4 === e && (n = new Point(o, slcNum(r, 32, 64), 1n)),
n ? n.ok() : err('Point is not on curve') n ? n.ok() : err("Point is not on curve")
) );
} }
static fromPrivateKey(t) { static fromPrivateKey(t) {
return G.mul(toPriv(t)) return G.mul(toPriv(t));
} }
get x() { get x() {
return this.aff().x return this.aff().x;
} }
get y() { get y() {
return this.aff().y return this.aff().y;
} }
equals(t) { equals(t) {
const { px: n, py: e, pz: r } = this, const { px: n, py: e, pz: r } = this,
@ -67,30 +67,30 @@ class Point {
a = mod(n * s), a = mod(n * s),
c = mod(o * r), c = mod(o * r),
d = mod(e * s), d = mod(e * s),
u = mod(i * r) u = mod(i * r);
return a === c && d === u return a === c && d === u;
} }
negate() { negate() {
return new Point(this.px, mod(-this.py), this.pz) return new Point(this.px, mod(-this.py), this.pz);
} }
double() { double() {
return this.add(this) return this.add(this);
} }
add(t) { add(t) {
const { px: n, py: e, pz: r } = this, const { px: n, py: e, pz: r } = this,
{ px: o, py: i, pz: s } = isPoint(t), { px: o, py: i, pz: s } = isPoint(t),
{a, b: c} = CURVE { a, b: c } = CURVE;
let d = 0n, let d = 0n,
u = 0n, u = 0n,
m = 0n m = 0n;
const h = mod(3n * c) const h = mod(3n * c);
let l = mod(n * o), let l = mod(n * o),
y = mod(e * i), y = mod(e * i),
f = mod(r * s), f = mod(r * s),
p = mod(n + e), p = mod(n + e),
b = mod(o + i) b = mod(o + i);
;((p = mod(p * b)), (b = mod(l + y)), (p = mod(p - b)), (b = mod(n + r))) (p = mod(p * b)), (b = mod(l + y)), (p = mod(p - b)), (b = mod(n + r));
let g = mod(o + s) let g = mod(o + s);
return ( return (
(b = mod(b * g)), (b = mod(b * g)),
(g = mod(l + f)), (g = mod(l + f)),
@ -123,301 +123,300 @@ class Point {
(m = mod(g * m)), (m = mod(g * m)),
(m = mod(m + l)), (m = mod(m + l)),
new Point(d, u, m) new Point(d, u, m)
) );
} }
mul(t, n = !0) { mul(t, n = !0) {
if (!n && 0n === t) return I if (!n && 0n === t) return I;
if ((ge(t) || err('invalid scalar'), this.equals(G))) return wNAF(t).p if ((ge(t) || err("invalid scalar"), this.equals(G))) return wNAF(t).p;
let e = I, let e = I,
r = G r = G;
for (let o = this; t > 0n; o = o.double(), t >>= 1n) for (let o = this; t > 0n; o = o.double(), t >>= 1n)
1n & t ? (e = e.add(o)) : n && (r = r.add(o)) 1n & t ? (e = e.add(o)) : n && (r = r.add(o));
return e return e;
} }
mulAddQUns(t, n, e) { mulAddQUns(t, n, e) {
return this.mul(n, !1).add(t.mul(e, !1)).ok() return this.mul(n, !1).add(t.mul(e, !1)).ok();
} }
toAffine() { toAffine() {
const {px: t, py: n, pz: e} = this const { px: t, py: n, pz: e } = this;
if (this.equals(I)) return {x: 0n, y: 0n} if (this.equals(I)) return { x: 0n, y: 0n };
if (1n === e) return {x: t, y: n} if (1n === e) return { x: t, y: n };
const r = inv(e) const r = inv(e);
return ( return (
1n !== mod(e * r) && err('invalid inverse'), 1n !== mod(e * r) && err("invalid inverse"),
{ x: mod(t * r), y: mod(n * r) } { x: mod(t * r), y: mod(n * r) }
) );
} }
assertValidity() { assertValidity() {
const {x: t, y: n} = this.aff() const { x: t, y: n } = this.aff();
return ( return (
(fe(t) && fe(n)) || err('Point invalid: x or y'), (fe(t) && fe(n)) || err("Point invalid: x or y"),
mod(n * n) === crv(t) ? this : err('Point invalid: not on curve') mod(n * n) === crv(t) ? this : err("Point invalid: not on curve")
) );
} }
multiply(t) { multiply(t) {
return this.mul(t) return this.mul(t);
} }
aff() { aff() {
return this.toAffine() return this.toAffine();
} }
ok() { ok() {
return this.assertValidity() return this.assertValidity();
} }
toHex(t = !0) { toHex(t = !0) {
const {x: n, y: e} = this.aff() const { x: n, y: e } = this.aff();
return ( return (
(t ? (0n === (1n & e) ? '02' : '03') : '04') + n2h(n) + (t ? '' : n2h(e)) (t ? (0n === (1n & e) ? "02" : "03") : "04") + n2h(n) + (t ? "" : n2h(e))
) );
} }
toRawBytes(t = !0) { toRawBytes(t = !0) {
return h2b(this.toHex(t)) return h2b(this.toHex(t));
} }
} }
;((Point.BASE = new Point(Gx, Gy, 1n)), (Point.ZERO = new Point(0n, 1n, 0n))) (Point.BASE = new Point(Gx, Gy, 1n)), (Point.ZERO = new Point(0n, 1n, 0n));
const { BASE: G, ZERO: I } = Point, const { BASE: G, ZERO: I } = Point,
padh = (t, n) => t.toString(16).padStart(n, '0'), padh = (t, n) => t.toString(16).padStart(n, "0"),
b2h = t => b2h = (t) =>
Array.from(t) Array.from(t)
.map(t => padh(t, 2)) .map((t) => padh(t, 2))
.join(''), .join(""),
h2b = t => { h2b = (t) => {
const n = t.length const n = t.length;
;(!str(t) || n % 2) && err('hex invalid 1') (!str(t) || n % 2) && err("hex invalid 1");
const e = u8n(n / 2) const e = u8n(n / 2);
for (let n = 0; n < e.length; n++) { for (let n = 0; n < e.length; n++) {
const r = 2 * n, const r = 2 * n,
o = t.slice(r, r + 2), o = t.slice(r, r + 2),
i = Number.parseInt(o, 16) i = Number.parseInt(o, 16);
;((Number.isNaN(i) || i < 0) && err('hex invalid 2'), (e[n] = i)) (Number.isNaN(i) || i < 0) && err("hex invalid 2"), (e[n] = i);
} }
return e return e;
}, },
b2n = t => BigInt('0x' + (b2h(t) || '0')), b2n = (t) => BigInt("0x" + (b2h(t) || "0")),
slcNum = (t, n, e) => b2n(t.slice(n, e)), slcNum = (t, n, e) => b2n(t.slice(n, e)),
n2b = t => n2b = (t) =>
big(t) && t >= 0n && t < B256 ? h2b(padh(t, 64)) : err('bigint expected'), big(t) && t >= 0n && t < B256 ? h2b(padh(t, 64)) : err("bigint expected"),
n2h = t => b2h(n2b(t)), n2h = (t) => b2h(n2b(t)),
concatB = (...t) => { concatB = (...t) => {
const n = u8n(t.reduce((t, n) => t + au8(n).length, 0)) const n = u8n(t.reduce((t, n) => t + au8(n).length, 0));
let e = 0 let e = 0;
return ( return (
t.forEach(t => { t.forEach((t) => {
;(n.set(t, e), (e += t.length)) n.set(t, e), (e += t.length);
}), }),
n n
) );
}, },
inv = (t, n = P) => { inv = (t, n = P) => {
;(0n === t || n <= 0n) && err('no inverse n=' + t + ' mod=' + n) (0n === t || n <= 0n) && err("no inverse n=" + t + " mod=" + n);
let e = mod(t, n), let e = mod(t, n),
r = n, r = n,
o = 0n, o = 0n,
i = 1n, i = 1n,
s = 1n, s = 1n,
a = 0n a = 0n;
for (; 0n !== e; ) { for (; 0n !== e; ) {
const t = r / e, const t = r / e,
n = r % e, n = r % e,
c = o - s * t, c = o - s * t,
d = i - a * t d = i - a * t;
;((r = e), (e = n), (o = s), (i = a), (s = c), (a = d)) (r = e), (e = n), (o = s), (i = a), (s = c), (a = d);
} }
return 1n === r ? mod(o, n) : err('no inverse') return 1n === r ? mod(o, n) : err("no inverse");
}, },
sqrt = t => { sqrt = (t) => {
let n = 1n let n = 1n;
for (let e = t, r = (P + 1n) / 4n; r > 0n; r >>= 1n) for (let e = t, r = (P + 1n) / 4n; r > 0n; r >>= 1n)
(1n & r && (n = (n * e) % P), (e = (e * e) % P)) 1n & r && (n = (n * e) % P), (e = (e * e) % P);
return mod(n * n) === t ? n : err('sqrt invalid') return mod(n * n) === t ? n : err("sqrt invalid");
}, },
toPriv = t => ( toPriv = (t) => (
big(t) || (t = b2n(toU8(t, 32))), big(t) || (t = b2n(toU8(t, 32))),
ge(t) ? t : err('private key out of range') ge(t) ? t : err("private key out of range")
), ),
moreThanHalfN = t => t > N >> 1n, moreThanHalfN = (t) => t > N >> 1n,
getPublicKey = (t, n = !0) => Point.fromPrivateKey(t).toRawBytes(n) getPublicKey = (t, n = !0) => Point.fromPrivateKey(t).toRawBytes(n);
class Signature { class Signature {
constructor(t, n, e) { constructor(t, n, e) {
;((this.r = t), (this.s = n), (this.recovery = e), this.assertValidity()) (this.r = t), (this.s = n), (this.recovery = e), this.assertValidity();
} }
static fromCompact(t) { static fromCompact(t) {
return ( return (
(t = toU8(t, 64)), (t = toU8(t, 64)), new Signature(slcNum(t, 0, 32), slcNum(t, 32, 64))
new Signature(slcNum(t, 0, 32), slcNum(t, 32, 64)) );
)
} }
assertValidity() { assertValidity() {
return ge(this.r) && ge(this.s) ? this : err() return ge(this.r) && ge(this.s) ? this : err();
} }
addRecoveryBit(t) { addRecoveryBit(t) {
return new Signature(this.r, this.s, t) return new Signature(this.r, this.s, t);
} }
hasHighS() { hasHighS() {
return moreThanHalfN(this.s) return moreThanHalfN(this.s);
} }
normalizeS() { normalizeS() {
return this.hasHighS() return this.hasHighS()
? new Signature(this.r, mod(this.s, N), this.recovery) ? new Signature(this.r, mod(this.s, N), this.recovery)
: this : this;
} }
recoverPublicKey(t) { recoverPublicKey(t) {
const {r: n, s: e, recovery: r} = this const { r: n, s: e, recovery: r } = this;
;[0, 1, 2, 3].includes(r) || err('recovery id invalid') [0, 1, 2, 3].includes(r) || err("recovery id invalid");
const o = bits2int_modN(toU8(t, 32)), const o = bits2int_modN(toU8(t, 32)),
i = 2 === r || 3 === r ? n + N : n i = 2 === r || 3 === r ? n + N : n;
i >= P && err('q.x invalid') i >= P && err("q.x invalid");
const s = 1 & r ? '03' : '02', const s = 1 & r ? "03" : "02",
a = Point.fromHex(s + n2h(i)), a = Point.fromHex(s + n2h(i)),
c = inv(i, N), c = inv(i, N),
d = mod(-o * c, N), d = mod(-o * c, N),
u = mod(e * c, N) u = mod(e * c, N);
return G.mulAddQUns(a, d, u) return G.mulAddQUns(a, d, u);
} }
toCompactRawBytes() { toCompactRawBytes() {
return h2b(this.toCompactHex()) return h2b(this.toCompactHex());
} }
toCompactHex() { toCompactHex() {
return n2h(this.r) + n2h(this.s) return n2h(this.r) + n2h(this.s);
} }
} }
const bits2int = t => { const bits2int = (t) => {
const n = 8 * t.length - 256, const n = 8 * t.length - 256,
e = b2n(t) e = b2n(t);
return n > 0 ? e >> BigInt(n) : e return n > 0 ? e >> BigInt(n) : e;
}, },
bits2int_modN = t => mod(bits2int(t), N), bits2int_modN = (t) => mod(bits2int(t), N),
i2o = t => n2b(t), i2o = (t) => n2b(t),
cr = () => cr = () =>
'object' == typeof globalThis && 'crypto' in globalThis "object" == typeof globalThis && "crypto" in globalThis
? globalThis.crypto ? globalThis.crypto
: void 0 : void 0;
let _hmacSync let _hmacSync;
const optS = { lowS: !0 }, const optS = { lowS: !0 },
optV = { lowS: !0 }, optV = { lowS: !0 },
prepSig = (t, n, e = optS) => { prepSig = (t, n, e = optS) => {
;['der', 'recovered', 'canonical'].some(t => t in e) && ["der", "recovered", "canonical"].some((t) => t in e) &&
err('sign() legacy options not supported') err("sign() legacy options not supported");
let {lowS: r} = e let { lowS: r } = e;
null == r && (r = !0) null == r && (r = !0);
const o = bits2int_modN(toU8(t)), const o = bits2int_modN(toU8(t)),
i = i2o(o), i = i2o(o),
s = toPriv(n), s = toPriv(n),
a = [i2o(s), i] a = [i2o(s), i];
let c = e.extraEntropy let c = e.extraEntropy;
if (c) { if (c) {
!0 === c && (c = etc.randomBytes(32)) !0 === c && (c = etc.randomBytes(32));
const t = toU8(c) const t = toU8(c);
;(32 !== t.length && err(), a.push(t)) 32 !== t.length && err(), a.push(t);
} }
const d = o const d = o;
return { return {
seed: concatB(...a), seed: concatB(...a),
k2sig: t => { k2sig: (t) => {
const n = bits2int(t) const n = bits2int(t);
if (!ge(n)) return if (!ge(n)) return;
const e = inv(n, N), const e = inv(n, N),
o = G.mul(n).aff(), o = G.mul(n).aff(),
i = mod(o.x, N) i = mod(o.x, N);
if (0n === i) return if (0n === i) return;
const a = mod(e * mod(d + mod(s * i, N), N), N) const a = mod(e * mod(d + mod(s * i, N), N), N);
if (0n === a) return if (0n === a) return;
let c = a, let c = a,
u = (o.x === i ? 0 : 2) | Number(1n & o.y) u = (o.x === i ? 0 : 2) | Number(1n & o.y);
return ( return (
r && moreThanHalfN(a) && ((c = mod(-a, N)), (u ^= 1)), r && moreThanHalfN(a) && ((c = mod(-a, N)), (u ^= 1)),
new Signature(i, c, u) new Signature(i, c, u)
) );
} },
} };
} };
function hmacDrbg(t) { function hmacDrbg(t) {
let n = u8n(32), let n = u8n(32),
e = u8n(32), e = u8n(32),
r = 0 r = 0;
const o = () => { const o = () => {
;(n.fill(1), e.fill(0), (r = 0)) n.fill(1), e.fill(0), (r = 0);
}, },
i = 'drbg: tried 1000 values' i = "drbg: tried 1000 values";
if (t) { if (t) {
const t = (...t) => etc.hmacSha256Async(e, n, ...t), const t = (...t) => etc.hmacSha256Async(e, n, ...t),
s = async (r = u8n()) => { s = async (r = u8n()) => {
;((e = await t(u8n([0]), r)), (e = await t(u8n([0]), r)),
(n = await t()), (n = await t()),
0 !== r.length && ((e = await t(u8n([1]), r)), (n = await t()))) 0 !== r.length && ((e = await t(u8n([1]), r)), (n = await t()));
}, },
a = async () => (r++ >= 1e3 && err(i), (n = await t()), n) a = async () => (r++ >= 1e3 && err(i), (n = await t()), n);
return async (t, n) => { return async (t, n) => {
let e let e;
for (o(), await s(t); !(e = n(await a())); ) await s() for (o(), await s(t); !(e = n(await a())); ) await s();
return (o(), e) return o(), e;
} };
} }
{ {
const t = (...t) => { const t = (...t) => {
const r = _hmacSync const r = _hmacSync;
return (r || err('etc.hmacSha256Sync not set'), r(e, n, ...t)) return r || err("etc.hmacSha256Sync not set"), r(e, n, ...t);
}, },
s = (r = u8n()) => { s = (r = u8n()) => {
;((e = t(u8n([0]), r)), (e = t(u8n([0]), r)),
(n = t()), (n = t()),
0 !== r.length && ((e = t(u8n([1]), r)), (n = t()))) 0 !== r.length && ((e = t(u8n([1]), r)), (n = t()));
}, },
a = () => (r++ >= 1e3 && err(i), (n = t()), n) a = () => (r++ >= 1e3 && err(i), (n = t()), n);
return (t, n) => { return (t, n) => {
let e let e;
for (o(), s(t); !(e = n(a())); ) s() for (o(), s(t); !(e = n(a())); ) s();
return (o(), e) return o(), e;
} };
} }
} }
const signAsync = async (t, n, e = optS) => { const signAsync = async (t, n, e = optS) => {
const {seed: r, k2sig: o} = prepSig(t, n, e) const { seed: r, k2sig: o } = prepSig(t, n, e);
return hmacDrbg(!0)(r, o) return hmacDrbg(!0)(r, o);
}, },
sign = (t, n, e = optS) => { sign = (t, n, e = optS) => {
const {seed: r, k2sig: o} = prepSig(t, n, e) const { seed: r, k2sig: o } = prepSig(t, n, e);
return hmacDrbg(!1)(r, o) return hmacDrbg(!1)(r, o);
}, },
verify = (t, n, e, r = optV) => { verify = (t, n, e, r = optV) => {
let o, let o,
i, i,
s, s,
{lowS: a} = r { lowS: a } = r;
;(null == a && (a = !0), null == a && (a = !0),
'strict' in r && err('verify() legacy options not supported')) "strict" in r && err("verify() legacy options not supported");
const c = t && 'object' == typeof t && 'r' in t const c = t && "object" == typeof t && "r" in t;
c || 64 === toU8(t).length || err('signature must be 64 bytes') c || 64 === toU8(t).length || err("signature must be 64 bytes");
try { try {
;((o = c (o = c
? new Signature(t.r, t.s).assertValidity() ? new Signature(t.r, t.s).assertValidity()
: Signature.fromCompact(t)), : Signature.fromCompact(t)),
(i = bits2int_modN(toU8(n))), (i = bits2int_modN(toU8(n))),
(s = e instanceof Point ? e.ok() : Point.fromHex(e))) (s = e instanceof Point ? e.ok() : Point.fromHex(e));
} catch (t) { } catch (t) {
return !1 return !1;
} }
if (!o) return !1 if (!o) return !1;
const {r: d, s: u} = o const { r: d, s: u } = o;
if (a && moreThanHalfN(u)) return !1 if (a && moreThanHalfN(u)) return !1;
let m let m;
try { try {
const t = inv(u, N), const t = inv(u, N),
n = mod(i * t, N), n = mod(i * t, N),
e = mod(d * t, N) e = mod(d * t, N);
m = G.mulAddQUns(s, n, e).aff() m = G.mulAddQUns(s, n, e).aff();
} catch (t) { } catch (t) {
return !1 return !1;
} }
if (!m) return !1 if (!m) return !1;
return mod(m.x, N) === d return mod(m.x, N) === d;
}, },
getSharedSecret = (t, n, e = !0) => getSharedSecret = (t, n, e = !0) =>
Point.fromHex(n).mul(toPriv(t)).toRawBytes(e), Point.fromHex(n).mul(toPriv(t)).toRawBytes(e),
hashToPrivateKey = t => { hashToPrivateKey = (t) => {
;((t = toU8(t)).length < 40 || t.length > 1024) && ((t = toU8(t)).length < 40 || t.length > 1024) &&
err('expected proper params') err("expected proper params");
const n = mod(b2n(t), N - 1n) + 1n const n = mod(b2n(t), N - 1n) + 1n;
return n2b(n) return n2b(n);
}, },
etc = { etc = {
hexToBytes: h2b, hexToBytes: h2b,
@ -429,84 +428,84 @@ const signAsync = async (t, n, e = optS) => {
invert: inv, invert: inv,
hmacSha256Async: async (t, ...n) => { hmacSha256Async: async (t, ...n) => {
const e = cr(), const e = cr(),
r = e && e.subtle r = e && e.subtle;
if (!r) return err('etc.hmacSha256Async not set') if (!r) return err("etc.hmacSha256Async not set");
const o = await r.importKey( const o = await r.importKey(
'raw', "raw",
t, t,
{name: 'HMAC', hash: {name: 'SHA-256'}}, { name: "HMAC", hash: { name: "SHA-256" } },
!1, !1,
['sign'] ["sign"],
) );
return u8n(await r.sign('HMAC', o, concatB(...n))) return u8n(await r.sign("HMAC", o, concatB(...n)));
}, },
hmacSha256Sync: _hmacSync, hmacSha256Sync: _hmacSync,
hashToPrivateKey, hashToPrivateKey,
randomBytes: (t = 32) => { randomBytes: (t = 32) => {
const n = cr() const n = cr();
return ( return (
(n && n.getRandomValues) || (n && n.getRandomValues) ||
err('crypto.getRandomValues must be defined'), err("crypto.getRandomValues must be defined"),
n.getRandomValues(u8n(t)) n.getRandomValues(u8n(t))
) );
} },
}, },
utils = { utils = {
normPrivateKeyToScalar: toPriv, normPrivateKeyToScalar: toPriv,
isValidPrivateKey: t => { isValidPrivateKey: (t) => {
try { try {
return !!toPriv(t) return !!toPriv(t);
} catch (t) { } catch (t) {
return !1 return !1;
} }
}, },
randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(48)), randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(48)),
precompute: (t = 8, n = G) => (n.multiply(3n), n) precompute: (t = 8, n = G) => (n.multiply(3n), n),
} };
Object.defineProperties(etc, { Object.defineProperties(etc, {
hmacSha256Sync: { hmacSha256Sync: {
configurable: !1, configurable: !1,
get: () => _hmacSync, get: () => _hmacSync,
set(t) { set(t) {
_hmacSync || (_hmacSync = t) _hmacSync || (_hmacSync = t);
} },
} },
}) });
const W = 8, const W = 8,
precompute = () => { precompute = () => {
const t = [] const t = [];
let n = G, let n = G,
e = n e = n;
for (let r = 0; r < 33; r++) { for (let r = 0; r < 33; r++) {
;((e = n), t.push(e)) (e = n), t.push(e);
for (let r = 1; r < 128; r++) ((e = e.add(n)), t.push(e)) for (let r = 1; r < 128; r++) (e = e.add(n)), t.push(e);
n = e.double() n = e.double();
} }
return t return t;
} };
let Gpows let Gpows;
const wNAF = t => { const wNAF = (t) => {
const n = Gpows || (Gpows = precompute()), const n = Gpows || (Gpows = precompute()),
e = (t, n) => { e = (t, n) => {
let e = n.negate() let e = n.negate();
return t ? e : n return t ? e : n;
} };
let r = I, let r = I,
o = G o = G;
const i = BigInt(255), const i = BigInt(255),
s = BigInt(8) s = BigInt(8);
for (let a = 0; a < 33; a++) { for (let a = 0; a < 33; a++) {
const c = 128 * a const c = 128 * a;
let d = Number(t & i) let d = Number(t & i);
;((t >>= s), d > 128 && ((d -= 256), (t += 1n))) (t >>= s), d > 128 && ((d -= 256), (t += 1n));
const u = c, const u = c,
m = c + Math.abs(d) - 1, m = c + Math.abs(d) - 1,
h = a % 2 != 0, h = a % 2 != 0,
l = d < 0 l = d < 0;
0 === d ? (o = o.add(e(h, n[u]))) : (r = r.add(e(l, n[m]))) 0 === d ? (o = o.add(e(h, n[u]))) : (r = r.add(e(l, n[m])));
}
return {p: r, f: o}
} }
return { p: r, f: o };
};
export { export {
getPublicKey, getPublicKey,
sign, sign,
@ -517,5 +516,5 @@ export {
etc, etc,
utils, utils,
Point as ProjectivePoint, Point as ProjectivePoint,
Signature Signature,
} };

View file

@ -6,56 +6,21 @@
<div class="col-12 q-gutter-y-md"> <div class="col-12 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
<div class="row items-center wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col"> <div class="col">
<h5 class="text-subtitle1 q-my-none"> <h5 class="text-subtitle1 q-my-none">
NWC Service Provider Configuration NWC Service Provider - Config
</h5> </h5>
</div> </div>
</div> </div>
<q-markup-table flat wrap-cells="true"> <q-markup-table flat>
<tbody> <tbody>
<q-tr> <q-tr v-for="entry in entries" :key="entry.key">
<q-td> <q-td>
<q-input <span> ${entry.key} </span>
v-model="config.relay"
label="Nostr Relay URL"
filled
wrap
:hint="'URL of the Nostr relay for dispatching and receiving NWC events. Use public relays or a custom one. Specify `nostrclient` to use the Nostr Client extension'"
>
</q-input>
</q-td> </q-td>
</q-tr>
<q-tr>
<q-td> <q-td>
<q-input <q-input v-model="entry.value" />
filled
label="Relay Alias"
v-model="config.relay_alias"
:hint="'Relay URL to display in pairing URLs. If your relay has a different public URL than the one set in \'Nostr Relay URL\' set it here.'"
/>
</q-td>
</q-tr>
<q-tr>
<q-td>
<q-input
filled
label="NWC Provider Secret Key "
v-model="config.provider_key"
:hint="'The secret key for the NWC Service Provider. You don\'t need to change this unless your key has been compromised.'"
/>
</q-td>
</q-tr>
<q-tr>
<q-td>
<q-input
filled
label="Time Period To Handle Missed Events"
v-model="config.handle_missed_events"
type="number"
:hint="'Number of seconds to look back for processing events missed while offline. Setting it to 0 disables this functionality.'"
/>
</q-td> </q-td>
</q-tr> </q-tr>
</tbody> </tbody>

View file

@ -1,8 +1,8 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context {% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block scripts %} {{ window_vars(user) }} %} {% block scripts %} {{ window_vars(user) }}
<script type="module"> <script type="module">
import * as NobleSecp256k1 from '/nwcprovider/static/js/noble-secp256k1.min.js' import * as NobleSecp256k1 from "/nwcprovider/static/js/noble-secp256k1.min.js";
window.NobleSecp256k1 = NobleSecp256k1 window.NobleSecp256k1 = NobleSecp256k1;
</script> </script>
<script src="{{ static_url_for('nwcprovider/static', path='js/index.js') }}"></script> <script src="{{ static_url_for('nwcprovider/static', path='js/index.js') }}"></script>
{% endblock %} {% block page %} {% endblock %} {% block page %}

View file

@ -997,23 +997,14 @@ async def create_valid_invoice(wallet, amount=1000):
raise Exception(f"Failed to create invoice: {error}") raise Exception(f"Failed to create invoice: {error}")
return result["invoice"] return result["invoice"]
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_list_transactions(): async def test_list_transactions():
# Create wallets with required permissions # Create wallets with required permissions
nwc1 = await create_nwc( nwc1 = await create_nwc(
"wallet1", "wallet1", "test_list_transactions", ["invoice", "pay", "balance", "history"], [], 0
"test_list_transactions",
["invoice", "pay", "balance", "history"],
[],
0,
) )
nwc2 = await create_nwc( nwc2 = await create_nwc(
"wallet2", "wallet2", "test_list_transactions", ["invoice", "pay", "balance", "history"], [], 0
"test_list_transactions",
["invoice", "pay", "balance", "history"],
[],
0,
) )
wallet1 = NWCWallet(nwc1["pairing"]) wallet1 = NWCWallet(nwc1["pairing"])