Compare commits

...

1 commit

Author SHA1 Message Date
Patrick Mulligan
f1d0c521b8 Add NIP-47 (Nostr Wallet Connect) support
Some checks failed
Docker Compose Actions Workflow / test (push) Has been cancelled
Implements NWC protocol alongside the existing CLINK/Ndebit system,
allowing any NWC-compatible wallet (Alby, Amethyst, Damus, etc.) to
connect to a Lightning Pub node.

Supported NIP-47 methods: pay_invoice, make_invoice, get_balance,
get_info, lookup_invoice, list_transactions.

New files:
- NwcConnection entity with per-connection spending limits
- NwcStorage for connection CRUD operations
- NwcManager for NIP-47 request handling and connection management
- Database migration for nwc_connection table

Modified files:
- nostrPool: subscribe to kind 23194 events
- nostrMiddleware: route kind 23194 to NwcManager
- main/index: wire NwcManager, publish kind 13194 info events
- storage: register NwcConnection entity and NwcStorage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:29:57 -05:00
10 changed files with 473 additions and 2 deletions

View file

@ -79,6 +79,13 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
const nmanageReq = j as NmanageRequest const nmanageReq = j as NmanageRequest
mainHandler.managementManager.handleRequest(nmanageReq, event); mainHandler.managementManager.handleRequest(nmanageReq, event);
return; return;
} else if (event.kind === 23194) {
if (event.relayConstraint === 'provider') {
log("got NWC request on provider only relay, ignoring")
return
}
mainHandler.nwcManager.handleNwcRequest(event.content, event)
return
} }
if (!j.rpcName) { if (!j.rpcName) {
if (event.relayConstraint === 'service') { if (event.relayConstraint === 'service') {

View file

@ -26,6 +26,7 @@ import { OfferManager } from "./offerManager.js"
import { parse } from "uri-template" import { parse } from "uri-template"
import webRTC from "../webRTC/index.js" import webRTC from "../webRTC/index.js"
import { ManagementManager } from "./managementManager.js" import { ManagementManager } from "./managementManager.js"
import { NwcManager } from "./nwcManager.js"
import { Agent } from "https" import { Agent } from "https"
import { NotificationsManager } from "./notificationsManager.js" import { NotificationsManager } from "./notificationsManager.js"
import { ApplicationUser } from '../storage/entity/ApplicationUser.js' import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
@ -58,6 +59,7 @@ export default class {
debitManager: DebitManager debitManager: DebitManager
offerManager: OfferManager offerManager: OfferManager
managementManager: ManagementManager managementManager: ManagementManager
nwcManager: NwcManager
utils: Utils utils: Utils
rugPullTracker: RugPullTracker rugPullTracker: RugPullTracker
unlocker: Unlocker unlocker: Unlocker
@ -88,6 +90,7 @@ export default class {
this.debitManager = new DebitManager(this.storage, this.lnd, this.applicationManager) this.debitManager = new DebitManager(this.storage, this.lnd, this.applicationManager)
this.offerManager = new OfferManager(this.storage, this.settings, this.lnd, this.applicationManager, this.productManager, this.liquidityManager) this.offerManager = new OfferManager(this.storage, this.settings, this.lnd, this.applicationManager, this.productManager, this.liquidityManager)
this.managementManager = new ManagementManager(this.storage, this.settings) this.managementManager = new ManagementManager(this.storage, this.settings)
this.nwcManager = new NwcManager(this.storage, this.settings, this.lnd, this.applicationManager)
this.notificationsManager = new NotificationsManager(this.settings) this.notificationsManager = new NotificationsManager(this.settings)
//this.webRTC = new webRTC(this.storage, this.utils) //this.webRTC = new webRTC(this.storage, this.utils)
} }
@ -103,6 +106,9 @@ export default class {
StartBeacons() { StartBeacons() {
this.applicationManager.StartAppsServiceBeacon((app, fees) => { this.applicationManager.StartAppsServiceBeacon((app, fees) => {
this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, fees }) this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, fees })
if (app.nostr_public_key) {
this.nwcManager.publishNwcInfo(app.app_id, app.nostr_public_key)
}
}) })
} }
@ -504,6 +510,9 @@ export default class {
const fees = this.paymentManager.GetFees() const fees = this.paymentManager.GetFees()
for (const app of apps) { for (const app of apps) {
await this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, nextRelay, fees }) await this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, nextRelay, fees })
if (app.nostr_public_key) {
this.nwcManager.publishNwcInfo(app.app_id, app.nostr_public_key)
}
} }
const defaultNames = ['wallet', 'wallet-test', this.settings.getSettings().serviceSettings.defaultAppName] const defaultNames = ['wallet', 'wallet-test', this.settings.getSettings().serviceSettings.defaultAppName]

View file

@ -0,0 +1,346 @@
import { generateSecretKey, getPublicKey, UnsignedEvent } from 'nostr-tools'
import { bytesToHex } from '@noble/hashes/utils'
import ApplicationManager from "./applicationManager.js"
import Storage from '../storage/index.js'
import LND from "../lnd/lnd.js"
import { ERROR, getLogger } from "../helpers/logger.js"
import { NostrEvent } from '../nostr/nostrPool.js'
import SettingsManager from "./settingsManager.js"
type NwcRequest = {
method: string
params: Record<string, any>
}
type NwcResponse = {
result_type: string
error?: { code: string, message: string }
result?: Record<string, any>
}
const NWC_REQUEST_KIND = 23194
const NWC_RESPONSE_KIND = 23195
const NWC_INFO_KIND = 13194
const SUPPORTED_METHODS = [
'pay_invoice',
'make_invoice',
'get_balance',
'get_info',
'lookup_invoice',
'list_transactions',
]
const NWC_ERRORS = {
UNAUTHORIZED: 'UNAUTHORIZED',
RESTRICTED: 'RESTRICTED',
PAYMENT_FAILED: 'PAYMENT_FAILED',
QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',
INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE',
NOT_IMPLEMENTED: 'NOT_IMPLEMENTED',
NOT_FOUND: 'NOT_FOUND',
INTERNAL: 'INTERNAL',
OTHER: 'OTHER',
} as const
const newNwcResponse = (content: string, event: { pub: string, id: string }): UnsignedEvent => {
return {
content,
created_at: Math.floor(Date.now() / 1000),
kind: NWC_RESPONSE_KIND,
pubkey: "",
tags: [
['p', event.pub],
['e', event.id],
],
}
}
export class NwcManager {
applicationManager: ApplicationManager
storage: Storage
settings: SettingsManager
lnd: LND
logger = getLogger({ component: 'NwcManager' })
constructor(storage: Storage, settings: SettingsManager, lnd: LND, applicationManager: ApplicationManager) {
this.storage = storage
this.settings = settings
this.lnd = lnd
this.applicationManager = applicationManager
}
handleNwcRequest = async (content: string, event: NostrEvent) => {
if (!this.storage.NostrSender().IsReady()) {
this.logger(ERROR, "Nostr sender not ready, dropping NWC request")
return
}
let request: NwcRequest
try {
request = JSON.parse(content)
} catch {
this.logger(ERROR, "invalid NWC request JSON")
this.sendError(event, 'unknown', NWC_ERRORS.OTHER, 'Invalid request JSON')
return
}
const { method, params } = request
if (!method) {
this.sendError(event, 'unknown', NWC_ERRORS.OTHER, 'Missing method')
return
}
const connection = await this.storage.nwcStorage.GetConnection(event.appId, event.pub)
if (!connection) {
this.logger("NWC request from unknown client pubkey:", event.pub)
this.sendError(event, method, NWC_ERRORS.UNAUTHORIZED, 'Unknown connection')
return
}
if (connection.expires_at > 0 && connection.expires_at < Math.floor(Date.now() / 1000)) {
this.logger("NWC connection expired for client:", event.pub)
this.sendError(event, method, NWC_ERRORS.UNAUTHORIZED, 'Connection expired')
return
}
if (connection.permissions && connection.permissions.length > 0 && !connection.permissions.includes(method)) {
this.sendError(event, method, NWC_ERRORS.RESTRICTED, `Method ${method} not permitted`)
return
}
try {
switch (method) {
case 'pay_invoice':
await this.handlePayInvoice(event, params, connection.app_user_id, connection.max_amount, connection.total_spent)
break
case 'make_invoice':
await this.handleMakeInvoice(event, params, connection.app_user_id)
break
case 'get_balance':
await this.handleGetBalance(event, connection.app_user_id)
break
case 'get_info':
await this.handleGetInfo(event)
break
case 'lookup_invoice':
await this.handleLookupInvoice(event, params)
break
case 'list_transactions':
await this.handleListTransactions(event, params, connection.app_user_id)
break
default:
this.sendError(event, method, NWC_ERRORS.NOT_IMPLEMENTED, `Method ${method} not supported`)
}
} catch (e: any) {
this.logger(ERROR, `NWC ${method} failed:`, e.message || e)
this.sendError(event, method, NWC_ERRORS.INTERNAL, e.message || 'Internal error')
}
}
private handlePayInvoice = async (event: NostrEvent, params: Record<string, any>, appUserId: string, maxAmount: number, totalSpent: number) => {
const { invoice } = params
if (!invoice) {
this.sendError(event, 'pay_invoice', NWC_ERRORS.OTHER, 'Missing invoice parameter')
return
}
if (maxAmount > 0) {
const decoded = await this.lnd.DecodeInvoice(invoice)
const amountSats = decoded.numSatoshis
if (amountSats > 0 && (totalSpent + amountSats) > maxAmount) {
this.sendError(event, 'pay_invoice', NWC_ERRORS.QUOTA_EXCEEDED, 'Spending limit exceeded')
return
}
}
try {
const paid = await this.applicationManager.PayAppUserInvoice(event.appId, {
amount: 0,
invoice,
user_identifier: appUserId,
debit_npub: event.pub,
})
await this.storage.nwcStorage.IncrementTotalSpent(event.appId, event.pub, paid.amount_paid + paid.service_fee)
this.sendResult(event, 'pay_invoice', { preimage: paid.preimage })
} catch (e: any) {
this.sendError(event, 'pay_invoice', NWC_ERRORS.PAYMENT_FAILED, e.message || 'Payment failed')
}
}
private handleMakeInvoice = async (event: NostrEvent, params: Record<string, any>, appUserId: string) => {
const amountMsats = params.amount
if (amountMsats === undefined || amountMsats === null) {
this.sendError(event, 'make_invoice', NWC_ERRORS.OTHER, 'Missing amount parameter')
return
}
const amountSats = Math.floor(amountMsats / 1000)
const description = params.description || ''
const expiry = params.expiry || undefined
const result = await this.applicationManager.AddAppUserInvoice(event.appId, {
receiver_identifier: appUserId,
payer_identifier: '',
http_callback_url: '',
invoice_req: {
amountSats,
memo: description,
expiry,
},
})
this.sendResult(event, 'make_invoice', {
type: 'incoming',
invoice: result.invoice,
description,
description_hash: '',
preimage: '',
payment_hash: '',
amount: amountMsats,
fees_paid: 0,
created_at: Math.floor(Date.now() / 1000),
expires_at: Math.floor(Date.now() / 1000) + (expiry || 3600),
metadata: {},
})
}
private handleGetBalance = async (event: NostrEvent, appUserId: string) => {
const app = await this.storage.applicationStorage.GetApplication(event.appId)
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, appUserId)
const balanceMsats = appUser.user.balance_sats * 1000
this.sendResult(event, 'get_balance', { balance: balanceMsats })
}
private handleGetInfo = async (event: NostrEvent) => {
const info = await this.lnd.GetInfo()
this.sendResult(event, 'get_info', {
alias: info.alias,
color: '',
pubkey: info.identityPubkey,
network: 'mainnet',
block_height: info.blockHeight,
block_hash: info.blockHash,
methods: SUPPORTED_METHODS,
})
}
private handleLookupInvoice = async (event: NostrEvent, params: Record<string, any>) => {
const { invoice, payment_hash } = params
if (!invoice && !payment_hash) {
this.sendError(event, 'lookup_invoice', NWC_ERRORS.OTHER, 'Missing invoice or payment_hash parameter')
return
}
if (invoice) {
const found = await this.storage.paymentStorage.GetInvoiceOwner(invoice)
if (!found) {
this.sendError(event, 'lookup_invoice', NWC_ERRORS.NOT_FOUND, 'Invoice not found')
return
}
this.sendResult(event, 'lookup_invoice', {
type: 'incoming',
invoice: found.invoice,
description: '',
description_hash: '',
preimage: '',
payment_hash: '',
amount: found.paid_amount * 1000,
fees_paid: 0,
created_at: Math.floor(found.created_at.getTime() / 1000),
settled_at: found.paid_at_unix > 0 ? found.paid_at_unix : undefined,
metadata: {},
})
} else {
this.sendError(event, 'lookup_invoice', NWC_ERRORS.NOT_IMPLEMENTED, 'Lookup by payment_hash not supported')
}
}
private handleListTransactions = async (event: NostrEvent, params: Record<string, any>, appUserId: string) => {
const app = await this.storage.applicationStorage.GetApplication(event.appId)
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, appUserId)
const from = params.from || 0
const limit = Math.min(params.limit || 50, 50)
const invoices = await this.storage.paymentStorage.GetUserInvoicesFlaggedAsPaid(appUser.user.serial_id, 0, from, limit)
const transactions = invoices.map(inv => ({
type: 'incoming' as const,
invoice: inv.invoice,
description: '',
description_hash: '',
preimage: '',
payment_hash: '',
amount: inv.paid_amount * 1000,
fees_paid: 0,
created_at: Math.floor(inv.created_at.getTime() / 1000),
settled_at: inv.paid_at_unix > 0 ? inv.paid_at_unix : undefined,
metadata: {},
}))
this.sendResult(event, 'list_transactions', { transactions })
}
// --- Connection management methods ---
createConnection = async (appId: string, appUserId: string, permissions?: string[], options?: { maxAmount?: number, expiresAt?: number }) => {
const secretBytes = generateSecretKey()
const clientPubkey = getPublicKey(secretBytes)
const secret = bytesToHex(secretBytes)
await this.storage.nwcStorage.AddConnection({
app_id: appId,
app_user_id: appUserId,
client_pubkey: clientPubkey,
permissions,
max_amount: options?.maxAmount,
expires_at: options?.expiresAt,
})
const app = await this.storage.applicationStorage.GetApplication(appId)
const relays = this.settings.getSettings().nostrRelaySettings.relays
const relay = relays[0] || ''
const uri = `nostr+walletconnect://${app.nostr_public_key}?relay=${encodeURIComponent(relay)}&secret=${secret}`
return { uri, clientPubkey, secret }
}
listConnections = async (appUserId: string) => {
return this.storage.nwcStorage.GetUserConnections(appUserId)
}
revokeConnection = async (appId: string, clientPubkey: string) => {
return this.storage.nwcStorage.DeleteConnectionByPubkey(appId, clientPubkey)
}
// --- Kind 13194 info event ---
publishNwcInfo = (appId: string, appPubkey: string) => {
const content = SUPPORTED_METHODS.join(' ')
const event: UnsignedEvent = {
content,
created_at: Math.floor(Date.now() / 1000),
kind: NWC_INFO_KIND,
pubkey: appPubkey,
tags: [],
}
this.storage.NostrSender().Send({ type: 'app', appId }, { type: 'event', event })
}
// --- Response helpers ---
private sendResult = (event: NostrEvent, resultType: string, result: Record<string, any>) => {
const response: NwcResponse = { result_type: resultType, result }
const e = newNwcResponse(JSON.stringify(response), event)
this.storage.NostrSender().Send(
{ type: 'app', appId: event.appId },
{ type: 'event', event: e, encrypt: { toPub: event.pub } }
)
}
private sendError = (event: NostrEvent, resultType: string, code: string, message: string) => {
const response: NwcResponse = { result_type: resultType, error: { code, message } }
const e = newNwcResponse(JSON.stringify(response), event)
this.storage.NostrSender().Send(
{ type: 'app', appId: event.appId },
{ type: 'event', event: e, encrypt: { toPub: event.pub } }
)
}
}

View file

@ -48,7 +48,7 @@ const splitContent = (content: string, maxLength: number) => {
} }
return parts return parts
} }
const actionKinds = [21000, 21001, 21002, 21003] const actionKinds = [21000, 21001, 21002, 21003, 23194]
const beaconKind = 30078 const beaconKind = 30078
const appTag = "Lightning.Pub" const appTag = "Lightning.Pub"
export class NostrPool { export class NostrPool {

View file

@ -21,6 +21,7 @@ import { LndNodeInfo } from "../entity/LndNodeInfo.js"
import { TrackedProvider } from "../entity/TrackedProvider.js" import { TrackedProvider } from "../entity/TrackedProvider.js"
import { InviteToken } from "../entity/InviteToken.js" import { InviteToken } from "../entity/InviteToken.js"
import { DebitAccess } from "../entity/DebitAccess.js" import { DebitAccess } from "../entity/DebitAccess.js"
import { NwcConnection } from "../entity/NwcConnection.js"
import { RootOperation } from "../entity/RootOperation.js" import { RootOperation } from "../entity/RootOperation.js"
import { UserOffer } from "../entity/UserOffer.js" import { UserOffer } from "../entity/UserOffer.js"
import { ManagementGrant } from "../entity/ManagementGrant.js" import { ManagementGrant } from "../entity/ManagementGrant.js"
@ -70,6 +71,7 @@ export const MainDbEntities = {
'TrackedProvider': TrackedProvider, 'TrackedProvider': TrackedProvider,
'InviteToken': InviteToken, 'InviteToken': InviteToken,
'DebitAccess': DebitAccess, 'DebitAccess': DebitAccess,
'NwcConnection': NwcConnection,
'UserOffer': UserOffer, 'UserOffer': UserOffer,
'Product': Product, 'Product': Product,
'ManagementGrant': ManagementGrant, 'ManagementGrant': ManagementGrant,

View file

@ -0,0 +1,37 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, CreateDateColumn, UpdateDateColumn } from "typeorm"
@Entity()
@Index("unique_nwc_connection", ["app_id", "client_pubkey"], { unique: true })
@Index("idx_nwc_app_user", ["app_user_id"])
export class NwcConnection {
@PrimaryGeneratedColumn()
serial_id: number
@Column()
app_id: string
@Column()
app_user_id: string
@Column()
client_pubkey: string
@Column({ type: 'simple-json', default: null, nullable: true })
permissions: string[] | null
@Column({ default: 0 })
max_amount: number
@Column({ default: 0 })
expires_at: number
@Column({ default: 0 })
total_spent: number
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -9,6 +9,7 @@ import MetricsEventStorage from "./tlv/metricsEventStorage.js";
import EventsLogManager from "./eventsLog.js"; import EventsLogManager from "./eventsLog.js";
import { LiquidityStorage } from "./liquidityStorage.js"; import { LiquidityStorage } from "./liquidityStorage.js";
import DebitStorage from "./debitStorage.js" import DebitStorage from "./debitStorage.js"
import NwcStorage from "./nwcStorage.js"
import OfferStorage from "./offerStorage.js" import OfferStorage from "./offerStorage.js"
import { ManagementStorage } from "./managementStorage.js"; import { ManagementStorage } from "./managementStorage.js";
import { StorageInterface, TX } from "./db/storageInterface.js"; import { StorageInterface, TX } from "./db/storageInterface.js";
@ -78,6 +79,7 @@ export default class {
metricsEventStorage: MetricsEventStorage metricsEventStorage: MetricsEventStorage
liquidityStorage: LiquidityStorage liquidityStorage: LiquidityStorage
debitStorage: DebitStorage debitStorage: DebitStorage
nwcStorage: NwcStorage
offerStorage: OfferStorage offerStorage: OfferStorage
managementStorage: ManagementStorage managementStorage: ManagementStorage
eventsLog: EventsLogManager eventsLog: EventsLogManager
@ -103,6 +105,7 @@ export default class {
this.metricsEventStorage = new MetricsEventStorage(this.settings, this.utils.tlvStorageFactory) this.metricsEventStorage = new MetricsEventStorage(this.settings, this.utils.tlvStorageFactory)
this.liquidityStorage = new LiquidityStorage(this.dbs) this.liquidityStorage = new LiquidityStorage(this.dbs)
this.debitStorage = new DebitStorage(this.dbs) this.debitStorage = new DebitStorage(this.dbs)
this.nwcStorage = new NwcStorage(this.dbs)
this.offerStorage = new OfferStorage(this.dbs) this.offerStorage = new OfferStorage(this.dbs)
this.managementStorage = new ManagementStorage(this.dbs); this.managementStorage = new ManagementStorage(this.dbs);
try { if (this.settings.dataDir) fs.mkdirSync(this.settings.dataDir) } catch (e) { } try { if (this.settings.dataDir) fs.mkdirSync(this.settings.dataDir) } catch (e) { }

View file

@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class NwcConnection1770000000000 implements MigrationInterface {
name = 'NwcConnection1770000000000'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "nwc_connection" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_id" varchar NOT NULL, "app_user_id" varchar NOT NULL, "client_pubkey" varchar NOT NULL, "permissions" text, "max_amount" integer NOT NULL DEFAULT (0), "expires_at" integer NOT NULL DEFAULT (0), "total_spent" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
await queryRunner.query(`CREATE UNIQUE INDEX "unique_nwc_connection" ON "nwc_connection" ("app_id", "client_pubkey") `);
await queryRunner.query(`CREATE INDEX "idx_nwc_app_user" ON "nwc_connection" ("app_user_id") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "idx_nwc_app_user"`);
await queryRunner.query(`DROP INDEX "unique_nwc_connection"`);
await queryRunner.query(`DROP TABLE "nwc_connection"`);
}
}

View file

@ -32,6 +32,7 @@ import { TxSwapAddress1764779178945 } from './1764779178945-tx_swap_address.js'
import { ClinkRequester1765497600000 } from './1765497600000-clink_requester.js' import { ClinkRequester1765497600000 } from './1765497600000-clink_requester.js'
import { TrackedProviderHeight1766504040000 } from './1766504040000-tracked_provider_height.js' import { TrackedProviderHeight1766504040000 } from './1766504040000-tracked_provider_height.js'
import { SwapsServiceUrl1768413055036 } from './1768413055036-swaps_service_url.js' import { SwapsServiceUrl1768413055036 } from './1768413055036-swaps_service_url.js'
import { NwcConnection1770000000000 } from './1770000000000-nwc_connection.js'
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189,
@ -39,7 +40,7 @@ export const allMigrations = [Initial1703170309875, LspOrder1718387847693, Liqui
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513, DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513,
InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175,
UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098,
TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036] TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036, NwcConnection1770000000000]
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411] export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411]
/* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => { /* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {

View file

@ -0,0 +1,49 @@
import { NwcConnection } from "./entity/NwcConnection.js";
import { StorageInterface } from "./db/storageInterface.js";
type ConnectionToAdd = {
app_id: string
app_user_id: string
client_pubkey: string
permissions?: string[]
max_amount?: number
expires_at?: number
}
export default class {
dbs: StorageInterface
constructor(dbs: StorageInterface) {
this.dbs = dbs
}
async AddConnection(connection: ConnectionToAdd) {
return this.dbs.CreateAndSave<NwcConnection>('NwcConnection', {
app_id: connection.app_id,
app_user_id: connection.app_user_id,
client_pubkey: connection.client_pubkey,
permissions: connection.permissions || null,
max_amount: connection.max_amount || 0,
expires_at: connection.expires_at || 0,
})
}
async GetConnection(appId: string, clientPubkey: string, txId?: string) {
return this.dbs.FindOne<NwcConnection>('NwcConnection', { where: { app_id: appId, client_pubkey: clientPubkey } }, txId)
}
async GetUserConnections(appUserId: string, txId?: string) {
return this.dbs.Find<NwcConnection>('NwcConnection', { where: { app_user_id: appUserId } }, txId)
}
async DeleteConnection(serialId: number, txId?: string) {
return this.dbs.Delete<NwcConnection>('NwcConnection', { serial_id: serialId }, txId)
}
async DeleteConnectionByPubkey(appId: string, clientPubkey: string, txId?: string) {
return this.dbs.Delete<NwcConnection>('NwcConnection', { app_id: appId, client_pubkey: clientPubkey }, txId)
}
async IncrementTotalSpent(appId: string, clientPubkey: string, amount: number, txId?: string) {
return this.dbs.Increment<NwcConnection>('NwcConnection', { app_id: appId, client_pubkey: clientPubkey }, 'total_spent', amount, txId)
}
}