From c909ad3172409df1bb26bf8537caaf82809efb7c Mon Sep 17 00:00:00 2001 From: boufni95 Date: Mon, 7 Oct 2024 17:09:47 +0000 Subject: [PATCH 1/3] zaps in nip69 --- package-lock.json | 6 +++--- package.json | 2 +- proto/autogenerated/client.md | 1 + proto/autogenerated/go/types.go | 1 + proto/autogenerated/ts/types.ts | 10 ++++++++-- proto/service/structs.proto | 1 + src/services/main/applicationManager.ts | 7 ++++++- src/services/main/index.ts | 9 ++++----- 8 files changed, 25 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index c3c48fc1..b83e319b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "grpc-tools": "^1.12.4", "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", - "nostr-tools": "github:shocknet/nostr-tools#19271c4bcc9ff9bf18f9208e2d9fd6870e5f350c", + "nostr-tools": "github:shocknet/nostr-tools#da188cd4bd195f44cc690074a3898f354ae85100", "pg": "^8.4.0", "reflect-metadata": "^0.2.2", "rimraf": "^3.0.2", @@ -3803,8 +3803,8 @@ }, "node_modules/nostr-tools": { "version": "2.8.0", - "resolved": "git+ssh://git@github.com/shocknet/nostr-tools.git#19271c4bcc9ff9bf18f9208e2d9fd6870e5f350c", - "integrity": "sha512-NWYu4yx9UELd4M333r6Eirg0lKK9vyEiUW/8DDorZtxSlBdH4B9FqH8w7VJrG2LfPJABC89EH6+VNLz7RENCfg==", + "resolved": "git+ssh://git@github.com/shocknet/nostr-tools.git#da188cd4bd195f44cc690074a3898f354ae85100", + "integrity": "sha512-kc41K75rXEnLhqIwlQmjaGsZ9yYTbyP8VW7B2Q+0U/pqaMyt25Nt0QCWiIYS04m0sanvD77OhmddvI1s2ntKog==", "license": "Unlicense", "dependencies": { "@noble/ciphers": "^0.5.1", diff --git a/package.json b/package.json index 635e9b84..388b1bda 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "grpc-tools": "^1.12.4", "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", - "nostr-tools": "github:shocknet/nostr-tools#19271c4bcc9ff9bf18f9208e2d9fd6870e5f350c", + "nostr-tools": "github:shocknet/nostr-tools#da188cd4bd195f44cc690074a3898f354ae85100", "pg": "^8.4.0", "reflect-metadata": "^0.2.2", "rimraf": "^3.0.2", diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 3e3eaca3..4075d92b 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -976,6 +976,7 @@ The nostr server will send back a message response, and inside the body there wi ### NewInvoiceRequest - __amountSats__: _number_ - __memo__: _string_ + - __zap__: _string_ *this field is optional ### NewInvoiceResponse - __invoice__: _string_ diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index 1f29f6b5..7abc7008 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -342,6 +342,7 @@ type NewAddressResponse struct { type NewInvoiceRequest struct { Amountsats int64 `json:"amountSats"` Memo string `json:"memo"` + Zap string `json:"zap"` } type NewInvoiceResponse struct { Invoice string `json:"invoice"` diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index a142deca..ff1bc5ff 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -1949,12 +1949,15 @@ export const NewAddressResponseValidate = (o?: NewAddressResponse, opts: NewAddr export type NewInvoiceRequest = { amountSats: number memo: string + zap?: string } -export const NewInvoiceRequestOptionalFields: [] = [] +export type NewInvoiceRequestOptionalField = 'zap' +export const NewInvoiceRequestOptionalFields: NewInvoiceRequestOptionalField[] = ['zap'] export type NewInvoiceRequestOptions = OptionsBaseMessage & { - checkOptionalsAreSet?: [] + checkOptionalsAreSet?: NewInvoiceRequestOptionalField[] amountSats_CustomCheck?: (v: number) => boolean memo_CustomCheck?: (v: string) => boolean + zap_CustomCheck?: (v?: string) => boolean } export const NewInvoiceRequestValidate = (o?: NewInvoiceRequest, opts: NewInvoiceRequestOptions = {}, path: string = 'NewInvoiceRequest::root.'): Error | null => { if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') @@ -1966,6 +1969,9 @@ export const NewInvoiceRequestValidate = (o?: NewInvoiceRequest, opts: NewInvoic if (typeof o.memo !== 'string') return new Error(`${path}.memo: is not a string`) if (opts.memo_CustomCheck && !opts.memo_CustomCheck(o.memo)) return new Error(`${path}.memo: custom check failed`) + if ((o.zap || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('zap')) && typeof o.zap !== 'string') return new Error(`${path}.zap: is not a string`) + if (opts.zap_CustomCheck && !opts.zap_CustomCheck(o.zap)) return new Error(`${path}.zap: custom check failed`) + return null } diff --git a/proto/service/structs.proto b/proto/service/structs.proto index 1d320f9e..5cf33160 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -270,6 +270,7 @@ message PayAddressResponse{ message NewInvoiceRequest{ int64 amountSats = 1; string memo = 2; + optional string zap = 3; } message NewInvoiceResponse{ diff --git a/src/services/main/applicationManager.ts b/src/services/main/applicationManager.ts index 97fa8448..0f23d691 100644 --- a/src/services/main/applicationManager.ts +++ b/src/services/main/applicationManager.ts @@ -10,6 +10,7 @@ import crypto from 'crypto' import { Application } from '../storage/entity/Application.js' import { nip69, nip19 } from 'nostr-tools' import { LoadNosrtSettingsFromEnv } from '../nostr/index.js' +import { ZapInfo } from '../storage/entity/UserReceivingInvoice.js' const { SendNofferRequest } = nip69 const { nofferEncode, ndebitEncode, OfferPriceType } = nip19 const TOKEN_EXPIRY_TIME = 2 * 60 * 1000 // 2 minutes, in milliseconds @@ -187,7 +188,11 @@ export default class { const receiver = await this.storage.applicationStorage.GetApplicationUser(app, req.receiver_identifier) const { user: payer } = await this.storage.applicationStorage.GetOrCreateApplicationUser(app, req.payer_identifier, 0) const cbUrl = req.http_callback_url || receiver.callback_url || "" - const opts: InboundOptionals = { callbackUrl: cbUrl, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app } + let zapInfo: ZapInfo | undefined = undefined + if (req.invoice_req.zap) { + zapInfo = this.paymentManager.validateZapEvent(req.invoice_req.zap, req.invoice_req.amountSats) + } + const opts: InboundOptionals = { callbackUrl: cbUrl, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app, zapInfo } const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts) return { invoice: appUserInvoice.invoice diff --git a/src/services/main/index.ts b/src/services/main/index.ts index 097418df..a4528b76 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -10,7 +10,7 @@ import { AddressPaidCb, HtlcCb, InvoicePaidCb, NewBlockCb } from "../lnd/setting import { ERROR, getLogger, PubLogger } from "../helpers/logger.js" import AppUserManager from "./appUserManager.js" import { Application } from '../storage/entity/Application.js' -import { UserReceivingInvoice } from '../storage/entity/UserReceivingInvoice.js' +import { UserReceivingInvoice, ZapInfo } from '../storage/entity/UserReceivingInvoice.js' import { UnsignedEvent } from 'nostr-tools' import { NostrEvent, NostrSend } from '../nostr/handler.js' import MetricsManager from '../metrics/index.js' @@ -23,6 +23,7 @@ import { AdminManager } from "./adminManager.js" import { Unlocker } from "./unlocker.js" import { defaultInvoiceExpiry } from "../storage/paymentStorage.js" import { DebitManager } from "./debitManager.js" +import { NofferData } from "nostr-tools/lib/types/nip69.js" type UserOperationsSub = { id: string @@ -32,7 +33,6 @@ type UserOperationsSub = { newOutgoingTx: (operation: Types.UserOperation) => void } const appTag = "Lightning.Pub" -export type NofferData = { offer: string, amount?: number } export default class { storage: Storage @@ -283,6 +283,7 @@ export default class { async getNofferInvoice(offerReq: NofferData, appId: string): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> { try { + const { remote } = await this.lnd.ChannelBalance() const { offer, amount } = offerReq const split = offer.split(':') @@ -292,7 +293,7 @@ export default class { } const res = await this.applicationManager.AddAppUserInvoice(appId, { http_callback_url: "", payer_identifier: split[0], receiver_identifier: split[0], - invoice_req: { amountSats: amount, memo: "Default NIP-69 Offer" } + invoice_req: { amountSats: amount, memo: "Default NIP-69 Offer", zap: offerReq.zap } }) return { success: true, invoice: res.invoice } } else if (split[0] === 'p') { @@ -319,8 +320,6 @@ export default class { this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } }) return } - - } const codeToMessage = (code: number) => { From c5b55817640fcb3ff7019879067a6207021aff06 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Mon, 7 Oct 2024 17:25:34 +0000 Subject: [PATCH 2/3] fix --- src/nostrMiddleware.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nostrMiddleware.ts b/src/nostrMiddleware.ts index 44b6add7..19a138af 100644 --- a/src/nostrMiddleware.ts +++ b/src/nostrMiddleware.ts @@ -1,10 +1,11 @@ -import Main, { NofferData } from "./services/main/index.js" +import Main from "./services/main/index.js" import Nostr from "./services/nostr/index.js" import { NostrEvent, 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'; import { ERROR, getLogger } from "./services/helpers/logger.js"; import { NdebitData } from "nostr-tools/lib/types/nip68.js"; +import { NofferData } from "nostr-tools/lib/types/nip69.js"; export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend } => { const log = getLogger({}) From d31466c0b8d159a01b03694b73fc5a47bb3fa0f7 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Mon, 7 Oct 2024 20:21:15 +0000 Subject: [PATCH 3/3] send zap to relays --- src/services/main/index.ts | 2 +- src/services/main/paymentManager.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/main/index.ts b/src/services/main/index.ts index a4528b76..368c378d 100644 --- a/src/services/main/index.ts +++ b/src/services/main/index.ts @@ -278,7 +278,7 @@ export default class { tags, } log({ unsigned: event }) - this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }) + this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined) } async getNofferInvoice(offerReq: NofferData, appId: string): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> { diff --git a/src/services/main/paymentManager.ts b/src/services/main/paymentManager.ts index 91f27554..a0079686 100644 --- a/src/services/main/paymentManager.ts +++ b/src/services/main/paymentManager.ts @@ -571,6 +571,9 @@ export default class { if (!verified) { throw new Error("nostr event not valid") } + if (nostrEvent.kind !== 9734) { + throw new Error("nostr event not a zap event") + } 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 })