zaps
This commit is contained in:
parent
969cd31775
commit
dcb68d0069
19 changed files with 2333 additions and 2221 deletions
|
|
@ -95,9 +95,9 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
|
||||
- __User__:
|
||||
- expected context content
|
||||
- __app_user_id__: _string_
|
||||
- __user_id__: _string_
|
||||
- __app_id__: _string_
|
||||
- __app_user_id__: _string_
|
||||
|
||||
- __Admin__:
|
||||
- expected context content
|
||||
|
|
@ -187,6 +187,8 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- the request url __query__ can take the following string items:
|
||||
- k1
|
||||
- amount
|
||||
- nostr
|
||||
- lnurl
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [HandleLnurlPayResponse](#HandleLnurlPayResponse)
|
||||
|
||||
|
|
@ -372,153 +374,32 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
## Messages
|
||||
### The content of requests and response from the methods
|
||||
|
||||
### EncryptionExchangeRequest
|
||||
- __publicKey__: _string_
|
||||
- __deviceId__: _string_
|
||||
|
||||
### SetMockInvoiceAsPaidRequest
|
||||
- __invoice__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### AddAppRequest
|
||||
- __name__: _string_
|
||||
- __allow_user_creation__: _boolean_
|
||||
|
||||
### SendAppUserToAppPaymentRequest
|
||||
- __from_user_identifier__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### GetAppUserLNURLInfoRequest
|
||||
- __user_identifier__: _string_
|
||||
- __base_url_override__: _string_
|
||||
|
||||
### DecodeInvoiceResponse
|
||||
- __amount__: _number_
|
||||
|
||||
### PayInvoiceResponse
|
||||
- __preimage__: _string_
|
||||
- __amount_paid__: _number_
|
||||
- __operation_id__: _string_
|
||||
- __service_fee__: _number_
|
||||
- __network_fee__: _number_
|
||||
|
||||
### Empty
|
||||
|
||||
### LnurlPayInfoResponse
|
||||
- __tag__: _string_
|
||||
- __callback__: _string_
|
||||
- __maxSendable__: _number_
|
||||
- __minSendable__: _number_
|
||||
- __metadata__: _string_
|
||||
|
||||
### GetUserOperationsRequest
|
||||
- __latestIncomingInvoice__: _number_
|
||||
- __latestOutgoingInvoice__: _number_
|
||||
- __latestIncomingTx__: _number_
|
||||
- __latestOutgoingTx__: _number_
|
||||
- __latestIncomingUserToUserPayment__: _number_
|
||||
- __latestOutgoingUserToUserPayment__: _number_
|
||||
|
||||
### Product
|
||||
- __id__: _string_
|
||||
- __name__: _string_
|
||||
- __price_sats__: _number_
|
||||
|
||||
### LnurlWithdrawInfoResponse
|
||||
- __tag__: _string_
|
||||
- __callback__: _string_
|
||||
- __k1__: _string_
|
||||
- __defaultDescription__: _string_
|
||||
- __minWithdrawable__: _number_
|
||||
- __maxWithdrawable__: _number_
|
||||
- __balanceCheck__: _string_
|
||||
- __payLink__: _string_
|
||||
|
||||
### AddAppUserInvoiceRequest
|
||||
- __receiver_identifier__: _string_
|
||||
- __payer_identifier__: _string_
|
||||
- __http_callback_url__: _string_
|
||||
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
|
||||
|
||||
### PayAppUserInvoiceRequest
|
||||
- __user_identifier__: _string_
|
||||
- __invoice__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### PayAddressRequest
|
||||
- __address__: _string_
|
||||
- __amoutSats__: _number_
|
||||
- __satsPerVByte__: _number_
|
||||
|
||||
### GetUserOperationsResponse
|
||||
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestOutgoingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
|
||||
|
||||
### LndGetInfoResponse
|
||||
- __alias__: _string_
|
||||
|
||||
### NewInvoiceResponse
|
||||
- __invoice__: _string_
|
||||
|
||||
### LnurlLinkResponse
|
||||
- __lnurl__: _string_
|
||||
- __k1__: _string_
|
||||
|
||||
### HandleLnurlPayResponse
|
||||
- __pr__: _string_
|
||||
- __routes__: ARRAY of: _[Empty](#Empty)_
|
||||
|
||||
### UserOperations
|
||||
- __fromIndex__: _number_
|
||||
- __toIndex__: _number_
|
||||
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
|
||||
|
||||
### GetProductBuyLinkResponse
|
||||
- __link__: _string_
|
||||
|
||||
### LiveUserOperation
|
||||
- __id__: _string_
|
||||
- __operation__: _[UserOperation](#UserOperation)_
|
||||
|
||||
### AddAppUserRequest
|
||||
- __identifier__: _string_
|
||||
- __fail_if_exists__: _boolean_
|
||||
- __balance__: _number_
|
||||
|
||||
### NewInvoiceRequest
|
||||
- __amountSats__: _number_
|
||||
- __memo__: _string_
|
||||
|
||||
### DecodeInvoiceRequest
|
||||
- __invoice__: _string_
|
||||
|
||||
### Application
|
||||
- __name__: _string_
|
||||
- __id__: _string_
|
||||
- __balance__: _number_
|
||||
- __npub__: _string_
|
||||
|
||||
### AppUser
|
||||
- __identifier__: _string_
|
||||
- __info__: _[UserInfo](#UserInfo)_
|
||||
- __max_withdrawable__: _number_
|
||||
|
||||
### SetMockAppUserBalanceRequest
|
||||
### GetAppUserLNURLInfoRequest
|
||||
- __user_identifier__: _string_
|
||||
- __amount__: _number_
|
||||
- __base_url_override__: _string_
|
||||
|
||||
### NewAddressRequest
|
||||
- __addressType__: _[AddressType](#AddressType)_
|
||||
|
||||
### PayAddressResponse
|
||||
- __txId__: _string_
|
||||
- __operation_id__: _string_
|
||||
- __service_fee__: _number_
|
||||
- __network_fee__: _number_
|
||||
### Product
|
||||
- __id__: _string_
|
||||
- __name__: _string_
|
||||
- __price_sats__: _number_
|
||||
|
||||
### GetProductBuyLinkResponse
|
||||
- __link__: _string_
|
||||
|
||||
### OpenChannelRequest
|
||||
- __destination__: _string_
|
||||
- __fundingAmount__: _number_
|
||||
- __pushAmount__: _number_
|
||||
- __closeAddress__: _string_
|
||||
|
||||
### UserOperation
|
||||
- __paidAtUnix__: _number_
|
||||
|
|
@ -530,57 +411,179 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __service_fee__: _number_
|
||||
- __network_fee__: _number_
|
||||
|
||||
### EncryptionExchangeRequest
|
||||
- __publicKey__: _string_
|
||||
- __deviceId__: _string_
|
||||
|
||||
### AuthApp
|
||||
- __app__: _[Application](#Application)_
|
||||
- __auth_token__: _string_
|
||||
|
||||
### AppUser
|
||||
- __identifier__: _string_
|
||||
- __info__: _[UserInfo](#UserInfo)_
|
||||
- __max_withdrawable__: _number_
|
||||
|
||||
### AddAppUserInvoiceRequest
|
||||
- __receiver_identifier__: _string_
|
||||
- __payer_identifier__: _string_
|
||||
- __http_callback_url__: _string_
|
||||
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
|
||||
|
||||
### SendAppUserToAppPaymentRequest
|
||||
- __from_user_identifier__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### DecodeInvoiceRequest
|
||||
- __invoice__: _string_
|
||||
|
||||
### LndGetInfoRequest
|
||||
- __nodeId__: _number_
|
||||
|
||||
### AddAppRequest
|
||||
- __name__: _string_
|
||||
- __allow_user_creation__: _boolean_
|
||||
|
||||
### GetAppUserRequest
|
||||
- __user_identifier__: _string_
|
||||
|
||||
### AuthAppRequest
|
||||
- __name__: _string_
|
||||
- __allow_user_creation__: _boolean_ *this field is optional
|
||||
### NewInvoiceResponse
|
||||
- __invoice__: _string_
|
||||
|
||||
### SetMockAppBalanceRequest
|
||||
- __amount__: _number_
|
||||
|
||||
### PayInvoiceRequest
|
||||
### SetMockInvoiceAsPaidRequest
|
||||
- __invoice__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### OpenChannelRequest
|
||||
- __destination__: _string_
|
||||
- __fundingAmount__: _number_
|
||||
- __pushAmount__: _number_
|
||||
- __closeAddress__: _string_
|
||||
### NewInvoiceRequest
|
||||
- __amountSats__: _number_
|
||||
- __memo__: _string_
|
||||
|
||||
### UserInfo
|
||||
- __userId__: _string_
|
||||
- __balance__: _number_
|
||||
- __max_withdrawable__: _number_
|
||||
|
||||
### AddProductRequest
|
||||
- __name__: _string_
|
||||
- __price_sats__: _number_
|
||||
### HandleLnurlPayResponse
|
||||
- __pr__: _string_
|
||||
- __routes__: ARRAY of: _[Empty](#Empty)_
|
||||
|
||||
### AddAppInvoiceRequest
|
||||
- __payer_identifier__: _string_
|
||||
- __http_callback_url__: _string_
|
||||
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
|
||||
|
||||
### PayAddressRequest
|
||||
- __address__: _string_
|
||||
- __amoutSats__: _number_
|
||||
- __satsPerVByte__: _number_
|
||||
|
||||
### PayInvoiceRequest
|
||||
- __invoice__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### UserOperations
|
||||
- __fromIndex__: _number_
|
||||
- __toIndex__: _number_
|
||||
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
|
||||
|
||||
### AddAppUserRequest
|
||||
- __identifier__: _string_
|
||||
- __fail_if_exists__: _boolean_
|
||||
- __balance__: _number_
|
||||
|
||||
### OpenChannelResponse
|
||||
- __channelId__: _string_
|
||||
|
||||
### LnurlLinkResponse
|
||||
- __lnurl__: _string_
|
||||
- __k1__: _string_
|
||||
|
||||
### LnurlWithdrawInfoResponse
|
||||
- __tag__: _string_
|
||||
- __callback__: _string_
|
||||
- __k1__: _string_
|
||||
- __defaultDescription__: _string_
|
||||
- __minWithdrawable__: _number_
|
||||
- __maxWithdrawable__: _number_
|
||||
- __balanceCheck__: _string_
|
||||
- __payLink__: _string_
|
||||
|
||||
### LnurlPayInfoResponse
|
||||
- __tag__: _string_
|
||||
- __callback__: _string_
|
||||
- __maxSendable__: _number_
|
||||
- __minSendable__: _number_
|
||||
- __metadata__: _string_
|
||||
- __allowsNostr__: _boolean_
|
||||
- __nostrPubkey__: _string_
|
||||
|
||||
### UserInfo
|
||||
- __userId__: _string_
|
||||
- __balance__: _number_
|
||||
- __max_withdrawable__: _number_
|
||||
|
||||
### PayAddressResponse
|
||||
- __txId__: _string_
|
||||
- __operation_id__: _string_
|
||||
- __service_fee__: _number_
|
||||
- __network_fee__: _number_
|
||||
|
||||
### DecodeInvoiceResponse
|
||||
- __amount__: _number_
|
||||
|
||||
### LndGetInfoResponse
|
||||
- __alias__: _string_
|
||||
|
||||
### AuthAppRequest
|
||||
- __name__: _string_
|
||||
- __allow_user_creation__: _boolean_ *this field is optional
|
||||
|
||||
### SendAppUserToAppUserPaymentRequest
|
||||
- __from_user_identifier__: _string_
|
||||
- __to_user_identifier__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### SetMockAppUserBalanceRequest
|
||||
- __user_identifier__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### SetMockAppBalanceRequest
|
||||
- __amount__: _number_
|
||||
|
||||
### NewAddressResponse
|
||||
- __address__: _string_
|
||||
|
||||
### OpenChannelResponse
|
||||
- __channelId__: _string_
|
||||
### LiveUserOperation
|
||||
- __operation__: _[UserOperation](#UserOperation)_
|
||||
|
||||
### AuthApp
|
||||
- __app__: _[Application](#Application)_
|
||||
- __auth_token__: _string_
|
||||
### Empty
|
||||
|
||||
### PayAppUserInvoiceRequest
|
||||
- __user_identifier__: _string_
|
||||
- __invoice__: _string_
|
||||
- __amount__: _number_
|
||||
|
||||
### PayInvoiceResponse
|
||||
- __preimage__: _string_
|
||||
- __amount_paid__: _number_
|
||||
- __operation_id__: _string_
|
||||
- __service_fee__: _number_
|
||||
- __network_fee__: _number_
|
||||
|
||||
### GetUserOperationsRequest
|
||||
- __latestIncomingInvoice__: _number_
|
||||
- __latestOutgoingInvoice__: _number_
|
||||
- __latestIncomingTx__: _number_
|
||||
- __latestOutgoingTx__: _number_
|
||||
- __latestIncomingUserToUserPayment__: _number_
|
||||
- __latestOutgoingUserToUserPayment__: _number_
|
||||
|
||||
### GetUserOperationsResponse
|
||||
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
|
||||
- __latestOutgoingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
|
||||
- __latestIncomingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
|
||||
|
||||
### AddProductRequest
|
||||
- __name__: _string_
|
||||
- __price_sats__: _number_
|
||||
## Enums
|
||||
### The enumerators used in the messages
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -136,7 +136,7 @@ service LightningPub {
|
|||
option (auth_type) = "Guest";
|
||||
option (http_method) = "get";
|
||||
option (http_route) = "/api/guest/lnurl_pay/handle";
|
||||
option (query) = {items: ["k1", "amount"]};
|
||||
option (query) = {items: ["k1", "amount", "nostr", "lnurl"]};
|
||||
}
|
||||
//</Guest>
|
||||
|
||||
|
|
|
|||
|
|
@ -191,6 +191,8 @@ message LnurlPayInfoResponse {
|
|||
int64 maxSendable = 3; // millisatoshi - unsafe overflow possible, but very unlikely
|
||||
int64 minSendable = 4; // millisatoshi - unsafe overflow possible, but very unlikely
|
||||
string metadata = 5;
|
||||
bool allowsNostr = 6;
|
||||
string nostrPubkey = 7;
|
||||
}
|
||||
message HandleLnurlPayResponse {
|
||||
string pr = 1;
|
||||
|
|
@ -260,6 +262,5 @@ message GetProductBuyLinkResponse {
|
|||
}
|
||||
|
||||
message LiveUserOperation {
|
||||
string id = 1;
|
||||
UserOperation operation = 2;
|
||||
UserOperation operation = 1;
|
||||
}
|
||||
|
|
@ -16,14 +16,15 @@ const start = async () => {
|
|||
const appsData = await mainHandler.storage.applicationStorage.GetApplications()
|
||||
|
||||
const apps = await Promise.all(appsData.map(app => {
|
||||
if (!app.nostr_private_key) { // TMP --
|
||||
if (!app.nostr_private_key || !app.nostr_public_key) { // TMP --
|
||||
return mainHandler.storage.applicationStorage.GenerateApplicationKeys(app);
|
||||
} // --
|
||||
else {
|
||||
return { privateKey: app.nostr_private_key, publicKey: app.nostr_public_key, appId: app.app_id, name: app.name }
|
||||
}
|
||||
}))
|
||||
nostrMiddleware(serverMethods, mainHandler, { ...nostrSettings, apps })
|
||||
const { Send } = nostrMiddleware(serverMethods, mainHandler, { ...nostrSettings, apps })
|
||||
mainHandler.attachNostrSend(Send)
|
||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||
if (process.argv[2] === 'unlock') {
|
||||
const u = process.argv[3]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import Main from "./services/main/index.js"
|
||||
import Nostr from "./services/nostr/index.js"
|
||||
import { NostrSettings } from "./services/nostr/handler.js"
|
||||
import { NostrSend, NostrSettings } from "./services/nostr/handler.js"
|
||||
import * as Types from '../proto/autogenerated/ts/types.js'
|
||||
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
|
||||
const handledRequests: string[] = [] // TODO: - big memory leak here, add TTL
|
||||
|
||||
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings) => {
|
||||
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings): { Stop: () => void, Send: NostrSend } => {
|
||||
const nostrTransport = NewNostrTransport(serverMethods, {
|
||||
NostrUserAuthGuard: async (appId, pub) => {
|
||||
const app = await mainHandler.storage.applicationStorage.GetApplication(appId || "")
|
||||
|
|
@ -22,10 +22,10 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
|||
return
|
||||
}
|
||||
nostrTransport({ ...j, appId: event.appId }, res => {
|
||||
nostr.Send(event.appId, event.pub, JSON.stringify({ ...res, requestId: j.requestId }))
|
||||
nostr.Send(event.appId, { type: 'content', pub: event.pub, content: JSON.stringify({ ...res, requestId: j.requestId }) })
|
||||
})
|
||||
})
|
||||
return { Stop: nostr.Stop }
|
||||
return { Stop: () => nostr.Stop, Send: (...args) => nostr.Send(...args) }
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export default class {
|
|||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
async GetUserInfo(ctx: Types.UserContext): Promise<Types.UserInfo> {
|
||||
const user = await this.storage.userStorage.GetUser(ctx.user_id)
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export default class {
|
|||
id: app.app_id,
|
||||
name: app.name,
|
||||
balance: app.owner.balance_sats,
|
||||
npub: app.nostr_public_key
|
||||
npub: app.nostr_public_key || ""
|
||||
},
|
||||
auth_token: this.SignAppToken(app.app_id)
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ export default class {
|
|||
id: app.app_id,
|
||||
name: app.name,
|
||||
balance: app.owner.balance_sats,
|
||||
npub: app.nostr_public_key
|
||||
npub: app.nostr_public_key || ""
|
||||
},
|
||||
auth_token: this.SignAppToken(app.app_id)
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ export default class {
|
|||
name: app.name,
|
||||
id: app.app_id,
|
||||
balance: app.owner.balance_sats,
|
||||
npub: app.nostr_public_key
|
||||
npub: app.nostr_public_key || ""
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ import NewLightningHandler, { LoadLndSettingsFromEnv, LightningHandler } from ".
|
|||
import { AddressPaidCb, InvoicePaidCb } from "../lnd/settings.js"
|
||||
import { getLogger, PubLogger } from "../helpers/logger.js"
|
||||
import AppUserManager from "./appUserManager.js"
|
||||
import { Application } from '../storage/entity/Application.js'
|
||||
import { UserReceivingInvoice, ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
|
||||
import { UnsignedEvent } from '../nostr/tools/event.js'
|
||||
import { NostrSend } from '../nostr/handler.js'
|
||||
export const LoadMainSettingsFromEnv = (test = false): MainSettings => {
|
||||
return {
|
||||
lndSettings: LoadLndSettingsFromEnv(test),
|
||||
|
|
@ -47,6 +51,7 @@ export default class {
|
|||
appUserManager: AppUserManager
|
||||
paymentManager: PaymentManager
|
||||
paymentSubs: Record<string, ((op: Types.UserOperation) => void) | null> = {}
|
||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||
|
||||
constructor(settings: MainSettings) {
|
||||
this.settings = settings
|
||||
|
|
@ -59,6 +64,10 @@ export default class {
|
|||
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
|
||||
}
|
||||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this.nostrSend = f
|
||||
}
|
||||
|
||||
addressPaidCb: AddressPaidCb = (txOutput, address, amount, internal) => {
|
||||
this.storage.StartTransaction(async tx => {
|
||||
const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx)
|
||||
|
|
@ -78,7 +87,7 @@ export default class {
|
|||
const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, internal, tx)
|
||||
await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, addedTx.paid_amount - fee, tx)
|
||||
const operationId = `${Types.UserOperationType.INCOMING_TX}-${userAddress.serial_id}`
|
||||
this.triggerSubs(userAddress.user.user_id, { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: fee })
|
||||
this.sendOperationToNostr(userAddress.linkedApplication, userAddress.user.user_id, { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: fee })
|
||||
} catch {
|
||||
|
||||
}
|
||||
|
|
@ -111,7 +120,8 @@ export default class {
|
|||
|
||||
await this.triggerPaidCallback(log, userInvoice.callbackUrl)
|
||||
const operationId = `${Types.UserOperationType.INCOMING_INVOICE}-${userInvoice.serial_id}`
|
||||
this.triggerSubs(userInvoice.user.user_id, { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_INVOICE, identifier: userInvoice.invoice, operationId, network_fee: 0, service_fee: fee })
|
||||
this.sendOperationToNostr(userInvoice.linkedApplication, userInvoice.user.user_id, { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_INVOICE, identifier: userInvoice.invoice, operationId, network_fee: 0, service_fee: fee })
|
||||
this.createZapReceipt(userInvoice)
|
||||
log("paid invoice processed successfully")
|
||||
} catch (err: any) {
|
||||
log("ERROR", "cannot process paid invoice", err.message || "")
|
||||
|
|
@ -130,29 +140,32 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
triggerSubs(userId: string, op: Types.UserOperation) {
|
||||
const sub = this.paymentSubs[userId]
|
||||
const log = getLogger({ userId })
|
||||
if (!sub) {
|
||||
log("no sub found for user")
|
||||
async sendOperationToNostr(app: Application, userId: string, op: Types.UserOperation) {
|
||||
const user = await this.storage.applicationStorage.GetAppUserFromUser(app, userId)
|
||||
if (!user || !user.nostr_public_key) {
|
||||
getLogger({})("cannot notify user, not a nostr user")
|
||||
return
|
||||
}
|
||||
log("notifyng user of payment")
|
||||
sub(op)
|
||||
const message: Types.LiveUserOperation & { requestId: string } = { operation: op, requestId: "GetLiveUserOperations" }
|
||||
this.nostrSend(app.app_id, { type: 'content', content: JSON.stringify(message), pub: user.nostr_public_key })
|
||||
}
|
||||
|
||||
async SubToPayment(ctx: Types.UserContext, cb: (res: Types.LiveUserOperation, err: Error | null) => void) {
|
||||
const sub = this.paymentSubs[ctx.user_id]
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||
const log = getLogger({ appName: app.name, userId: ctx.user_id })
|
||||
log("subbing user to payment")
|
||||
await this.storage.applicationStorage.GetApplicationUser(app, ctx.app_user_id)
|
||||
if (sub) {
|
||||
log("overriding user payment stream")
|
||||
async createZapReceipt(invoice: UserReceivingInvoice) {
|
||||
const zapInfo = invoice.zap_info
|
||||
if (!zapInfo || !invoice.linkedApplication || !invoice.linkedApplication.nostr_public_key) {
|
||||
return
|
||||
}
|
||||
this.paymentSubs[ctx.user_id] = (op) => {
|
||||
const rand = crypto.randomBytes(16).toString('hex')
|
||||
cb({ id: rand, operation: op }, null)
|
||||
const tags = [["p", zapInfo.pub], ["bolt11", invoice.invoice], ["description", zapInfo.description]]
|
||||
if (zapInfo.eventId) {
|
||||
tags.push(["e", zapInfo.eventId])
|
||||
}
|
||||
const event: UnsignedEvent = {
|
||||
content: "",
|
||||
created_at: invoice.paid_at_unix,
|
||||
kind: 9735,
|
||||
pubkey: invoice.linkedApplication.nostr_public_key,
|
||||
tags,
|
||||
}
|
||||
this.nostrSend(invoice.linkedApplication.app_id, { type: 'event', event })
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,9 @@ import { Application } from '../storage/entity/Application.js'
|
|||
import { getLogger } from '../helpers/logger.js'
|
||||
import { UserReceivingAddress } from '../storage/entity/UserReceivingAddress.js'
|
||||
import { AddressPaidCb, InvoicePaidCb, PaidInvoice } from '../lnd/settings.js'
|
||||
import { UserReceivingInvoice } from '../storage/entity/UserReceivingInvoice.js'
|
||||
import { UserReceivingInvoice, ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
|
||||
import { SendCoinsResponse } from '../../../proto/lnd/lightning.js'
|
||||
import { Event, verifiedSymbol, verifySignature } from '../nostr/tools/event.js'
|
||||
interface UserOperationInfo {
|
||||
serial_id: number
|
||||
paid_amount: number
|
||||
|
|
@ -283,24 +284,83 @@ export default class {
|
|||
callback: `${url}?k1=${payK1.key}`,
|
||||
maxSendable: remote * 1000,
|
||||
minSendable: 10000,
|
||||
metadata: defaultLnurlPayMetadata
|
||||
metadata: defaultLnurlPayMetadata,
|
||||
allowsNostr: !!linkedApplication.nostr_public_key,
|
||||
nostrPubkey: linkedApplication.nostr_public_key || ""
|
||||
}
|
||||
}
|
||||
|
||||
async GetLnurlPayInfo(payInfoK1: string): Promise<Types.LnurlPayInfoResponse> {
|
||||
const key = await this.storage.paymentStorage.UseUserEphemeralKey(payInfoK1, 'pay', true)
|
||||
if (!key.linkedApplication) {
|
||||
throw new Error("invalid lnurl request")
|
||||
}
|
||||
const { remote } = await this.lnd.ChannelBalance()
|
||||
return {
|
||||
tag: 'payRequest',
|
||||
callback: `${this.settings.serviceUrl}/api/guest/lnurl_pay/handle?k1=${payInfoK1}`,
|
||||
maxSendable: remote * 1000,
|
||||
minSendable: 10000,
|
||||
metadata: defaultLnurlPayMetadata
|
||||
metadata: defaultLnurlPayMetadata,
|
||||
allowsNostr: !!key.linkedApplication.nostr_public_key,
|
||||
nostrPubkey: key.linkedApplication.nostr_public_key || ""
|
||||
}
|
||||
}
|
||||
|
||||
async HandleLnurlPay(payK1: string, amountMillis: number): Promise<Types.HandleLnurlPayResponse> {
|
||||
const key = await this.storage.paymentStorage.UseUserEphemeralKey(payK1, 'pay', true)
|
||||
parseTags(tag: string, tags: string[][], opts: { multiples?: boolean, required?: boolean } = {}): string[] {
|
||||
const { multiples, required } = opts
|
||||
const found = tags.filter(t => t && t.length >= 2 && t[0] === tag)
|
||||
if (found.length === 0) {
|
||||
if (required) {
|
||||
throw new Error(`missing tag for "${tag}"`)
|
||||
}
|
||||
return []
|
||||
}
|
||||
if (found.length === 1) {
|
||||
const elements = found[0]
|
||||
elements.shift()
|
||||
if (elements.length === 0) {
|
||||
throw new Error(`invalid content for "${tag}" tag`)
|
||||
}
|
||||
if (!multiples && elements.length !== 1) {
|
||||
throw new Error(`too many contents for "${tag}" tag`)
|
||||
|
||||
}
|
||||
return elements
|
||||
}
|
||||
throw new Error(`too many entries for "${tag}" tag`)
|
||||
}
|
||||
|
||||
validateZapEvent(event: string, amt: number): ZapInfo {
|
||||
const nostrEvent = JSON.parse(event) as Event
|
||||
delete nostrEvent[verifiedSymbol]
|
||||
const verified = verifySignature(nostrEvent)
|
||||
if (!verified) {
|
||||
throw new Error("nostr event not valid")
|
||||
}
|
||||
const p = this.parseTags("p", nostrEvent.tags, { required: true })
|
||||
const e = this.parseTags("e", nostrEvent.tags)
|
||||
const relays = this.parseTags("relays", nostrEvent.tags, { required: true, multiples: true })
|
||||
const amount = this.parseTags("amount", nostrEvent.tags)
|
||||
if (+amount !== amt) {
|
||||
throw new Error("amount mismatch")
|
||||
}
|
||||
return { pub: p[0], eventId: e.length > 0 ? e[0] : "", relays, description: event }
|
||||
}
|
||||
|
||||
async HandleLnurlPay(ctx: Types.HandleLnurlPay_Context): Promise<Types.HandleLnurlPayResponse> {
|
||||
if (!ctx.k1 || !ctx.amount) {
|
||||
throw new Error("invalid lnurl pay to handle")
|
||||
}
|
||||
const amountMillis = +ctx.amount
|
||||
if (isNaN(amountMillis)) {
|
||||
throw new Error("invalid amount in lnurl pay to handle")
|
||||
}
|
||||
let zapInfo: ZapInfo | undefined
|
||||
if (ctx.nostr) {
|
||||
zapInfo = this.validateZapEvent(ctx.nostr, amountMillis)
|
||||
}
|
||||
const key = await this.storage.paymentStorage.UseUserEphemeralKey(ctx.k1, 'pay', true)
|
||||
const sats = amountMillis / 1000
|
||||
if (!Number.isInteger(sats)) {
|
||||
throw new Error("millisats amount must be integer sats amount")
|
||||
|
|
@ -310,8 +370,8 @@ export default class {
|
|||
}
|
||||
const invoice = await this.NewInvoice(key.user.user_id, {
|
||||
amountSats: sats,
|
||||
memo: defaultLnurlPayMetadata
|
||||
}, { expiry: defaultInvoiceExpiry, linkedApplication: key.linkedApplication })
|
||||
memo: zapInfo ? zapInfo.description : defaultLnurlPayMetadata
|
||||
}, { expiry: defaultInvoiceExpiry, linkedApplication: key.linkedApplication, zapInfo })
|
||||
return {
|
||||
pr: invoice.invoice,
|
||||
routes: []
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import { SimplePool, Sub, Event, UnsignedEvent, getEventHash, finishEvent, relay
|
|||
import { encryptData, decryptData, getSharedSecret, decodePayload, encodePayload } from './nip44.js'
|
||||
const handledEvents: string[] = [] // TODO: - big memory leak here, add TTL
|
||||
type AppInfo = { appId: string, publicKey: string, privateKey: string, name: string }
|
||||
export type SendData = { type: "content", content: string, pub: string } | { type: "event", event: UnsignedEvent }
|
||||
export type NostrSend = (appId: string, data: SendData, relays?: string[] | undefined) => void
|
||||
|
||||
export type NostrSettings = {
|
||||
apps: AppInfo[]
|
||||
relays: string[]
|
||||
|
|
@ -21,8 +24,8 @@ type SettingsRequest = {
|
|||
type SendRequest = {
|
||||
type: 'send'
|
||||
appId: string
|
||||
pub: string
|
||||
message: string
|
||||
data: SendData
|
||||
relays?: string[]
|
||||
}
|
||||
type ReadyResponse = {
|
||||
type: 'ready'
|
||||
|
|
@ -46,7 +49,7 @@ process.on("message", (message: ChildProcessRequest) => {
|
|||
initSubprocessHandler(message.settings)
|
||||
break
|
||||
case 'send':
|
||||
sendToNostr(message.appId, message.pub, message.message)
|
||||
sendToNostr(message.appId, message.data, message.relays)
|
||||
break
|
||||
default:
|
||||
console.error("unknown nostr request", message)
|
||||
|
|
@ -65,12 +68,12 @@ const initSubprocessHandler = (settings: NostrSettings) => {
|
|||
})
|
||||
})
|
||||
}
|
||||
const sendToNostr = (appId: string, pub: string, message: string) => {
|
||||
const sendToNostr: NostrSend = (appId, data, relays) => {
|
||||
if (!subProcessHandler) {
|
||||
console.error("nostr was not initialized")
|
||||
return
|
||||
}
|
||||
subProcessHandler.Send(appId, pub, message)
|
||||
subProcessHandler.Send(appId, data, relays)
|
||||
}
|
||||
send({ type: 'ready' })
|
||||
|
||||
|
|
@ -123,19 +126,25 @@ export default class Handler {
|
|||
this.subs.push(sub)
|
||||
}
|
||||
|
||||
async Send(appId: string, pubKey: string, message: string) {
|
||||
async Send(appId: string, data: SendData, relays?: string[]) {
|
||||
const appInfo = this.GetAppKeys({ appId })
|
||||
const decoded = await encryptData(message, getSharedSecret(appInfo.privateKey, pubKey))
|
||||
let toSign: UnsignedEvent
|
||||
if (data.type === 'content') {
|
||||
const decoded = await encryptData(data.content, getSharedSecret(appInfo.privateKey, data.pub))
|
||||
const content = encodePayload(decoded)
|
||||
const event: UnsignedEvent = {
|
||||
toSign = {
|
||||
content,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 4,
|
||||
pubkey: appInfo.publicKey,
|
||||
tags: [['p', pubKey]],
|
||||
tags: [['p', data.pub]],
|
||||
}
|
||||
const signed = finishEvent(event, appInfo.privateKey)
|
||||
this.pool.publish(this.settings.relays, signed).forEach(p => {
|
||||
} else {
|
||||
toSign = data.event
|
||||
}
|
||||
|
||||
const signed = finishEvent(toSign, appInfo.privateKey)
|
||||
this.pool.publish(relays || this.settings.relays, signed).forEach(p => {
|
||||
p.then(() => console.log("sent ok"))
|
||||
p.catch(() => console.log("failed to send"))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ChildProcess, fork } from 'child_process'
|
||||
import { EnvMustBeNonEmptyString } from "../helpers/envParser.js"
|
||||
import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse } from "./handler.js"
|
||||
import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse, SendData } from "./handler.js"
|
||||
type EventCallback = (event: NostrEvent) => void
|
||||
export const LoadNosrtSettingsFromEnv = (test = false) => {
|
||||
return {
|
||||
|
|
@ -31,8 +31,8 @@ export default class NostrSubprocess {
|
|||
this.childProcess.send(message)
|
||||
}
|
||||
|
||||
Send(appId: string, pub: string, message: string) {
|
||||
this.sendToChildProcess({ type: 'send', pub, message, appId })
|
||||
Send(appId: string, data: SendData, relays?: string[]) {
|
||||
this.sendToChildProcess({ type: 'send', data, appId, relays })
|
||||
}
|
||||
Stop() {
|
||||
this.childProcess.kill()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import Main from '../main/index.js'
|
||||
export default (mainHandler: Main): Types.ServerMethods => {
|
||||
return {
|
||||
|
|
@ -70,13 +71,7 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
return mainHandler.paymentManager.GetLnurlPayInfo(ctx.k1)
|
||||
},
|
||||
HandleLnurlPay: async (ctx) => {
|
||||
if (!ctx.k1 || !ctx.amount) {
|
||||
throw new Error("invalid lnurl pay to handle")
|
||||
}
|
||||
if (isNaN(+ctx.amount)) {
|
||||
throw new Error("invalid amount in lnurl pay to handle")
|
||||
}
|
||||
return mainHandler.paymentManager.HandleLnurlPay(ctx.k1, +ctx.amount)
|
||||
return mainHandler.paymentManager.HandleLnurlPay(ctx)
|
||||
},
|
||||
AddProduct: async (ctx, req) => {
|
||||
return mainHandler.productManager.AddProduct(ctx.user_id, req)
|
||||
|
|
@ -178,7 +173,6 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
await mainHandler.applicationManager.SetMockAppBalance(ctx.app_id, req)
|
||||
},
|
||||
GetLiveUserOperations: async (ctx, cb) => {
|
||||
mainHandler.SubToPayment(ctx, cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -119,8 +119,8 @@ export default class {
|
|||
return found
|
||||
}
|
||||
|
||||
async IsApplicationUser(userId: string, entityManager = this.DB): Promise<ApplicationUser | null> {
|
||||
return await entityManager.getRepository(ApplicationUser).findOne({ where: { user: { user_id: userId } } })
|
||||
async GetAppUserFromUser(application: Application, userId: string, entityManager = this.DB): Promise<ApplicationUser | null> {
|
||||
return await entityManager.getRepository(ApplicationUser).findOne({ where: { user: { user_id: userId }, application: { app_id: application.app_id } } })
|
||||
}
|
||||
|
||||
async IsApplicationOwner(userId: string, entityManager = this.DB) {
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ export class Application {
|
|||
allow_user_creation: boolean
|
||||
|
||||
@Column({ nullable: true, unique: true })
|
||||
nostr_private_key: string
|
||||
nostr_private_key?: string
|
||||
|
||||
@Column({ nullable: true, unique: true })
|
||||
nostr_public_key: string
|
||||
nostr_public_key?: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export class ApplicationUser {
|
|||
identifier: string
|
||||
|
||||
@Column({ nullable: true, unique: true })
|
||||
nostr_public_key: string
|
||||
nostr_public_key?: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
|
|
|||
|
|
@ -2,7 +2,12 @@ import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinCo
|
|||
import { Product } from "./Product.js"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
|
||||
export type ZapInfo = {
|
||||
pub: string
|
||||
eventId: string
|
||||
relays: string[]
|
||||
description: string
|
||||
}
|
||||
@Entity()
|
||||
export class UserReceivingInvoice {
|
||||
|
||||
|
|
@ -47,6 +52,12 @@ export class UserReceivingInvoice {
|
|||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: 'simple-json'
|
||||
})
|
||||
zap_info?: ZapInfo
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { DataSource, EntityManager, MoreThan, MoreThanOrEqual } from "typeorm"
|
|||
import { User } from './entity/User.js';
|
||||
import { UserTransactionPayment } from './entity/UserTransactionPayment.js';
|
||||
import { EphemeralKeyType, UserEphemeralKey } from './entity/UserEphemeralKey.js';
|
||||
import { UserReceivingInvoice } from './entity/UserReceivingInvoice.js';
|
||||
import { UserReceivingInvoice, ZapInfo } from './entity/UserReceivingInvoice.js';
|
||||
import { UserReceivingAddress } from './entity/UserReceivingAddress.js';
|
||||
import { Product } from './entity/Product.js';
|
||||
import UserStorage from './userStorage.js';
|
||||
|
|
@ -11,7 +11,7 @@ import { AddressReceivingTransaction } from './entity/AddressReceivingTransactio
|
|||
import { UserInvoicePayment } from './entity/UserInvoicePayment.js';
|
||||
import { UserToUserPayment } from './entity/UserToUserPayment.js';
|
||||
import { Application } from './entity/Application.js';
|
||||
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application }
|
||||
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application, zapInfo?: ZapInfo }
|
||||
export const defaultInvoiceExpiry = 60 * 60
|
||||
export default class {
|
||||
DB: DataSource | EntityManager
|
||||
|
|
@ -89,7 +89,8 @@ export default class {
|
|||
product: options.product,
|
||||
expires_at_unix: Math.floor(Date.now() / 1000) + options.expiry,
|
||||
payer: options.expectedPayer,
|
||||
linkedApplication: options.linkedApplication
|
||||
linkedApplication: options.linkedApplication,
|
||||
zap_info: options.zapInfo
|
||||
})
|
||||
return entityManager.getRepository(UserReceivingInvoice).save(newUserInvoice)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue