nmanage backend flow
This commit is contained in:
parent
acb09396ec
commit
668a5bbac5
17 changed files with 303 additions and 247 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -13,7 +13,7 @@
|
||||||
"@protobuf-ts/grpc-transport": "^2.9.4",
|
"@protobuf-ts/grpc-transport": "^2.9.4",
|
||||||
"@protobuf-ts/plugin": "^2.5.0",
|
"@protobuf-ts/plugin": "^2.5.0",
|
||||||
"@protobuf-ts/runtime": "^2.5.0",
|
"@protobuf-ts/runtime": "^2.5.0",
|
||||||
"@shocknet/clink-sdk": "^1.0.4",
|
"@shocknet/clink-sdk": "^1.1.2",
|
||||||
"@stablelib/xchacha20": "^1.0.1",
|
"@stablelib/xchacha20": "^1.0.1",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/node": "^17.0.31",
|
"@types/node": "^17.0.31",
|
||||||
|
|
@ -591,9 +591,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shocknet/clink-sdk": {
|
"node_modules/@shocknet/clink-sdk": {
|
||||||
"version": "1.0.4",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@shocknet/clink-sdk/-/clink-sdk-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@shocknet/clink-sdk/-/clink-sdk-1.1.2.tgz",
|
||||||
"integrity": "sha512-ekkfJpP+YPry4/5+V+3JPx9zOVEjCDOWW7AHzfOLyVGnLuIR6jEBjDkg7avM2f3BVvFKSl4l0mkS9ImK9lX0eQ==",
|
"integrity": "sha512-nICsXlLZRIs6E+wy3PfQccIidmQ/D7uSHfHfqrJzNJFOUH2+XGDkApB9TQU1eTrNgD/BHxm9tSZkEmG0it7I3w==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/hashes": "^1.8.0",
|
"@noble/hashes": "^1.8.0",
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
"@protobuf-ts/grpc-transport": "^2.9.4",
|
"@protobuf-ts/grpc-transport": "^2.9.4",
|
||||||
"@protobuf-ts/plugin": "^2.5.0",
|
"@protobuf-ts/plugin": "^2.5.0",
|
||||||
"@protobuf-ts/runtime": "^2.5.0",
|
"@protobuf-ts/runtime": "^2.5.0",
|
||||||
"@shocknet/clink-sdk": "^1.0.4",
|
"@shocknet/clink-sdk": "^1.1.2",
|
||||||
"@stablelib/xchacha20": "^1.0.1",
|
"@stablelib/xchacha20": "^1.0.1",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/node": "^17.0.31",
|
"@types/node": "^17.0.31",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import 'dotenv/config'
|
||||||
import NewServer from '../proto/autogenerated/ts/express_server.js'
|
import NewServer from '../proto/autogenerated/ts/express_server.js'
|
||||||
import GetServerMethods from './services/serverMethods/index.js'
|
import GetServerMethods from './services/serverMethods/index.js'
|
||||||
import serverOptions from './auth.js';
|
import serverOptions from './auth.js';
|
||||||
import { LoadNosrtSettingsFromEnv } from './services/nostr/index.js'
|
|
||||||
import nostrMiddleware from './nostrMiddleware.js'
|
import nostrMiddleware from './nostrMiddleware.js'
|
||||||
import { getLogger } from './services/helpers/logger.js';
|
import { getLogger } from './services/helpers/logger.js';
|
||||||
import { initMainHandler } from './services/main/init.js';
|
import { initMainHandler } from './services/main/init.js';
|
||||||
|
|
@ -22,7 +21,7 @@ const start = async () => {
|
||||||
|
|
||||||
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
||||||
const serverMethods = GetServerMethods(mainHandler)
|
const serverMethods = GetServerMethods(mainHandler)
|
||||||
const nostrSettings = LoadNosrtSettingsFromEnv()
|
const nostrSettings = mainSettings.nostrRelaySettings
|
||||||
log("initializing nostr middleware")
|
log("initializing nostr middleware")
|
||||||
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
||||||
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import 'dotenv/config'
|
||||||
import NewServer from '../proto/autogenerated/ts/express_server.js'
|
import NewServer from '../proto/autogenerated/ts/express_server.js'
|
||||||
import GetServerMethods from './services/serverMethods/index.js'
|
import GetServerMethods from './services/serverMethods/index.js'
|
||||||
import serverOptions from './auth.js';
|
import serverOptions from './auth.js';
|
||||||
import { LoadNosrtSettingsFromEnv } from './services/nostr/index.js'
|
|
||||||
import nostrMiddleware from './nostrMiddleware.js'
|
import nostrMiddleware from './nostrMiddleware.js'
|
||||||
import { getLogger } from './services/helpers/logger.js';
|
import { getLogger } from './services/helpers/logger.js';
|
||||||
import { initMainHandler } from './services/main/init.js';
|
import { initMainHandler } from './services/main/init.js';
|
||||||
|
|
@ -22,10 +21,9 @@ const start = async () => {
|
||||||
|
|
||||||
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
||||||
const serverMethods = GetServerMethods(mainHandler)
|
const serverMethods = GetServerMethods(mainHandler)
|
||||||
const nostrSettings = LoadNosrtSettingsFromEnv()
|
|
||||||
log("initializing nostr middleware")
|
log("initializing nostr middleware")
|
||||||
const { Send, Stop, Ping } = nostrMiddleware(serverMethods, mainHandler,
|
const { Send, Stop, Ping } = nostrMiddleware(serverMethods, mainHandler,
|
||||||
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
{ ...mainSettings.nostrRelaySettings, apps, clients: [liquidityProviderInfo] },
|
||||||
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
||||||
)
|
)
|
||||||
exitHandler(() => { Stop(); mainHandler.Stop() })
|
exitHandler(() => { Stop(); mainHandler.Stop() })
|
||||||
|
|
@ -33,9 +31,9 @@ const start = async () => {
|
||||||
mainHandler.attachNostrSend(Send)
|
mainHandler.attachNostrSend(Send)
|
||||||
mainHandler.attachNostrProcessPing(Ping)
|
mainHandler.attachNostrProcessPing(Ping)
|
||||||
mainHandler.StartBeacons()
|
mainHandler.StartBeacons()
|
||||||
const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays: nostrSettings.relays })
|
const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays: mainSettings.nostrRelaySettings.relays })
|
||||||
if (wizard) {
|
if (wizard) {
|
||||||
wizard.AddConnectInfo(appNprofile, nostrSettings.relays)
|
wizard.AddConnectInfo(appNprofile, mainSettings.nostrRelaySettings.relays)
|
||||||
}
|
}
|
||||||
adminManager.setAppNprofile(appNprofile)
|
adminManager.setAppNprofile(appNprofile)
|
||||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import * as Types from '../proto/autogenerated/ts/types.js'
|
||||||
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
||||||
import { ERROR, getLogger } from "./services/helpers/logger.js";
|
import { ERROR, getLogger } from "./services/helpers/logger.js";
|
||||||
import { NdebitData, NofferData } from "@shocknet/clink-sdk";
|
import { NdebitData, NofferData } from "@shocknet/clink-sdk";
|
||||||
import { ManagementManager } from "./services/main/managementManager.js";
|
import { NmanageRequest } from "./services/main/managementManager.js";
|
||||||
|
|
||||||
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend, Ping: () => Promise<void> } => {
|
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend, Ping: () => Promise<void> } => {
|
||||||
const log = getLogger({})
|
const log = getLogger({})
|
||||||
|
|
@ -39,14 +39,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
||||||
logger: { log: console.log, error: err => log(ERROR, err) },
|
logger: { log: console.log, error: err => log(ERROR, err) },
|
||||||
})
|
})
|
||||||
|
|
||||||
let nostr: Nostr;
|
const nostr = new Nostr(nostrSettings, mainHandler.utils, event => {
|
||||||
if (!mainHandler.managementManager) {
|
|
||||||
throw new Error("management manager not initialized on main handler")
|
|
||||||
}
|
|
||||||
|
|
||||||
const managementManager = mainHandler.managementManager;
|
|
||||||
|
|
||||||
nostr = new Nostr(nostrSettings, mainHandler.utils, event => {
|
|
||||||
let j: NostrRequest
|
let j: NostrRequest
|
||||||
try {
|
try {
|
||||||
j = JSON.parse(event.content)
|
j = JSON.parse(event.content)
|
||||||
|
|
@ -64,7 +57,8 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
||||||
mainHandler.debitManager.handleNip68Debit(debitReq, event)
|
mainHandler.debitManager.handleNip68Debit(debitReq, event)
|
||||||
return
|
return
|
||||||
} else if (event.kind === 21003) {
|
} else if (event.kind === 21003) {
|
||||||
managementManager.handleRequest(event);
|
const nmanageReq = j as NmanageRequest
|
||||||
|
mainHandler.managementManager.handleRequest(nmanageReq, event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!j.rpcName) {
|
if (!j.rpcName) {
|
||||||
|
|
@ -80,7 +74,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
||||||
}, event.startAtNano, event.startAtMs)
|
}, event.startAtNano, event.startAtMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
return { Stop: () => nostr.Stop, Send: (...args: Parameters<NostrSend>) => nostr.Send(...args), Ping: () => nostr.Ping() }
|
return { Stop: () => nostr.Stop, Send: (...args) => nostr.Send(...args), Ping: () => nostr.Ping() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
|
|
||||||
import { MainSettings } from './settings.js'
|
import { MainSettings } from './settings.js'
|
||||||
import ApplicationManager from './applicationManager.js'
|
import ApplicationManager from './applicationManager.js'
|
||||||
import { LoadNosrtSettingsFromEnv } from '../nostr/index.js'
|
|
||||||
import { OfferPriceType, ndebitEncode, nofferEncode } from '@shocknet/clink-sdk'
|
import { OfferPriceType, ndebitEncode, nofferEncode } from '@shocknet/clink-sdk'
|
||||||
export default class {
|
export default class {
|
||||||
|
|
||||||
|
|
@ -66,7 +65,7 @@ export default class {
|
||||||
if (!appUser) {
|
if (!appUser) {
|
||||||
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
|
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
|
||||||
}
|
}
|
||||||
const nostrSettings = LoadNosrtSettingsFromEnv()
|
const nostrSettings = this.settings.nostrRelaySettings
|
||||||
return {
|
return {
|
||||||
userId: ctx.user_id,
|
userId: ctx.user_id,
|
||||||
balance: user.balance_sats,
|
balance: user.balance_sats,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
|
||||||
import { PubLogger, getLogger } from '../helpers/logger.js'
|
import { PubLogger, getLogger } from '../helpers/logger.js'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import { Application } from '../storage/entity/Application.js'
|
import { Application } from '../storage/entity/Application.js'
|
||||||
import { LoadNosrtSettingsFromEnv } from '../nostr/index.js'
|
|
||||||
import { ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
|
import { ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
|
||||||
import { nofferEncode, ndebitEncode, OfferPriceType } from '@shocknet/clink-sdk'
|
import { nofferEncode, ndebitEncode, OfferPriceType } from '@shocknet/clink-sdk'
|
||||||
const TOKEN_EXPIRY_TIME = 2 * 60 * 1000 // 2 minutes, in milliseconds
|
const TOKEN_EXPIRY_TIME = 2 * 60 * 1000 // 2 minutes, in milliseconds
|
||||||
|
|
@ -151,7 +150,7 @@ export default class {
|
||||||
u = user
|
u = user
|
||||||
if (created) log(u.identifier, u.user.user_id, "user created")
|
if (created) log(u.identifier, u.user.user_id, "user created")
|
||||||
}
|
}
|
||||||
const nostrSettings = LoadNosrtSettingsFromEnv()
|
const nostrSettings = this.settings.nostrRelaySettings
|
||||||
return {
|
return {
|
||||||
identifier: u.identifier,
|
identifier: u.identifier,
|
||||||
info: {
|
info: {
|
||||||
|
|
@ -205,7 +204,7 @@ export default class {
|
||||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||||
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
|
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
|
||||||
const nostrSettings = LoadNosrtSettingsFromEnv()
|
const nostrSettings = this.settings.nostrRelaySettings
|
||||||
return {
|
return {
|
||||||
max_withdrawable: max, identifier: req.user_identifier, info: {
|
max_withdrawable: max, identifier: req.user_identifier, info: {
|
||||||
userId: user.user.user_id, balance: user.user.balance_sats,
|
userId: user.user.user_id, balance: user.user.balance_sats,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import { DebitManager } from "./debitManager.js"
|
||||||
import { OfferManager } from "./offerManager.js"
|
import { OfferManager } from "./offerManager.js"
|
||||||
import webRTC from "../webRTC/index.js"
|
import webRTC from "../webRTC/index.js"
|
||||||
import { ManagementManager } from "./managementManager.js"
|
import { ManagementManager } from "./managementManager.js"
|
||||||
import NostrSubprocess, { LoadNosrtSettingsFromEnv } from "../nostr/index.js"
|
|
||||||
|
|
||||||
type UserOperationsSub = {
|
type UserOperationsSub = {
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -53,7 +52,7 @@ export default class {
|
||||||
liquidityProvider: LiquidityProvider
|
liquidityProvider: LiquidityProvider
|
||||||
debitManager: DebitManager
|
debitManager: DebitManager
|
||||||
offerManager: OfferManager
|
offerManager: OfferManager
|
||||||
managementManager?: ManagementManager
|
managementManager: ManagementManager
|
||||||
utils: Utils
|
utils: Utils
|
||||||
rugPullTracker: RugPullTracker
|
rugPullTracker: RugPullTracker
|
||||||
unlocker: Unlocker
|
unlocker: Unlocker
|
||||||
|
|
@ -79,7 +78,7 @@ export default class {
|
||||||
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
|
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
|
||||||
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.lnd, this.applicationManager, this.productManager, this.liquidityManager)
|
this.offerManager = new OfferManager(this.storage, this.lnd, this.applicationManager, this.productManager, this.liquidityManager)
|
||||||
this.managementManager = new ManagementManager(this.nostrSend, this.storage)
|
this.managementManager = new ManagementManager(this.storage, this.offerManager)
|
||||||
//this.webRTC = new webRTC(this.storage, this.utils)
|
//this.webRTC = new webRTC(this.storage, this.utils)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +101,7 @@ export default class {
|
||||||
this.liquidityProvider.attachNostrSend(f)
|
this.liquidityProvider.attachNostrSend(f)
|
||||||
this.debitManager.attachNostrSend(f)
|
this.debitManager.attachNostrSend(f)
|
||||||
this.offerManager.attachNostrSend(f)
|
this.offerManager.attachNostrSend(f)
|
||||||
|
this.managementManager.attachNostrSend(f)
|
||||||
this.utils.attachNostrSend(f)
|
this.utils.attachNostrSend(f)
|
||||||
//this.webRTC.attachNostrSend(f)
|
//this.webRTC.attachNostrSend(f)
|
||||||
}
|
}
|
||||||
|
|
@ -344,10 +344,6 @@ export default class {
|
||||||
log({ unsigned: event })
|
log({ unsigned: event })
|
||||||
this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined)
|
this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
async Start() {
|
|
||||||
// ... existing code ...
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,198 +4,259 @@ import { UserOffer } from "../storage/entity/UserOffer.js";
|
||||||
import { ManagementGrant } from "../storage/entity/ManagementGrant.js";
|
import { ManagementGrant } from "../storage/entity/ManagementGrant.js";
|
||||||
import { NostrEvent, NostrSend } from "../nostr/handler.js";
|
import { NostrEvent, NostrSend } from "../nostr/handler.js";
|
||||||
import Storage from "../storage/index.js";
|
import Storage from "../storage/index.js";
|
||||||
|
import { OfferManager } from "./offerManager.js";
|
||||||
|
import * as Types from "../../../proto/autogenerated/ts/types.js";
|
||||||
|
import { MainSettings } from "./settings.js";
|
||||||
|
import { nofferEncode, OfferPointer, OfferPriceType, NmanageRequest, NmanageResponse, NmanageCreateOffer, NmanageUpdateOffer, NmanageDeleteOffer, NmanageGetOffer, NmanageListOffers, OfferData, OfferFields } from "@shocknet/clink-sdk";
|
||||||
|
import { UnsignedEvent } from "nostr-tools";
|
||||||
|
type Result<T> = { success: true, result: T } | { success: false, error: string, code: number }
|
||||||
|
|
||||||
export class ManagementManager {
|
export class ManagementManager {
|
||||||
private nostrSend: NostrSend;
|
private nostrSend: NostrSend;
|
||||||
private storage: Storage;
|
private storage: Storage;
|
||||||
|
private settings: MainSettings;
|
||||||
|
|
||||||
constructor(nostrSend: NostrSend, storage: Storage) {
|
constructor(storage: Storage, settings: MainSettings) {
|
||||||
this.nostrSend = nostrSend;
|
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
attachNostrSend(f: NostrSend) {
|
||||||
* Handles an incoming CLINK Manage request
|
this.nostrSend = f
|
||||||
* @param event The raw Nostr event
|
}
|
||||||
*/
|
|
||||||
public async handleRequest(event: NostrEvent) {
|
public async handleRequest(nmanageReq: NmanageRequest, event: NostrEvent): Promise<void> {
|
||||||
let app;
|
|
||||||
try {
|
try {
|
||||||
app = await this.storage.applicationStorage.GetApplication(event.appId);
|
const r = await this.doNmanage(nmanageReq, event)
|
||||||
} catch {
|
let e: UnsignedEvent
|
||||||
console.error(`App with id ${event.appId} not found`);
|
if (!r.success) {
|
||||||
return;
|
e = newNmanageResponse(JSON.stringify({ code: r.code, error: codeToMessage(r.code) }), event)
|
||||||
|
} else {
|
||||||
|
e = newNmanageResponse(JSON.stringify(r.result), event)
|
||||||
|
}
|
||||||
|
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||||
|
} catch (err) {
|
||||||
|
const e = newNmanageResponse(JSON.stringify({ code: 2, error: codeToMessage(2) }), event)
|
||||||
|
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._validateRequest(event)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!app.nostr_public_key) {
|
|
||||||
console.error(`App with id ${event.appId} has no nostr public key configured.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const grant = await this._checkGrant(event, app.nostr_public_key);
|
|
||||||
if (!grant) {
|
|
||||||
this.sendErrorResponse(event.pubkey, "Permission denied.", { publicKey: app.nostr_public_key, appId: app.app_id });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const appInfo = { publicKey: app.nostr_public_key, appId: app.app_id };
|
|
||||||
await this._performAction(event, appInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _validateRequest(event: NostrEvent): boolean {
|
private async doNmanage(nmanageReq: NmanageRequest, event: NostrEvent): Promise<Result<NmanageResponse>> {
|
||||||
// TODO: NIP-44 validation or similar
|
const action = nmanageReq.action
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _checkGrant(event: NostrEvent, appPubkey: string): Promise<ManagementGrant | null> {
|
|
||||||
const userIdTag = event.tags.find((t: string[]) => t[0] === 'p');
|
|
||||||
if (!userIdTag) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const userId = userIdTag[1];
|
|
||||||
|
|
||||||
const grant = await this.storage.managementStorage.getGrant(userId, appPubkey);
|
|
||||||
|
|
||||||
if (!grant || (grant.expires_at && grant.expires_at.getTime() < Date.now())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return grant;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _performAction(event: NostrEvent, app: { publicKey: string; appId: string }) {
|
|
||||||
const actionTag = event.tags.find((t: string[]) => t[0] === 'a');
|
|
||||||
if (!actionTag) {
|
|
||||||
console.error("No action specified in event");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const action = actionTag[1];
|
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "create":
|
case "create":
|
||||||
await this._createOffer(event, app);
|
const createResult = await this.createOffer(nmanageReq)
|
||||||
break;
|
return this.getNmanageResponse(event.appId, createResult)
|
||||||
case "update":
|
case "update":
|
||||||
await this._updateOffer(event, app);
|
const updateResult = await this.updateOffer(nmanageReq, event.pub);
|
||||||
break;
|
return this.getNmanageResponse(event.appId, updateResult)
|
||||||
case "delete":
|
case "delete":
|
||||||
await this._deleteOffer(event, app);
|
const deleteResult = await this.deleteOffer(nmanageReq, event.pub);
|
||||||
break;
|
return this.getNmanageResponse(event.appId, deleteResult)
|
||||||
|
case "get":
|
||||||
|
const getResult = await this.getOffer(nmanageReq, event.pub);
|
||||||
|
return this.getNmanageResponse(event.appId, getResult)
|
||||||
|
case "list":
|
||||||
|
const listResult = await this.listOffers(nmanageReq, event.pub);
|
||||||
|
return this.getNmanageResponse(event.appId, listResult)
|
||||||
default:
|
default:
|
||||||
console.error(`Unknown action: ${action}`);
|
return { success: false, error: `Unknown action: ${action}`, code: 1 }
|
||||||
this.sendErrorResponse(event.pubkey, `Unknown action: ${action}`, app);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _createOffer(event: NostrEvent, app: { publicKey: string; appId: string }) {
|
private getOfferData(offer: UserOffer, appPub: string): OfferData {
|
||||||
const createDetailsTag = event.tags.find((t: string[]) => t[0] === 'd');
|
const pointer: OfferPointer = {
|
||||||
if (!createDetailsTag || !createDetailsTag[1]) {
|
offer: offer.offer_id,
|
||||||
console.error("No details provided for create action");
|
pubkey: appPub,
|
||||||
return;
|
relay: this.settings.nostrRelaySettings.relays[0],
|
||||||
|
priceType: offer.price_sats > 0 ? OfferPriceType.Fixed : OfferPriceType.Spontaneous,
|
||||||
|
price: offer.price_sats,
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
const userId = event.tags.find((t: string[]) => t[0] === 'p')![1];
|
id: offer.offer_id,
|
||||||
|
label: offer.label,
|
||||||
try {
|
price_sats: offer.price_sats,
|
||||||
const offerData = JSON.parse(createDetailsTag[1]);
|
callback_url: offer.callback_url,
|
||||||
const offerRepo = getRepository(UserOffer);
|
payer_data: Object.keys(offer.expected_data || {}),
|
||||||
const newOffer = offerRepo.create({
|
noffer: nofferEncode(pointer),
|
||||||
...offerData,
|
|
||||||
user_id: userId,
|
|
||||||
managing_app_pubkey: app.publicKey
|
|
||||||
});
|
|
||||||
await offerRepo.save(newOffer);
|
|
||||||
this.sendSuccessResponse(event.pubkey, "Offer created successfully", app);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to parse or save offer data", e);
|
|
||||||
this.sendErrorResponse(event.pubkey, "Failed to create offer", app);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updateOffer(event: NostrEvent, app: { publicKey: string; appId: string }) {
|
private async getNmanageResponse(appId: string, result: Result<UserOffer | UserOffer[] | void>): Promise<Result<NmanageResponse>> {
|
||||||
const updateTags = event.tags.filter((t: string[]) => t[0] === 'd');
|
if (!result.success) {
|
||||||
if (updateTags.length < 2) {
|
return result
|
||||||
console.error("Insufficient details for update action");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const offerIdToUpdate = updateTags[0][1];
|
const args = result.result
|
||||||
const updateData = JSON.parse(updateTags[1][1]);
|
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||||
const offerRepo = getRepository(UserOffer);
|
if (args && Array.isArray(args)) {
|
||||||
|
return {
|
||||||
try {
|
success: true, result: {
|
||||||
const existingOffer = await offerRepo.findOne({where: { offer_id: offerIdToUpdate }});
|
res: 'ok', resource: 'offer', details: args.map(offer => this.getOfferData(offer, app.nostr_public_key!))
|
||||||
if (!existingOffer) {
|
}
|
||||||
console.error(`Offer ${offerIdToUpdate} not found`);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (existingOffer.managing_app_pubkey !== app.publicKey) {
|
if (!args) {
|
||||||
console.error(`App ${app.publicKey} not authorized to update offer ${offerIdToUpdate}`);
|
return { success: true, result: { res: 'ok', resource: 'offer' } }
|
||||||
return;
|
}
|
||||||
|
return {
|
||||||
|
success: true, result: {
|
||||||
|
res: 'ok', resource: 'offer', details: this.getOfferData(args, app.nostr_public_key!)
|
||||||
}
|
}
|
||||||
|
|
||||||
offerRepo.merge(existingOffer, updateData);
|
|
||||||
await offerRepo.save(existingOffer);
|
|
||||||
this.sendSuccessResponse(event.pubkey, "Offer updated successfully", app);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to update offer data", e);
|
|
||||||
this.sendErrorResponse(event.pubkey, "Failed to update offer", app);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _deleteOffer(event: NostrEvent, app: { publicKey: string; appId: string }) {
|
private async getOffer(nmanageReq: NmanageGetOffer, requestorPub: string): Promise<Result<UserOffer>> {
|
||||||
const deleteDetailsTag = event.tags.find((t: string[]) => t[0] === 'd');
|
const offer = await this.validateOfferAccess(nmanageReq.offer.id, requestorPub)
|
||||||
if (!deleteDetailsTag || !deleteDetailsTag[1]) {
|
if (!offer.success) {
|
||||||
console.error("No details provided for delete action");
|
return offer
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const offerIdToDelete = deleteDetailsTag[1];
|
return { success: true, result: offer.result }
|
||||||
const offerRepo = getRepository(UserOffer);
|
}
|
||||||
|
|
||||||
try {
|
private async listOffers(nmanageReq: NmanageListOffers, requestorPub: string): Promise<Result<UserOffer[]>> {
|
||||||
const offerToDelete = await offerRepo.findOne({where: { offer_id: offerIdToDelete }});
|
const appUserId = nmanageReq.pointer
|
||||||
if (!offerToDelete) {
|
if (!appUserId) {
|
||||||
console.error(`Offer ${offerIdToDelete} not found`);
|
return { success: false, error: 'No pointer provided', code: 1 }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offerToDelete.managing_app_pubkey !== app.publicKey) {
|
|
||||||
console.error(`App ${app.publicKey} not authorized to delete offer ${offerIdToDelete}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await offerRepo.remove(offerToDelete);
|
|
||||||
this.sendSuccessResponse(event.pubkey, "Offer deleted successfully", app);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to delete offer", e);
|
|
||||||
this.sendErrorResponse(event.pubkey, "Failed to delete offer", app);
|
|
||||||
}
|
}
|
||||||
|
const grantResult = await this.validateGrantAccess(appUserId, requestorPub)
|
||||||
|
if (!grantResult.success) {
|
||||||
|
return grantResult
|
||||||
|
}
|
||||||
|
const offers = await this.storage.offerStorage.getManagedUserOffers(appUserId, requestorPub)
|
||||||
|
return { success: true, result: offers }
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendSuccessResponse(recipient: string, message: string, app: {publicKey: string, appId: string}) {
|
private validateOfferFields(fields: OfferFields): Result<void> {
|
||||||
const responseEvent = {
|
if (!fields.label || typeof fields.label !== 'string') {
|
||||||
kind: 21003,
|
return { success: false, error: 'Label is required', code: 1 }
|
||||||
pubkey: app.publicKey,
|
}
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
if (fields.price_sats && typeof fields.price_sats !== 'number') {
|
||||||
content: JSON.stringify({ status: "success", message }),
|
return { success: false, error: 'Price must be a number', code: 1 }
|
||||||
tags: [['p', recipient]],
|
}
|
||||||
};
|
if (fields.callback_url && typeof fields.callback_url !== 'string') {
|
||||||
this.nostrSend({ type: 'app', appId: app.appId }, { type: 'event', event: responseEvent, encrypt: { toPub: recipient } });
|
return { success: false, error: 'Callback URL must be a string', code: 1 }
|
||||||
|
}
|
||||||
|
if (fields.payer_data && !Array.isArray(fields.payer_data)) {
|
||||||
|
return { success: false, error: 'Payer data must be an array', code: 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, result: undefined }
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendErrorResponse(recipient: string, message: string, app: {publicKey: string, appId: string}) {
|
private async createOffer(nmanageReq: NmanageCreateOffer): Promise<Result<UserOffer>> {
|
||||||
const responseEvent = {
|
const appUserId = nmanageReq.pointer
|
||||||
kind: 21003,
|
if (!appUserId) {
|
||||||
pubkey: app.publicKey,
|
return { success: false, error: 'No pointer provided', code: 1 }
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
}
|
||||||
content: JSON.stringify({ status: "error", message }),
|
const grantResult = await this.validateGrantAccess(appUserId, appUserId)
|
||||||
tags: [['p', recipient]],
|
if (!grantResult.success) {
|
||||||
};
|
return grantResult
|
||||||
this.nostrSend({ type: 'app', appId: app.appId }, { type: 'event', event: responseEvent, encrypt: { toPub: recipient } });
|
}
|
||||||
|
const validateResult = this.validateOfferFields(nmanageReq.offer.fields)
|
||||||
|
if (!validateResult.success) {
|
||||||
|
return validateResult
|
||||||
|
}
|
||||||
|
const dataMap: Record<string, Types.OfferDataType> = {}
|
||||||
|
nmanageReq.offer.fields.payer_data.forEach(data => {
|
||||||
|
dataMap[data] = Types.OfferDataType.DATA_STRING
|
||||||
|
})
|
||||||
|
const offer = await this.storage.offerStorage.AddUserOffer(appUserId, {
|
||||||
|
label: nmanageReq.offer.fields.label,
|
||||||
|
callback_url: nmanageReq.offer.fields.callback_url,
|
||||||
|
price_sats: nmanageReq.offer.fields.price_sats,
|
||||||
|
expected_data: dataMap,
|
||||||
|
})
|
||||||
|
return { success: true, result: offer }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private async validateGrantAccess(appUserId: string, requestorPub: string): Promise<Result<void>> {
|
||||||
|
const grant = await this.storage.managementStorage.getGrant(appUserId, requestorPub)
|
||||||
|
|
||||||
|
if (!grant) {
|
||||||
|
// TODO request from user
|
||||||
|
return { success: false, error: 'No grant found', code: 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grant.expires_at_unix < Date.now()) {
|
||||||
|
return { success: false, error: 'Grant expired', code: 3 }
|
||||||
|
}
|
||||||
|
return { success: true, result: undefined }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async validateOfferAccess(offerId: string, requestorPub: string): Promise<Result<UserOffer>> {
|
||||||
|
const offer = await this.storage.offerStorage.GetOffer(offerId)
|
||||||
|
if (!offer) {
|
||||||
|
return { success: false, error: 'Offer not found', code: 1 }
|
||||||
|
}
|
||||||
|
if (offer.management_pubkey !== requestorPub) {
|
||||||
|
return { success: false, error: 'App not authorized to update offer', code: 1 }
|
||||||
|
}
|
||||||
|
const grantResult = await this.validateGrantAccess(offer.app_user_id, requestorPub)
|
||||||
|
if (!grantResult.success) {
|
||||||
|
return grantResult
|
||||||
|
}
|
||||||
|
return { success: true, result: offer }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateOffer(nmanageReq: NmanageUpdateOffer, requestorPub: string): Promise<Result<UserOffer>> {
|
||||||
|
const offer = await this.validateOfferAccess(nmanageReq.offer.id, requestorPub)
|
||||||
|
if (!offer.success) {
|
||||||
|
return offer
|
||||||
|
}
|
||||||
|
const validateResult = this.validateOfferFields(nmanageReq.offer.fields)
|
||||||
|
if (!validateResult.success) {
|
||||||
|
return validateResult
|
||||||
|
}
|
||||||
|
const dataMap: Record<string, Types.OfferDataType> = {}
|
||||||
|
for (const data of nmanageReq.offer.fields.payer_data || []) {
|
||||||
|
if (typeof data !== 'string') {
|
||||||
|
return { success: false, error: 'Payer data must be a string', code: 1 }
|
||||||
|
}
|
||||||
|
dataMap[data] = Types.OfferDataType.DATA_STRING
|
||||||
|
}
|
||||||
|
await this.storage.offerStorage.UpdateUserOffer(offer.result.app_user_id, nmanageReq.offer.id, {
|
||||||
|
label: nmanageReq.offer.fields.label,
|
||||||
|
callback_url: nmanageReq.offer.fields.callback_url,
|
||||||
|
price_sats: nmanageReq.offer.fields.price_sats,
|
||||||
|
expected_data: dataMap,
|
||||||
|
})
|
||||||
|
const updatedOffer = await this.storage.offerStorage.GetOffer(nmanageReq.offer.id)
|
||||||
|
if (!updatedOffer) {
|
||||||
|
return { success: false, error: 'Offer not found', code: 2 }
|
||||||
|
}
|
||||||
|
return { success: true, result: updatedOffer }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async deleteOffer(nmanageReq: NmanageDeleteOffer, requestorPub: string): Promise<Result<void>> {
|
||||||
|
const offerResult = await this.validateOfferAccess(nmanageReq.offer.id, requestorPub)
|
||||||
|
if (!offerResult.success) {
|
||||||
|
return offerResult
|
||||||
|
}
|
||||||
|
await this.storage.offerStorage.DeleteUserOffer(offerResult.result.app_user_id, offerResult.result.offer_id)
|
||||||
|
return { success: true, result: undefined }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newNmanageResponse = (content: string, event: NostrEvent): UnsignedEvent => {
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
kind: 21003,
|
||||||
|
pubkey: "",
|
||||||
|
tags: [
|
||||||
|
['p', event.pub],
|
||||||
|
['e', event.id],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const codeToMessage = (code: number, reason = "") => {
|
||||||
|
switch (code) {
|
||||||
|
case 1: return 'Request Denied'
|
||||||
|
case 2: return 'Temporary Failure: ' + reason
|
||||||
|
case 3: return 'Expired Request'
|
||||||
|
case 4: return 'Rate Limited'
|
||||||
|
case 5: return 'Invalid Field or Value'
|
||||||
|
case 6: return 'Invalid Request: ' + reason
|
||||||
|
default: throw new Error("unknown error code" + code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,9 +7,9 @@ import { ERROR, getLogger } from "../helpers/logger.js";
|
||||||
import { NostrEvent, NostrSend, SendData, SendInitiator } from '../nostr/handler.js';
|
import { NostrEvent, NostrSend, SendData, SendInitiator } from '../nostr/handler.js';
|
||||||
import { UnsignedEvent } from 'nostr-tools';
|
import { UnsignedEvent } from 'nostr-tools';
|
||||||
import { UserOffer } from '../storage/entity/UserOffer.js';
|
import { UserOffer } from '../storage/entity/UserOffer.js';
|
||||||
import { LoadNosrtSettingsFromEnv } from '../nostr/index.js';
|
|
||||||
import { LiquidityManager } from "./liquidityManager.js"
|
import { LiquidityManager } from "./liquidityManager.js"
|
||||||
import { NofferData, OfferPriceType, nofferEncode } from '@shocknet/clink-sdk';
|
import { NofferData, OfferPriceType, nofferEncode } from '@shocknet/clink-sdk';
|
||||||
|
import { MainSettings } from "./settings.js";
|
||||||
|
|
||||||
const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay }: { pubkey: string, relay: string }): Types.OfferConfig => {
|
const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay }: { pubkey: string, relay: string }): Types.OfferConfig => {
|
||||||
if (offer.expected_data) {
|
if (offer.expected_data) {
|
||||||
|
|
@ -38,15 +38,16 @@ export class OfferManager {
|
||||||
|
|
||||||
|
|
||||||
_nostrSend: NostrSend | null = null
|
_nostrSend: NostrSend | null = null
|
||||||
|
settings: MainSettings
|
||||||
applicationManager: ApplicationManager
|
applicationManager: ApplicationManager
|
||||||
productManager: ProductManager
|
productManager: ProductManager
|
||||||
storage: Storage
|
storage: Storage
|
||||||
lnd: LND
|
lnd: LND
|
||||||
liquidityManager: LiquidityManager
|
liquidityManager: LiquidityManager
|
||||||
logger = getLogger({ component: 'DebitManager' })
|
logger = getLogger({ component: 'DebitManager' })
|
||||||
constructor(storage: Storage, lnd: LND, applicationManager: ApplicationManager, productManager: ProductManager, liquidityManager: LiquidityManager) {
|
constructor(storage: Storage, settings: MainSettings, lnd: LND, applicationManager: ApplicationManager, productManager: ProductManager, liquidityManager: LiquidityManager) {
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
|
this.settings = settings
|
||||||
this.lnd = lnd
|
this.lnd = lnd
|
||||||
this.applicationManager = applicationManager
|
this.applicationManager = applicationManager
|
||||||
this.productManager = productManager
|
this.productManager = productManager
|
||||||
|
|
@ -113,7 +114,7 @@ export class OfferManager {
|
||||||
if (!offer) {
|
if (!offer) {
|
||||||
throw new Error("Offer not found")
|
throw new Error("Offer not found")
|
||||||
}
|
}
|
||||||
const nostrSettings = LoadNosrtSettingsFromEnv()
|
const nostrSettings = this.settings.nostrRelaySettings
|
||||||
return mapToOfferConfig(ctx.app_user_id, offer, { pubkey: app.npub, relay: nostrSettings.relays[0] })
|
return mapToOfferConfig(ctx.app_user_id, offer, { pubkey: app.npub, relay: nostrSettings.relays[0] })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,7 +132,7 @@ export class OfferManager {
|
||||||
if (toAppend) {
|
if (toAppend) {
|
||||||
offers.push(toAppend)
|
offers.push(toAppend)
|
||||||
}
|
}
|
||||||
const nostrSettings = LoadNosrtSettingsFromEnv()
|
const nostrSettings = this.settings.nostrRelaySettings
|
||||||
return {
|
return {
|
||||||
offers: offers.map(o => mapToOfferConfig(ctx.app_user_id, o, { pubkey: app.npub, relay: nostrSettings.relays[0] }))
|
offers: offers.map(o => mapToOfferConfig(ctx.app_user_id, o, { pubkey: app.npub, relay: nostrSettings.relays[0] }))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,14 @@ import { getLogger } from '../helpers/logger.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { LiquiditySettings, LoadLiquiditySettingsFromEnv } from './liquidityManager.js'
|
import { LiquiditySettings, LoadLiquiditySettingsFromEnv } from './liquidityManager.js'
|
||||||
|
import { LoadNosrtRelaySettingsFromEnv, NostrRelaySettings } from '../nostr/handler.js'
|
||||||
|
|
||||||
export type MainSettings = {
|
export type MainSettings = {
|
||||||
storageSettings: StorageSettings,
|
storageSettings: StorageSettings,
|
||||||
lndSettings: LndSettings,
|
lndSettings: LndSettings,
|
||||||
watchDogSettings: WatchdogSettings,
|
watchDogSettings: WatchdogSettings,
|
||||||
liquiditySettings: LiquiditySettings,
|
liquiditySettings: LiquiditySettings,
|
||||||
|
nostrRelaySettings: NostrRelaySettings,
|
||||||
jwtSecret: string
|
jwtSecret: string
|
||||||
walletPasswordPath: string
|
walletPasswordPath: string
|
||||||
walletSecretPath: string
|
walletSecretPath: string
|
||||||
|
|
@ -49,12 +51,13 @@ export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettin
|
||||||
export const LoadMainSettingsFromEnv = (): MainSettings => {
|
export const LoadMainSettingsFromEnv = (): MainSettings => {
|
||||||
const storageSettings = LoadStorageSettingsFromEnv()
|
const storageSettings = LoadStorageSettingsFromEnv()
|
||||||
const outgoingAppUserInvoiceFeeBps = EnvCanBeInteger("OUTGOING_INVOICE_FEE_USER_BPS", 0)
|
const outgoingAppUserInvoiceFeeBps = EnvCanBeInteger("OUTGOING_INVOICE_FEE_USER_BPS", 0)
|
||||||
|
const nostrRelaySettings = LoadNosrtRelaySettingsFromEnv()
|
||||||
return {
|
return {
|
||||||
watchDogSettings: LoadWatchdogSettingsFromEnv(),
|
watchDogSettings: LoadWatchdogSettingsFromEnv(),
|
||||||
lndSettings: LoadLndSettingsFromEnv(),
|
lndSettings: LoadLndSettingsFromEnv(),
|
||||||
storageSettings: storageSettings,
|
storageSettings: storageSettings,
|
||||||
liquiditySettings: LoadLiquiditySettingsFromEnv(),
|
liquiditySettings: LoadLiquiditySettingsFromEnv(),
|
||||||
|
nostrRelaySettings: nostrRelaySettings,
|
||||||
jwtSecret: loadJwtSecret(storageSettings.dataDir),
|
jwtSecret: loadJwtSecret(storageSettings.dataDir),
|
||||||
walletSecretPath: process.env.WALLET_SECRET_PATH || getDataPath(storageSettings.dataDir, ".wallet_secret"),
|
walletSecretPath: process.env.WALLET_SECRET_PATH || getDataPath(storageSettings.dataDir, ".wallet_secret"),
|
||||||
walletPasswordPath: process.env.WALLET_PASSWORD_PATH || getDataPath(storageSettings.dataDir, ".wallet_password"),
|
walletPasswordPath: process.env.WALLET_PASSWORD_PATH || getDataPath(storageSettings.dataDir, ".wallet_password"),
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { ERROR, getLogger } from '../helpers/logger.js'
|
||||||
import { nip19 } from 'nostr-tools'
|
import { nip19 } from 'nostr-tools'
|
||||||
import { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.js'
|
import { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.js'
|
||||||
import { ProcessMetrics, ProcessMetricsCollector } from '../storage/tlv/processMetricsCollector.js'
|
import { ProcessMetrics, ProcessMetricsCollector } from '../storage/tlv/processMetricsCollector.js'
|
||||||
|
import { EnvCanBeInteger, } from '../helpers/envParser.js'
|
||||||
const { nprofileEncode } = nip19
|
const { nprofileEncode } = nip19
|
||||||
const { v2 } = nip44
|
const { v2 } = nip44
|
||||||
const { encrypt: encryptV2, decrypt: decryptV2, utils } = v2
|
const { encrypt: encryptV2, decrypt: decryptV2, utils } = v2
|
||||||
|
|
@ -26,16 +27,34 @@ export type NostrSettings = {
|
||||||
clients: ClientInfo[]
|
clients: ClientInfo[]
|
||||||
maxEventContentLength: number
|
maxEventContentLength: number
|
||||||
}
|
}
|
||||||
export type NostrEvent = Event & {
|
|
||||||
/** Identifier of the application as defined in NostrSettings.apps */
|
export type NostrRelaySettings = {
|
||||||
appId: string;
|
relays: string[],
|
||||||
/** High-resolution timer capture when processing began (BigInt serialized as string to keep JSON friendly) */
|
maxEventContentLength: number
|
||||||
startAtNano: string;
|
}
|
||||||
/** wall-clock millis when processing began */
|
|
||||||
startAtMs: number;
|
const getEnvOrDefault = (name: string, defaultValue: string): string => {
|
||||||
/** Convenience duplicate of the sender pubkey (e.pubkey) kept for backwards-compat */
|
return process.env[name] || defaultValue;
|
||||||
pub: string;
|
}
|
||||||
};
|
|
||||||
|
export const LoadNosrtRelaySettingsFromEnv = (test = false): NostrRelaySettings => {
|
||||||
|
const relaysEnv = getEnvOrDefault("NOSTR_RELAYS", "wss://relay.lightning.pub");
|
||||||
|
const maxEventContentLength = EnvCanBeInteger("NOSTR_MAX_EVENT_CONTENT_LENGTH", 40000)
|
||||||
|
return {
|
||||||
|
relays: relaysEnv.split(' '),
|
||||||
|
maxEventContentLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NostrEvent = {
|
||||||
|
id: string
|
||||||
|
pub: string
|
||||||
|
content: string
|
||||||
|
appId: string
|
||||||
|
startAtNano: string
|
||||||
|
startAtMs: number
|
||||||
|
kind: number
|
||||||
|
}
|
||||||
|
|
||||||
type SettingsRequest = {
|
type SettingsRequest = {
|
||||||
type: 'settings'
|
type: 'settings'
|
||||||
|
|
@ -217,15 +236,7 @@ export default class Handler {
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
const nostrEvent: NostrEvent = {
|
this.eventCallback({ id: eventId, content, pub: e.pubkey, appId: app.appId, startAtNano, startAtMs, kind: e.kind })
|
||||||
...e,
|
|
||||||
content,
|
|
||||||
appId: app.appId,
|
|
||||||
startAtNano,
|
|
||||||
startAtMs,
|
|
||||||
pub: e.pubkey,
|
|
||||||
}
|
|
||||||
this.eventCallback(nostrEvent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Send(initiator: SendInitiator, data: SendData, relays?: string[]) {
|
async Send(initiator: SendInitiator, data: SendData, relays?: string[]) {
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,9 @@ import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse, S
|
||||||
import { Utils } from '../helpers/utilsWrapper.js'
|
import { Utils } from '../helpers/utilsWrapper.js'
|
||||||
type EventCallback = (event: NostrEvent) => void
|
type EventCallback = (event: NostrEvent) => void
|
||||||
|
|
||||||
const getEnvOrDefault = (name: string, defaultValue: string): string => {
|
|
||||||
return process.env[name] || defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LoadNosrtSettingsFromEnv = (test = false) => {
|
|
||||||
const relaysEnv = getEnvOrDefault("NOSTR_RELAYS", "wss://relay.lightning.pub");
|
|
||||||
const maxEventContentLength = EnvCanBeInteger("NOSTR_MAX_EVENT_CONTENT_LENGTH", 40000)
|
|
||||||
return {
|
|
||||||
relays: relaysEnv.split(' '),
|
|
||||||
maxEventContentLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class NostrSubprocess {
|
export default class NostrSubprocess {
|
||||||
settings: NostrSettings
|
settings: NostrSettings
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from "typeorm";
|
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn, UpdateDateColumn } from "typeorm";
|
||||||
import { User } from "./User";
|
import { User } from "./User";
|
||||||
|
|
||||||
@Entity("management_grants")
|
@Entity("management_grants")
|
||||||
export class ManagementGrant {
|
export class ManagementGrant {
|
||||||
@PrimaryGeneratedColumn("uuid")
|
@PrimaryGeneratedColumn()
|
||||||
id: string;
|
serial_id: number
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
user_id: string;
|
app_user_id: string
|
||||||
|
|
||||||
@ManyToOne(() => User)
|
|
||||||
@JoinColumn({ name: "user_id", referencedColumnName: "user_id" })
|
|
||||||
user: User;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
app_pubkey: string;
|
app_pubkey: string;
|
||||||
|
|
||||||
@CreateDateColumn()
|
@Column()
|
||||||
created_at: Date;
|
expires_at_unix: number
|
||||||
|
|
||||||
@Column({ type: 'timestamp', nullable: true })
|
@CreateDateColumn()
|
||||||
expires_at: Date;
|
created_at: Date
|
||||||
}
|
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updated_at: Date
|
||||||
|
}
|
||||||
|
|
@ -13,8 +13,8 @@ export class UserOffer {
|
||||||
@Column({ unique: true, nullable: false })
|
@Column({ unique: true, nullable: false })
|
||||||
offer_id: string
|
offer_id: string
|
||||||
|
|
||||||
@Column({ type: "text", nullable: true })
|
@Column({ default: "" })
|
||||||
managing_app_pubkey: string | null
|
management_pubkey: string
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
label: string
|
label: string
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ export class ManagementStorage {
|
||||||
this.dbs = dbs;
|
this.dbs = dbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
getGrant(user_id: string, app_pubkey: string) {
|
getGrant(appUserId: string, appPubkey: string) {
|
||||||
return this.dbs.FindOne<ManagementGrant>('ManagementGrant' as any, { where: { user_id, app_pubkey } });
|
return this.dbs.FindOne<ManagementGrant>('ManagementGrant', { where: { app_pubkey: appPubkey, app_user_id: appUserId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async addGrant(user_id: string, app_pubkey: string, expires_at?: Date) {
|
async addGrant(appUserId: string, appPubkey: string, expires_at_unix: number) {
|
||||||
return this.dbs.CreateAndSave<ManagementGrant>('ManagementGrant' as any, { user_id, app_pubkey, expires_at });
|
return this.dbs.CreateAndSave<ManagementGrant>('ManagementGrant', { app_user_id: appUserId, app_pubkey: appPubkey, expires_at_unix });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +34,11 @@ export default class {
|
||||||
async GetUserOffers(app_user_id: string): Promise<UserOffer[]> {
|
async GetUserOffers(app_user_id: string): Promise<UserOffer[]> {
|
||||||
return this.dbs.Find<UserOffer>('UserOffer', { where: { app_user_id } })
|
return this.dbs.Find<UserOffer>('UserOffer', { where: { app_user_id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getManagedUserOffers(app_user_id: string, management_pubkey: string): Promise<UserOffer[]> {
|
||||||
|
return this.dbs.Find<UserOffer>('UserOffer', { where: { app_user_id, management_pubkey } })
|
||||||
|
}
|
||||||
|
|
||||||
async GetUserOffer(app_user_id: string, offer_id: string): Promise<UserOffer | null> {
|
async GetUserOffer(app_user_id: string, offer_id: string): Promise<UserOffer | null> {
|
||||||
return this.dbs.FindOne<UserOffer>('UserOffer', { where: { app_user_id, offer_id } })
|
return this.dbs.FindOne<UserOffer>('UserOffer', { where: { app_user_id, offer_id } })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue