Merge pull request #746 from shocknet/debits

debits draft (pre db)
This commit is contained in:
Justin (shocknet) 2024-10-06 14:35:52 -04:00 committed by GitHub
commit fb17b05441
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 10182 additions and 7105 deletions

View file

@ -15,6 +15,7 @@ import { LspOrder } from "./build/src/services/storage/entity/LspOrder.js"
import { LndNodeInfo } from "./build/src/services/storage/entity/LndNodeInfo.js"
import { TrackedProvider } from "./build/src/services/storage/entity/TrackedProvider.js"
import { InviteToken } from "./build/src/services/storage/entity/InviteToken.js"
import { DebitAccess } from "./build/src/services/storage/entity/DebitAccess.js"
import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js'
import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js'
@ -22,13 +23,17 @@ import { LndNodeInfo1720187506189 } from './build/src/services/storage/migration
import { LiquidityProvider1719335699480 } from './build/src/services/storage/migrations/1719335699480-liquidity_provider.js'
import { CreateInviteTokenTable1721751414878 } from './build/src/services/storage/migrations/1721751414878-create_invite_token_table.js'
import { PaymentIndex1721760297610 } from './build/src/services/storage/migrations/1721760297610-payment_index.js'
import { DebitAccess1726496225078 } from './build/src/services/storage/migrations/1726496225078-debit_access.js'
import { DebitAccessFixes1726685229264 } from './build/src/services/storage/migrations/1726685229264-debit_access_fixes.js'
import { DebitToPub1727105758354 } from './build/src/services/storage/migrations/1727105758354-debit_to_pub.js'
import { UserCbUrl1727112281043 } from './build/src/services/storage/migrations/1727112281043-user_cb_url.js'
export default new DataSource({
type: "sqlite",
database: "db.sqlite",
// logging: true,
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610],
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043],
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, InviteToken],
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, InviteToken, DebitAccess],
// synchronize: true,
})
//npx typeorm migration:generate ./src/services/storage/migrations/lnd_node_info -d ./datasource.js
//npx typeorm migration:generate ./src/services/storage/migrations/usert_cb_url -d ./datasource.js

11629
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@
"test": "npm run clean && tsc && node build/src/tests/testRunner.js",
"start": "npm run clean && tsc && node build/src/index.js",
"start:ci": "git reset --hard && git pull && npm run start",
"gen": "cd proto && rimraf autogenerated && export PATH=$PATH:~/Lightning.Pub/proto && protoc -I ./service --pub_out=. service/*",
"build_autogenerated": "cd proto && rimraf autogenerated && protoc -I ./service --pub_out=. service/*",
"build_lnd_client_1": "cd proto && protoc -I ./others --plugin=.\\node_modules\\.bin\\protoc-gen-ts_proto.cmd --ts_proto_out=./lnd --ts_proto_opt=esModuleInterop=true others/* ",
"build_lnd_client": "cd proto && rimraf lnd/* && npx protoc --ts_out ./lnd --ts_opt long_type_string --proto_path others others/* ",
@ -48,7 +49,7 @@
"grpc-tools": "^1.12.4",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"nostr-tools": "^1.9.0",
"nostr-tools": "github:shocknet/nostr-tools#19271c4bcc9ff9bf18f9208e2d9fd6870e5f350c",
"pg": "^8.4.0",
"reflect-metadata": "^0.2.2",
"rimraf": "^3.0.2",
@ -62,7 +63,8 @@
"uuid": "^8.3.2",
"websocket": "^1.0.34",
"websocket-polyfill": "^0.0.3",
"why-is-node-running": "^3.2.0"
"why-is-node-running": "^3.2.0",
"ws": "^8.18.0"
},
"devDependencies": {
"@types/chai": "^4.3.4",
@ -75,8 +77,9 @@
"@types/node-fetch": "^2.6.3",
"@types/uuid": "^8.3.4",
"@types/websocket": "^1.0.6",
"@types/ws": "^8.5.12",
"nodemon": "^2.0.20",
"ts-node": "10.7.0",
"typescript": "5.5.4"
}
}
}

View file

@ -28,6 +28,16 @@ The nostr server will send back a message response, and inside the body there wi
- input: [AuthAppRequest](#AuthAppRequest)
- output: [AuthApp](#AuthApp)
- AuthorizeDebit
- auth type: __User__
- input: [DebitAuthorizationRequest](#DebitAuthorizationRequest)
- output: [DebitAuthorization](#DebitAuthorization)
- BanDebit
- auth type: __User__
- input: [DebitOperation](#DebitOperation)
- This methods has an __empty__ __response__ body
- BanUser
- auth type: __Admin__
- input: [BanUserRequest](#BanUserRequest)
@ -48,6 +58,11 @@ The nostr server will send back a message response, and inside the body there wi
- input: [DecodeInvoiceRequest](#DecodeInvoiceRequest)
- output: [DecodeInvoiceResponse](#DecodeInvoiceResponse)
- EditDebit
- auth type: __User__
- input: [DebitAuthorizationRequest](#DebitAuthorizationRequest)
- This methods has an __empty__ __response__ body
- EnrollAdminToken
- auth type: __User__
- input: [EnrollAdminTokenRequest](#EnrollAdminTokenRequest)
@ -58,6 +73,11 @@ The nostr server will send back a message response, and inside the body there wi
- input: [AppsMetricsRequest](#AppsMetricsRequest)
- output: [AppsMetrics](#AppsMetrics)
- GetDebitAuthorizations
- auth type: __User__
- This methods has an __empty__ __request__ body
- output: [DebitAuthorizations](#DebitAuthorizations)
- GetHttpCreds
- auth type: __User__
- This methods has an __empty__ __request__ body
@ -73,6 +93,11 @@ The nostr server will send back a message response, and inside the body there wi
- This methods has an __empty__ __request__ body
- output: [LnurlLinkResponse](#LnurlLinkResponse)
- GetLiveDebitRequests
- auth type: __User__
- This methods has an __empty__ __request__ body
- output: [LiveDebitRequest](#LiveDebitRequest)
- GetLiveUserOperations
- auth type: __User__
- This methods has an __empty__ __request__ body
@ -170,6 +195,21 @@ The nostr server will send back a message response, and inside the body there wi
- input: [PayInvoiceRequest](#PayInvoiceRequest)
- output: [PayInvoiceResponse](#PayInvoiceResponse)
- ResetDebit
- auth type: __User__
- input: [DebitOperation](#DebitOperation)
- This methods has an __empty__ __response__ body
- RespondToDebit
- auth type: __User__
- input: [DebitResponse](#DebitResponse)
- This methods has an __empty__ __response__ body
- UpdateCallbackUrl
- auth type: __User__
- input: [CallbackUrl](#CallbackUrl)
- output: [CallbackUrl](#CallbackUrl)
- UseInviteLink
- auth type: __GuestWithPub__
- input: [UseInviteLinkRequest](#UseInviteLinkRequest)
@ -256,6 +296,20 @@ The nostr server will send back a message response, and inside the body there wi
- input: [AuthAppRequest](#AuthAppRequest)
- output: [AuthApp](#AuthApp)
- AuthorizeDebit
- auth type: __User__
- http method: __post__
- http route: __/api/user/debit/authorize__
- input: [DebitAuthorizationRequest](#DebitAuthorizationRequest)
- output: [DebitAuthorization](#DebitAuthorization)
- BanDebit
- auth type: __User__
- http method: __post__
- http route: __/api/user/debit/ban__
- input: [DebitOperation](#DebitOperation)
- This methods has an __empty__ __response__ body
- BanUser
- auth type: __Admin__
- http method: __post__
@ -284,6 +338,13 @@ The nostr server will send back a message response, and inside the body there wi
- input: [DecodeInvoiceRequest](#DecodeInvoiceRequest)
- output: [DecodeInvoiceResponse](#DecodeInvoiceResponse)
- EditDebit
- auth type: __User__
- http method: __post__
- http route: __/api/user/debit/edit__
- input: [DebitAuthorizationRequest](#DebitAuthorizationRequest)
- This methods has an __empty__ __response__ body
- EncryptionExchange
- auth type: __Guest__
- http method: __post__
@ -326,6 +387,13 @@ The nostr server will send back a message response, and inside the body there wi
- input: [AppsMetricsRequest](#AppsMetricsRequest)
- output: [AppsMetrics](#AppsMetrics)
- GetDebitAuthorizations
- auth type: __User__
- http method: __get__
- http route: __/api/user/debit/get__
- This methods has an __empty__ __request__ body
- output: [DebitAuthorizations](#DebitAuthorizations)
- GetHttpCreds
- auth type: __User__
- http method: __post__
@ -347,6 +415,13 @@ The nostr server will send back a message response, and inside the body there wi
- This methods has an __empty__ __request__ body
- output: [LnurlLinkResponse](#LnurlLinkResponse)
- GetLiveDebitRequests
- auth type: __User__
- http method: __post__
- http route: __/api/user/debit/sub__
- This methods has an __empty__ __request__ body
- output: [LiveDebitRequest](#LiveDebitRequest)
- GetLiveUserOperations
- auth type: __User__
- http method: __post__
@ -552,6 +627,13 @@ The nostr server will send back a message response, and inside the body there wi
- input: [RequestNPubLinkingTokenRequest](#RequestNPubLinkingTokenRequest)
- output: [RequestNPubLinkingTokenResponse](#RequestNPubLinkingTokenResponse)
- ResetDebit
- auth type: __User__
- http method: __post__
- http route: __/api/user/debit/reset__
- input: [DebitOperation](#DebitOperation)
- This methods has an __empty__ __response__ body
- ResetNPubLinkingToken
- auth type: __App__
- http method: __post__
@ -559,6 +641,13 @@ The nostr server will send back a message response, and inside the body there wi
- input: [RequestNPubLinkingTokenRequest](#RequestNPubLinkingTokenRequest)
- output: [RequestNPubLinkingTokenResponse](#RequestNPubLinkingTokenResponse)
- RespondToDebit
- auth type: __User__
- http method: __post__
- http route: __/api/user/debit/finish__
- input: [DebitResponse](#DebitResponse)
- This methods has an __empty__ __response__ body
- SendAppUserToAppPayment
- auth type: __App__
- http method: __post__
@ -594,6 +683,13 @@ The nostr server will send back a message response, and inside the body there wi
- input: [SetMockInvoiceAsPaidRequest](#SetMockInvoiceAsPaidRequest)
- This methods has an __empty__ __response__ body
- UpdateCallbackUrl
- auth type: __User__
- http method: __post__
- http route: __/api/user/cb/update__
- input: [CallbackUrl](#CallbackUrl)
- output: [CallbackUrl](#CallbackUrl)
- UseInviteLink
- auth type: __GuestWithPub__
- http method: __post__
@ -688,6 +784,9 @@ The nostr server will send back a message response, and inside the body there wi
- __nostr_pub__: _string_
- __user_identifier__: _string_
### CallbackUrl
- __url__: _string_
### ClosedChannel
- __capacity__: _number_
- __channel_id__: _string_
@ -702,6 +801,34 @@ The nostr server will send back a message response, and inside the body there wi
### CreateOneTimeInviteLinkResponse
- __invitation_link__: _string_
### DebitAuthorization
- __authorized__: _boolean_
- __debit_id__: _string_
- __npub__: _string_
- __rules__: ARRAY of: _[DebitRule](#DebitRule)_
### DebitAuthorizationRequest
- __authorize_npub__: _string_
- __request_id__: _string_ *this field is optional
- __rules__: ARRAY of: _[DebitRule](#DebitRule)_
### DebitAuthorizations
- __debits__: ARRAY of: _[DebitAuthorization](#DebitAuthorization)_
### DebitExpirationRule
- __expires_at_unix__: _number_
### DebitOperation
- __npub__: _string_
### DebitResponse
- __npub__: _string_
- __request_id__: _string_
- __response__: _[DebitResponse_response](#DebitResponse_response)_
### DebitRule
- __rule__: _[DebitRule_rule](#DebitRule_rule)_
### DecodeInvoiceRequest
- __invoice__: _string_
@ -717,6 +844,11 @@ The nostr server will send back a message response, and inside the body there wi
### EnrollAdminTokenRequest
- __admin_token__: _string_
### FrequencyRule
- __amount__: _number_
- __interval__: _[IntervalType](#IntervalType)_
- __number_of_intervals__: _number_
### GetAppUserLNURLInfoRequest
- __base_url_override__: _string_
- __user_identifier__: _string_
@ -768,6 +900,11 @@ The nostr server will send back a message response, and inside the body there wi
### LinkNPubThroughTokenRequest
- __token__: _string_
### LiveDebitRequest
- __debit__: _[LiveDebitRequest_debit](#LiveDebitRequest_debit)_
- __npub__: _string_
- __request_id__: _string_
### LiveUserOperation
- __operation__: _[UserOperation](#UserOperation)_
@ -874,11 +1011,13 @@ The nostr server will send back a message response, and inside the body there wi
### PayAppUserInvoiceRequest
- __amount__: _number_
- __debit_npub__: _string_ *this field is optional
- __invoice__: _string_
- __user_identifier__: _string_
### PayInvoiceRequest
- __amount__: _number_
- __debit_npub__: _string_ *this field is optional
- __invoice__: _string_
### PayInvoiceResponse
@ -963,7 +1102,9 @@ The nostr server will send back a message response, and inside the body there wi
### UserInfo
- __balance__: _number_
- __bridge_url__: _string_
- __callback_url__: _string_
- __max_withdrawable__: _number_
- __ndebit__: _string_
- __network_max_fee_bps__: _number_
- __network_max_fee_fixed__: _number_
- __noffer__: _string_
@ -1004,6 +1145,11 @@ The nostr server will send back a message response, and inside the body there wi
- __TAPROOT_PUBKEY__
- __WITNESS_PUBKEY_HASH__
### IntervalType
- __DAY__
- __MONTH__
- __WEEK__
### UserOperationType
- __INCOMING_INVOICE__
- __INCOMING_TX__

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,544 @@
// This file was autogenerated from a .proto file, DO NOT EDIT!
package lightning_pub
type ResultError struct {
Status string `json:"status"`
Reason string `json:"reason"`
}
type AdminContext struct {
Admin_id string `json:"admin_id"`
}
type AppContext struct {
App_id string `json:"app_id"`
}
type GuestContext struct {
}
type GuestWithPubContext struct {
App_id string `json:"app_id"`
Pub string `json:"pub"`
}
type MetricsContext struct {
Operator_id string `json:"operator_id"`
}
type UserContext struct {
App_id string `json:"app_id"`
App_user_id string `json:"app_user_id"`
User_id string `json:"user_id"`
}
type GetLnurlPayInfo_Query struct {
K1 *string `json:"k1,omitempty"`
}
type GetLnurlWithdrawInfo_Query struct {
K1 *string `json:"k1,omitempty"`
}
type HandleLnurlAddress_RouteParams struct {
Address_name string `json:"address_name"`
}
type HandleLnurlPay_Query struct {
Amount *string `json:"amount,omitempty"`
K1 *string `json:"k1,omitempty"`
Lnurl *string `json:"lnurl,omitempty"`
Nostr *string `json:"nostr,omitempty"`
}
type HandleLnurlWithdraw_Query struct {
K1 *string `json:"k1,omitempty"`
Pr *string `json:"pr,omitempty"`
}
type NewProductInvoice_Query struct {
Id *string `json:"id,omitempty"`
}
type AddressType string
const (
NESTED_PUBKEY_HASH AddressType = "NESTED_PUBKEY_HASH"
TAPROOT_PUBKEY AddressType = "TAPROOT_PUBKEY"
WITNESS_PUBKEY_HASH AddressType = "WITNESS_PUBKEY_HASH"
)
type IntervalType string
const (
DAY IntervalType = "DAY"
MONTH IntervalType = "MONTH"
WEEK IntervalType = "WEEK"
)
type UserOperationType string
const (
INCOMING_INVOICE UserOperationType = "INCOMING_INVOICE"
INCOMING_TX UserOperationType = "INCOMING_TX"
INCOMING_USER_TO_USER UserOperationType = "INCOMING_USER_TO_USER"
OUTGOING_INVOICE UserOperationType = "OUTGOING_INVOICE"
OUTGOING_TX UserOperationType = "OUTGOING_TX"
OUTGOING_USER_TO_USER UserOperationType = "OUTGOING_USER_TO_USER"
)
type AddAppInvoiceRequest struct {
Http_callback_url string `json:"http_callback_url"`
Invoice_req *NewInvoiceRequest `json:"invoice_req"`
Payer_identifier string `json:"payer_identifier"`
}
type AddAppRequest struct {
Allow_user_creation bool `json:"allow_user_creation"`
Name string `json:"name"`
}
type AddAppUserInvoiceRequest struct {
Http_callback_url string `json:"http_callback_url"`
Invoice_req *NewInvoiceRequest `json:"invoice_req"`
Payer_identifier string `json:"payer_identifier"`
Receiver_identifier string `json:"receiver_identifier"`
}
type AddAppUserRequest struct {
Balance int64 `json:"balance"`
Fail_if_exists bool `json:"fail_if_exists"`
Identifier string `json:"identifier"`
}
type AddProductRequest struct {
Name string `json:"name"`
Price_sats int64 `json:"price_sats"`
}
type AppMetrics struct {
App *Application `json:"app"`
Available int64 `json:"available"`
Fees int64 `json:"fees"`
Invoices int64 `json:"invoices"`
Operations []UserOperation `json:"operations"`
Received int64 `json:"received"`
Spent int64 `json:"spent"`
Total_fees int64 `json:"total_fees"`
Users *UsersInfo `json:"users"`
}
type AppUser struct {
Identifier string `json:"identifier"`
Info *UserInfo `json:"info"`
Max_withdrawable int64 `json:"max_withdrawable"`
}
type Application struct {
Balance int64 `json:"balance"`
Id string `json:"id"`
Name string `json:"name"`
Npub string `json:"npub"`
}
type AppsMetrics struct {
Apps []AppMetrics `json:"apps"`
}
type AppsMetricsRequest struct {
From_unix int64 `json:"from_unix"`
Include_operations bool `json:"include_operations"`
To_unix int64 `json:"to_unix"`
}
type AuthApp struct {
App *Application `json:"app"`
Auth_token string `json:"auth_token"`
}
type AuthAppRequest struct {
Allow_user_creation bool `json:"allow_user_creation"`
Name string `json:"name"`
}
type BanUserRequest struct {
User_id string `json:"user_id"`
}
type BanUserResponse struct {
Balance_sats int64 `json:"balance_sats"`
Banned_app_users []BannedAppUser `json:"banned_app_users"`
}
type BannedAppUser struct {
App_id string `json:"app_id"`
App_name string `json:"app_name"`
Nostr_pub string `json:"nostr_pub"`
User_identifier string `json:"user_identifier"`
}
type CallbackUrl struct {
Url string `json:"url"`
}
type ClosedChannel struct {
Capacity int64 `json:"capacity"`
Channel_id string `json:"channel_id"`
Closed_height int64 `json:"closed_height"`
}
type ClosureMigration struct {
Closes_at_unix int64 `json:"closes_at_unix"`
}
type CreateOneTimeInviteLinkRequest struct {
Sats int64 `json:"sats"`
}
type CreateOneTimeInviteLinkResponse struct {
Invitation_link string `json:"invitation_link"`
}
type DebitAuthorization struct {
Authorized bool `json:"authorized"`
Debit_id string `json:"debit_id"`
Npub string `json:"npub"`
Rules []DebitRule `json:"rules"`
}
type DebitAuthorizationRequest struct {
Authorize_npub string `json:"authorize_npub"`
Request_id string `json:"request_id"`
Rules []DebitRule `json:"rules"`
}
type DebitAuthorizations struct {
Debits []DebitAuthorization `json:"debits"`
}
type DebitExpirationRule struct {
Expires_at_unix int64 `json:"expires_at_unix"`
}
type DebitOperation struct {
Npub string `json:"npub"`
}
type DebitResponse struct {
Npub string `json:"npub"`
Request_id string `json:"request_id"`
Response *DebitResponse_response `json:"response"`
}
type DebitRule struct {
Rule *DebitRule_rule `json:"rule"`
}
type DecodeInvoiceRequest struct {
Invoice string `json:"invoice"`
}
type DecodeInvoiceResponse struct {
Amount int64 `json:"amount"`
}
type Empty struct {
}
type EncryptionExchangeRequest struct {
Deviceid string `json:"deviceId"`
Publickey string `json:"publicKey"`
}
type EnrollAdminTokenRequest struct {
Admin_token string `json:"admin_token"`
}
type FrequencyRule struct {
Amount int64 `json:"amount"`
Interval IntervalType `json:"interval"`
Number_of_intervals int64 `json:"number_of_intervals"`
}
type GetAppUserLNURLInfoRequest struct {
Base_url_override string `json:"base_url_override"`
User_identifier string `json:"user_identifier"`
}
type GetAppUserRequest struct {
User_identifier string `json:"user_identifier"`
}
type GetInviteTokenStateRequest struct {
Invite_token string `json:"invite_token"`
}
type GetInviteTokenStateResponse struct {
Used bool `json:"used"`
}
type GetPaymentStateRequest struct {
Invoice string `json:"invoice"`
}
type GetProductBuyLinkResponse struct {
Link string `json:"link"`
}
type GetUserOperationsRequest struct {
Latestincominginvoice int64 `json:"latestIncomingInvoice"`
Latestincomingtx int64 `json:"latestIncomingTx"`
Latestincomingusertouserpayment int64 `json:"latestIncomingUserToUserPayment"`
Latestoutgoinginvoice int64 `json:"latestOutgoingInvoice"`
Latestoutgoingtx int64 `json:"latestOutgoingTx"`
Latestoutgoingusertouserpayment int64 `json:"latestOutgoingUserToUserPayment"`
Max_size int64 `json:"max_size"`
}
type GetUserOperationsResponse struct {
Latestincominginvoiceoperations *UserOperations `json:"latestIncomingInvoiceOperations"`
Latestincomingtxoperations *UserOperations `json:"latestIncomingTxOperations"`
Latestincomingusertouserpayemnts *UserOperations `json:"latestIncomingUserToUserPayemnts"`
Latestoutgoinginvoiceoperations *UserOperations `json:"latestOutgoingInvoiceOperations"`
Latestoutgoingtxoperations *UserOperations `json:"latestOutgoingTxOperations"`
Latestoutgoingusertouserpayemnts *UserOperations `json:"latestOutgoingUserToUserPayemnts"`
}
type GraphPoint struct {
X int64 `json:"x"`
Y int64 `json:"y"`
}
type HandleLnurlPayResponse struct {
Pr string `json:"pr"`
Routes []Empty `json:"routes"`
}
type HttpCreds struct {
Token string `json:"token"`
Url string `json:"url"`
}
type LinkNPubThroughTokenRequest struct {
Token string `json:"token"`
}
type LiveDebitRequest struct {
Debit *LiveDebitRequest_debit `json:"debit"`
Npub string `json:"npub"`
Request_id string `json:"request_id"`
}
type LiveUserOperation struct {
Operation *UserOperation `json:"operation"`
}
type LndChannels struct {
Open_channels []OpenChannel `json:"open_channels"`
}
type LndGetInfoRequest struct {
Nodeid int64 `json:"nodeId"`
}
type LndGetInfoResponse struct {
Alias string `json:"alias"`
}
type LndMetrics struct {
Nodes []LndNodeMetrics `json:"nodes"`
}
type LndMetricsRequest struct {
From_unix int64 `json:"from_unix"`
To_unix int64 `json:"to_unix"`
}
type LndNodeMetrics struct {
Chain_balance []GraphPoint `json:"chain_balance"`
Channel_balance []GraphPoint `json:"channel_balance"`
Closed_channels []ClosedChannel `json:"closed_channels"`
Closing_channels int64 `json:"closing_channels"`
External_balance []GraphPoint `json:"external_balance"`
Forwarding_events int64 `json:"forwarding_events"`
Forwarding_fees int64 `json:"forwarding_fees"`
Offline_channels int64 `json:"offline_channels"`
Online_channels int64 `json:"online_channels"`
Open_channels []OpenChannel `json:"open_channels"`
Pending_channels int64 `json:"pending_channels"`
}
type LndSeed struct {
Seed []string `json:"seed"`
}
type LnurlLinkResponse struct {
K1 string `json:"k1"`
Lnurl string `json:"lnurl"`
}
type LnurlPayInfoResponse struct {
Allowsnostr bool `json:"allowsNostr"`
Callback string `json:"callback"`
Maxsendable int64 `json:"maxSendable"`
Metadata string `json:"metadata"`
Minsendable int64 `json:"minSendable"`
Nostrpubkey string `json:"nostrPubkey"`
Tag string `json:"tag"`
}
type LnurlWithdrawInfoResponse struct {
Balancecheck string `json:"balanceCheck"`
Callback string `json:"callback"`
Defaultdescription string `json:"defaultDescription"`
K1 string `json:"k1"`
Maxwithdrawable int64 `json:"maxWithdrawable"`
Minwithdrawable int64 `json:"minWithdrawable"`
Paylink string `json:"payLink"`
Tag string `json:"tag"`
}
type MigrationUpdate struct {
Closure *ClosureMigration `json:"closure"`
Relays *RelaysMigration `json:"relays"`
}
type NewAddressRequest struct {
Addresstype AddressType `json:"addressType"`
}
type NewAddressResponse struct {
Address string `json:"address"`
}
type NewInvoiceRequest struct {
Amountsats int64 `json:"amountSats"`
Memo string `json:"memo"`
}
type NewInvoiceResponse struct {
Invoice string `json:"invoice"`
}
type OpenChannel struct {
Active bool `json:"active"`
Capacity int64 `json:"capacity"`
Channel_id string `json:"channel_id"`
Label string `json:"label"`
Lifetime int64 `json:"lifetime"`
Local_balance int64 `json:"local_balance"`
Remote_balance int64 `json:"remote_balance"`
}
type OpenChannelRequest struct {
Closeaddress string `json:"closeAddress"`
Destination string `json:"destination"`
Fundingamount int64 `json:"fundingAmount"`
Pushamount int64 `json:"pushAmount"`
}
type OpenChannelResponse struct {
Channelid string `json:"channelId"`
}
type PayAddressRequest struct {
Address string `json:"address"`
Amoutsats int64 `json:"amoutSats"`
Satspervbyte int64 `json:"satsPerVByte"`
}
type PayAddressResponse struct {
Network_fee int64 `json:"network_fee"`
Operation_id string `json:"operation_id"`
Service_fee int64 `json:"service_fee"`
Txid string `json:"txId"`
}
type PayAppUserInvoiceRequest struct {
Amount int64 `json:"amount"`
Debit_npub string `json:"debit_npub"`
Invoice string `json:"invoice"`
User_identifier string `json:"user_identifier"`
}
type PayInvoiceRequest struct {
Amount int64 `json:"amount"`
Debit_npub string `json:"debit_npub"`
Invoice string `json:"invoice"`
}
type PayInvoiceResponse struct {
Amount_paid int64 `json:"amount_paid"`
Network_fee int64 `json:"network_fee"`
Operation_id string `json:"operation_id"`
Preimage string `json:"preimage"`
Service_fee int64 `json:"service_fee"`
}
type PaymentState struct {
Amount int64 `json:"amount"`
Network_fee int64 `json:"network_fee"`
Paid_at_unix int64 `json:"paid_at_unix"`
Service_fee int64 `json:"service_fee"`
}
type Product struct {
Id string `json:"id"`
Name string `json:"name"`
Noffer string `json:"noffer"`
Price_sats int64 `json:"price_sats"`
}
type RelaysMigration struct {
Relays []string `json:"relays"`
}
type RequestNPubLinkingTokenRequest struct {
User_identifier string `json:"user_identifier"`
}
type RequestNPubLinkingTokenResponse struct {
Token string `json:"token"`
}
type RoutingEvent struct {
Event_type string `json:"event_type"`
Failure_string string `json:"failure_string"`
Forward_fail_event bool `json:"forward_fail_event"`
Incoming_amt_msat int64 `json:"incoming_amt_msat"`
Incoming_channel_id int64 `json:"incoming_channel_id"`
Incoming_htlc_id int64 `json:"incoming_htlc_id"`
Offchain bool `json:"offchain"`
Outgoing_amt_msat int64 `json:"outgoing_amt_msat"`
Outgoing_channel_id int64 `json:"outgoing_channel_id"`
Outgoing_htlc_id int64 `json:"outgoing_htlc_id"`
Settled bool `json:"settled"`
Timestamp_ns int64 `json:"timestamp_ns"`
}
type SendAppUserToAppPaymentRequest struct {
Amount int64 `json:"amount"`
From_user_identifier string `json:"from_user_identifier"`
}
type SendAppUserToAppUserPaymentRequest struct {
Amount int64 `json:"amount"`
From_user_identifier string `json:"from_user_identifier"`
To_user_identifier string `json:"to_user_identifier"`
}
type SetMockAppBalanceRequest struct {
Amount int64 `json:"amount"`
}
type SetMockAppUserBalanceRequest struct {
Amount int64 `json:"amount"`
User_identifier string `json:"user_identifier"`
}
type SetMockInvoiceAsPaidRequest struct {
Amount int64 `json:"amount"`
Invoice string `json:"invoice"`
}
type UsageMetric struct {
Auth_in_nano int64 `json:"auth_in_nano"`
Batch bool `json:"batch"`
Batch_size int64 `json:"batch_size"`
Handle_in_nano int64 `json:"handle_in_nano"`
Nostr bool `json:"nostr"`
Parsed_in_nano int64 `json:"parsed_in_nano"`
Processed_at_ms int64 `json:"processed_at_ms"`
Rpc_name string `json:"rpc_name"`
Validate_in_nano int64 `json:"validate_in_nano"`
}
type UsageMetrics struct {
Metrics []UsageMetric `json:"metrics"`
}
type UseInviteLinkRequest struct {
Invite_token string `json:"invite_token"`
}
type UserInfo struct {
Balance int64 `json:"balance"`
Bridge_url string `json:"bridge_url"`
Callback_url string `json:"callback_url"`
Max_withdrawable int64 `json:"max_withdrawable"`
Ndebit string `json:"ndebit"`
Network_max_fee_bps int64 `json:"network_max_fee_bps"`
Network_max_fee_fixed int64 `json:"network_max_fee_fixed"`
Noffer string `json:"noffer"`
Service_fee_bps int64 `json:"service_fee_bps"`
Userid string `json:"userId"`
User_identifier string `json:"user_identifier"`
}
type UserOperation struct {
Amount int64 `json:"amount"`
Confirmed bool `json:"confirmed"`
Identifier string `json:"identifier"`
Inbound bool `json:"inbound"`
Internal bool `json:"internal"`
Network_fee int64 `json:"network_fee"`
Operationid string `json:"operationId"`
Paidatunix int64 `json:"paidAtUnix"`
Service_fee int64 `json:"service_fee"`
Tx_hash string `json:"tx_hash"`
Type UserOperationType `json:"type"`
}
type UserOperations struct {
Fromindex int64 `json:"fromIndex"`
Operations []UserOperation `json:"operations"`
Toindex int64 `json:"toIndex"`
}
type UsersInfo struct {
Always_been_inactive int64 `json:"always_been_inactive"`
Balance_avg int64 `json:"balance_avg"`
Balance_median int64 `json:"balance_median"`
Negative_balance int64 `json:"negative_balance"`
No_balance int64 `json:"no_balance"`
Total int64 `json:"total"`
}
type DebitResponse_response_type string
const (
DENIED DebitResponse_response_type = "denied"
INVOICE DebitResponse_response_type = "invoice"
)
type DebitResponse_response struct {
Type DebitResponse_response_type `json:"type"`
Denied *Empty `json:"denied"`
Invoice *string `json:"invoice"`
}
type DebitRule_rule_type string
const (
EXPIRATION_RULE DebitRule_rule_type = "expiration_rule"
FREQUENCY_RULE DebitRule_rule_type = "frequency_rule"
)
type DebitRule_rule struct {
Type DebitRule_rule_type `json:"type"`
Expiration_rule *DebitExpirationRule `json:"expiration_rule"`
Frequency_rule *FrequencyRule `json:"frequency_rule"`
}
type LiveDebitRequest_debit_type string
const (
FREQUENCY LiveDebitRequest_debit_type = "frequency"
FULL_ACCESS LiveDebitRequest_debit_type = "full_access"
INVOICE LiveDebitRequest_debit_type = "invoice"
)
type LiveDebitRequest_debit struct {
Type LiveDebitRequest_debit_type `json:"type"`
Frequency *FrequencyRule `json:"frequency"`
Full_access *Empty `json:"full_access"`
Invoice *string `json:"invoice"`
}

View file

@ -166,6 +166,50 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AuthorizeDebit) throw new Error('method: AuthorizeDebit is not implemented')
app.post('/api/user/debit/authorize', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'AuthorizeDebit', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.AuthorizeDebit) throw new Error('method: AuthorizeDebit is not implemented')
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.DebitAuthorizationRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
const response = await methods.AuthorizeDebit({rpcName:'AuthorizeDebit', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.BanDebit) throw new Error('method: BanDebit is not implemented')
app.post('/api/user/debit/ban', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'BanDebit', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.BanDebit) throw new Error('method: BanDebit is not implemented')
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.DebitOperationValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
await methods.BanDebit({rpcName:'BanDebit', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.BanUser) throw new Error('method: BanUser is not implemented')
app.post('/api/admin/user/ban', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'BanUser', batch: false, nostr: false, batchSize: 0}
@ -221,6 +265,30 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'AuthorizeDebit':
if (!methods.AuthorizeDebit) {
throw new Error('method AuthorizeDebit not found' )
} else {
const error = Types.DebitAuthorizationRequestValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
const res = await methods.AuthorizeDebit({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'BanDebit':
if (!methods.BanDebit) {
throw new Error('method BanDebit not found' )
} else {
const error = Types.DebitOperationValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.BanDebit({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'DecodeInvoice':
if (!methods.DecodeInvoice) {
throw new Error('method DecodeInvoice not found' )
@ -233,6 +301,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'EditDebit':
if (!methods.EditDebit) {
throw new Error('method EditDebit not found' )
} else {
const error = Types.DebitAuthorizationRequestValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.EditDebit({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'EnrollAdminToken':
if (!methods.EnrollAdminToken) {
throw new Error('method EnrollAdminToken not found' )
@ -245,6 +325,16 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetDebitAuthorizations':
if (!methods.GetDebitAuthorizations) {
throw new Error('method GetDebitAuthorizations not found' )
} else {
opStats.validate = opStats.guard
const res = await methods.GetDebitAuthorizations({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetLNURLChannelLink':
if (!methods.GetLNURLChannelLink) {
throw new Error('method GetLNURLChannelLink not found' )
@ -379,6 +469,42 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'ResetDebit':
if (!methods.ResetDebit) {
throw new Error('method ResetDebit not found' )
} else {
const error = Types.DebitOperationValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.ResetDebit({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'RespondToDebit':
if (!methods.RespondToDebit) {
throw new Error('method RespondToDebit not found' )
} else {
const error = Types.DebitResponseValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.RespondToDebit({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'UpdateCallbackUrl':
if (!methods.UpdateCallbackUrl) {
throw new Error('method UpdateCallbackUrl not found' )
} else {
const error = Types.CallbackUrlValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
const res = await methods.UpdateCallbackUrl({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'UserHealth':
if (!methods.UserHealth) {
throw new Error('method UserHealth not found' )
@ -443,6 +569,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.EditDebit) throw new Error('method: EditDebit is not implemented')
app.post('/api/user/debit/edit', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'EditDebit', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.EditDebit) throw new Error('method: EditDebit is not implemented')
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.DebitAuthorizationRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
await methods.EditDebit({rpcName:'EditDebit', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.EncryptionExchange) throw new Error('method: EncryptionExchange is not implemented')
app.post('/api/encryption/exchange', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'EncryptionExchange', batch: false, nostr: false, batchSize: 0}
@ -572,6 +720,25 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetDebitAuthorizations) throw new Error('method: GetDebitAuthorizations is not implemented')
app.get('/api/user/debit/get', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetDebitAuthorizations', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.GetDebitAuthorizations) throw new Error('method: GetDebitAuthorizations is not implemented')
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
stats.validate = stats.guard
const query = req.query
const params = req.params
const response = await methods.GetDebitAuthorizations({rpcName:'GetDebitAuthorizations', ctx:authContext })
stats.handle = process.hrtime.bigint()
res.json({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetInviteLinkState) throw new Error('method: GetInviteLinkState is not implemented')
app.post('/api/admin/app/invite/get', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetInviteLinkState', batch: false, nostr: false, batchSize: 0}
@ -1124,6 +1291,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.ResetDebit) throw new Error('method: ResetDebit is not implemented')
app.post('/api/user/debit/reset', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'ResetDebit', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.ResetDebit) throw new Error('method: ResetDebit is not implemented')
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.DebitOperationValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
await methods.ResetDebit({rpcName:'ResetDebit', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.ResetNPubLinkingToken) throw new Error('method: ResetNPubLinkingToken is not implemented')
app.post('/api/app/user/npub/token/reset', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'ResetNPubLinkingToken', batch: false, nostr: false, batchSize: 0}
@ -1146,6 +1335,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.RespondToDebit) throw new Error('method: RespondToDebit is not implemented')
app.post('/api/user/debit/finish', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'RespondToDebit', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.RespondToDebit) throw new Error('method: RespondToDebit is not implemented')
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.DebitResponseValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
await methods.RespondToDebit({rpcName:'RespondToDebit', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SendAppUserToAppPayment) throw new Error('method: SendAppUserToAppPayment is not implemented')
app.post('/api/app/internal/pay', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'SendAppUserToAppPayment', batch: false, nostr: false, batchSize: 0}
@ -1256,6 +1467,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.UpdateCallbackUrl) throw new Error('method: UpdateCallbackUrl is not implemented')
app.post('/api/user/cb/update', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'UpdateCallbackUrl', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.UpdateCallbackUrl) throw new Error('method: UpdateCallbackUrl is not implemented')
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.CallbackUrlValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
const response = await methods.UpdateCallbackUrl({rpcName:'UpdateCallbackUrl', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.UseInviteLink) throw new Error('method: UseInviteLink is not implemented')
app.post('/api/guest/invite', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'UseInviteLink', batch: false, nostr: false, batchSize: 0}

View file

@ -101,6 +101,31 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
AuthorizeDebit: async (request: Types.DebitAuthorizationRequest): Promise<ResultError | ({ status: 'OK' }& Types.DebitAuthorization)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/debit/authorize'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.DebitAuthorizationValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
BanDebit: async (request: Types.DebitOperation): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/debit/ban'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
BanUser: async (request: Types.BanUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.BanUserResponse)> => {
const auth = await params.retrieveAdminAuth()
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
@ -154,6 +179,17 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
EditDebit: async (request: Types.DebitAuthorizationRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/debit/edit'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
EncryptionExchange: async (request: Types.EncryptionExchangeRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveGuestAuth()
if (auth === null) throw new Error('retrieveGuestAuth() returned null')
@ -232,6 +268,20 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetDebitAuthorizations: async (): Promise<ResultError | ({ status: 'OK' }& Types.DebitAuthorizations)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/debit/get'
const { data } = await axios.get(params.baseUrl + finalRoute, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.DebitAuthorizationsValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetHttpCreds: async (cb: (v:ResultError | ({ status: 'OK' }& Types.HttpCreds)) => void): Promise<void> => { throw new Error('http streams are not supported')},
GetInviteLinkState: async (request: Types.GetInviteTokenStateRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetInviteTokenStateResponse)> => {
const auth = await params.retrieveAdminAuth()
@ -261,6 +311,7 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetLiveDebitRequests: async (cb: (v:ResultError | ({ status: 'OK' }& Types.LiveDebitRequest)) => void): Promise<void> => { throw new Error('http streams are not supported')},
GetLiveUserOperations: async (cb: (v:ResultError | ({ status: 'OK' }& Types.LiveUserOperation)) => void): Promise<void> => { throw new Error('http streams are not supported')},
GetLndMetrics: async (request: Types.LndMetricsRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndMetrics)> => {
const auth = await params.retrieveMetricsAuth()
@ -615,6 +666,17 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
ResetDebit: async (request: Types.DebitOperation): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/debit/reset'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
ResetNPubLinkingToken: async (request: Types.RequestNPubLinkingTokenRequest): Promise<ResultError | ({ status: 'OK' }& Types.RequestNPubLinkingTokenResponse)> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
@ -629,6 +691,17 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
RespondToDebit: async (request: Types.DebitResponse): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/debit/finish'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
SendAppUserToAppPayment: async (request: Types.SendAppUserToAppPaymentRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')
@ -684,6 +757,20 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
UpdateCallbackUrl: async (request: Types.CallbackUrl): Promise<ResultError | ({ status: 'OK' }& Types.CallbackUrl)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/cb/update'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.CallbackUrlValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
UseInviteLink: async (request: Types.UseInviteLinkRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveGuestWithPubAuth()
if (auth === null) throw new Error('retrieveGuestWithPubAuth() returned null')

View file

@ -57,6 +57,33 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
AuthorizeDebit: async (request: Types.DebitAuthorizationRequest): Promise<ResultError | ({ status: 'OK' }& Types.DebitAuthorization)> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'AuthorizeDebit',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.DebitAuthorizationValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
BanDebit: async (request: Types.DebitOperation): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'BanDebit',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
BanUser: async (request: Types.BanUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.BanUserResponse)> => {
const auth = await params.retrieveNostrAdminAuth()
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
@ -113,6 +140,18 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
EditDebit: async (request: Types.DebitAuthorizationRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'EditDebit',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
EnrollAdminToken: async (request: Types.EnrollAdminTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
@ -140,6 +179,20 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetDebitAuthorizations: async (): Promise<ResultError | ({ status: 'OK' }& Types.DebitAuthorizations)> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
const data = await send(params.pubDestination, {rpcName:'GetDebitAuthorizations',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.DebitAuthorizationsValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetHttpCreds: async (cb: (res:ResultError | ({ status: 'OK' }& Types.HttpCreds)) => void): Promise<void> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
@ -184,6 +237,21 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetLiveDebitRequests: async (cb: (res:ResultError | ({ status: 'OK' }& Types.LiveDebitRequest)) => void): Promise<void> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
subscribe(params.pubDestination, {rpcName:'GetLiveDebitRequests',authIdentifier:auth, ...nostrRequest }, (data) => {
if (data.status === 'ERROR' && typeof data.reason === 'string') return cb(data)
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return cb({ status: 'OK', ...result })
const error = Types.LiveDebitRequestValidate(result)
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
}
return cb({ status: 'ERROR', reason: 'invalid response' })
})
},
GetLiveUserOperations: async (cb: (res:ResultError | ({ status: 'OK' }& Types.LiveUserOperation)) => void): Promise<void> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
@ -460,6 +528,45 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
ResetDebit: async (request: Types.DebitOperation): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'ResetDebit',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
RespondToDebit: async (request: Types.DebitResponse): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'RespondToDebit',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
UpdateCallbackUrl: async (request: Types.CallbackUrl): Promise<ResultError | ({ status: 'OK' }& Types.CallbackUrl)> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'UpdateCallbackUrl',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.CallbackUrlValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
UseInviteLink: async (request: Types.UseInviteLinkRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrGuestWithPubAuth()
if (auth === null) throw new Error('retrieveNostrGuestWithPubAuth() returned null')

View file

@ -80,6 +80,38 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'AuthorizeDebit':
try {
if (!methods.AuthorizeDebit) throw new Error('method: AuthorizeDebit is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.DebitAuthorizationRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.AuthorizeDebit({rpcName:'AuthorizeDebit', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'BanDebit':
try {
if (!methods.BanDebit) throw new Error('method: BanDebit is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.DebitOperationValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
await methods.BanDebit({rpcName:'BanDebit', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'BanUser':
try {
if (!methods.BanUser) throw new Error('method: BanUser is not implemented')
@ -127,6 +159,30 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'AuthorizeDebit':
if (!methods.AuthorizeDebit) {
throw new Error('method not defined: AuthorizeDebit')
} else {
const error = Types.DebitAuthorizationRequestValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
const res = await methods.AuthorizeDebit({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'BanDebit':
if (!methods.BanDebit) {
throw new Error('method not defined: BanDebit')
} else {
const error = Types.DebitOperationValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.BanDebit({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'DecodeInvoice':
if (!methods.DecodeInvoice) {
throw new Error('method not defined: DecodeInvoice')
@ -139,6 +195,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'EditDebit':
if (!methods.EditDebit) {
throw new Error('method not defined: EditDebit')
} else {
const error = Types.DebitAuthorizationRequestValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.EditDebit({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'EnrollAdminToken':
if (!methods.EnrollAdminToken) {
throw new Error('method not defined: EnrollAdminToken')
@ -151,6 +219,16 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetDebitAuthorizations':
if (!methods.GetDebitAuthorizations) {
throw new Error('method not defined: GetDebitAuthorizations')
} else {
opStats.validate = opStats.guard
const res = await methods.GetDebitAuthorizations({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetLNURLChannelLink':
if (!methods.GetLNURLChannelLink) {
throw new Error('method not defined: GetLNURLChannelLink')
@ -285,6 +363,42 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'ResetDebit':
if (!methods.ResetDebit) {
throw new Error('method not defined: ResetDebit')
} else {
const error = Types.DebitOperationValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.ResetDebit({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'RespondToDebit':
if (!methods.RespondToDebit) {
throw new Error('method not defined: RespondToDebit')
} else {
const error = Types.DebitResponseValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.RespondToDebit({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'UpdateCallbackUrl':
if (!methods.UpdateCallbackUrl) {
throw new Error('method not defined: UpdateCallbackUrl')
} else {
const error = Types.CallbackUrlValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
const res = await methods.UpdateCallbackUrl({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'UserHealth':
if (!methods.UserHealth) {
throw new Error('method not defined: UserHealth')
@ -337,6 +451,22 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'EditDebit':
try {
if (!methods.EditDebit) throw new Error('method: EditDebit is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.DebitAuthorizationRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
await methods.EditDebit({rpcName:'EditDebit', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'EnrollAdminToken':
try {
if (!methods.EnrollAdminToken) throw new Error('method: EnrollAdminToken is not implemented')
@ -369,6 +499,19 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'GetDebitAuthorizations':
try {
if (!methods.GetDebitAuthorizations) throw new Error('method: GetDebitAuthorizations is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
stats.validate = stats.guard
const response = await methods.GetDebitAuthorizations({rpcName:'GetDebitAuthorizations', ctx:authContext })
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'GetHttpCreds':
try {
if (!methods.GetHttpCreds) throw new Error('method: GetHttpCreds is not implemented')
@ -411,6 +554,19 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'GetLiveDebitRequests':
try {
if (!methods.GetLiveDebitRequests) throw new Error('method: GetLiveDebitRequests is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
stats.validate = stats.guard
methods.GetLiveDebitRequests({rpcName:'GetLiveDebitRequests', ctx:authContext ,cb: (response, err) => {
stats.handle = process.hrtime.bigint()
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)} else { res({status: 'OK', ...response});opts.metricsCallback([{ ...info, ...stats, ...authContext }])}
}})
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'GetLiveUserOperations':
try {
if (!methods.GetLiveUserOperations) throw new Error('method: GetLiveUserOperations is not implemented')
@ -688,6 +844,54 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'ResetDebit':
try {
if (!methods.ResetDebit) throw new Error('method: ResetDebit is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.DebitOperationValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
await methods.ResetDebit({rpcName:'ResetDebit', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'RespondToDebit':
try {
if (!methods.RespondToDebit) throw new Error('method: RespondToDebit is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.DebitResponseValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
await methods.RespondToDebit({rpcName:'RespondToDebit', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'UpdateCallbackUrl':
try {
if (!methods.UpdateCallbackUrl) throw new Error('method: UpdateCallbackUrl is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.CallbackUrlValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.UpdateCallbackUrl({rpcName:'UpdateCallbackUrl', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'UseInviteLink':
try {
if (!methods.UseInviteLink) throw new Error('method: UseInviteLink is not implemented')

View file

@ -34,8 +34,8 @@ export type UserContext = {
app_user_id: string
user_id: string
}
export type UserMethodInputs = AddProduct_Input | DecodeInvoice_Input | EnrollAdminToken_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | OpenChannel_Input | PayAddress_Input | PayInvoice_Input | UserHealth_Input
export type UserMethodOutputs = AddProduct_Output | DecodeInvoice_Output | EnrollAdminToken_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | OpenChannel_Output | PayAddress_Output | PayInvoice_Output | UserHealth_Output
export type UserMethodInputs = AddProduct_Input | AuthorizeDebit_Input | BanDebit_Input | DecodeInvoice_Input | EditDebit_Input | EnrollAdminToken_Input | GetDebitAuthorizations_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | OpenChannel_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UserHealth_Input
export type UserMethodOutputs = AddProduct_Output | AuthorizeDebit_Output | BanDebit_Output | DecodeInvoice_Output | EditDebit_Output | EnrollAdminToken_Output | GetDebitAuthorizations_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | OpenChannel_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UserHealth_Output
export type AuthContext = AdminContext | AppContext | GuestContext | GuestWithPubContext | MetricsContext | UserContext
export type AddApp_Input = {rpcName:'AddApp', req: AddAppRequest}
@ -56,6 +56,12 @@ export type AddProduct_Output = ResultError | ({ status: 'OK' } & Product)
export type AuthApp_Input = {rpcName:'AuthApp', req: AuthAppRequest}
export type AuthApp_Output = ResultError | ({ status: 'OK' } & AuthApp)
export type AuthorizeDebit_Input = {rpcName:'AuthorizeDebit', req: DebitAuthorizationRequest}
export type AuthorizeDebit_Output = ResultError | ({ status: 'OK' } & DebitAuthorization)
export type BanDebit_Input = {rpcName:'BanDebit', req: DebitOperation}
export type BanDebit_Output = ResultError | { status: 'OK' }
export type BanUser_Input = {rpcName:'BanUser', req: BanUserRequest}
export type BanUser_Output = ResultError | ({ status: 'OK' } & BanUserResponse)
@ -68,6 +74,9 @@ export type CreateOneTimeInviteLink_Output = ResultError | ({ status: 'OK' } & C
export type DecodeInvoice_Input = {rpcName:'DecodeInvoice', req: DecodeInvoiceRequest}
export type DecodeInvoice_Output = ResultError | ({ status: 'OK' } & DecodeInvoiceResponse)
export type EditDebit_Input = {rpcName:'EditDebit', req: DebitAuthorizationRequest}
export type EditDebit_Output = ResultError | { status: 'OK' }
export type EncryptionExchange_Input = {rpcName:'EncryptionExchange', req: EncryptionExchangeRequest}
export type EncryptionExchange_Output = ResultError | { status: 'OK' }
@ -86,6 +95,9 @@ export type GetAppUserLNURLInfo_Output = ResultError | ({ status: 'OK' } & Lnurl
export type GetAppsMetrics_Input = {rpcName:'GetAppsMetrics', req: AppsMetricsRequest}
export type GetAppsMetrics_Output = ResultError | ({ status: 'OK' } & AppsMetrics)
export type GetDebitAuthorizations_Input = {rpcName:'GetDebitAuthorizations'}
export type GetDebitAuthorizations_Output = ResultError | ({ status: 'OK' } & DebitAuthorizations)
export type GetHttpCreds_Input = {rpcName:'GetHttpCreds', cb:(res: HttpCreds, err:Error|null)=> void}
export type GetHttpCreds_Output = ResultError | { status: 'OK' }
@ -95,6 +107,9 @@ export type GetInviteLinkState_Output = ResultError | ({ status: 'OK' } & GetInv
export type GetLNURLChannelLink_Input = {rpcName:'GetLNURLChannelLink'}
export type GetLNURLChannelLink_Output = ResultError | ({ status: 'OK' } & LnurlLinkResponse)
export type GetLiveDebitRequests_Input = {rpcName:'GetLiveDebitRequests', cb:(res: LiveDebitRequest, err:Error|null)=> void}
export type GetLiveDebitRequests_Output = ResultError | { status: 'OK' }
export type GetLiveUserOperations_Input = {rpcName:'GetLiveUserOperations', cb:(res: LiveUserOperation, err:Error|null)=> void}
export type GetLiveUserOperations_Output = ResultError | { status: 'OK' }
@ -198,9 +213,15 @@ export type PayInvoice_Output = ResultError | ({ status: 'OK' } & PayInvoiceResp
export type RequestNPubLinkingToken_Input = {rpcName:'RequestNPubLinkingToken', req: RequestNPubLinkingTokenRequest}
export type RequestNPubLinkingToken_Output = ResultError | ({ status: 'OK' } & RequestNPubLinkingTokenResponse)
export type ResetDebit_Input = {rpcName:'ResetDebit', req: DebitOperation}
export type ResetDebit_Output = ResultError | { status: 'OK' }
export type ResetNPubLinkingToken_Input = {rpcName:'ResetNPubLinkingToken', req: RequestNPubLinkingTokenRequest}
export type ResetNPubLinkingToken_Output = ResultError | ({ status: 'OK' } & RequestNPubLinkingTokenResponse)
export type RespondToDebit_Input = {rpcName:'RespondToDebit', req: DebitResponse}
export type RespondToDebit_Output = ResultError | { status: 'OK' }
export type SendAppUserToAppPayment_Input = {rpcName:'SendAppUserToAppPayment', req: SendAppUserToAppPaymentRequest}
export type SendAppUserToAppPayment_Output = ResultError | { status: 'OK' }
@ -216,6 +237,9 @@ export type SetMockAppUserBalance_Output = ResultError | { status: 'OK' }
export type SetMockInvoiceAsPaid_Input = {rpcName:'SetMockInvoiceAsPaid', req: SetMockInvoiceAsPaidRequest}
export type SetMockInvoiceAsPaid_Output = ResultError | { status: 'OK' }
export type UpdateCallbackUrl_Input = {rpcName:'UpdateCallbackUrl', req: CallbackUrl}
export type UpdateCallbackUrl_Output = ResultError | ({ status: 'OK' } & CallbackUrl)
export type UseInviteLink_Input = {rpcName:'UseInviteLink', req: UseInviteLinkRequest}
export type UseInviteLink_Output = ResultError | { status: 'OK' }
@ -229,18 +253,23 @@ export type ServerMethods = {
AddAppUserInvoice?: (req: AddAppUserInvoice_Input & {ctx: AppContext }) => Promise<NewInvoiceResponse>
AddProduct?: (req: AddProduct_Input & {ctx: UserContext }) => Promise<Product>
AuthApp?: (req: AuthApp_Input & {ctx: AdminContext }) => Promise<AuthApp>
AuthorizeDebit?: (req: AuthorizeDebit_Input & {ctx: UserContext }) => Promise<DebitAuthorization>
BanDebit?: (req: BanDebit_Input & {ctx: UserContext }) => Promise<void>
BanUser?: (req: BanUser_Input & {ctx: AdminContext }) => Promise<BanUserResponse>
CreateOneTimeInviteLink?: (req: CreateOneTimeInviteLink_Input & {ctx: AdminContext }) => Promise<CreateOneTimeInviteLinkResponse>
DecodeInvoice?: (req: DecodeInvoice_Input & {ctx: UserContext }) => Promise<DecodeInvoiceResponse>
EditDebit?: (req: EditDebit_Input & {ctx: UserContext }) => Promise<void>
EncryptionExchange?: (req: EncryptionExchange_Input & {ctx: GuestContext }) => Promise<void>
EnrollAdminToken?: (req: EnrollAdminToken_Input & {ctx: UserContext }) => Promise<void>
GetApp?: (req: GetApp_Input & {ctx: AppContext }) => Promise<Application>
GetAppUser?: (req: GetAppUser_Input & {ctx: AppContext }) => Promise<AppUser>
GetAppUserLNURLInfo?: (req: GetAppUserLNURLInfo_Input & {ctx: AppContext }) => Promise<LnurlPayInfoResponse>
GetAppsMetrics?: (req: GetAppsMetrics_Input & {ctx: MetricsContext }) => Promise<AppsMetrics>
GetDebitAuthorizations?: (req: GetDebitAuthorizations_Input & {ctx: UserContext }) => Promise<DebitAuthorizations>
GetHttpCreds?: (req: GetHttpCreds_Input & {ctx: UserContext }) => Promise<void>
GetInviteLinkState?: (req: GetInviteLinkState_Input & {ctx: AdminContext }) => Promise<GetInviteTokenStateResponse>
GetLNURLChannelLink?: (req: GetLNURLChannelLink_Input & {ctx: UserContext }) => Promise<LnurlLinkResponse>
GetLiveDebitRequests?: (req: GetLiveDebitRequests_Input & {ctx: UserContext }) => Promise<void>
GetLiveUserOperations?: (req: GetLiveUserOperations_Input & {ctx: UserContext }) => Promise<void>
GetLndMetrics?: (req: GetLndMetrics_Input & {ctx: MetricsContext }) => Promise<LndMetrics>
GetLnurlPayInfo?: (req: GetLnurlPayInfo_Input & {ctx: GuestContext }) => Promise<LnurlPayInfoResponse>
@ -268,12 +297,15 @@ export type ServerMethods = {
PayAppUserInvoice?: (req: PayAppUserInvoice_Input & {ctx: AppContext }) => Promise<PayInvoiceResponse>
PayInvoice?: (req: PayInvoice_Input & {ctx: UserContext }) => Promise<PayInvoiceResponse>
RequestNPubLinkingToken?: (req: RequestNPubLinkingToken_Input & {ctx: AppContext }) => Promise<RequestNPubLinkingTokenResponse>
ResetDebit?: (req: ResetDebit_Input & {ctx: UserContext }) => Promise<void>
ResetNPubLinkingToken?: (req: ResetNPubLinkingToken_Input & {ctx: AppContext }) => Promise<RequestNPubLinkingTokenResponse>
RespondToDebit?: (req: RespondToDebit_Input & {ctx: UserContext }) => Promise<void>
SendAppUserToAppPayment?: (req: SendAppUserToAppPayment_Input & {ctx: AppContext }) => Promise<void>
SendAppUserToAppUserPayment?: (req: SendAppUserToAppUserPayment_Input & {ctx: AppContext }) => Promise<void>
SetMockAppBalance?: (req: SetMockAppBalance_Input & {ctx: AppContext }) => Promise<void>
SetMockAppUserBalance?: (req: SetMockAppUserBalance_Input & {ctx: AppContext }) => Promise<void>
SetMockInvoiceAsPaid?: (req: SetMockInvoiceAsPaid_Input & {ctx: GuestContext }) => Promise<void>
UpdateCallbackUrl?: (req: UpdateCallbackUrl_Input & {ctx: UserContext }) => Promise<CallbackUrl>
UseInviteLink?: (req: UseInviteLink_Input & {ctx: GuestWithPubContext }) => Promise<void>
UserHealth?: (req: UserHealth_Input & {ctx: UserContext }) => Promise<void>
}
@ -287,6 +319,15 @@ export const enumCheckAddressType = (e?: AddressType): boolean => {
for (const v in AddressType) if (e === v) return true
return false
}
export enum IntervalType {
DAY = 'DAY',
MONTH = 'MONTH',
WEEK = 'WEEK',
}
export const enumCheckIntervalType = (e?: IntervalType): boolean => {
for (const v in IntervalType) if (e === v) return true
return false
}
export enum UserOperationType {
INCOMING_INVOICE = 'INCOMING_INVOICE',
INCOMING_TX = 'INCOMING_TX',
@ -747,6 +788,24 @@ export const BannedAppUserValidate = (o?: BannedAppUser, opts: BannedAppUserOpti
return null
}
export type CallbackUrl = {
url: string
}
export const CallbackUrlOptionalFields: [] = []
export type CallbackUrlOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
url_CustomCheck?: (v: string) => boolean
}
export const CallbackUrlValidate = (o?: CallbackUrl, opts: CallbackUrlOptions = {}, path: string = 'CallbackUrl::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.url !== 'string') return new Error(`${path}.url: is not a string`)
if (opts.url_CustomCheck && !opts.url_CustomCheck(o.url)) return new Error(`${path}.url: custom check failed`)
return null
}
export type ClosedChannel = {
capacity: number
channel_id: string
@ -830,6 +889,185 @@ export const CreateOneTimeInviteLinkResponseValidate = (o?: CreateOneTimeInviteL
return null
}
export type DebitAuthorization = {
authorized: boolean
debit_id: string
npub: string
rules: DebitRule[]
}
export const DebitAuthorizationOptionalFields: [] = []
export type DebitAuthorizationOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
authorized_CustomCheck?: (v: boolean) => boolean
debit_id_CustomCheck?: (v: string) => boolean
npub_CustomCheck?: (v: string) => boolean
rules_ItemOptions?: DebitRuleOptions
rules_CustomCheck?: (v: DebitRule[]) => boolean
}
export const DebitAuthorizationValidate = (o?: DebitAuthorization, opts: DebitAuthorizationOptions = {}, path: string = 'DebitAuthorization::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.authorized !== 'boolean') return new Error(`${path}.authorized: is not a boolean`)
if (opts.authorized_CustomCheck && !opts.authorized_CustomCheck(o.authorized)) return new Error(`${path}.authorized: custom check failed`)
if (typeof o.debit_id !== 'string') return new Error(`${path}.debit_id: is not a string`)
if (opts.debit_id_CustomCheck && !opts.debit_id_CustomCheck(o.debit_id)) return new Error(`${path}.debit_id: custom check failed`)
if (typeof o.npub !== 'string') return new Error(`${path}.npub: is not a string`)
if (opts.npub_CustomCheck && !opts.npub_CustomCheck(o.npub)) return new Error(`${path}.npub: custom check failed`)
if (!Array.isArray(o.rules)) return new Error(`${path}.rules: is not an array`)
for (let index = 0; index < o.rules.length; index++) {
const rulesErr = DebitRuleValidate(o.rules[index], opts.rules_ItemOptions, `${path}.rules[${index}]`)
if (rulesErr !== null) return rulesErr
}
if (opts.rules_CustomCheck && !opts.rules_CustomCheck(o.rules)) return new Error(`${path}.rules: custom check failed`)
return null
}
export type DebitAuthorizationRequest = {
authorize_npub: string
request_id?: string
rules: DebitRule[]
}
export type DebitAuthorizationRequestOptionalField = 'request_id'
export const DebitAuthorizationRequestOptionalFields: DebitAuthorizationRequestOptionalField[] = ['request_id']
export type DebitAuthorizationRequestOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: DebitAuthorizationRequestOptionalField[]
authorize_npub_CustomCheck?: (v: string) => boolean
request_id_CustomCheck?: (v?: string) => boolean
rules_ItemOptions?: DebitRuleOptions
rules_CustomCheck?: (v: DebitRule[]) => boolean
}
export const DebitAuthorizationRequestValidate = (o?: DebitAuthorizationRequest, opts: DebitAuthorizationRequestOptions = {}, path: string = 'DebitAuthorizationRequest::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.authorize_npub !== 'string') return new Error(`${path}.authorize_npub: is not a string`)
if (opts.authorize_npub_CustomCheck && !opts.authorize_npub_CustomCheck(o.authorize_npub)) return new Error(`${path}.authorize_npub: custom check failed`)
if ((o.request_id || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('request_id')) && typeof o.request_id !== 'string') return new Error(`${path}.request_id: is not a string`)
if (opts.request_id_CustomCheck && !opts.request_id_CustomCheck(o.request_id)) return new Error(`${path}.request_id: custom check failed`)
if (!Array.isArray(o.rules)) return new Error(`${path}.rules: is not an array`)
for (let index = 0; index < o.rules.length; index++) {
const rulesErr = DebitRuleValidate(o.rules[index], opts.rules_ItemOptions, `${path}.rules[${index}]`)
if (rulesErr !== null) return rulesErr
}
if (opts.rules_CustomCheck && !opts.rules_CustomCheck(o.rules)) return new Error(`${path}.rules: custom check failed`)
return null
}
export type DebitAuthorizations = {
debits: DebitAuthorization[]
}
export const DebitAuthorizationsOptionalFields: [] = []
export type DebitAuthorizationsOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
debits_ItemOptions?: DebitAuthorizationOptions
debits_CustomCheck?: (v: DebitAuthorization[]) => boolean
}
export const DebitAuthorizationsValidate = (o?: DebitAuthorizations, opts: DebitAuthorizationsOptions = {}, path: string = 'DebitAuthorizations::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (!Array.isArray(o.debits)) return new Error(`${path}.debits: is not an array`)
for (let index = 0; index < o.debits.length; index++) {
const debitsErr = DebitAuthorizationValidate(o.debits[index], opts.debits_ItemOptions, `${path}.debits[${index}]`)
if (debitsErr !== null) return debitsErr
}
if (opts.debits_CustomCheck && !opts.debits_CustomCheck(o.debits)) return new Error(`${path}.debits: custom check failed`)
return null
}
export type DebitExpirationRule = {
expires_at_unix: number
}
export const DebitExpirationRuleOptionalFields: [] = []
export type DebitExpirationRuleOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
expires_at_unix_CustomCheck?: (v: number) => boolean
}
export const DebitExpirationRuleValidate = (o?: DebitExpirationRule, opts: DebitExpirationRuleOptions = {}, path: string = 'DebitExpirationRule::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.expires_at_unix !== 'number') return new Error(`${path}.expires_at_unix: is not a number`)
if (opts.expires_at_unix_CustomCheck && !opts.expires_at_unix_CustomCheck(o.expires_at_unix)) return new Error(`${path}.expires_at_unix: custom check failed`)
return null
}
export type DebitOperation = {
npub: string
}
export const DebitOperationOptionalFields: [] = []
export type DebitOperationOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
npub_CustomCheck?: (v: string) => boolean
}
export const DebitOperationValidate = (o?: DebitOperation, opts: DebitOperationOptions = {}, path: string = 'DebitOperation::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.npub !== 'string') return new Error(`${path}.npub: is not a string`)
if (opts.npub_CustomCheck && !opts.npub_CustomCheck(o.npub)) return new Error(`${path}.npub: custom check failed`)
return null
}
export type DebitResponse = {
npub: string
request_id: string
response: DebitResponse_response
}
export const DebitResponseOptionalFields: [] = []
export type DebitResponseOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
npub_CustomCheck?: (v: string) => boolean
request_id_CustomCheck?: (v: string) => boolean
response_Options?: DebitResponse_responseOptions
}
export const DebitResponseValidate = (o?: DebitResponse, opts: DebitResponseOptions = {}, path: string = 'DebitResponse::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.npub !== 'string') return new Error(`${path}.npub: is not a string`)
if (opts.npub_CustomCheck && !opts.npub_CustomCheck(o.npub)) return new Error(`${path}.npub: custom check failed`)
if (typeof o.request_id !== 'string') return new Error(`${path}.request_id: is not a string`)
if (opts.request_id_CustomCheck && !opts.request_id_CustomCheck(o.request_id)) return new Error(`${path}.request_id: custom check failed`)
const responseErr = DebitResponse_responseValidate(o.response, opts.response_Options, `${path}.response`)
if (responseErr !== null) return responseErr
return null
}
export type DebitRule = {
rule: DebitRule_rule
}
export const DebitRuleOptionalFields: [] = []
export type DebitRuleOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
rule_Options?: DebitRule_ruleOptions
}
export const DebitRuleValidate = (o?: DebitRule, opts: DebitRuleOptions = {}, path: string = 'DebitRule::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
const ruleErr = DebitRule_ruleValidate(o.rule, opts.rule_Options, `${path}.rule`)
if (ruleErr !== null) return ruleErr
return null
}
export type DecodeInvoiceRequest = {
invoice: string
}
@ -920,6 +1158,34 @@ export const EnrollAdminTokenRequestValidate = (o?: EnrollAdminTokenRequest, opt
return null
}
export type FrequencyRule = {
amount: number
interval: IntervalType
number_of_intervals: number
}
export const FrequencyRuleOptionalFields: [] = []
export type FrequencyRuleOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
amount_CustomCheck?: (v: number) => boolean
interval_CustomCheck?: (v: IntervalType) => boolean
number_of_intervals_CustomCheck?: (v: number) => boolean
}
export const FrequencyRuleValidate = (o?: FrequencyRule, opts: FrequencyRuleOptions = {}, path: string = 'FrequencyRule::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.amount !== 'number') return new Error(`${path}.amount: is not a number`)
if (opts.amount_CustomCheck && !opts.amount_CustomCheck(o.amount)) return new Error(`${path}.amount: custom check failed`)
if (!enumCheckIntervalType(o.interval)) return new Error(`${path}.interval: is not a valid IntervalType`)
if (opts.interval_CustomCheck && !opts.interval_CustomCheck(o.interval)) return new Error(`${path}.interval: custom check failed`)
if (typeof o.number_of_intervals !== 'number') return new Error(`${path}.number_of_intervals: is not a number`)
if (opts.number_of_intervals_CustomCheck && !opts.number_of_intervals_CustomCheck(o.number_of_intervals)) return new Error(`${path}.number_of_intervals: custom check failed`)
return null
}
export type GetAppUserLNURLInfoRequest = {
base_url_override: string
user_identifier: string
@ -1222,6 +1488,35 @@ export const LinkNPubThroughTokenRequestValidate = (o?: LinkNPubThroughTokenRequ
return null
}
export type LiveDebitRequest = {
debit: LiveDebitRequest_debit
npub: string
request_id: string
}
export const LiveDebitRequestOptionalFields: [] = []
export type LiveDebitRequestOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
debit_Options?: LiveDebitRequest_debitOptions
npub_CustomCheck?: (v: string) => boolean
request_id_CustomCheck?: (v: string) => boolean
}
export const LiveDebitRequestValidate = (o?: LiveDebitRequest, opts: LiveDebitRequestOptions = {}, path: string = 'LiveDebitRequest::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
const debitErr = LiveDebitRequest_debitValidate(o.debit, opts.debit_Options, `${path}.debit`)
if (debitErr !== null) return debitErr
if (typeof o.npub !== 'string') return new Error(`${path}.npub: is not a string`)
if (opts.npub_CustomCheck && !opts.npub_CustomCheck(o.npub)) return new Error(`${path}.npub: custom check failed`)
if (typeof o.request_id !== 'string') return new Error(`${path}.request_id: is not a string`)
if (opts.request_id_CustomCheck && !opts.request_id_CustomCheck(o.request_id)) return new Error(`${path}.request_id: custom check failed`)
return null
}
export type LiveUserOperation = {
operation: UserOperation
}
@ -1854,13 +2149,16 @@ export const PayAddressResponseValidate = (o?: PayAddressResponse, opts: PayAddr
export type PayAppUserInvoiceRequest = {
amount: number
debit_npub?: string
invoice: string
user_identifier: string
}
export const PayAppUserInvoiceRequestOptionalFields: [] = []
export type PayAppUserInvoiceRequestOptionalField = 'debit_npub'
export const PayAppUserInvoiceRequestOptionalFields: PayAppUserInvoiceRequestOptionalField[] = ['debit_npub']
export type PayAppUserInvoiceRequestOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
checkOptionalsAreSet?: PayAppUserInvoiceRequestOptionalField[]
amount_CustomCheck?: (v: number) => boolean
debit_npub_CustomCheck?: (v?: string) => boolean
invoice_CustomCheck?: (v: string) => boolean
user_identifier_CustomCheck?: (v: string) => boolean
}
@ -1871,6 +2169,9 @@ export const PayAppUserInvoiceRequestValidate = (o?: PayAppUserInvoiceRequest, o
if (typeof o.amount !== 'number') return new Error(`${path}.amount: is not a number`)
if (opts.amount_CustomCheck && !opts.amount_CustomCheck(o.amount)) return new Error(`${path}.amount: custom check failed`)
if ((o.debit_npub || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('debit_npub')) && typeof o.debit_npub !== 'string') return new Error(`${path}.debit_npub: is not a string`)
if (opts.debit_npub_CustomCheck && !opts.debit_npub_CustomCheck(o.debit_npub)) return new Error(`${path}.debit_npub: custom check failed`)
if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`)
if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`)
@ -1882,12 +2183,15 @@ export const PayAppUserInvoiceRequestValidate = (o?: PayAppUserInvoiceRequest, o
export type PayInvoiceRequest = {
amount: number
debit_npub?: string
invoice: string
}
export const PayInvoiceRequestOptionalFields: [] = []
export type PayInvoiceRequestOptionalField = 'debit_npub'
export const PayInvoiceRequestOptionalFields: PayInvoiceRequestOptionalField[] = ['debit_npub']
export type PayInvoiceRequestOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
checkOptionalsAreSet?: PayInvoiceRequestOptionalField[]
amount_CustomCheck?: (v: number) => boolean
debit_npub_CustomCheck?: (v?: string) => boolean
invoice_CustomCheck?: (v: string) => boolean
}
export const PayInvoiceRequestValidate = (o?: PayInvoiceRequest, opts: PayInvoiceRequestOptions = {}, path: string = 'PayInvoiceRequest::root.'): Error | null => {
@ -1897,6 +2201,9 @@ export const PayInvoiceRequestValidate = (o?: PayInvoiceRequest, opts: PayInvoic
if (typeof o.amount !== 'number') return new Error(`${path}.amount: is not a number`)
if (opts.amount_CustomCheck && !opts.amount_CustomCheck(o.amount)) return new Error(`${path}.amount: custom check failed`)
if ((o.debit_npub || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('debit_npub')) && typeof o.debit_npub !== 'string') return new Error(`${path}.debit_npub: is not a string`)
if (opts.debit_npub_CustomCheck && !opts.debit_npub_CustomCheck(o.debit_npub)) return new Error(`${path}.debit_npub: custom check failed`)
if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`)
if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`)
@ -2354,7 +2661,9 @@ export const UseInviteLinkRequestValidate = (o?: UseInviteLinkRequest, opts: Use
export type UserInfo = {
balance: number
bridge_url: string
callback_url: string
max_withdrawable: number
ndebit: string
network_max_fee_bps: number
network_max_fee_fixed: number
noffer: string
@ -2367,7 +2676,9 @@ export type UserInfoOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
balance_CustomCheck?: (v: number) => boolean
bridge_url_CustomCheck?: (v: string) => boolean
callback_url_CustomCheck?: (v: string) => boolean
max_withdrawable_CustomCheck?: (v: number) => boolean
ndebit_CustomCheck?: (v: string) => boolean
network_max_fee_bps_CustomCheck?: (v: number) => boolean
network_max_fee_fixed_CustomCheck?: (v: number) => boolean
noffer_CustomCheck?: (v: string) => boolean
@ -2385,9 +2696,15 @@ export const UserInfoValidate = (o?: UserInfo, opts: UserInfoOptions = {}, path:
if (typeof o.bridge_url !== 'string') return new Error(`${path}.bridge_url: is not a string`)
if (opts.bridge_url_CustomCheck && !opts.bridge_url_CustomCheck(o.bridge_url)) return new Error(`${path}.bridge_url: custom check failed`)
if (typeof o.callback_url !== 'string') return new Error(`${path}.callback_url: is not a string`)
if (opts.callback_url_CustomCheck && !opts.callback_url_CustomCheck(o.callback_url)) return new Error(`${path}.callback_url: custom check failed`)
if (typeof o.max_withdrawable !== 'number') return new Error(`${path}.max_withdrawable: is not a number`)
if (opts.max_withdrawable_CustomCheck && !opts.max_withdrawable_CustomCheck(o.max_withdrawable)) return new Error(`${path}.max_withdrawable: custom check failed`)
if (typeof o.ndebit !== 'string') return new Error(`${path}.ndebit: is not a string`)
if (opts.ndebit_CustomCheck && !opts.ndebit_CustomCheck(o.ndebit)) return new Error(`${path}.ndebit: custom check failed`)
if (typeof o.network_max_fee_bps !== 'number') return new Error(`${path}.network_max_fee_bps: is not a number`)
if (opts.network_max_fee_bps_CustomCheck && !opts.network_max_fee_bps_CustomCheck(o.network_max_fee_bps)) return new Error(`${path}.network_max_fee_bps: custom check failed`)
@ -2553,3 +2870,121 @@ export const UsersInfoValidate = (o?: UsersInfo, opts: UsersInfoOptions = {}, pa
return null
}
export enum DebitResponse_response_type {
DENIED = 'denied',
INVOICE = 'invoice',
}
export const enumCheckDebitResponse_response_type = (e?: DebitResponse_response_type): boolean => {
for (const v in DebitResponse_response_type) if (e === v) return true
return false
}
export type DebitResponse_response =
{type:DebitResponse_response_type.DENIED, denied:Empty}|
{type:DebitResponse_response_type.INVOICE, invoice:string}
export type DebitResponse_responseOptions = {
denied_Options?: EmptyOptions
invoice_CustomCheck?: (v: string) => boolean
}
export const DebitResponse_responseValidate = (o?: DebitResponse_response, opts:DebitResponse_responseOptions = {}, path: string = 'DebitResponse_response::root.'): Error | null => {
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
const stringType: string = o.type
switch (o.type) {
case DebitResponse_response_type.DENIED:
const deniedErr = EmptyValidate(o.denied, opts.denied_Options, `${path}.denied`)
if (deniedErr !== null) return deniedErr
break
case DebitResponse_response_type.INVOICE:
if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`)
if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`)
break
default:
return new Error(path + ': unknown type '+ stringType)
}
return null
}
export enum DebitRule_rule_type {
EXPIRATION_RULE = 'expiration_rule',
FREQUENCY_RULE = 'frequency_rule',
}
export const enumCheckDebitRule_rule_type = (e?: DebitRule_rule_type): boolean => {
for (const v in DebitRule_rule_type) if (e === v) return true
return false
}
export type DebitRule_rule =
{type:DebitRule_rule_type.EXPIRATION_RULE, expiration_rule:DebitExpirationRule}|
{type:DebitRule_rule_type.FREQUENCY_RULE, frequency_rule:FrequencyRule}
export type DebitRule_ruleOptions = {
expiration_rule_Options?: DebitExpirationRuleOptions
frequency_rule_Options?: FrequencyRuleOptions
}
export const DebitRule_ruleValidate = (o?: DebitRule_rule, opts:DebitRule_ruleOptions = {}, path: string = 'DebitRule_rule::root.'): Error | null => {
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
const stringType: string = o.type
switch (o.type) {
case DebitRule_rule_type.EXPIRATION_RULE:
const expiration_ruleErr = DebitExpirationRuleValidate(o.expiration_rule, opts.expiration_rule_Options, `${path}.expiration_rule`)
if (expiration_ruleErr !== null) return expiration_ruleErr
break
case DebitRule_rule_type.FREQUENCY_RULE:
const frequency_ruleErr = FrequencyRuleValidate(o.frequency_rule, opts.frequency_rule_Options, `${path}.frequency_rule`)
if (frequency_ruleErr !== null) return frequency_ruleErr
break
default:
return new Error(path + ': unknown type '+ stringType)
}
return null
}
export enum LiveDebitRequest_debit_type {
FREQUENCY = 'frequency',
FULL_ACCESS = 'full_access',
INVOICE = 'invoice',
}
export const enumCheckLiveDebitRequest_debit_type = (e?: LiveDebitRequest_debit_type): boolean => {
for (const v in LiveDebitRequest_debit_type) if (e === v) return true
return false
}
export type LiveDebitRequest_debit =
{type:LiveDebitRequest_debit_type.FREQUENCY, frequency:FrequencyRule}|
{type:LiveDebitRequest_debit_type.FULL_ACCESS, full_access:Empty}|
{type:LiveDebitRequest_debit_type.INVOICE, invoice:string}
export type LiveDebitRequest_debitOptions = {
frequency_Options?: FrequencyRuleOptions
full_access_Options?: EmptyOptions
invoice_CustomCheck?: (v: string) => boolean
}
export const LiveDebitRequest_debitValidate = (o?: LiveDebitRequest_debit, opts:LiveDebitRequest_debitOptions = {}, path: string = 'LiveDebitRequest_debit::root.'): Error | null => {
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
const stringType: string = o.type
switch (o.type) {
case LiveDebitRequest_debit_type.FREQUENCY:
const frequencyErr = FrequencyRuleValidate(o.frequency, opts.frequency_Options, `${path}.frequency`)
if (frequencyErr !== null) return frequencyErr
break
case LiveDebitRequest_debit_type.FULL_ACCESS:
const full_accessErr = EmptyValidate(o.full_access, opts.full_access_Options, `${path}.full_access`)
if (full_accessErr !== null) return full_accessErr
break
case LiveDebitRequest_debit_type.INVOICE:
if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`)
if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`)
break
default:
return new Error(path + ': unknown type '+ stringType)
}
return null
}

Binary file not shown.

View file

@ -343,6 +343,12 @@ service LightningPub {
option (http_route) = "/api/user/info";
option (nostr) = true;
}
rpc UpdateCallbackUrl(structs.CallbackUrl)returns(structs.CallbackUrl){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/cb/update";
option (nostr) = true;
}
rpc AddProduct(structs.AddProductRequest) returns (structs.Product){
option (auth_type) = "User";
@ -430,11 +436,53 @@ service LightningPub {
}
rpc GetLNURLChannelLink(structs.Empty) returns (structs.LnurlLinkResponse){
option (auth_type) = "User";
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/lnurl_channel/url";
option (nostr) = true;
}
rpc GetDebitAuthorizations(structs.Empty) returns (structs.DebitAuthorizations){
option (auth_type) = "User";
option (http_method) = "get";
option (http_route) = "/api/user/debit/get";
option (nostr) = true;
}
rpc AuthorizeDebit(structs.DebitAuthorizationRequest) returns (structs.DebitAuthorization){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/debit/authorize";
option (nostr) = true;
}
rpc EditDebit(structs.DebitAuthorizationRequest) returns (structs.Empty){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/debit/edit";
option (nostr) = true;
}
rpc BanDebit(structs.DebitOperation) returns (structs.Empty){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/debit/ban";
option (nostr) = true;
}
rpc ResetDebit(structs.DebitOperation) returns (structs.Empty){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/debit/reset";
option (nostr) = true;
}
rpc RespondToDebit(structs.DebitResponse) returns (structs.Empty){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/debit/finish";
option (nostr) = true;
}
rpc GetLiveDebitRequests(structs.Empty) returns (stream structs.LiveDebitRequest){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/debit/sub";
option (nostr) = true;
}
rpc GetLiveUserOperations(structs.Empty) returns (stream structs.LiveUserOperation){
option (auth_type) = "User";
option (http_method) = "post";

View file

@ -215,6 +215,7 @@ message PayAppUserInvoiceRequest {
string user_identifier = 1;
string invoice = 2;
int64 amount = 3;
optional string debit_npub = 4;
}
message SendAppUserToAppUserPaymentRequest {
@ -283,6 +284,7 @@ message DecodeInvoiceResponse{
message PayInvoiceRequest{
string invoice = 1;
int64 amount = 2;
optional string debit_npub = 3;
}
message PayInvoiceResponse{
@ -345,6 +347,10 @@ message HandleLnurlPayResponse {
repeated Empty routes = 2;
}
message CallbackUrl {
string url = 1;
}
message UserInfo{
string userId = 1;
int64 balance = 2;
@ -354,9 +360,10 @@ message UserInfo{
int64 network_max_fee_bps = 6;
int64 network_max_fee_fixed = 7;
string noffer = 8;
string bridge_url = 9;
string ndebit = 9;
string callback_url = 10;
string bridge_url = 11;
}
message GetUserOperationsRequest{
int64 latestIncomingInvoice = 1;
int64 latestOutgoingInvoice = 2;
@ -477,4 +484,67 @@ message GetInviteTokenStateRequest {
message GetInviteTokenStateResponse {
bool used = 1;
}
message DebitOperation {
string npub = 1;
}
message DebitAuthorizationRequest {
string authorize_npub = 1;
repeated DebitRule rules = 2;
optional string request_id = 3;
}
message DebitAuthorization {
string debit_id = 1;
bool authorized = 2;
string npub = 3;
repeated DebitRule rules = 4;
}
message DebitAuthorizations {
repeated DebitAuthorization debits = 1;
}
message DebitExpirationRule {
int64 expires_at_unix = 1;
}
enum IntervalType {
DAY = 0;
WEEK = 1;
MONTH = 2;
}
message FrequencyRule {
int64 number_of_intervals = 1;
IntervalType interval = 2;
int64 amount = 3;
}
message DebitRule {
oneof rule {
DebitExpirationRule expiration_rule = 1;
FrequencyRule frequency_rule = 2;
}
}
message LiveDebitRequest {
string request_id = 1;
string npub = 2;
oneof debit {
string invoice = 3;
FrequencyRule frequency = 4;
Empty full_access = 5;
}
}
message DebitResponse {
string request_id = 1;
string npub = 2;
oneof response {
Empty denied = 3;
string invoice = 4;
}
}

View file

@ -1,143 +0,0 @@
/*
This file contains functions that deal with encoding and decoding nprofiles,
but with he addition of bridge urls in the nprofile.
These functions are basically the same functions from nostr-tools package
but with some tweaks to allow for the bridge inclusion.
*/
import { bytesToHex, concatBytes, hexToBytes } from '@noble/hashes/utils';
import { bech32 } from 'bech32';
import { LoadNosrtSettingsFromEnv } from './services/nostr/index.js';
export const utf8Decoder = new TextDecoder('utf-8')
export const utf8Encoder = new TextEncoder()
export type CustomProfilePointer = {
pubkey: string
relays?: string[]
bridge?: string[] // one bridge
}
export type OfferPointer = {
pubkey: string,
relay: string,
offer: string
priceType: PriceType,
price?: number
}
export enum PriceType {
fixed = 0,
variable = 1,
spontaneous = 2,
}
type TLV = { [t: number]: Uint8Array[] }
const encodeTLV = (tlv: TLV): Uint8Array => {
const entries: Uint8Array[] = []
Object.entries(tlv)
/*
the original function does a reverse() here,
but here it causes the nprofile string to be different,
even though it would still decode to the correct original inputs
*/
//.reverse()
.forEach(([t, vs]) => {
vs.forEach(v => {
const entry = new Uint8Array(v.length + 2)
entry.set([parseInt(t)], 0)
entry.set([v.length], 1)
entry.set(v, 2)
entries.push(entry)
})
})
return concatBytes(...entries);
}
export const encodeNprofile = (profile: CustomProfilePointer): string => {
const data = encodeTLV({
0: [hexToBytes(profile.pubkey)],
1: (profile.relays || []).map(url => utf8Encoder.encode(url)),
2: (profile.bridge || []).map(url => utf8Encoder.encode(url))
});
const words = bech32.toWords(data)
return bech32.encode("nprofile", words, 5000);
}
export const encodeNoffer = (offer: OfferPointer): string => {
let relay = offer.relay
if (!relay) {
const settings = LoadNosrtSettingsFromEnv()
relay = settings.relays[0]
}
const o: TLV = {
0: [hexToBytes(offer.pubkey)],
1: [utf8Encoder.encode(relay)],
2: [utf8Encoder.encode(offer.offer)],
3: [new Uint8Array([Number(offer.priceType)])],
}
if (offer.price) {
o[4] = [new Uint8Array(new BigUint64Array([BigInt(offer.price)]).buffer)]
}
const data = encodeTLV(o);
const words = bech32.toWords(data)
return bech32.encode("noffer", words, 5000);
}
const parseTLV = (data: Uint8Array): TLV => {
const result: TLV = {}
let rest = data
while (rest.length > 0) {
const t = rest[0]
const l = rest[1]
const v = rest.slice(2, 2 + l)
rest = rest.slice(2 + l)
if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`)
result[t] = result[t] || []
result[t].push(v)
}
return result
}
export const decodeNoffer = (noffer: string): OfferPointer => {
const { prefix, words } = bech32.decode(noffer, 5000)
if (prefix !== "noffer") {
throw new Error("Expected nprofile prefix");
}
const data = new Uint8Array(bech32.fromWords(words))
const tlv = parseTLV(data);
if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for noffer')
if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes')
if (!tlv[1]?.[0]) throw new Error('missing TLV 1 for noffer')
if (!tlv[2]?.[0]) throw new Error('missing TLV 2 for noffer')
if (!tlv[3]?.[0]) throw new Error('missing TLV 3 for noffer')
return {
pubkey: bytesToHex(tlv[0][0]),
relay: utf8Decoder.decode(tlv[1][0]),
offer: utf8Decoder.decode(tlv[2][0]),
priceType: tlv[3][0][0],
price: tlv[4] ? Number(new BigUint64Array(tlv[4][0])[0]) : undefined
}
}
export const decodeNprofile = (nprofile: string): CustomProfilePointer => {
const { prefix, words } = bech32.decode(nprofile, 5000)
if (prefix !== "nprofile") {
throw new Error("Expected nprofile prefix");
}
const data = new Uint8Array(bech32.fromWords(words))
const tlv = parseTLV(data);
if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nprofile')
if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes')
return {
pubkey: bytesToHex(tlv[0][0]),
relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [],
bridge: tlv[2] ? tlv[2].map(d => utf8Decoder.decode(d)) : []
}
}

View file

@ -7,7 +7,9 @@ import nostrMiddleware from './nostrMiddleware.js'
import { getLogger } from './services/helpers/logger.js';
import { initMainHandler } from './services/main/init.js';
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
import { encodeNprofile } from './custom-nip19.js';
import { nip19 } from 'nostr-tools'
//@ts-ignore
const { nprofileEncode } = nip19
const start = async () => {
const log = getLogger({})
@ -29,7 +31,7 @@ const start = async () => {
log("starting server")
mainHandler.attachNostrSend(Send)
mainHandler.StartBeacons()
const appNprofile = encodeNprofile({ pubkey: liquidityProviderInfo.publicKey, relays: nostrSettings.relays })
const appNprofile = nprofileEncode({ pubkey: liquidityProviderInfo.publicKey, relays: nostrSettings.relays })
if (wizard) {
wizard.AddConnectInfo(appNprofile, nostrSettings.relays)
}

View file

@ -4,9 +4,7 @@ import { NostrEvent, NostrSend, NostrSettings } from "./services/nostr/handler.j
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 { UnsignedEvent } from "./services/nostr/tools/event.js";
import { defaultInvoiceExpiry } from "./services/storage/paymentStorage.js";
import { Application } from "./services/storage/entity/Application.js";
import { NdebitData } from "nostr-tools/lib/types/nip68.js";
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend } => {
const log = getLogger({})
@ -52,6 +50,10 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
const offerReq = j as NofferData
mainHandler.handleNip69Noffer(offerReq, event)
return
} else if (event.kind === 21002) {
const debitReq = j as NdebitData
mainHandler.debitManager.handleNip68Debit(debitReq, event)
return
}
if (!j.rpcName) {
onClientEvent(j as { requestId: string }, event.pub)

View file

@ -4,8 +4,11 @@ import * as Types from '../../../proto/autogenerated/ts/types.js'
import { MainSettings } from './settings.js'
import ApplicationManager from './applicationManager.js'
import { encodeNoffer, PriceType } from '../../custom-nip19.js'
import { nip19 } from 'nostr-tools'
import { LoadNosrtSettingsFromEnv } from '../nostr/index.js'
const { ndebitEncode, nofferEncode, OfferPriceType } = nip19
export default class {
storage: Storage
settings: MainSettings
applicationManager: ApplicationManager
@ -50,9 +53,12 @@ export default class {
const user = await this.storage.userStorage.GetUser(ctx.user_id)
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
const appUser = await this.storage.applicationStorage.GetAppUserFromUser(app, user.user_id)
console.log("User Identifier/pointer here", appUser?.identifier)
if (!appUser) {
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
}
const nostrSettings = LoadNosrtSettingsFromEnv()
return {
userId: ctx.user_id,
balance: user.balance_sats,
@ -61,11 +67,19 @@ export default class {
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps,
noffer: encodeNoffer({ pubkey: app.nostr_public_key!, offer: appUser.identifier, priceType: PriceType.spontaneous, relay: "" }),
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: appUser.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: appUser.identifier, relay: nostrSettings.relays[0] }),
callback_url: appUser.callback_url,
bridge_url: this.settings.bridgeUrl
}
}
async UpdateCallbackUrl(ctx: Types.UserContext, req: Types.CallbackUrl): Promise<Types.CallbackUrl> {
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
await this.storage.applicationStorage.UpdateUserCallbackUrl(app, ctx.app_user_id, req.url)
return { url: req.url }
}
async NewInvoice(ctx: Types.UserContext, req: Types.NewInvoiceRequest): Promise<Types.NewInvoiceResponse> {
return this.applicationManager.AddAppUserInvoice(ctx.app_id, {
http_callback_url: "",

View file

@ -8,8 +8,10 @@ import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
import { PubLogger, getLogger } from '../helpers/logger.js'
import crypto from 'crypto'
import { Application } from '../storage/entity/Application.js'
import { encodeNoffer, PriceType } from '../../custom-nip19.js'
import { nip69, nip19 } from 'nostr-tools'
import { LoadNosrtSettingsFromEnv } from '../nostr/index.js'
const { SendNofferRequest } = nip69
const { nofferEncode, ndebitEncode, OfferPriceType } = nip19
const TOKEN_EXPIRY_TIME = 2 * 60 * 1000 // 2 minutes, in milliseconds
type NsecLinkingData = {
@ -149,6 +151,7 @@ export default class {
u = user
if (created) log(u.identifier, u.user.user_id, "user created")
}
const nostrSettings = LoadNosrtSettingsFromEnv()
return {
identifier: u.identifier,
info: {
@ -159,7 +162,9 @@ export default class {
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps,
noffer: encodeNoffer({ pubkey: app.nostr_public_key!, offer: u.identifier, priceType: PriceType.spontaneous, relay: "" }),
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: u.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] }),
callback_url: u.callback_url,
bridge_url: this.settings.bridgeUrl
},
@ -181,7 +186,8 @@ export default class {
const log = getLogger({ appName: app.name })
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 opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app }
const cbUrl = req.http_callback_url || receiver.callback_url || ""
const opts: InboundOptionals = { callbackUrl: cbUrl, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app }
const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts)
return {
invoice: appUserInvoice.invoice
@ -192,6 +198,7 @@ export default class {
const app = await this.storage.applicationStorage.GetApplication(appId)
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
const nostrSettings = LoadNosrtSettingsFromEnv()
return {
max_withdrawable: max, identifier: req.user_identifier, info: {
userId: user.user.user_id, balance: user.user.balance_sats,
@ -200,7 +207,9 @@ export default class {
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps,
noffer: encodeNoffer({ pubkey: app.nostr_public_key!, offer: user.identifier, priceType: PriceType.spontaneous, relay: "" }),
noffer: nofferEncode({ pubkey: app.nostr_public_key!, offer: user.identifier, priceType: OfferPriceType.Spontaneous, relay: nostrSettings.relays[0] }),
ndebit: ndebitEncode({ pubkey: app.nostr_public_key!, pointer: user.identifier, relay: nostrSettings.relays[0] }),
callback_url: user.callback_url,
bridge_url: this.settings.bridgeUrl
},
}

View file

@ -0,0 +1,395 @@
import crypto from 'crypto';
import * as Types from "../../../proto/autogenerated/ts/types.js";
import ApplicationManager from "./applicationManager.js";
import Storage from '../storage/index.js'
import LND from "../lnd/lnd.js"
import { ERROR, getLogger } from "../helpers/logger.js";
import { DebitAccess, DebitAccessRules } from '../storage/entity/DebitAccess.js';
import paymentManager from './paymentManager.js';
import { Application } from '../storage/entity/Application.js';
import { ApplicationUser } from '../storage/entity/ApplicationUser.js';
import { NostrEvent, NostrSend, SendData, SendInitiator } from '../nostr/handler.js';
import { UnsignedEvent } from 'nostr-tools';
import { BudgetFrequency, NdebitData, NdebitFailure, NdebitSuccess, NdebitSuccessPayment, RecurringDebitTimeUnit } from 'nostr-tools/lib/types/nip68.js';
export const expirationRuleName = 'expiration'
export const frequencyRuleName = 'frequency'
const unitToIntervalType = (unit: RecurringDebitTimeUnit) => {
switch (unit) {
case 'day': return Types.IntervalType.DAY
case 'week': return Types.IntervalType.WEEK
case 'month': return Types.IntervalType.MONTH
default: throw new Error("invalid unit")
}
}
const intervalTypeToUnit = (interval: Types.IntervalType): RecurringDebitTimeUnit => {
switch (interval) {
case Types.IntervalType.DAY: return 'day'
case Types.IntervalType.WEEK: return 'week'
case Types.IntervalType.MONTH: return 'month'
default: throw new Error("invalid interval")
}
}
const IntervalTypeToSeconds = (interval: Types.IntervalType) => {
switch (interval) {
case Types.IntervalType.DAY: return 24 * 60 * 60
case Types.IntervalType.WEEK: return 7 * 24 * 60 * 60
case Types.IntervalType.MONTH: return 30 * 24 * 60 * 60
default: throw new Error("invalid interval")
}
}
const debitRulesToDebitAccessRules = (rule: Types.DebitRule[]): DebitAccessRules | undefined => {
let rules: DebitAccessRules | undefined = undefined
rule.forEach(r => {
if (!rules) {
rules = {}
}
const { rule } = r
switch (rule.type) {
case Types.DebitRule_rule_type.EXPIRATION_RULE:
rules[expirationRuleName] = [rule.expiration_rule.expires_at_unix.toString()]
break
case Types.DebitRule_rule_type.FREQUENCY_RULE:
const intervals = rule.frequency_rule.number_of_intervals.toString()
const unit = intervalTypeToUnit(rule.frequency_rule.interval)
rules[frequencyRuleName] = [intervals, unit, rule.frequency_rule.amount.toString()];
break
default:
throw new Error("invalid rule")
}
})
return rules
}
const debitAccessRulesToDebitRules = (rules: DebitAccessRules | null): Types.DebitRule[] => {
if (!rules) {
return []
}
return Object.entries(rules).map(([key, val]) => {
switch (key) {
case expirationRuleName:
return {
rule: {
type: Types.DebitRule_rule_type.EXPIRATION_RULE,
expiration_rule: {
expires_at_unix: +val[0]
}
}
}
case frequencyRuleName:
return {
rule: {
type: Types.DebitRule_rule_type.FREQUENCY_RULE,
frequency_rule: {
number_of_intervals: +val[0],
interval: unitToIntervalType(val[1] as RecurringDebitTimeUnit),
amount: +val[2]
}
}
}
default:
throw new Error("invalid rule")
}
})
}
const nip68errs = {
1: "Request Denied Warning",
2: "Temporary Failure",
3: "Expired Request",
4: "Rate Limited",
5: "Invalid Amount",
6: "Invalid Request",
}
type HandleNdebitRes = { status: 'fail', debitRes: NdebitFailure }
| { status: 'invoicePaid', op: Types.UserOperation, app: Application, appUser: ApplicationUser, debitRes: NdebitSuccessPayment }
| { status: 'authRequired', liveDebitReq: Types.LiveDebitRequest, app: Application, appUser: ApplicationUser }
| { status: 'authOk', debitRes: NdebitSuccess }
export class DebitManager {
_nostrSend: NostrSend | null = null
applicationManager: ApplicationManager
storage: Storage
lnd: LND
logger = getLogger({ component: 'DebitManager' })
constructor(storage: Storage, lnd: LND, applicationManager: ApplicationManager) {
this.storage = storage
this.lnd = lnd
this.applicationManager = applicationManager
}
attachNostrSend = (nostrSend: NostrSend) => {
this._nostrSend = nostrSend
}
nostrSend: NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
if (!this._nostrSend) {
throw new Error("No nostrSend attached")
}
this._nostrSend(initiator, data, relays)
}
AuthorizeDebit = async (ctx: Types.UserContext, req: Types.DebitAuthorizationRequest): Promise<Types.DebitAuthorization> => {
const access = await this.storage.debitStorage.AddDebitAccess(ctx.app_user_id, {
authorize: true,
npub: req.authorize_npub,
rules: debitRulesToDebitAccessRules(req.rules)
})
if (req.request_id) {
this.sendDebitResponse({ res: 'ok' }, { pub: req.authorize_npub, id: req.request_id, appId: ctx.app_id })
}
return {
debit_id: access.serial_id.toString(),
npub: req.authorize_npub,
authorized: true,
rules: req.rules
}
}
GetDebitAuthorizations = async (ctx: Types.UserContext): Promise<Types.DebitAuthorizations> => {
const allDebitsAccesses = await this.storage.debitStorage.GetAllUserDebitAccess(ctx.app_user_id)
const debits: Types.DebitAuthorization[] = allDebitsAccesses.map(access => ({
debit_id: access.serial_id.toString(),
authorized: access.authorized,
npub: access.npub,
rules: debitAccessRulesToDebitRules(access.rules)
}))
return { debits }
}
EditDebit = async (ctx: Types.UserContext, req: Types.DebitAuthorizationRequest): Promise<void> => {
const access = await this.storage.debitStorage.GetDebitAccess(ctx.app_user_id, req.authorize_npub);
if (!access) {
throw new Error("Debit does not exist")
}
await this.storage.debitStorage.UpdateDebitAccessRules(ctx.app_user_id, req.authorize_npub, debitRulesToDebitAccessRules(req.rules));
}
BanDebit = async (ctx: Types.UserContext, req: Types.DebitOperation): Promise<void> => {
await this.storage.debitStorage.DenyDebitAccess(ctx.app_user_id, req.npub)
}
ResetDebit = async (ctx: Types.UserContext, req: Types.DebitOperation): Promise<void> => {
await this.storage.debitStorage.RemoveDebitAccess(ctx.app_user_id, req.npub)
}
RespondToDebit = async (ctx: Types.UserContext, req: Types.DebitResponse): Promise<void> => {
switch (req.response.type) {
case Types.DebitResponse_response_type.DENIED:
this.sendDebitResponse({ res: 'GFY', error: nip68errs[1], code: 1 }, { pub: req.npub, id: req.request_id, appId: ctx.app_id })
return
case Types.DebitResponse_response_type.INVOICE:
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, ctx.app_user_id)
const { op, payment } = await this.sendDebitPayment(ctx.app_id, ctx.app_user_id, req.npub, req.response.invoice)
const debitRes: NdebitSuccessPayment = { res: 'ok', preimage: payment.preimage }
this.notifyPaymentSuccess(appUser, debitRes, op, { appId: ctx.app_id, pub: req.npub, id: req.request_id })
return
default:
throw new Error("invalid response type")
}
}
handleNip68Debit = async (pointerdata: NdebitData, event: NostrEvent) => {
if (!this._nostrSend) {
throw new Error("No nostrSend attached")
}
console.log({ pointerdata, event })
const res = await this.payNdebitInvoice(event, pointerdata)
console.log({ debitRes: res })
if (res.status === 'fail' || res.status === 'authOk') {
const e = newNdebitResponse(JSON.stringify(res.debitRes), event)
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
return
}
const { appUser } = res
if (res.status === 'authRequired') {
const message: Types.LiveDebitRequest & { requestId: string, status: 'OK' } = { ...res.liveDebitReq, requestId: "GetLiveDebitRequests", status: 'OK' }
if (appUser.nostr_public_key) {// TODO - fix before support for http streams
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'content', content: JSON.stringify(message), pub: appUser.nostr_public_key })
}
return
}
const { op, debitRes } = res
this.notifyPaymentSuccess(appUser, debitRes, op, event)
}
notifyPaymentSuccess = (appUser: ApplicationUser, debitRes: NdebitSuccessPayment, op: Types.UserOperation, event: { pub: string, id: string, appId: string }) => {
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } = { operation: op, requestId: "GetLiveUserOperations", status: 'OK' }
if (appUser.nostr_public_key) { // TODO - fix before support for http streams
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'content', content: JSON.stringify(message), pub: appUser.nostr_public_key })
}
this.sendDebitResponse(debitRes, event)
}
sendDebitResponse = (debitRes: NdebitFailure | NdebitSuccess | NdebitSuccessPayment, event: { pub: string, id: string, appId: string }) => {
const e = newNdebitResponse(JSON.stringify(debitRes), event)
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
}
payNdebitInvoice = async (event: NostrEvent, pointerdata: NdebitData): Promise<HandleNdebitRes> => {
try {
return await this.doNdebit(event, pointerdata)
} catch (e: any) {
this.logger(ERROR, e.message || e)
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[1], code: 1 } }
}
}
doNdebit = async (event: NostrEvent, pointerdata: NdebitData): Promise<HandleNdebitRes> => {
const { appId, pub: requestorPub } = event
const { amount_sats, pointer, bolt11, frequency } = pointerdata
if (!pointer) {
// TODO: debit from app owner balance
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[1], code: 1 } }
}
const appUserId = pointer
const app = await this.storage.applicationStorage.GetApplication(appId)
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, appUserId)
let decodedAmount = null
if (bolt11) {
const decoded = await this.lnd.DecodeInvoice(bolt11)
decodedAmount = decoded.numSatoshis
}
if (frequency) {
const amt = amount_sats || decodedAmount
if (!amt) {
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[5], code: 5 } }
}
const debitAccess = await this.storage.debitStorage.GetDebitAccess(appUserId, requestorPub)
if (!debitAccess) {
return {
status: 'authRequired', app, appUser, liveDebitReq: {
request_id: event.id,
npub: requestorPub,
debit: {
type: Types.LiveDebitRequest_debit_type.FREQUENCY,
frequency: {
interval: unitToIntervalType(frequency.unit),
number_of_intervals: frequency.number,
amount: amt,
}
}
}
}
} else if (!debitAccess.authorized) {
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[1], code: 1 } }
}
return { status: 'authOk', debitRes: { res: 'ok' } }
}
if (!bolt11) {
if (!amount_sats) {
const debitAccess = await this.storage.debitStorage.GetDebitAccess(appUserId, requestorPub)
if (!debitAccess) {
return {
status: 'authRequired', app, appUser, liveDebitReq: {
request_id: event.id,
npub: requestorPub,
debit: {
type: Types.LiveDebitRequest_debit_type.FULL_ACCESS,
full_access: {}
}
}
}
} else if (!debitAccess.authorized) {
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[1], code: 1 } }
}
return { status: 'authOk', debitRes: { res: 'ok' } }
}
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[6], code: 6 } }
}
if (!decodedAmount) {
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[6], code: 6 } }
}
if (amount_sats && amount_sats !== decodedAmount) {
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[5], code: 5 } }
}
const authorization = await this.storage.debitStorage.GetDebitAccess(appUserId, requestorPub)
if (!authorization) {
return {
status: 'authRequired', app, appUser, liveDebitReq: {
request_id: event.id,
npub: requestorPub,
debit: {
type: Types.LiveDebitRequest_debit_type.INVOICE,
invoice: bolt11
}
}
}
}
if (!authorization.authorized) {
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[1], code: 1 } }
}
await this.validateAccessRules(authorization, app, appUser)
const { op, payment } = await this.sendDebitPayment(appId, appUserId, requestorPub, bolt11)
return { status: 'invoicePaid', op, app, appUser, debitRes: { res: 'ok', preimage: payment.preimage } }
}
sendDebitPayment = async (appId: string, appUserId: string, requestorPub: string, bolt11: string) => {
const payment = await this.applicationManager.PayAppUserInvoice(appId, { amount: 0, invoice: bolt11, user_identifier: appUserId, debit_npub: requestorPub })
await this.storage.debitStorage.IncrementDebitAccess(appUserId, requestorPub, payment.amount_paid + payment.service_fee + payment.network_fee)
const op = this.newPaymentOperation(payment, bolt11)
return { payment, op }
}
validateAccessRules = async (access: DebitAccess, app: Application, appUser: ApplicationUser): Promise<boolean> => {
const { rules } = access
if (!rules) {
return true
}
if (rules[expirationRuleName]) {
const [expiration] = rules[expirationRuleName]
if (+expiration < Date.now()) {
await this.storage.debitStorage.RemoveDebitAccess(access.app_user_id, access.npub)
return false
}
}
if (rules[frequencyRuleName]) {
const [number, unit, max] = rules[frequencyRuleName]
const intervalType = unitToIntervalType(unit as RecurringDebitTimeUnit)
const seconds = IntervalTypeToSeconds(intervalType) * (+number)
const sinceUnix = Math.floor(Date.now() / 1000) * seconds
const payments = await this.storage.paymentStorage.GetUserDebitPayments(appUser.user.user_id, sinceUnix, access.npub)
let total = 0
for (const payment of payments) {
total += payment.paid_amount
}
if (total > +max) {
return false
}
}
return true
}
newPaymentOperation = (payment: Types.PayInvoiceResponse, bolt11: string) => {
return {
amount: payment.amount_paid,
paidAtUnix: Math.floor(Date.now() / 1000),
inbound: false,
type: Types.UserOperationType.OUTGOING_INVOICE,
identifier: bolt11,
operationId: payment.operation_id,
network_fee: payment.network_fee,
service_fee: payment.service_fee,
confirmed: true,
tx_hash: "",
internal: payment.network_fee === 0
}
}
}
const newNdebitResponse = (content: string, event: { pub: string, id: string }): UnsignedEvent => {
return {
content,
created_at: Math.floor(Date.now() / 1000),
kind: 21002,
pubkey: "",
tags: [
['p', event.pub],
['e', event.id],
],
}
}

View file

@ -11,7 +11,7 @@ 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 { UnsignedEvent } from '../nostr/tools/event.js'
import { UnsignedEvent } from 'nostr-tools'
import { NostrEvent, NostrSend } from '../nostr/handler.js'
import MetricsManager from '../metrics/index.js'
import { LoggedEvent } from '../storage/eventsLog.js'
@ -22,6 +22,7 @@ import { RugPullTracker } from "./rugPullTracker.js"
import { AdminManager } from "./adminManager.js"
import { Unlocker } from "./unlocker.js"
import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
import { DebitManager } from "./debitManager.js"
type UserOperationsSub = {
id: string
@ -32,6 +33,7 @@ type UserOperationsSub = {
}
const appTag = "Lightning.Pub"
export type NofferData = { offer: string, amount?: number }
export default class {
storage: Storage
lnd: LND
@ -46,6 +48,7 @@ export default class {
metricsManager: MetricsManager
liquidityManager: LiquidityManager
liquidityProvider: LiquidityProvider
debitManager: DebitManager
utils: Utils
rugPullTracker: RugPullTracker
unlocker: Unlocker
@ -67,6 +70,7 @@ export default class {
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager)
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
this.debitManager = new DebitManager(this.storage, this.lnd, this.applicationManager)
}
Stop() {
@ -84,6 +88,7 @@ export default class {
attachNostrSend(f: NostrSend) {
this.nostrSend = f
this.liquidityProvider.attachNostrSend(f)
this.debitManager.attachNostrSend(f)
}
htlcCb: HtlcCb = (e) => {
@ -223,7 +228,8 @@ export default class {
return
}
try {
await fetch(url + "&ok=true")
const symbol = url.includes('?') ? "&" : "?"
await fetch(url + symbol + "ok=true")
} catch (err: any) {
log(ERROR, "error sending paid callback for invoice", err.message || "")
}
@ -313,6 +319,8 @@ export default class {
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
return
}
}
const codeToMessage = (code: number) => {
@ -338,3 +346,5 @@ const newNofferResponse = (content: string, event: NostrEvent): UnsignedEvent =>
],
}
}

View file

@ -9,7 +9,6 @@ import { LoadMainSettingsFromEnv, MainSettings } from "./settings.js"
import { Utils } from "../helpers/utilsWrapper.js"
import { Wizard } from "../wizard/index.js"
import { AdminManager } from "./adminManager.js"
import { encodeNprofile } from "../../custom-nip19.js"
export type AppData = {
privateKey: string;
publicKey: string;

View file

@ -1,11 +1,9 @@
import newNostrClient from '../../../proto/autogenerated/ts/nostr_client.js'
import { NostrRequest } from '../../../proto/autogenerated/ts/nostr_transport.js'
import * as Types from '../../../proto/autogenerated/ts/types.js'
import { decodeNprofile } from '../../custom-nip19.js'
import { getLogger } from '../helpers/logger.js'
import { Utils } from '../helpers/utilsWrapper.js'
import { NostrEvent, NostrSend } from '../nostr/handler.js'
import { relayInit } from '../nostr/tools/relay.js'
import { InvoicePaidCb } from '../lnd/settings.js'
import Storage from '../storage/index.js'
export type LiquidityRequest = { action: 'spend' | 'receive', amount: number }

View file

@ -11,7 +11,7 @@ import { UserReceivingAddress } from '../storage/entity/UserReceivingAddress.js'
import { AddressPaidCb, InvoicePaidCb, PaidInvoice } from '../lnd/settings.js'
import { UserReceivingInvoice, ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
import { Payment_PaymentStatus, SendCoinsResponse } from '../../../proto/lnd/lightning.js'
import { Event, verifiedSymbol, verifySignature } from '../nostr/tools/event.js'
import { Event, verifiedSymbol, verifyEvent } from 'nostr-tools'
import { AddressReceivingTransaction } from '../storage/entity/AddressReceivingTransaction.js'
import { UserTransactionPayment } from '../storage/entity/UserTransactionPayment.js'
import { Watchdog } from './watchdog.js'
@ -286,9 +286,9 @@ export default class {
}
let paymentInfo = { preimage: "", amtPaid: 0, networkFee: 0, serialId: 0 }
if (internalInvoice) {
paymentInfo = await this.PayInternalInvoice(userId, internalInvoice, { payAmount, serviceFee }, linkedApplication)
paymentInfo = await this.PayInternalInvoice(userId, internalInvoice, { payAmount, serviceFee }, linkedApplication, req.debit_npub)
} else {
paymentInfo = await this.PayExternalInvoice(userId, req.invoice, { payAmount, serviceFee, amountForLnd: req.amount }, linkedApplication)
paymentInfo = await this.PayExternalInvoice(userId, req.invoice, { payAmount, serviceFee, amountForLnd: req.amount }, linkedApplication, req.debit_npub)
}
if (isAppUserPayment && serviceFee > 0) {
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee, "fees")
@ -304,7 +304,7 @@ export default class {
}
}
async PayExternalInvoice(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, amountForLnd: number }, linkedApplication: Application) {
async PayExternalInvoice(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, amountForLnd: number }, linkedApplication: Application, debitNpub?: string) {
if (this.settings.disableExternalPayments) {
throw new Error("something went wrong sending payment, please try again later")
}
@ -325,7 +325,7 @@ export default class {
const pendingPayment = await this.storage.txQueue.PushToQueue({
dbTx: true, description: "payment started", exec: async tx => {
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, invoice, tx)
return await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, { payAmount, serviceFee, networkFee: routingFeeLimit }, linkedApplication, provider, tx)
return await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, { payAmount, serviceFee, networkFee: routingFeeLimit }, linkedApplication, provider, tx, debitNpub)
}
})
this.log("ready to pay")
@ -348,7 +348,7 @@ export default class {
}
}
async PayInternalInvoice(userId: string, internalInvoice: UserReceivingInvoice, amounts: { payAmount: number, serviceFee: number }, linkedApplication: Application) {
async PayInternalInvoice(userId: string, internalInvoice: UserReceivingInvoice, amounts: { payAmount: number, serviceFee: number }, linkedApplication: Application, debitNpub?: string) {
if (internalInvoice.paid_at_unix > 0) {
throw new Error("this invoice was already paid")
}
@ -357,7 +357,7 @@ export default class {
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement, internalInvoice.invoice)
try {
await this.invoicePaidCb(internalInvoice.invoice, payAmount, 'internal')
const newPayment = await this.storage.paymentStorage.AddInternalPayment(userId, internalInvoice.invoice, payAmount, serviceFee, linkedApplication)
const newPayment = await this.storage.paymentStorage.AddInternalPayment(userId, internalInvoice.invoice, payAmount, serviceFee, linkedApplication, debitNpub)
this.utils.stateBundler.AddTxPoint('paidAnInvoice', payAmount, { used: 'internal', from: 'user' })
return { preimage: "", amtPaid: payAmount, networkFee: 0, serialId: newPayment.serial_id }
} catch (err) {
@ -567,7 +567,7 @@ export default class {
validateZapEvent(event: string, amt: number): ZapInfo {
const nostrEvent = JSON.parse(event) as Event
delete nostrEvent[verifiedSymbol]
const verified = verifySignature(nostrEvent)
const verified = verifyEvent(nostrEvent)
if (!verified) {
throw new Error("nostr event not valid")
}

View file

@ -5,7 +5,8 @@ import * as Types from '../../../proto/autogenerated/ts/types.js'
import { MainSettings } from './settings.js'
import PaymentManager from './paymentManager.js'
import { defaultInvoiceExpiry } from '../storage/paymentStorage.js'
import { encodeNoffer, PriceType } from '../../custom-nip19.js'
import { nip19 } from 'nostr-tools'
const { nofferEncode, OfferPriceType } = nip19
export default class {
storage: Storage
@ -26,7 +27,7 @@ export default class {
id: newProduct.product_id,
name: newProduct.name,
price_sats: newProduct.price_sats,
noffer: encodeNoffer({ pubkey: user.user_id, offer: offer, priceType: PriceType.fixed, price: newProduct.price_sats, relay: "" })
noffer: nofferEncode({ pubkey: user.user_id, offer: offer, priceType: OfferPriceType.Fixed, price: newProduct.price_sats, relay: "" })
}
}

View file

@ -1,8 +1,15 @@
//import { SimplePool, Sub, Event, UnsignedEvent, getEventHash, signEvent } from 'nostr-tools'
import { SimplePool, Sub, Event, UnsignedEvent, getEventHash, finishEvent, relayInit } from './tools/index.js'
import { encryptData, decryptData, getSharedSecret, decodePayload, encodePayload, EncryptedData } from './nip44.js'
import WebSocket from 'ws'
Object.assign(global, { WebSocket: WebSocket });
import { SimplePool, Event, UnsignedEvent, getEventHash, finalizeEvent, Relay, nip44 } from 'nostr-tools'
//import { encryptData, decryptData, getSharedSecret, decodePayload, encodePayload, EncryptedData, nip44 } from 'nostr-tools'
import { ERROR, getLogger } from '../helpers/logger.js'
import { encodeNprofile } from '../../custom-nip19.js'
import { nip19 } from 'nostr-tools'
import { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.js'
const { nprofileEncode } = nip19
const { v2 } = nip44
const { encrypt: encryptV2, decrypt: decryptV2, utils } = v2
const { getConversationKey: getConversationKeyV2 } = utils
const handledEvents: string[] = [] // TODO: - big memory leak here, add TTL
type AppInfo = { appId: string, publicKey: string, privateKey: string, name: string }
type ClientInfo = { clientId: string, publicKey: string, privateKey: string, name: string }
@ -90,11 +97,10 @@ const sendToNostr: NostrSend = (initiator, data, relays) => {
subProcessHandler.Send(initiator, data, relays)
}
send({ type: 'ready' })
const supportedKinds = [21000, 21001]
const supportedKinds = [21000, 21001, 21002]
export default class Handler {
pool = new SimplePool()
settings: NostrSettings
subs: Sub[] = []
apps: Record<string, AppInfo> = {}
eventCallback: (event: NostrEvent) => void
log = getLogger({ component: "nostrMiddleware" })
@ -102,7 +108,7 @@ export default class Handler {
this.settings = settings
this.log("connecting to relays:", settings.relays)
this.settings.apps.forEach(app => {
this.log("appId:", app.appId, "pubkey:", app.publicKey, "nprofile:", encodeNprofile({ pubkey: app.publicKey, relays: settings.relays }))
this.log("appId:", app.appId, "pubkey:", app.publicKey, "nprofile:", nprofileEncode({ pubkey: app.publicKey, relays: settings.relays }))
})
this.eventCallback = eventCallback
this.settings.apps.forEach(app => {
@ -114,9 +120,13 @@ export default class Handler {
async Connect() {
const log = getLogger({})
log("conneting to relay...", this.settings.relays[0])
const relay = relayInit(this.settings.relays[0]) // TODO: create multiple conns for multiple relays
let relay: Relay | null = null
//const relay = relayInit(this.settings.relays[0]) // TODO: create multiple conns for multiple relays
try {
await relay.connect()
relay = await Relay.connect(this.settings.relays[0])
if (!relay.connected) {
throw new Error("failed to connect to relay")
}
} catch (err) {
log("failed to connect to relay, will try again in 2 seconds")
setTimeout(() => {
@ -124,34 +134,36 @@ export default class Handler {
}, 2000)
return
}
log("connected, subbing...")
relay.on('disconnect', () => {
relay.onclose = (() => {
log("relay disconnected, will try to reconnect")
relay.close()
this.Connect()
})
const sub = relay.sub([
const sub = relay.subscribe([
{
since: Math.ceil(Date.now() / 1000),
kinds: supportedKinds,
'#p': Object.keys(this.apps),
}
])
sub.on('eose', () => {
log("up to date with nostr events")
})
sub.on('event', async (e) => {
if (!supportedKinds.includes(e.kind) || !e.pubkey) {
return
}
const pubTags = e.tags.find(tags => tags && tags.length > 1 && tags[0] === 'p')
if (!pubTags) {
return
}
const app = this.apps[pubTags[1]]
if (app) {
await this.processEvent(e, app)
return
], {
oneose: () => {
log("up to date with nostr events")
},
onevent: async (e) => {
if (!supportedKinds.includes(e.kind) || !e.pubkey) {
return
}
const pubTags = e.tags.find(tags => tags && tags.length > 1 && tags[0] === 'p')
if (!pubTags) {
return
}
const app = this.apps[pubTags[1]]
if (app) {
await this.processEvent(e, app)
return
}
}
})
}
@ -167,8 +179,11 @@ export default class Handler {
const startAtNano = process.hrtime.bigint().toString()
let content = ""
try {
const decoded = decodePayload(e.content)
content = await decryptData(decoded, getSharedSecret(app.privateKey, e.pubkey))
if (e.kind === 21000) {
content = decryptV1(e.content, getConversationKeyV1(app.privateKey, e.pubkey))
} else {
content = decryptV2(e.content, getConversationKeyV2(Buffer.from(app.privateKey, 'hex'), e.pubkey))
}
} catch (e: any) {
this.log(ERROR, "failed to decrypt event", e.message, e.content)
return
@ -179,12 +194,12 @@ export default class Handler {
async Send(initiator: SendInitiator, data: SendData, relays?: string[]) {
const keys = this.GetSendKeys(initiator)
const privateKey = Buffer.from(keys.privateKey, 'hex')
let toSign: UnsignedEvent
if (data.type === 'content') {
let content: string
try {
const decoded = await encryptData(data.content, getSharedSecret(keys.privateKey, data.pub))
content = encodePayload(decoded)
content = encryptV1(data.content, getConversationKeyV1(keys.privateKey, data.pub))
} catch (e: any) {
this.log(ERROR, "failed to encrypt content", e.message, data.content)
return
@ -197,11 +212,11 @@ export default class Handler {
tags: [['p', data.pub]],
}
} else {
console.log(data)
toSign = data.event
if (data.encrypt) {
try {
const content = await encryptData(data.event.content, getSharedSecret(keys.privateKey, data.encrypt.toPub))
toSign.content = encodePayload(content)
toSign.content = encryptV2(data.event.content, getConversationKeyV2(Buffer.from(keys.privateKey, 'hex'), data.encrypt.toPub))
} catch (e: any) {
this.log(ERROR, "failed to encrypt content", e.message)
return
@ -212,7 +227,7 @@ export default class Handler {
}
}
const signed = finishEvent(toSign, keys.privateKey)
const signed = finalizeEvent(toSign, Buffer.from(keys.privateKey, 'hex'))
let sent = false
const log = getLogger({ appName: keys.name })
await Promise.all(this.pool.publish(relays || this.settings.relays, signed).map(async p => {

View file

@ -12,17 +12,15 @@ export const getSharedSecret = (privateKey: string, publicKey: string) => {
return sha256(key.slice(1, 33));
}
export const encryptData = (content: string, sharedSecret: Uint8Array) => {
export const encrypt = (content: string, sharedSecret: Uint8Array) => {
const nonce = randomBytes(24);
const plaintext = new TextEncoder().encode(content);
const ciphertext = xchacha20(sharedSecret, nonce, plaintext, plaintext);
return {
ciphertext: Uint8Array.from(ciphertext),
nonce: nonce,
} as EncryptedData;
return encodePayload({ ciphertext, nonce });
}
export const decryptData = (payload: EncryptedData, sharedSecret: Uint8Array) => {
export const decrypt = (content: string, sharedSecret: Uint8Array) => {
const payload = decodePayload(content);
const dst = xchacha20(sharedSecret, payload.nonce, payload.ciphertext, payload.ciphertext);
const decoded = new TextDecoder().decode(dst);
return decoded;

View file

@ -1,144 +0,0 @@
import { schnorr } from '@noble/curves/secp256k1'
import { sha256 } from '@noble/hashes/sha256'
import { bytesToHex } from '@noble/hashes/utils'
import { getPublicKey } from './keys.js'
import { utf8Encoder } from './utils.js'
/** Designates a verified event signature. */
export const verifiedSymbol = Symbol('verified')
/** @deprecated Use numbers instead. */
/* eslint-disable no-unused-vars */
export enum Kind {
Metadata = 0,
Text = 1,
RecommendRelay = 2,
Contacts = 3,
EncryptedDirectMessage = 4,
EventDeletion = 5,
Repost = 6,
Reaction = 7,
BadgeAward = 8,
ChannelCreation = 40,
ChannelMetadata = 41,
ChannelMessage = 42,
ChannelHideMessage = 43,
ChannelMuteUser = 44,
Blank = 255,
Report = 1984,
ZapRequest = 9734,
Zap = 9735,
RelayList = 10002,
ClientAuth = 22242,
HttpAuth = 27235,
ProfileBadge = 30008,
BadgeDefinition = 30009,
Article = 30023,
FileMetadata = 1063,
}
export interface Event<K extends number = number> {
kind: K
tags: string[][]
content: string
created_at: number
pubkey: string
id: string
sig: string
[verifiedSymbol]?: boolean
}
export type EventTemplate<K extends number = number> = Pick<Event<K>, 'kind' | 'tags' | 'content' | 'created_at'>
export type UnsignedEvent<K extends number = number> = Pick<
Event<K>,
'kind' | 'tags' | 'content' | 'created_at' | 'pubkey'
>
/** An event whose signature has been verified. */
export interface VerifiedEvent<K extends number = number> extends Event<K> {
[verifiedSymbol]: true
}
export function getBlankEvent(): EventTemplate<Kind.Blank>
export function getBlankEvent<K extends number>(kind: K): EventTemplate<K>
export function getBlankEvent<K>(kind: K | Kind.Blank = Kind.Blank) {
return {
kind,
content: '',
tags: [],
created_at: 0,
}
}
export function finishEvent<K extends number = number>(t: EventTemplate<K>, privateKey: string): VerifiedEvent<K> {
const event = t as VerifiedEvent<K>
event.pubkey = getPublicKey(privateKey)
event.id = getEventHash(event)
event.sig = getSignature(event, privateKey)
event[verifiedSymbol] = true
return event
}
export function serializeEvent(evt: UnsignedEvent<number>): string {
if (!validateEvent(evt)) throw new Error("can't serialize event with wrong or missing properties")
return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content])
}
export function getEventHash(event: UnsignedEvent<number>): string {
let eventHash = sha256(utf8Encoder.encode(serializeEvent(event)))
return bytesToHex(eventHash)
}
const isRecord = (obj: unknown): obj is Record<string, unknown> => obj instanceof Object
export function validateEvent<T>(event: T): event is T & UnsignedEvent<number> {
if (!isRecord(event)) return false
if (typeof event.kind !== 'number') return false
if (typeof event.content !== 'string') return false
if (typeof event.created_at !== 'number') return false
if (typeof event.pubkey !== 'string') return false
if (!event.pubkey.match(/^[a-f0-9]{64}$/)) return false
if (!Array.isArray(event.tags)) return false
for (let i = 0; i < event.tags.length; i++) {
let tag = event.tags[i]
if (!Array.isArray(tag)) return false
for (let j = 0; j < tag.length; j++) {
if (typeof tag[j] === 'object') return false
}
}
return true
}
/** Verify the event's signature. This function mutates the event with a `verified` symbol, making it idempotent. */
export function verifySignature<K extends number>(event: Event<K>): event is VerifiedEvent<K> {
//@ts-ignore
if (typeof event[verifiedSymbol] === 'boolean') return event[verifiedSymbol]
const hash = getEventHash(event)
if (hash !== event.id) {
return (event[verifiedSymbol] = false)
}
try {
return (event[verifiedSymbol] = schnorr.verify(event.sig, hash, event.pubkey))
} catch (err) {
return (event[verifiedSymbol] = false)
}
}
/** @deprecated Use `getSignature` instead. */
export function signEvent(event: UnsignedEvent<number>, key: string): string {
console.warn(
'nostr-tools: `signEvent` is deprecated and will be removed or changed in the future. Please use `getSignature` instead.',
)
return getSignature(event, key)
}
/** Calculate the signature for an event. */
export function getSignature(event: UnsignedEvent<number>, key: string): string {
return bytesToHex(schnorr.sign(getEventHash(event), key))
}

View file

@ -1,41 +0,0 @@
export function getHex64(json: string, field: string): string {
let len = field.length + 3
let idx = json.indexOf(`"${field}":`) + len
let s = json.slice(idx).indexOf(`"`) + idx + 1
return json.slice(s, s + 64)
}
export function getInt(json: string, field: string): number {
let len = field.length
let idx = json.indexOf(`"${field}":`) + len + 3
let sliced = json.slice(idx)
let end = Math.min(sliced.indexOf(','), sliced.indexOf('}'))
return parseInt(sliced.slice(0, end), 10)
}
export function getSubscriptionId(json: string): string | null {
let idx = json.slice(0, 22).indexOf(`"EVENT"`)
if (idx === -1) return null
let pstart = json.slice(idx + 7 + 1).indexOf(`"`)
if (pstart === -1) return null
let start = idx + 7 + 1 + pstart
let pend = json.slice(start + 1, 80).indexOf(`"`)
if (pend === -1) return null
let end = start + 1 + pend
return json.slice(start + 1, end)
}
export function matchEventId(json: string, id: string): boolean {
return id === getHex64(json, 'id')
}
export function matchEventPubkey(json: string, pubkey: string): boolean {
return pubkey === getHex64(json, 'pubkey')
}
export function matchEventKind(json: string, kind: number): boolean {
return kind === getInt(json, 'kind')
}

View file

@ -1,72 +0,0 @@
import { Event } from './event.js'
export type Filter<K extends number = number> = {
ids?: string[]
kinds?: K[]
authors?: string[]
since?: number
until?: number
limit?: number
search?: string
[key: `#${string}`]: string[] | undefined
}
export function matchFilter(filter: Filter<number>, event: Event<number>): boolean {
if (filter.ids && filter.ids.indexOf(event.id) === -1) {
if (!filter.ids.some(prefix => event.id.startsWith(prefix))) {
return false
}
}
if (filter.kinds && filter.kinds.indexOf(event.kind) === -1) return false
if (filter.authors && filter.authors.indexOf(event.pubkey) === -1) {
if (!filter.authors.some(prefix => event.pubkey.startsWith(prefix))) {
return false
}
}
for (let f in filter) {
if (f[0] === '#') {
let tagName = f.slice(1)
let values = filter[`#${tagName}`]
if (values && !event.tags.find(([t, v]) => t === f.slice(1) && values!.indexOf(v) !== -1)) return false
}
}
if (filter.since && event.created_at < filter.since) return false
if (filter.until && event.created_at > filter.until) return false
return true
}
export function matchFilters(filters: Filter<number>[], event: Event<number>): boolean {
for (let i = 0; i < filters.length; i++) {
if (matchFilter(filters[i], event)) return true
}
return false
}
export function mergeFilters(...filters: Filter<number>[]): Filter<number> {
let result: Filter<number> = {}
for (let i = 0; i < filters.length; i++) {
let filter = filters[i]
Object.entries(filter).forEach(([property, values]) => {
if (property === 'kinds' || property === 'ids' || property === 'authors' || property[0] === '#') {
// @ts-ignore
result[property] = result[property] || []
// @ts-ignore
for (let v = 0; v < values.length; v++) {
// @ts-ignore
let value = values[v]
// @ts-ignore
if (!result[property].includes(value)) result[property].push(value)
}
}
})
if (filter.limit && (!result.limit || filter.limit > result.limit)) result.limit = filter.limit
if (filter.until && (!result.until || filter.until > result.until)) result.until = filter.until
if (filter.since && (!result.since || filter.since < result.since)) result.since = filter.since
}
return result
}

View file

@ -1,7 +0,0 @@
export * from './event.js'
export * from './fakejson.js'
export * from './filter.js'
export * from './keys.js'
export * from './pool.js'
export * from './relay.js'
export * from './utils.js'

View file

@ -1,10 +0,0 @@
import { schnorr } from '@noble/curves/secp256k1'
import { bytesToHex } from '@noble/hashes/utils'
export function generatePrivateKey(): string {
return bytesToHex(schnorr.utils.randomPrivateKey())
}
export function getPublicKey(privateKey: string): string {
return bytesToHex(schnorr.getPublicKey(privateKey))
}

View file

@ -1,249 +0,0 @@
import { relayInit, eventsGenerator, type Relay, type Sub, type SubscriptionOptions } from './relay.js'
import { normalizeURL } from './utils.js'
import type { Event } from './event.js'
import { matchFilters, type Filter } from './filter.js'
type BatchedRequest = {
filters: Filter<any>[]
relays: string[]
resolve: (events: Event<any>[]) => void
events: Event<any>[]
}
export class SimplePool {
private _conn: { [url: string]: Relay }
private _seenOn: { [id: string]: Set<string> } = {} // a map of all events we've seen in each relay
private batchedByKey: { [batchKey: string]: BatchedRequest[] } = {}
private eoseSubTimeout: number
private getTimeout: number
private seenOnEnabled: boolean = true
private batchInterval: number = 100
constructor(
options: {
eoseSubTimeout?: number
getTimeout?: number
seenOnEnabled?: boolean
batchInterval?: number
} = {},
) {
this._conn = {}
this.eoseSubTimeout = options.eoseSubTimeout || 3400
this.getTimeout = options.getTimeout || 3400
this.seenOnEnabled = options.seenOnEnabled !== false
this.batchInterval = options.batchInterval || 100
}
close(relays: string[]): void {
relays.forEach(url => {
let relay = this._conn[normalizeURL(url)]
if (relay) relay.close()
})
}
async ensureRelay(url: string): Promise<Relay> {
const nm = normalizeURL(url)
if (!this._conn[nm]) {
this._conn[nm] = relayInit(nm, {
getTimeout: this.getTimeout * 0.9,
listTimeout: this.getTimeout * 0.9,
})
}
const relay = this._conn[nm]
await relay.connect()
return relay
}
sub<K extends number = number>(relays: string[], filters: Filter<K>[], opts?: SubscriptionOptions): Sub<K> {
let _knownIds: Set<string> = new Set()
let modifiedOpts = { ...(opts || {}) }
modifiedOpts.alreadyHaveEvent = (id, url) => {
if (opts?.alreadyHaveEvent?.(id, url)) {
return true
}
if (this.seenOnEnabled) {
let set = this._seenOn[id] || new Set()
set.add(url)
this._seenOn[id] = set
}
return _knownIds.has(id)
}
let subs: Sub[] = []
let eventListeners: Set<any> = new Set()
let eoseListeners: Set<() => void> = new Set()
let eosesMissing = relays.length
let eoseSent = false
let eoseTimeout = setTimeout(
() => {
eoseSent = true
for (let cb of eoseListeners.values()) cb()
},
opts?.eoseSubTimeout || this.eoseSubTimeout,
)
relays
.filter((r, i, a) => a.indexOf(r) === i)
.forEach(async relay => {
let r
try {
r = await this.ensureRelay(relay)
} catch (err) {
handleEose()
return
}
if (!r) return
let s = r.sub(filters, modifiedOpts)
s.on('event', event => {
_knownIds.add(event.id as string)
for (let cb of eventListeners.values()) cb(event)
})
s.on('eose', () => {
if (eoseSent) return
handleEose()
})
subs.push(s)
function handleEose() {
eosesMissing--
if (eosesMissing === 0) {
clearTimeout(eoseTimeout)
for (let cb of eoseListeners.values()) cb()
}
}
})
let greaterSub: Sub<K> = {
sub(filters, opts) {
subs.forEach(sub => sub.sub(filters, opts))
return greaterSub as any
},
unsub() {
subs.forEach(sub => sub.unsub())
},
on(type, cb) {
if (type === 'event') {
eventListeners.add(cb)
} else if (type === 'eose') {
eoseListeners.add(cb as () => void | Promise<void>)
}
},
off(type, cb) {
if (type === 'event') {
eventListeners.delete(cb)
} else if (type === 'eose') eoseListeners.delete(cb as () => void | Promise<void>)
},
get events() {
return eventsGenerator(greaterSub)
},
}
return greaterSub
}
get<K extends number = number>(
relays: string[],
filter: Filter<K>,
opts?: SubscriptionOptions,
): Promise<Event<K> | null> {
return new Promise(resolve => {
let sub = this.sub(relays, [filter], opts)
let timeout = setTimeout(() => {
sub.unsub()
resolve(null)
}, this.getTimeout)
sub.on('event', event => {
resolve(event)
clearTimeout(timeout)
sub.unsub()
})
})
}
list<K extends number = number>(
relays: string[],
filters: Filter<K>[],
opts?: SubscriptionOptions,
): Promise<Event<K>[]> {
return new Promise(resolve => {
let events: Event<K>[] = []
let sub = this.sub(relays, filters, opts)
sub.on('event', event => {
events.push(event)
})
// we can rely on an eose being emitted here because pool.sub() will fake one
sub.on('eose', () => {
sub.unsub()
resolve(events)
})
})
}
batchedList<K extends number = number>(
batchKey: string,
relays: string[],
filters: Filter<K>[],
): Promise<Event<K>[]> {
return new Promise(resolve => {
if (!this.batchedByKey[batchKey]) {
this.batchedByKey[batchKey] = [
{
filters,
relays,
resolve,
events: [],
},
]
setTimeout(() => {
Object.keys(this.batchedByKey).forEach(async batchKey => {
const batchedRequests = this.batchedByKey[batchKey]
const filters = [] as Filter[]
const relays = [] as string[]
batchedRequests.forEach(br => {
filters.push(...br.filters)
relays.push(...br.relays)
})
const sub = this.sub(relays, filters)
sub.on('event', event => {
batchedRequests.forEach(br => matchFilters(br.filters, event) && br.events.push(event))
})
sub.on('eose', () => {
sub.unsub()
batchedRequests.forEach(br => br.resolve(br.events))
})
delete this.batchedByKey[batchKey]
})
}, this.batchInterval)
} else {
this.batchedByKey[batchKey].push({
filters,
relays,
resolve,
events: [],
})
}
})
}
publish(relays: string[], event: Event<number>): Promise<void>[] {
return relays.map(async relay => {
let r = await this.ensureRelay(relay)
return r.publish(event)
})
}
seenOn(id: string): string[] {
return Array.from(this._seenOn[id]?.values?.() || [])
}
}

View file

@ -1,402 +0,0 @@
/* global WebSocket */
import "websocket-polyfill"
import { verifySignature, validateEvent, type Event } from './event.js'
import { matchFilters, type Filter } from './filter.js'
import { getHex64, getSubscriptionId } from './fakejson.js'
import { MessageQueue } from './utils.js'
type RelayEvent = {
connect: () => void | Promise<void>
disconnect: () => void | Promise<void>
error: () => void | Promise<void>
notice: (msg: string) => void | Promise<void>
auth: (challenge: string) => void | Promise<void>
}
export type CountPayload = {
count: number
}
export type SubEvent<K extends number> = {
event: (event: Event<K>) => void | Promise<void>
count: (payload: CountPayload) => void | Promise<void>
eose: () => void | Promise<void>
}
export type Relay = {
url: string
status: number
connect: () => Promise<void>
close: () => void
sub: <K extends number = number>(filters: Filter<K>[], opts?: SubscriptionOptions) => Sub<K>
list: <K extends number = number>(filters: Filter<K>[], opts?: SubscriptionOptions) => Promise<Event<K>[]>
get: <K extends number = number>(filter: Filter<K>, opts?: SubscriptionOptions) => Promise<Event<K> | null>
count: (filters: Filter[], opts?: SubscriptionOptions) => Promise<CountPayload | null>
publish: (event: Event<number>) => Promise<void>
auth: (event: Event<number>) => Promise<void>
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void
}
export type Sub<K extends number = number> = {
sub: <K extends number = number>(filters: Filter<K>[], opts: SubscriptionOptions) => Sub<K>
unsub: () => void
on: <T extends keyof SubEvent<K>, U extends SubEvent<K>[T]>(event: T, listener: U) => void
off: <T extends keyof SubEvent<K>, U extends SubEvent<K>[T]>(event: T, listener: U) => void
events: AsyncGenerator<Event<K>, void, unknown>
}
export type SubscriptionOptions = {
id?: string
verb?: 'REQ' | 'COUNT'
skipVerification?: boolean
alreadyHaveEvent?: null | ((id: string, relay: string) => boolean)
eoseSubTimeout?: number
}
const newListeners = (): { [TK in keyof RelayEvent]: RelayEvent[TK][] } => ({
connect: [],
disconnect: [],
error: [],
notice: [],
auth: [],
})
export function relayInit(
url: string,
options: {
getTimeout?: number
listTimeout?: number
countTimeout?: number
} = {},
): Relay {
let { listTimeout = 3000, getTimeout = 3000, countTimeout = 3000 } = options
var ws: WebSocket
var openSubs: { [id: string]: { filters: Filter[] } & SubscriptionOptions } = {}
var listeners = newListeners()
var subListeners: {
[subid: string]: { [TK in keyof SubEvent<any>]: SubEvent<any>[TK][] }
} = {}
var pubListeners: {
[eventid: string]: {
resolve: (_: unknown) => void
reject: (err: Error) => void
}
} = {}
var connectionPromise: Promise<void> | undefined
async function connectRelay(): Promise<void> {
if (connectionPromise) return connectionPromise
connectionPromise = new Promise((resolve, reject) => {
try {
ws = new WebSocket(url)
} catch (err) {
reject(err)
}
ws.onopen = () => {
listeners.connect.forEach(cb => cb())
resolve()
}
ws.onerror = () => {
connectionPromise = undefined
listeners.error.forEach(cb => cb())
reject()
}
ws.onclose = async () => {
connectionPromise = undefined
listeners.disconnect.forEach(cb => cb())
}
let incomingMessageQueue: MessageQueue = new MessageQueue()
let handleNextInterval: any
ws.onmessage = e => {
incomingMessageQueue.enqueue(e.data)
if (!handleNextInterval) {
handleNextInterval = setInterval(handleNext, 0)
}
}
function handleNext() {
if (incomingMessageQueue.size === 0) {
clearInterval(handleNextInterval)
handleNextInterval = null
return
}
var json = incomingMessageQueue.dequeue()
if (!json) return
let subid = getSubscriptionId(json)
if (subid) {
let so = openSubs[subid]
if (so && so.alreadyHaveEvent && so.alreadyHaveEvent(getHex64(json, 'id'), url)) {
return
}
}
try {
let data = JSON.parse(json)
// we won't do any checks against the data since all failures (i.e. invalid messages from relays)
// will naturally be caught by the encompassing try..catch block
switch (data[0]) {
case 'EVENT': {
let id = data[1]
let event = data[2]
if (
validateEvent(event) &&
openSubs[id] &&
(openSubs[id].skipVerification || verifySignature(event)) &&
matchFilters(openSubs[id].filters, event)
) {
openSubs[id]
; (subListeners[id]?.event || []).forEach(cb => cb(event))
}
return
}
case 'COUNT':
let id = data[1]
let payload = data[2]
if (openSubs[id]) {
; (subListeners[id]?.count || []).forEach(cb => cb(payload))
}
return
case 'EOSE': {
let id = data[1]
if (id in subListeners) {
subListeners[id].eose.forEach(cb => cb())
subListeners[id].eose = [] // 'eose' only happens once per sub, so stop listeners here
}
return
}
case 'OK': {
let id: string = data[1]
let ok: boolean = data[2]
let reason: string = data[3] || ''
if (id in pubListeners) {
let { resolve, reject } = pubListeners[id]
if (ok) resolve(null)
else reject(new Error(reason))
}
return
}
case 'NOTICE':
let notice = data[1]
listeners.notice.forEach(cb => cb(notice))
return
case 'AUTH': {
let challenge = data[1]
listeners.auth?.forEach(cb => cb(challenge))
return
}
}
} catch (err) {
return
}
}
})
return connectionPromise
}
function connected() {
return ws?.readyState === 1
}
async function connect(): Promise<void> {
if (connected()) return // ws already open
await connectRelay()
}
async function trySend(params: [string, ...any]) {
let msg = JSON.stringify(params)
if (!connected()) {
await new Promise(resolve => setTimeout(resolve, 1000))
if (!connected()) {
return
}
}
try {
ws.send(msg)
} catch (err) {
console.log(err)
}
}
const sub = <K extends number = number>(
filters: Filter<K>[],
{
verb = 'REQ',
skipVerification = false,
alreadyHaveEvent = null,
id = Math.random().toString().slice(2),
}: SubscriptionOptions = {},
): Sub<K> => {
let subid = id
openSubs[subid] = {
id: subid,
filters,
skipVerification,
alreadyHaveEvent,
}
trySend([verb, subid, ...filters])
let subscription: Sub<K> = {
sub: (newFilters, newOpts = {}) =>
sub(newFilters || filters, {
skipVerification: newOpts.skipVerification || skipVerification,
alreadyHaveEvent: newOpts.alreadyHaveEvent || alreadyHaveEvent,
id: subid,
}),
unsub: () => {
delete openSubs[subid]
delete subListeners[subid]
trySend(['CLOSE', subid])
},
on: (type, cb) => {
subListeners[subid] = subListeners[subid] || {
event: [],
count: [],
eose: [],
}
//@ts-ignore
subListeners[subid][type].push(cb)
},
off: (type, cb): void => {
let listeners = subListeners[subid]
//@ts-ignore
let idx = listeners[type].indexOf(cb)
if (idx >= 0) listeners[type].splice(idx, 1)
},
get events() {
return eventsGenerator(subscription)
},
}
return subscription
}
function _publishEvent(event: Event<number>, type: string) {
return new Promise((resolve, reject) => {
if (!event.id) {
reject(new Error(`event ${event} has no id`))
return
}
let id = event.id
trySend([type, event])
pubListeners[id] = { resolve, reject }
})
}
return {
url,
sub,
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(type: T, cb: U): void => {
//@ts-ignore
listeners[type].push(cb)
if (type === 'connect' && ws?.readyState === 1) {
// i would love to know why we need this
; (cb as () => void)()
}
},
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(type: T, cb: U): void => {
//@ts-ignore
let index = listeners[type].indexOf(cb)
if (index !== -1) listeners[type].splice(index, 1)
},
list: (filters, opts?: SubscriptionOptions) =>
new Promise(resolve => {
let s = sub(filters, opts)
let events: Event<any>[] = []
let timeout = setTimeout(() => {
s.unsub()
resolve(events)
}, listTimeout)
s.on('eose', () => {
s.unsub()
clearTimeout(timeout)
resolve(events)
})
s.on('event', event => {
events.push(event)
})
}),
get: (filter, opts?: SubscriptionOptions) =>
new Promise(resolve => {
let s = sub([filter], opts)
let timeout = setTimeout(() => {
s.unsub()
resolve(null)
}, getTimeout)
s.on('event', event => {
s.unsub()
clearTimeout(timeout)
resolve(event)
})
}),
count: (filters: Filter[]): Promise<CountPayload | null> =>
new Promise(resolve => {
let s = sub(filters, { ...sub, verb: 'COUNT' })
let timeout = setTimeout(() => {
s.unsub()
resolve(null)
}, countTimeout)
s.on('count', (event: CountPayload) => {
s.unsub()
clearTimeout(timeout)
resolve(event)
})
}),
async publish(event): Promise<void> {
await _publishEvent(event, 'EVENT')
},
async auth(event): Promise<void> {
await _publishEvent(event, 'AUTH')
},
connect,
close(): void {
listeners = newListeners()
subListeners = {}
pubListeners = {}
if (ws?.readyState === WebSocket.OPEN) {
ws.close()
}
},
get status() {
return ws?.readyState ?? 3
},
}
}
export async function* eventsGenerator<K extends number>(sub: Sub<K>): AsyncGenerator<Event<K>, void, unknown> {
let nextResolve: ((event: Event<K>) => void) | undefined
const eventQueue: Event<K>[] = []
const pushToQueue = (event: Event<K>) => {
if (nextResolve) {
nextResolve(event)
nextResolve = undefined
} else {
eventQueue.push(event)
}
}
sub.on('event', pushToQueue)
try {
while (true) {
if (eventQueue.length > 0) {
yield eventQueue.shift()!
} else {
const event = await new Promise<Event<K>>(resolve => {
nextResolve = resolve
})
yield event
}
}
} finally {
sub.off('event', pushToQueue)
}
}

View file

@ -1,169 +0,0 @@
import type { Event } from './event.js'
export const utf8Decoder = new TextDecoder('utf-8')
export const utf8Encoder = new TextEncoder()
export function normalizeURL(url: string): string {
let p = new URL(url)
p.pathname = p.pathname.replace(/\/+/g, '/')
if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)
if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''
p.searchParams.sort()
p.hash = ''
return p.toString()
}
//
// fast insert-into-sorted-array functions adapted from https://github.com/terrymorse58/fast-sorted-array
//
export function insertEventIntoDescendingList(sortedArray: Event<number>[], event: Event<number>) {
let start = 0
let end = sortedArray.length - 1
let midPoint
let position = start
if (end < 0) {
position = 0
} else if (event.created_at < sortedArray[end].created_at) {
position = end + 1
} else if (event.created_at >= sortedArray[start].created_at) {
position = start
} else
while (true) {
if (end <= start + 1) {
position = end
break
}
midPoint = Math.floor(start + (end - start) / 2)
if (sortedArray[midPoint].created_at > event.created_at) {
start = midPoint
} else if (sortedArray[midPoint].created_at < event.created_at) {
end = midPoint
} else {
// aMidPoint === num
position = midPoint
break
}
}
// insert when num is NOT already in (no duplicates)
if (sortedArray[position]?.id !== event.id) {
return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)]
}
return sortedArray
}
export function insertEventIntoAscendingList(sortedArray: Event<number>[], event: Event<number>) {
let start = 0
let end = sortedArray.length - 1
let midPoint
let position = start
if (end < 0) {
position = 0
} else if (event.created_at > sortedArray[end].created_at) {
position = end + 1
} else if (event.created_at <= sortedArray[start].created_at) {
position = start
} else
while (true) {
if (end <= start + 1) {
position = end
break
}
midPoint = Math.floor(start + (end - start) / 2)
if (sortedArray[midPoint].created_at < event.created_at) {
start = midPoint
} else if (sortedArray[midPoint].created_at > event.created_at) {
end = midPoint
} else {
// aMidPoint === num
position = midPoint
break
}
}
// insert when num is NOT already in (no duplicates)
if (sortedArray[position]?.id !== event.id) {
return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)]
}
return sortedArray
}
export class MessageNode {
private _value: string
private _next: MessageNode | null
public get value(): string {
return this._value
}
public set value(message: string) {
this._value = message
}
public get next(): MessageNode | null {
return this._next
}
public set next(node: MessageNode | null) {
this._next = node
}
constructor(message: string) {
this._value = message
this._next = null
}
}
export class MessageQueue {
private _first: MessageNode | null
private _last: MessageNode | null
public get first(): MessageNode | null {
return this._first
}
public set first(messageNode: MessageNode | null) {
this._first = messageNode
}
public get last(): MessageNode | null {
return this._last
}
public set last(messageNode: MessageNode | null) {
this._last = messageNode
}
private _size: number
public get size(): number {
return this._size
}
public set size(v: number) {
this._size = v
}
constructor() {
this._first = null
this._last = null
this._size = 0
}
enqueue(message: string): boolean {
const newNode = new MessageNode(message)
if (this._size === 0 || !this._last) {
this._first = newNode
this._last = newNode
} else {
this._last.next = newNode
this._last = newNode
}
this._size++
return true
}
dequeue(): string | null {
if (this._size === 0 || !this._first) return null
let prev = this._first
this._first = prev.next
prev.next = null
this._size--
return prev.value
}
}

View file

@ -41,6 +41,9 @@ export default (mainHandler: Main): Types.ServerMethods => {
},
UserHealth: async () => { },
GetUserInfo: ({ ctx }) => mainHandler.appUserManager.GetUserInfo(ctx),
UpdateCallbackUrl: async ({ ctx, req }) => {
return mainHandler.appUserManager.UpdateCallbackUrl(ctx, req)
},
GetUserOperations: async ({ ctx, req }) => {
return mainHandler.paymentManager.GetUserOperations(ctx.user_id, req)
},
@ -209,6 +212,7 @@ export default (mainHandler: Main): Types.ServerMethods => {
SetMockAppBalance: async ({ ctx, req }) => {
await mainHandler.applicationManager.SetMockAppBalance(ctx.app_id, req)
},
GetLiveDebitRequests: async ({ ctx }) => { },
GetLiveUserOperations: async ({ ctx, cb }) => {
},
GetMigrationUpdate: async ({ ctx, cb }) => {
@ -261,7 +265,32 @@ export default (mainHandler: Main): Types.ServerMethods => {
})
if (err != null) throw new Error(err.message)
return mainHandler.adminManager.GetInviteTokenState(ctx, req);
},
AuthorizeDebit: async ({ ctx, req }) => {
return mainHandler.debitManager.AuthorizeDebit(ctx, req)
},
GetDebitAuthorizations: async ({ ctx }) => {
return mainHandler.debitManager.GetDebitAuthorizations(ctx)
},
BanDebit: async ({ ctx, req }) => {
const err = Types.DebitOperationValidate(req, {
npub_CustomCheck: pub => pub !== '',
})
if (err != null) throw new Error(err.message)
return mainHandler.debitManager.BanDebit(ctx, req)
},
ResetDebit: async ({ ctx, req }) => {
const err = Types.DebitOperationValidate(req, {
npub_CustomCheck: pub => pub !== '',
})
if (err != null) throw new Error(err.message)
return mainHandler.debitManager.ResetDebit(ctx, req)
},
EditDebit: async ({ ctx, req }) => {
return mainHandler.debitManager.EditDebit(ctx, req);
},
RespondToDebit: async ({ ctx, req }) => {
return mainHandler.debitManager.RespondToDebit(ctx, req);
}
}
}

View file

@ -1,6 +1,6 @@
import crypto from 'crypto';
import { Between, DataSource, EntityManager, FindOperator, IsNull, LessThanOrEqual, MoreThanOrEqual } from "typeorm"
import { generatePrivateKey, getPublicKey } from 'nostr-tools';
import { generateSecretKey, getPublicKey } from 'nostr-tools';
import { Application } from "./entity/Application.js"
import UserStorage from './userStorage.js';
import { ApplicationUser } from './entity/ApplicationUser.js';
@ -67,10 +67,11 @@ export default class {
}
async GenerateApplicationKeys(app: Application) {
const priv = generatePrivateKey()
const priv = generateSecretKey()
const pub = getPublicKey(priv)
await this.UpdateApplication(app, { nostr_private_key: priv, nostr_public_key: pub })
return { privateKey: priv, publicKey: pub, appId: app.app_id, name: app.name }
const privString = Buffer.from(priv).toString('hex')
await this.UpdateApplication(app, { nostr_private_key: privString, nostr_public_key: pub })
return { privateKey: privString, publicKey: pub, appId: app.app_id, name: app.name }
}
async AddApplicationUser(application: Application, userIdentifier: string, balance: number, nostrPub?: string) {
@ -161,9 +162,11 @@ export default class {
async AddNPubToApplicationUser(serialId: number, nPub: string, entityManager = this.DB) {
return entityManager.getRepository(ApplicationUser).update(serialId, { nostr_public_key: nPub })
}
async UpdateUserCallbackUrl(application: Application, userIdentifier: string, callbackUrl: string, entityManager = this.DB) {
return entityManager.getRepository(ApplicationUser).update({ application: { app_id: application.app_id }, identifier: userIdentifier }, { callback_url: callbackUrl })
}
async RemoveApplicationUserAndBaseUser(appUser: ApplicationUser, entityManager = this.DB) {
const baseUser = appUser.user;

View file

@ -21,6 +21,7 @@ import { Product } from "./entity/Product.js"
import { LndNodeInfo } from "./entity/LndNodeInfo.js"
import { TrackedProvider } from "./entity/TrackedProvider.js"
import { InviteToken } from "./entity/InviteToken.js"
import { DebitAccess } from "./entity/DebitAccess.js"
export type DbSettings = {
@ -61,7 +62,7 @@ export default async (settings: DbSettings, migrations: Function[]): Promise<{ s
// logging: true,
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider,
InviteToken
InviteToken, DebitAccess
],
//synchronize: true,
migrations

View file

@ -0,0 +1,58 @@
import { DataSource, EntityManager } from "typeorm"
import UserStorage from './userStorage.js';
import TransactionsQueue from "./transactionsQueue.js";
import { DebitAccess, DebitAccessRules } from "./entity/DebitAccess.js";
type AccessToAdd = {
npub: string
rules?: DebitAccessRules
authorize: boolean
}
export default class {
DB: DataSource | EntityManager
txQueue: TransactionsQueue
constructor(DB: DataSource | EntityManager, txQueue: TransactionsQueue) {
this.DB = DB
this.txQueue = txQueue
}
async AddDebitAccess(appUserId: string, access: AccessToAdd, entityManager = this.DB) {
const entry = entityManager.getRepository(DebitAccess).create({
app_user_id: appUserId,
npub: access.npub,
authorized: access.authorize,
rules: access.rules,
})
return this.txQueue.PushToQueue<DebitAccess>({ exec: async db => db.getRepository(DebitAccess).save(entry), dbTx: false })
}
async GetAllUserDebitAccess(appUserId: string) {
return this.DB.getRepository(DebitAccess).find({ where: { app_user_id: appUserId } })
}
async GetDebitAccess(appUserId: string, authorizedPub: string) {
return this.DB.getRepository(DebitAccess).findOne({ where: { app_user_id: appUserId, npub: authorizedPub } })
}
async IncrementDebitAccess(appUserId: string, authorizedPub: string, amount: number) {
return this.DB.getRepository(DebitAccess).increment({ app_user_id: appUserId, npub: authorizedPub }, 'total_debits', amount)
}
async UpdateDebitAccess(appUserId: string, authorizedPub: string, authorized: boolean) {
return this.DB.getRepository(DebitAccess).update({ app_user_id: appUserId, npub: authorizedPub }, { authorized })
}
async UpdateDebitAccessRules(appUserId: string, authorizedPub: string, rules?: DebitAccessRules) {
return this.DB.getRepository(DebitAccess).update({ app_user_id: appUserId, npub: authorizedPub }, { rules: rules || null })
}
async DenyDebitAccess(appUserId: string, pub: string) {
const access = await this.GetDebitAccess(appUserId, pub)
if (!access) {
await this.AddDebitAccess(appUserId, { npub: pub, authorize: false })
}
await this.UpdateDebitAccess(appUserId, pub, false)
}
async RemoveDebitAccess(appUserId: string, authorizedPub: string) {
return this.DB.getRepository(DebitAccess).delete({ app_user_id: appUserId, npub: authorizedPub })
}
}

View file

@ -23,6 +23,9 @@ export class ApplicationUser {
@Column({ nullable: true, unique: true })
nostr_public_key?: string
@Column({ default: "" })
callback_url: string
@CreateDateColumn()
created_at: Date

View file

@ -0,0 +1,30 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
export type DebitAccessRules = Record<string/* rule name */, string[]/* rule values */>
@Entity()
@Index("unique_debit_access", ["app_user_id", "npub"], { unique: true })
export class DebitAccess {
@PrimaryGeneratedColumn()
serial_id: number
@Column()
app_user_id: string
@Column()
npub: string
@Column()
authorized: boolean
@Column({ type: 'simple-json', default: null, nullable: true })
rules: DebitAccessRules | null
@Column({ default: 0 })
total_debits: number
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -44,6 +44,9 @@ export class UserInvoicePayment {
})
paymentIndex: number
@Column({ nullable: true })
debit_to_pub: string
@CreateDateColumn()
created_at: Date

View file

@ -10,6 +10,7 @@ import TransactionsQueue, { TX } from "./transactionsQueue.js";
import EventsLogManager from "./eventsLog.js";
import { LiquidityStorage } from "./liquidityStorage.js";
import { StateBundler } from "./stateBundler.js";
import DebitStorage from "./debitStorage.js"
export type StorageSettings = {
dbSettings: DbSettings
eventLogPath: string
@ -28,6 +29,7 @@ export default class {
paymentStorage: PaymentStorage
metricsStorage: MetricsStorage
liquidityStorage: LiquidityStorage
debitStorage: DebitStorage
eventsLog: EventsLogManager
stateBundler: StateBundler
constructor(settings: StorageSettings) {
@ -44,6 +46,7 @@ export default class {
this.paymentStorage = new PaymentStorage(this.DB, this.userStorage, this.txQueue)
this.metricsStorage = new MetricsStorage(this.settings)
this.liquidityStorage = new LiquidityStorage(this.DB, this.txQueue)
this.debitStorage = new DebitStorage(this.DB, this.txQueue)
try { if (this.settings.dataDir) fs.mkdirSync(this.settings.dataDir) } catch (e) { }
const executedMetricsMigrations = await this.metricsStorage.Connect(metricsMigrations)
return { executedMigrations, executedMetricsMigrations };

View file

@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class DebitAccess1726496225078 implements MigrationInterface {
name = 'DebitAccess1726496225078'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "debit_access" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_user_id" varchar NOT NULL, "key" varchar NOT NULL, "key_type" varchar NOT NULL, "total_debits" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
await queryRunner.query(`CREATE UNIQUE INDEX "unique_debit_access" ON "debit_access" ("app_user_id", "key", "key_type") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "unique_debit_access"`);
await queryRunner.query(`DROP TABLE "debit_access"`);
}
}

View file

@ -0,0 +1,32 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class DebitAccessFixes1726685229264 implements MigrationInterface {
name = 'DebitAccessFixes1726685229264'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "unique_debit_access"`);
await queryRunner.query(`CREATE TABLE "temporary_debit_access" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_user_id" varchar NOT NULL, "total_debits" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
await queryRunner.query(`INSERT INTO "temporary_debit_access"("serial_id", "app_user_id", "total_debits", "created_at", "updated_at") SELECT "serial_id", "app_user_id", "total_debits", "created_at", "updated_at" FROM "debit_access"`);
await queryRunner.query(`DROP TABLE "debit_access"`);
await queryRunner.query(`ALTER TABLE "temporary_debit_access" RENAME TO "debit_access"`);
await queryRunner.query(`CREATE TABLE "temporary_debit_access" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_user_id" varchar NOT NULL, "total_debits" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "npub" varchar NOT NULL, "authorized" boolean NOT NULL, "rules" text)`);
await queryRunner.query(`INSERT INTO "temporary_debit_access"("serial_id", "app_user_id", "total_debits", "created_at", "updated_at") SELECT "serial_id", "app_user_id", "total_debits", "created_at", "updated_at" FROM "debit_access"`);
await queryRunner.query(`DROP TABLE "debit_access"`);
await queryRunner.query(`ALTER TABLE "temporary_debit_access" RENAME TO "debit_access"`);
await queryRunner.query(`CREATE UNIQUE INDEX "unique_debit_access" ON "debit_access" ("app_user_id", "npub") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "unique_debit_access"`);
await queryRunner.query(`ALTER TABLE "debit_access" RENAME TO "temporary_debit_access"`);
await queryRunner.query(`CREATE TABLE "debit_access" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_user_id" varchar NOT NULL, "total_debits" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
await queryRunner.query(`INSERT INTO "debit_access"("serial_id", "app_user_id", "total_debits", "created_at", "updated_at") SELECT "serial_id", "app_user_id", "total_debits", "created_at", "updated_at" FROM "temporary_debit_access"`);
await queryRunner.query(`DROP TABLE "temporary_debit_access"`);
await queryRunner.query(`ALTER TABLE "debit_access" RENAME TO "temporary_debit_access"`);
await queryRunner.query(`CREATE TABLE "debit_access" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_user_id" varchar NOT NULL, "key" varchar NOT NULL, "key_type" varchar NOT NULL, "total_debits" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
await queryRunner.query(`INSERT INTO "debit_access"("serial_id", "app_user_id", "total_debits", "created_at", "updated_at") SELECT "serial_id", "app_user_id", "total_debits", "created_at", "updated_at" FROM "temporary_debit_access"`);
await queryRunner.query(`DROP TABLE "temporary_debit_access"`);
await queryRunner.query(`CREATE UNIQUE INDEX "unique_debit_access" ON "debit_access" ("app_user_id", "key", "key_type") `);
}
}

View file

@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class DebitToPub1727105758354 implements MigrationInterface {
name = 'DebitToPub1727105758354'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_a609a4d3d8d9b07b90692a3c45"`);
await queryRunner.query(`CREATE TABLE "temporary_user_invoice_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "paid_amount" integer NOT NULL, "routing_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, "liquidityProvider" varchar, "paymentIndex" integer NOT NULL DEFAULT (-1), "debit_to_pub" varchar, CONSTRAINT "FK_6bcac90887eea1dc61d37db2994" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_ef2aa6761ab681bbbd5f94e0fcb" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "temporary_user_invoice_payment"("serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId", "liquidityProvider", "paymentIndex") SELECT "serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId", "liquidityProvider", "paymentIndex" FROM "user_invoice_payment"`);
await queryRunner.query(`DROP TABLE "user_invoice_payment"`);
await queryRunner.query(`ALTER TABLE "temporary_user_invoice_payment" RENAME TO "user_invoice_payment"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a609a4d3d8d9b07b90692a3c45" ON "user_invoice_payment" ("invoice") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_a609a4d3d8d9b07b90692a3c45"`);
await queryRunner.query(`ALTER TABLE "user_invoice_payment" RENAME TO "temporary_user_invoice_payment"`);
await queryRunner.query(`CREATE TABLE "user_invoice_payment" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "paid_amount" integer NOT NULL, "routing_fees" integer NOT NULL, "service_fees" integer NOT NULL, "paid_at_unix" integer NOT NULL, "internal" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "linkedApplicationSerialId" integer, "liquidityProvider" varchar, "paymentIndex" integer NOT NULL DEFAULT (-1), CONSTRAINT "FK_6bcac90887eea1dc61d37db2994" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_ef2aa6761ab681bbbd5f94e0fcb" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "user_invoice_payment"("serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId", "liquidityProvider", "paymentIndex") SELECT "serial_id", "invoice", "paid_amount", "routing_fees", "service_fees", "paid_at_unix", "internal", "created_at", "updated_at", "userSerialId", "linkedApplicationSerialId", "liquidityProvider", "paymentIndex" FROM "temporary_user_invoice_payment"`);
await queryRunner.query(`DROP TABLE "temporary_user_invoice_payment"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a609a4d3d8d9b07b90692a3c45" ON "user_invoice_payment" ("invoice") `);
}
}

View file

@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class UserCbUrl1727112281043 implements MigrationInterface {
name = 'UserCbUrl1727112281043'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_0a0dbb25a73306b037dec82251"`);
await queryRunner.query(`CREATE TABLE "temporary_application_user" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "identifier" varchar NOT NULL, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "applicationSerialId" integer, "callback_url" varchar NOT NULL DEFAULT (''), CONSTRAINT "REL_0796a381bcc624f52e9a155712" UNIQUE ("userSerialId"), CONSTRAINT "UQ_3175dc397c8285d1e532554dea5" UNIQUE ("nostr_public_key"), CONSTRAINT "FK_1b3bdb6f660cd99533a1e673ef1" FOREIGN KEY ("applicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_0796a381bcc624f52e9a155712b" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "temporary_application_user"("serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId") SELECT "serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId" FROM "application_user"`);
await queryRunner.query(`DROP TABLE "application_user"`);
await queryRunner.query(`ALTER TABLE "temporary_application_user" RENAME TO "application_user"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a0dbb25a73306b037dec82251" ON "application_user" ("identifier") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_0a0dbb25a73306b037dec82251"`);
await queryRunner.query(`ALTER TABLE "application_user" RENAME TO "temporary_application_user"`);
await queryRunner.query(`CREATE TABLE "application_user" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "identifier" varchar NOT NULL, "nostr_public_key" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "applicationSerialId" integer, CONSTRAINT "REL_0796a381bcc624f52e9a155712" UNIQUE ("userSerialId"), CONSTRAINT "UQ_3175dc397c8285d1e532554dea5" UNIQUE ("nostr_public_key"), CONSTRAINT "FK_1b3bdb6f660cd99533a1e673ef1" FOREIGN KEY ("applicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_0796a381bcc624f52e9a155712b" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "application_user"("serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId") SELECT "serial_id", "identifier", "nostr_public_key", "created_at", "updated_at", "userSerialId", "applicationSerialId" FROM "temporary_application_user"`);
await queryRunner.query(`DROP TABLE "temporary_application_user"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a0dbb25a73306b037dec82251" ON "application_user" ("identifier") `);
}
}

View file

@ -12,7 +12,11 @@ import { CreateInviteTokenTable1721751414878 } from "./1721751414878-create_invi
import { PaymentIndex1721760297610 } from './1721760297610-payment_index.js'
import { HtlcCount1724266887195 } from './1724266887195-htlc_count.js'
import { BalanceEvents1724860966825 } from './1724860966825-balance_events.js'
const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610]
import { DebitAccess1726496225078 } from './1726496225078-debit_access.js'
import { DebitAccessFixes1726685229264 } from './1726685229264-debit_access_fixes.js'
import { DebitToPub1727105758354 } from './1727105758354-debit_to_pub.js'
import { UserCbUrl1727112281043 } from './1727112281043-user_cb_url.js'
const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043]
const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825]
export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
if (arg === 'fake_initial_migration') {

View file

@ -1,5 +1,5 @@
import crypto from 'crypto';
import { Between, DataSource, EntityManager, FindOperator, IsNull, LessThanOrEqual, MoreThan, MoreThanOrEqual } from "typeorm"
import { Between, DataSource, EntityManager, FindOperator, IsNull, LessThanOrEqual, MoreThan, MoreThanOrEqual, Not } from "typeorm"
import { User } from './entity/User.js';
import { UserTransactionPayment } from './entity/UserTransactionPayment.js';
import { EphemeralKeyType, UserEphemeralKey } from './entity/UserEphemeralKey.js';
@ -154,9 +154,9 @@ export default class {
})
}
async AddPendingExternalPayment(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, networkFee: number }, linkedApplication: Application, liquidityProvider: string | undefined,dbTx:DataSource|EntityManager): Promise<UserInvoicePayment> {
async AddPendingExternalPayment(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, networkFee: number }, linkedApplication: Application, liquidityProvider: string | undefined, dbTx: DataSource | EntityManager, debitNpub?: string): Promise<UserInvoicePayment> {
const newPayment = dbTx.getRepository(UserInvoicePayment).create({
user: await this.userStorage.GetUser(userId,dbTx),
user: await this.userStorage.GetUser(userId, dbTx),
paid_amount: amounts.payAmount,
invoice,
routing_fees: amounts.networkFee,
@ -164,7 +164,8 @@ export default class {
paid_at_unix: 0,
internal: false,
linkedApplication,
liquidityProvider
liquidityProvider,
debit_to_pub: debitNpub
})
return dbTx.getRepository(UserInvoicePayment).save(newPayment)
}
@ -185,7 +186,7 @@ export default class {
})
}
async AddInternalPayment(userId: string, invoice: string, amount: number, serviceFees: number, linkedApplication: Application): Promise<UserInvoicePayment> {
async AddInternalPayment(userId: string, invoice: string, amount: number, serviceFees: number, linkedApplication: Application, debitNpub?: string): Promise<UserInvoicePayment> {
const newPayment = this.DB.getRepository(UserInvoicePayment).create({
user: await this.userStorage.GetUser(userId),
paid_amount: amount,
@ -194,7 +195,8 @@ export default class {
service_fees: serviceFees,
paid_at_unix: Math.floor(Date.now() / 1000),
internal: true,
linkedApplication
linkedApplication,
debit_to_pub: debitNpub
})
return this.txQueue.PushToQueue<UserInvoicePayment>({ exec: async db => db.getRepository(UserInvoicePayment).save(newPayment), dbTx: false, description: `add internal invoice payment for ${userId} linked to ${linkedApplication.app_id}: ${invoice}, amt: ${amount} ` })
}
@ -215,6 +217,25 @@ export default class {
})
}
GetUserDebitPayments(userId: string, sinceUnix: number, debitToNpub: string, entityManager = this.DB): Promise<UserInvoicePayment[]> {
const pending = {
user: { user_id: userId },
debit_to_pub: debitToNpub,
paid_at_unix: 0,
}
const paid = {
user: { user_id: userId },
debit_to_pub: debitToNpub,
paid_at_unix: MoreThan(sinceUnix),
}
return entityManager.getRepository(UserInvoicePayment).find({
where: [pending, paid],
order: {
paid_at_unix: 'DESC'
}
})
}
async AddUserTransactionPayment(userId: string, address: string, txHash: string, txOutput: number, amount: number, chainFees: number, serviceFees: number, internal: boolean, height: number, linkedApplication: Application): Promise<UserTransactionPayment> {
const newTx = this.DB.getRepository(UserTransactionPayment).create({
user: await this.userStorage.GetUser(userId),