Merge pull request #778 from shocknet/custom-offers

custom ofers
This commit is contained in:
Justin (shocknet) 2025-01-14 10:54:22 -05:00 committed by GitHub
commit a3c1f954d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 2453 additions and 163 deletions

2
.gitignore vendored
View file

@ -15,8 +15,10 @@ data/
.wallet_secret
.wallet_password
.admin_enroll
admin.enroll
admin.npub
app.nprofile
.admin_connect
admin.connect
debug.txt
proto/autogenerated/debug.txt

View file

@ -16,6 +16,7 @@ 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 { UserOffer } from "./build/src/services/storage/entity/UserOffer.js"
import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js'
import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js'
@ -27,13 +28,14 @@ import { DebitAccess1726496225078 } from './build/src/services/storage/migration
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'
import { UserOffer1733502626042 } from './build/src/services/storage/migrations/1733502626042-user_offer.js'
export default new DataSource({
type: "sqlite",
database: "db.sqlite",
// logging: true,
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043],
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042],
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, InviteToken, DebitAccess],
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, InviteToken, DebitAccess, UserOffer],
// synchronize: true,
})
//npx typeorm migration:generate ./src/services/storage/migrations/usert_cb_url -d ./datasource.js
//npx typeorm migration:generate ./src/services/storage/migrations/user_offer -d ./datasource.js

10
package-lock.json generated
View file

@ -32,7 +32,7 @@
"grpc-tools": "^1.12.4",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"nostr-tools": "github:shocknet/nostr-tools#da188cd4bd195f44cc690074a3898f354ae85100",
"nostr-tools": "github:shocknet/nostr-tools#27575ffb69d615691242df433a0ccc063f6b8346",
"pg": "^8.4.0",
"reflect-metadata": "^0.2.2",
"rimraf": "^3.0.2",
@ -3796,9 +3796,9 @@
}
},
"node_modules/nostr-tools": {
"version": "2.8.0",
"resolved": "git+ssh://git@github.com/shocknet/nostr-tools.git#da188cd4bd195f44cc690074a3898f354ae85100",
"integrity": "sha512-kc41K75rXEnLhqIwlQmjaGsZ9yYTbyP8VW7B2Q+0U/pqaMyt25Nt0QCWiIYS04m0sanvD77OhmddvI1s2ntKog==",
"version": "2.10.4",
"resolved": "git+ssh://git@github.com/shocknet/nostr-tools.git#27575ffb69d615691242df433a0ccc063f6b8346",
"integrity": "sha512-ZQxr1yalFLi5coqG5pHWmjHGehLgCZbQusE/59mre/CgqrFMbGJY77AGTyhDnaGgqRc7B/UJnIvqGVVKhTVRmQ==",
"license": "Unlicense",
"dependencies": {
"@noble/ciphers": "^0.5.1",
@ -3809,7 +3809,7 @@
"@scure/bip39": "1.2.1"
},
"optionalDependencies": {
"nostr-wasm": "v0.1.0"
"nostr-wasm": "0.1.0"
},
"peerDependencies": {
"typescript": ">=5.0.0"

View file

@ -49,7 +49,7 @@
"grpc-tools": "^1.12.4",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"nostr-tools": "github:shocknet/nostr-tools#da188cd4bd195f44cc690074a3898f354ae85100",
"nostr-tools": "github:shocknet/nostr-tools#27575ffb69d615691242df433a0ccc063f6b8346",
"pg": "^8.4.0",
"reflect-metadata": "^0.2.2",
"rimraf": "^3.0.2",

View file

@ -28,6 +28,11 @@ The nostr server will send back a message response, and inside the body there wi
- input: [AddProductRequest](#AddProductRequest)
- output: [Product](#Product)
- AddUserOffer
- auth type: __User__
- input: [OfferConfig](#OfferConfig)
- output: [OfferId](#OfferId)
- AuthApp
- auth type: __Admin__
- input: [AuthAppRequest](#AuthAppRequest)
@ -68,6 +73,11 @@ The nostr server will send back a message response, and inside the body there wi
- input: [DecodeInvoiceRequest](#DecodeInvoiceRequest)
- output: [DecodeInvoiceResponse](#DecodeInvoiceResponse)
- DeleteUserOffer
- auth type: __User__
- input: [OfferId](#OfferId)
- This methods has an __empty__ __response__ body
- EditDebit
- auth type: __User__
- input: [DebitAuthorizationRequest](#DebitAuthorizationRequest)
@ -88,6 +98,11 @@ The nostr server will send back a message response, and inside the body there wi
- This methods has an __empty__ __request__ body
- output: [DebitAuthorizations](#DebitAuthorizations)
- GetErrorStats
- auth type: __Metrics__
- This methods has an __empty__ __request__ body
- output: [ErrorStats](#ErrorStats)
- GetHttpCreds
- auth type: __User__
- This methods has an __empty__ __request__ body
@ -153,6 +168,21 @@ The nostr server will send back a message response, and inside the body there wi
- This methods has an __empty__ __request__ body
- output: [UserInfo](#UserInfo)
- GetUserOffer
- auth type: __User__
- input: [OfferId](#OfferId)
- output: [OfferConfig](#OfferConfig)
- GetUserOfferInvoices
- auth type: __User__
- input: [GetUserOfferInvoicesReq](#GetUserOfferInvoicesReq)
- output: [OfferInvoices](#OfferInvoices)
- GetUserOffers
- auth type: __User__
- This methods has an __empty__ __request__ body
- output: [UserOffers](#UserOffers)
- GetUserOperations
- auth type: __User__
- input: [GetUserOperationsRequest](#GetUserOperationsRequest)
@ -225,6 +255,11 @@ The nostr server will send back a message response, and inside the body there wi
- input: [UpdateChannelPolicyRequest](#UpdateChannelPolicyRequest)
- This methods has an __empty__ __response__ body
- UpdateUserOffer
- auth type: __User__
- input: [OfferConfig](#OfferConfig)
- This methods has an __empty__ __response__ body
- UseInviteLink
- auth type: __GuestWithPub__
- input: [UseInviteLinkRequest](#UseInviteLinkRequest)
@ -233,7 +268,7 @@ The nostr server will send back a message response, and inside the body there wi
- UserHealth
- auth type: __User__
- This methods has an __empty__ __request__ body
- This methods has an __empty__ __response__ body
- output: [UserHealthState](#UserHealthState)
# HTTP API DEFINITION
@ -311,6 +346,13 @@ The nostr server will send back a message response, and inside the body there wi
- input: [AddProductRequest](#AddProductRequest)
- output: [Product](#Product)
- AddUserOffer
- auth type: __User__
- http method: __post__
- http route: __/api/user/offer/add__
- input: [OfferConfig](#OfferConfig)
- output: [OfferId](#OfferId)
- AuthApp
- auth type: __Admin__
- http method: __post__
@ -367,6 +409,13 @@ The nostr server will send back a message response, and inside the body there wi
- input: [DecodeInvoiceRequest](#DecodeInvoiceRequest)
- output: [DecodeInvoiceResponse](#DecodeInvoiceResponse)
- DeleteUserOffer
- auth type: __User__
- http method: __post__
- http route: __/api/user/offer/delete__
- input: [OfferId](#OfferId)
- This methods has an __empty__ __response__ body
- EditDebit
- auth type: __User__
- http method: __post__
@ -423,6 +472,13 @@ The nostr server will send back a message response, and inside the body there wi
- This methods has an __empty__ __request__ body
- output: [DebitAuthorizations](#DebitAuthorizations)
- GetErrorStats
- auth type: __Metrics__
- http method: __post__
- http route: __/api/reports/errors__
- This methods has an __empty__ __request__ body
- output: [ErrorStats](#ErrorStats)
- GetHttpCreds
- auth type: __User__
- http method: __post__
@ -539,6 +595,27 @@ The nostr server will send back a message response, and inside the body there wi
- This methods has an __empty__ __request__ body
- output: [UserInfo](#UserInfo)
- GetUserOffer
- auth type: __User__
- http method: __post__
- http route: __/api/user/offer/get__
- input: [OfferId](#OfferId)
- output: [OfferConfig](#OfferConfig)
- GetUserOfferInvoices
- auth type: __User__
- http method: __post__
- http route: __/api/user/offer/get/invoices__
- input: [GetUserOfferInvoicesReq](#GetUserOfferInvoicesReq)
- output: [OfferInvoices](#OfferInvoices)
- GetUserOffers
- auth type: __User__
- http method: __get__
- http route: __/api/user/offers/get__
- This methods has an __empty__ __request__ body
- output: [UserOffers](#UserOffers)
- GetUserOperations
- auth type: __User__
- http method: __post__
@ -733,6 +810,13 @@ The nostr server will send back a message response, and inside the body there wi
- input: [UpdateChannelPolicyRequest](#UpdateChannelPolicyRequest)
- This methods has an __empty__ __response__ body
- UpdateUserOffer
- auth type: __User__
- http method: __post__
- http route: __/api/user/offer/update__
- input: [OfferConfig](#OfferConfig)
- This methods has an __empty__ __response__ body
- UseInviteLink
- auth type: __GuestWithPub__
- http method: __post__
@ -745,7 +829,7 @@ The nostr server will send back a message response, and inside the body there wi
- http method: __post__
- http route: __/api/user/health__
- This methods has an __empty__ __request__ body
- This methods has an __empty__ __response__ body
- output: [UserHealthState](#UserHealthState)
# INPUTS AND OUTPUTS
@ -764,6 +848,8 @@ The nostr server will send back a message response, and inside the body there wi
### AddAppUserInvoiceRequest
- __http_callback_url__: _string_
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
- __offer_string__: _string_ *this field is optional
- __payer_data__: _[PayerData](#PayerData)_ *this field is optional
- __payer_identifier__: _string_
- __receiver_identifier__: _string_
@ -792,6 +878,9 @@ The nostr server will send back a message response, and inside the body there wi
- __total_fees__: _number_
- __users__: _[UsersInfo](#UsersInfo)_
### AppUsageMetrics
- __app_metrics__: MAP with key: _string_ and value: _[UsageMetricTlv](#UsageMetricTlv)_
### AppUser
- __identifier__: _string_
- __info__: _[UserInfo](#UserInfo)_
@ -909,6 +998,18 @@ The nostr server will send back a message response, and inside the body there wi
### EnrollAdminTokenRequest
- __admin_token__: _string_
### ErrorStat
- __errors__: _number_
- __from_unix__: _number_
- __total__: _number_
### ErrorStats
- __past10m__: _[ErrorStat](#ErrorStat)_
- __past1h__: _[ErrorStat](#ErrorStat)_
- __past1m__: _[ErrorStat](#ErrorStat)_
- __past24h__: _[ErrorStat](#ErrorStat)_
- __past6h__: _[ErrorStat](#ErrorStat)_
### FrequencyRule
- __amount__: _number_
- __interval__: _[IntervalType](#IntervalType)_
@ -936,6 +1037,10 @@ The nostr server will send back a message response, and inside the body there wi
### GetProductBuyLinkResponse
- __link__: _string_
### GetUserOfferInvoicesReq
- __include_unpaid__: _boolean_
- __offer_id__: _string_
### GetUserOperationsRequest
- __latestIncomingInvoice__: _number_
- __latestIncomingTx__: _number_
@ -1056,6 +1161,28 @@ The nostr server will send back a message response, and inside the body there wi
### NewInvoiceResponse
- __invoice__: _string_
### OfferConfig
- __callback_url__: _string_
- __default_offer__: _boolean_
- __expected_data__: MAP with key: _string_ and value: _[OfferDataType](#OfferDataType)_
- __label__: _string_
- __noffer__: _string_
- __offer_id__: _string_
- __price_sats__: _number_
### OfferId
- __offer_id__: _string_
### OfferInvoice
- __amount__: _number_
- __data__: MAP with key: _string_ and value: _string_
- __invoice__: _string_
- __offer_id__: _string_
- __paid_at_unix__: _number_
### OfferInvoices
- __invoices__: ARRAY of: _[OfferInvoice](#OfferInvoice)_
### OpenChannel
- __active__: _boolean_
- __capacity__: _number_
@ -1106,6 +1233,9 @@ The nostr server will send back a message response, and inside the body there wi
- __preimage__: _string_
- __service_fee__: _number_
### PayerData
- __data__: MAP with key: _string_ and value: _string_
### PaymentState
- __amount__: _number_
- __network_fee__: _number_
@ -1172,6 +1302,7 @@ The nostr server will send back a message response, and inside the body there wi
- __update__: _[UpdateChannelPolicyRequest_update](#UpdateChannelPolicyRequest_update)_
### UsageMetric
- __app_id__: _string_ *this field is optional
- __auth_in_nano__: _number_
- __batch__: _boolean_
- __batch_size__: _number_
@ -1180,14 +1311,23 @@ The nostr server will send back a message response, and inside the body there wi
- __parsed_in_nano__: _number_
- __processed_at_ms__: _number_
- __rpc_name__: _string_
- __success__: _boolean_
- __validate_in_nano__: _number_
### UsageMetricTlv
- __available_chunks__: ARRAY of: _number_
- __base_64_tlvs__: ARRAY of: _string_
- __current_chunk__: _number_
### UsageMetrics
- __metrics__: ARRAY of: _[UsageMetric](#UsageMetric)_
- __apps__: MAP with key: _string_ and value: _[AppUsageMetrics](#AppUsageMetrics)_
### UseInviteLinkRequest
- __invite_token__: _string_
### UserHealthState
- __downtime_reason__: _string_
### UserInfo
- __balance__: _number_
- __bridge_url__: _string_
@ -1201,6 +1341,9 @@ The nostr server will send back a message response, and inside the body there wi
- __userId__: _string_
- __user_identifier__: _string_
### UserOffers
- __offers__: ARRAY of: _[OfferConfig](#OfferConfig)_
### UserOperation
- __amount__: _number_
- __confirmed__: _boolean_
@ -1239,6 +1382,9 @@ The nostr server will send back a message response, and inside the body there wi
- __MONTH__
- __WEEK__
### OfferDataType
- __DATA_STRING__
### OperationType
- __CHAIN_OP__
- __INVOICE_OP__

View file

@ -60,6 +60,7 @@ type Client struct {
AddAppUserInvoice func(req AddAppUserInvoiceRequest) (*NewInvoiceResponse, error)
AddPeer func(req AddPeerRequest) error
AddProduct func(req AddProductRequest) (*Product, error)
AddUserOffer func(req OfferConfig) (*OfferId, error)
AuthApp func(req AuthAppRequest) (*AuthApp, error)
AuthorizeDebit func(req DebitAuthorizationRequest) (*DebitAuthorization, error)
BanDebit func(req DebitOperation) error
@ -68,6 +69,7 @@ type Client struct {
CloseChannel func(req CloseChannelRequest) (*CloseChannelResponse, error)
CreateOneTimeInviteLink func(req CreateOneTimeInviteLinkRequest) (*CreateOneTimeInviteLinkResponse, error)
DecodeInvoice func(req DecodeInvoiceRequest) (*DecodeInvoiceResponse, error)
DeleteUserOffer func(req OfferId) error
EditDebit func(req DebitAuthorizationRequest) error
EncryptionExchange func(req EncryptionExchangeRequest) error
EnrollAdminToken func(req EnrollAdminTokenRequest) error
@ -76,6 +78,7 @@ type Client struct {
GetAppUserLNURLInfo func(req GetAppUserLNURLInfoRequest) (*LnurlPayInfoResponse, error)
GetAppsMetrics func(req AppsMetricsRequest) (*AppsMetrics, error)
GetDebitAuthorizations func() (*DebitAuthorizations, error)
GetErrorStats func() (*ErrorStats, error)
GetHttpCreds func() (*HttpCreds, error)
GetInviteLinkState func(req GetInviteTokenStateRequest) (*GetInviteTokenStateResponse, error)
GetLNURLChannelLink func() (*LnurlLinkResponse, error)
@ -92,6 +95,9 @@ type Client struct {
GetSeed func() (*LndSeed, error)
GetUsageMetrics func() (*UsageMetrics, error)
GetUserInfo func() (*UserInfo, error)
GetUserOffer func(req OfferId) (*OfferConfig, error)
GetUserOfferInvoices func(req GetUserOfferInvoicesReq) (*OfferInvoices, error)
GetUserOffers func() (*UserOffers, error)
GetUserOperations func(req GetUserOperationsRequest) (*GetUserOperationsResponse, error)
HandleLnurlAddress func(routeParams HandleLnurlAddress_RouteParams) (*LnurlPayInfoResponse, error)
HandleLnurlPay func(query HandleLnurlPay_Query) (*HandleLnurlPayResponse, error)
@ -118,8 +124,9 @@ type Client struct {
SetMockInvoiceAsPaid func(req SetMockInvoiceAsPaidRequest) error
UpdateCallbackUrl func(req CallbackUrl) (*CallbackUrl, error)
UpdateChannelPolicy func(req UpdateChannelPolicyRequest) error
UpdateUserOffer func(req OfferConfig) error
UseInviteLink func(req UseInviteLinkRequest) error
UserHealth func() error
UserHealth func() (*UserHealthState, error)
}
func NewClient(params ClientParams) *Client {
@ -293,6 +300,35 @@ func NewClient(params ClientParams) *Client {
}
return &res, nil
},
AddUserOffer: func(req OfferConfig) (*OfferId, error) {
auth, err := params.RetrieveUserAuth()
if err != nil {
return nil, err
}
finalRoute := "/api/user/offer/add"
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
if err != nil {
return nil, err
}
result := ResultError{}
err = json.Unmarshal(resBody, &result)
if err != nil {
return nil, err
}
if result.Status == "ERROR" {
return nil, fmt.Errorf(result.Reason)
}
res := OfferId{}
err = json.Unmarshal(resBody, &res)
if err != nil {
return nil, err
}
return &res, nil
},
AuthApp: func(req AuthAppRequest) (*AuthApp, error) {
auth, err := params.RetrieveAdminAuth()
if err != nil {
@ -492,6 +528,30 @@ func NewClient(params ClientParams) *Client {
}
return &res, nil
},
DeleteUserOffer: func(req OfferId) error {
auth, err := params.RetrieveUserAuth()
if err != nil {
return err
}
finalRoute := "/api/user/offer/delete"
body, err := json.Marshal(req)
if err != nil {
return err
}
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
if err != nil {
return err
}
result := ResultError{}
err = json.Unmarshal(resBody, &result)
if err != nil {
return err
}
if result.Status == "ERROR" {
return fmt.Errorf(result.Reason)
}
return nil
},
EditDebit: func(req DebitAuthorizationRequest) error {
auth, err := params.RetrieveUserAuth()
if err != nil {
@ -699,6 +759,32 @@ func NewClient(params ClientParams) *Client {
}
return &res, nil
},
GetErrorStats: func() (*ErrorStats, error) {
auth, err := params.RetrieveMetricsAuth()
if err != nil {
return nil, err
}
finalRoute := "/api/reports/errors"
body := []byte{}
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
if err != nil {
return nil, err
}
result := ResultError{}
err = json.Unmarshal(resBody, &result)
if err != nil {
return nil, err
}
if result.Status == "ERROR" {
return nil, fmt.Errorf(result.Reason)
}
res := ErrorStats{}
err = json.Unmarshal(resBody, &res)
if err != nil {
return nil, err
}
return &res, nil
},
// server streaming method: GetHttpCreds not implemented
GetInviteLinkState: func(req GetInviteTokenStateRequest) (*GetInviteTokenStateResponse, error) {
auth, err := params.RetrieveAdminAuth()
@ -1023,6 +1109,86 @@ func NewClient(params ClientParams) *Client {
}
return &res, nil
},
GetUserOffer: func(req OfferId) (*OfferConfig, error) {
auth, err := params.RetrieveUserAuth()
if err != nil {
return nil, err
}
finalRoute := "/api/user/offer/get"
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
if err != nil {
return nil, err
}
result := ResultError{}
err = json.Unmarshal(resBody, &result)
if err != nil {
return nil, err
}
if result.Status == "ERROR" {
return nil, fmt.Errorf(result.Reason)
}
res := OfferConfig{}
err = json.Unmarshal(resBody, &res)
if err != nil {
return nil, err
}
return &res, nil
},
GetUserOfferInvoices: func(req GetUserOfferInvoicesReq) (*OfferInvoices, error) {
auth, err := params.RetrieveUserAuth()
if err != nil {
return nil, err
}
finalRoute := "/api/user/offer/get/invoices"
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
if err != nil {
return nil, err
}
result := ResultError{}
err = json.Unmarshal(resBody, &result)
if err != nil {
return nil, err
}
if result.Status == "ERROR" {
return nil, fmt.Errorf(result.Reason)
}
res := OfferInvoices{}
err = json.Unmarshal(resBody, &res)
if err != nil {
return nil, err
}
return &res, nil
},
GetUserOffers: func() (*UserOffers, error) {
auth, err := params.RetrieveUserAuth()
if err != nil {
return nil, err
}
finalRoute := "/api/user/offers/get"
resBody, err := doGetRequest(params.BaseURL+finalRoute, auth)
result := ResultError{}
err = json.Unmarshal(resBody, &result)
if err != nil {
return nil, err
}
if result.Status == "ERROR" {
return nil, fmt.Errorf(result.Reason)
}
res := UserOffers{}
err = json.Unmarshal(resBody, &res)
if err != nil {
return nil, err
}
return &res, nil
},
GetUserOperations: func(req GetUserOperationsRequest) (*GetUserOperationsResponse, error) {
auth, err := params.RetrieveUserAuth()
if err != nil {
@ -1717,6 +1883,30 @@ func NewClient(params ClientParams) *Client {
}
return nil
},
UpdateUserOffer: func(req OfferConfig) error {
auth, err := params.RetrieveUserAuth()
if err != nil {
return err
}
finalRoute := "/api/user/offer/update"
body, err := json.Marshal(req)
if err != nil {
return err
}
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
if err != nil {
return err
}
result := ResultError{}
err = json.Unmarshal(resBody, &result)
if err != nil {
return err
}
if result.Status == "ERROR" {
return fmt.Errorf(result.Reason)
}
return nil
},
UseInviteLink: func(req UseInviteLinkRequest) error {
auth, err := params.RetrieveGuestWithPubAuth()
if err != nil {
@ -1741,26 +1931,31 @@ func NewClient(params ClientParams) *Client {
}
return nil
},
UserHealth: func() error {
UserHealth: func() (*UserHealthState, error) {
auth, err := params.RetrieveUserAuth()
if err != nil {
return err
return nil, err
}
finalRoute := "/api/user/health"
body := []byte{}
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
if err != nil {
return err
return nil, err
}
result := ResultError{}
err = json.Unmarshal(resBody, &result)
if err != nil {
return err
return nil, err
}
if result.Status == "ERROR" {
return fmt.Errorf(result.Reason)
return nil, fmt.Errorf(result.Reason)
}
return nil
res := UserHealthState{}
err = json.Unmarshal(resBody, &res)
if err != nil {
return nil, err
}
return &res, nil
},
}
}

View file

@ -64,6 +64,12 @@ const (
WEEK IntervalType = "WEEK"
)
type OfferDataType string
const (
DATA_STRING OfferDataType = "DATA_STRING"
)
type OperationType string
const (
@ -94,6 +100,8 @@ type AddAppRequest struct {
type AddAppUserInvoiceRequest struct {
Http_callback_url string `json:"http_callback_url"`
Invoice_req *NewInvoiceRequest `json:"invoice_req"`
Offer_string string `json:"offer_string"`
Payer_data *PayerData `json:"payer_data"`
Payer_identifier string `json:"payer_identifier"`
Receiver_identifier string `json:"receiver_identifier"`
}
@ -122,6 +130,9 @@ type AppMetrics struct {
Total_fees int64 `json:"total_fees"`
Users *UsersInfo `json:"users"`
}
type AppUsageMetrics struct {
App_metrics map[string]UsageMetricTlv `json:"app_metrics"`
}
type AppUser struct {
Identifier string `json:"identifier"`
Info *UserInfo `json:"info"`
@ -239,6 +250,18 @@ type EncryptionExchangeRequest struct {
type EnrollAdminTokenRequest struct {
Admin_token string `json:"admin_token"`
}
type ErrorStat struct {
Errors int64 `json:"errors"`
From_unix int64 `json:"from_unix"`
Total int64 `json:"total"`
}
type ErrorStats struct {
Past10m *ErrorStat `json:"past10m"`
Past1h *ErrorStat `json:"past1h"`
Past1m *ErrorStat `json:"past1m"`
Past24h *ErrorStat `json:"past24h"`
Past6h *ErrorStat `json:"past6h"`
}
type FrequencyRule struct {
Amount int64 `json:"amount"`
Interval IntervalType `json:"interval"`
@ -266,6 +289,10 @@ type GetPaymentStateRequest struct {
type GetProductBuyLinkResponse struct {
Link string `json:"link"`
}
type GetUserOfferInvoicesReq struct {
Include_unpaid bool `json:"include_unpaid"`
Offer_id string `json:"offer_id"`
}
type GetUserOperationsRequest struct {
Latestincominginvoice int64 `json:"latestIncomingInvoice"`
Latestincomingtx int64 `json:"latestIncomingTx"`
@ -386,6 +413,28 @@ type NewInvoiceRequest struct {
type NewInvoiceResponse struct {
Invoice string `json:"invoice"`
}
type OfferConfig struct {
Callback_url string `json:"callback_url"`
Default_offer bool `json:"default_offer"`
Expected_data map[string]OfferDataType `json:"expected_data"`
Label string `json:"label"`
Noffer string `json:"noffer"`
Offer_id string `json:"offer_id"`
Price_sats int64 `json:"price_sats"`
}
type OfferId struct {
Offer_id string `json:"offer_id"`
}
type OfferInvoice struct {
Amount int64 `json:"amount"`
Data map[string]string `json:"data"`
Invoice string `json:"invoice"`
Offer_id string `json:"offer_id"`
Paid_at_unix int64 `json:"paid_at_unix"`
}
type OfferInvoices struct {
Invoices []OfferInvoice `json:"invoices"`
}
type OpenChannel struct {
Active bool `json:"active"`
Capacity int64 `json:"capacity"`
@ -436,6 +485,9 @@ type PayInvoiceResponse struct {
Preimage string `json:"preimage"`
Service_fee int64 `json:"service_fee"`
}
type PayerData struct {
Data map[string]string `json:"data"`
}
type PaymentState struct {
Amount int64 `json:"amount"`
Network_fee int64 `json:"network_fee"`
@ -502,6 +554,7 @@ type UpdateChannelPolicyRequest struct {
Update *UpdateChannelPolicyRequest_update `json:"update"`
}
type UsageMetric struct {
App_id string `json:"app_id"`
Auth_in_nano int64 `json:"auth_in_nano"`
Batch bool `json:"batch"`
Batch_size int64 `json:"batch_size"`
@ -510,14 +563,23 @@ type UsageMetric struct {
Parsed_in_nano int64 `json:"parsed_in_nano"`
Processed_at_ms int64 `json:"processed_at_ms"`
Rpc_name string `json:"rpc_name"`
Success bool `json:"success"`
Validate_in_nano int64 `json:"validate_in_nano"`
}
type UsageMetricTlv struct {
Available_chunks []int64 `json:"available_chunks"`
Base_64_tlvs []string `json:"base_64_tlvs"`
Current_chunk int64 `json:"current_chunk"`
}
type UsageMetrics struct {
Metrics []UsageMetric `json:"metrics"`
Apps map[string]AppUsageMetrics `json:"apps"`
}
type UseInviteLinkRequest struct {
Invite_token string `json:"invite_token"`
}
type UserHealthState struct {
Downtime_reason string `json:"downtime_reason"`
}
type UserInfo struct {
Balance int64 `json:"balance"`
Bridge_url string `json:"bridge_url"`
@ -531,6 +593,9 @@ type UserInfo struct {
Userid string `json:"userId"`
User_identifier string `json:"user_identifier"`
}
type UserOffers struct {
Offers []OfferConfig `json:"offers"`
}
type UserOperation struct {
Amount int64 `json:"amount"`
Confirmed bool `json:"confirmed"`

View file

@ -166,6 +166,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.AddUserOffer) throw new Error('method: AddUserOffer is not implemented')
app.post('/api/user/offer/add', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'AddUserOffer', 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.AddUserOffer) throw new Error('method: AddUserOffer 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.OfferConfigValidate(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.AddUserOffer({rpcName:'AddUserOffer', 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.AuthApp) throw new Error('method: AuthApp is not implemented')
app.post('/api/admin/app/auth', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'AuthApp', batch: false, nostr: false, batchSize: 0}
@ -287,6 +309,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'AddUserOffer':
if (!methods.AddUserOffer) {
throw new Error('method AddUserOffer not found' )
} else {
const error = Types.OfferConfigValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
const res = await methods.AddUserOffer({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'AuthorizeDebit':
if (!methods.AuthorizeDebit) {
throw new Error('method AuthorizeDebit not found' )
@ -323,6 +357,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'DeleteUserOffer':
if (!methods.DeleteUserOffer) {
throw new Error('method DeleteUserOffer not found' )
} else {
const error = Types.OfferIdValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.DeleteUserOffer({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'EditDebit':
if (!methods.EditDebit) {
throw new Error('method EditDebit not found' )
@ -409,6 +455,40 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetUserOffer':
if (!methods.GetUserOffer) {
throw new Error('method GetUserOffer not found' )
} else {
const error = Types.OfferIdValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
const res = await methods.GetUserOffer({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetUserOfferInvoices':
if (!methods.GetUserOfferInvoices) {
throw new Error('method GetUserOfferInvoices not found' )
} else {
const error = Types.GetUserOfferInvoicesReqValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
const res = await methods.GetUserOfferInvoices({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetUserOffers':
if (!methods.GetUserOffers) {
throw new Error('method GetUserOffers not found' )
} else {
opStats.validate = opStats.guard
const res = await methods.GetUserOffers({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetUserOperations':
if (!methods.GetUserOperations) {
throw new Error('method GetUserOperations not found' )
@ -515,12 +595,24 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'UpdateUserOffer':
if (!methods.UpdateUserOffer) {
throw new Error('method UpdateUserOffer not found' )
} else {
const error = Types.OfferConfigValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.UpdateUserOffer({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'UserHealth':
if (!methods.UserHealth) {
throw new Error('method UserHealth not found' )
} else {
opStats.validate = opStats.guard
await methods.UserHealth({...operation, ctx}); responses.push({ status: 'OK' })
const res = await methods.UserHealth({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
@ -601,6 +693,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.DeleteUserOffer) throw new Error('method: DeleteUserOffer is not implemented')
app.post('/api/user/offer/delete', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'DeleteUserOffer', 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.DeleteUserOffer) throw new Error('method: DeleteUserOffer 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.OfferIdValidate(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.DeleteUserOffer({rpcName:'DeleteUserOffer', 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.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}
@ -771,6 +885,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.GetErrorStats) throw new Error('method: GetErrorStats is not implemented')
app.post('/api/reports/errors', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetErrorStats', 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.GetErrorStats) throw new Error('method: GetErrorStats is not implemented')
const authContext = await opts.MetricsAuthGuard(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.GetErrorStats({rpcName:'GetErrorStats', 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}
@ -1011,6 +1144,69 @@ 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.GetUserOffer) throw new Error('method: GetUserOffer is not implemented')
app.post('/api/user/offer/get', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetUserOffer', 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.GetUserOffer) throw new Error('method: GetUserOffer 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.OfferIdValidate(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.GetUserOffer({rpcName:'GetUserOffer', 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.GetUserOfferInvoices) throw new Error('method: GetUserOfferInvoices is not implemented')
app.post('/api/user/offer/get/invoices', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetUserOfferInvoices', 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.GetUserOfferInvoices) throw new Error('method: GetUserOfferInvoices 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.GetUserOfferInvoicesReqValidate(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.GetUserOfferInvoices({rpcName:'GetUserOfferInvoices', 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.GetUserOffers) throw new Error('method: GetUserOffers is not implemented')
app.get('/api/user/offers/get', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetUserOffers', 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.GetUserOffers) throw new Error('method: GetUserOffers 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.GetUserOffers({rpcName:'GetUserOffers', 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.GetUserOperations) throw new Error('method: GetUserOperations is not implemented')
app.post('/api/user/operations', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetUserOperations', batch: false, nostr: false, batchSize: 0}
@ -1565,6 +1761,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.UpdateUserOffer) throw new Error('method: UpdateUserOffer is not implemented')
app.post('/api/user/offer/update', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'UpdateUserOffer', 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.UpdateUserOffer) throw new Error('method: UpdateUserOffer 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.OfferConfigValidate(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.UpdateUserOffer({rpcName:'UpdateUserOffer', 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.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}
@ -1600,9 +1818,9 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
stats.validate = stats.guard
const query = req.query
const params = req.params
await methods.UserHealth({rpcName:'UserHealth', ctx:authContext })
const response = await methods.UserHealth({rpcName:'UserHealth', ctx:authContext })
stats.handle = process.hrtime.bigint()
res.json({status: 'OK'})
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 }
})

View file

@ -98,6 +98,20 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddUserOffer: async (request: Types.OfferConfig): Promise<ResultError | ({ status: 'OK' }& Types.OfferId)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/offer/add'
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.OfferIdValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
AuthApp: async (request: Types.AuthAppRequest): Promise<ResultError | ({ status: 'OK' }& Types.AuthApp)> => {
const auth = await params.retrieveAdminAuth()
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
@ -204,6 +218,17 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
DeleteUserOffer: async (request: Types.OfferId): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/offer/delete'
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' }
},
EditDebit: async (request: Types.DebitAuthorizationRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
@ -307,6 +332,20 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetErrorStats: async (): Promise<ResultError | ({ status: 'OK' }& Types.ErrorStats)> => {
const auth = await params.retrieveMetricsAuth()
if (auth === null) throw new Error('retrieveMetricsAuth() returned null')
let finalRoute = '/api/reports/errors'
const { data } = await axios.post(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.ErrorStatsValidate(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()
@ -483,6 +522,48 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUserOffer: async (request: Types.OfferId): Promise<ResultError | ({ status: 'OK' }& Types.OfferConfig)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/offer/get'
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.OfferConfigValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUserOfferInvoices: async (request: Types.GetUserOfferInvoicesReq): Promise<ResultError | ({ status: 'OK' }& Types.OfferInvoices)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/offer/get/invoices'
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.OfferInvoicesValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUserOffers: async (): Promise<ResultError | ({ status: 'OK' }& Types.UserOffers)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/offers/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.UserOffersValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetUserOperationsResponse)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
@ -821,6 +902,17 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
UpdateUserOffer: async (request: Types.OfferConfig): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/offer/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') {
return data
}
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')
@ -832,14 +924,17 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
UserHealth: async (): Promise<ResultError | ({ status: 'OK' })> => {
UserHealth: async (): Promise<ResultError | ({ status: 'OK' }& Types.UserHealthState)> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/user/health'
const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.UserHealthStateValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},

View file

@ -54,6 +54,21 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddUserOffer: async (request: Types.OfferConfig): Promise<ResultError | ({ status: 'OK' }& Types.OfferId)> => {
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:'AddUserOffer',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.OfferIdValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
AuthApp: async (request: Types.AuthAppRequest): Promise<ResultError | ({ status: 'OK' }& Types.AuthApp)> => {
const auth = await params.retrieveNostrAdminAuth()
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
@ -167,6 +182,18 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
DeleteUserOffer: async (request: Types.OfferId): 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:'DeleteUserOffer',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' }
},
EditDebit: async (request: Types.DebitAuthorizationRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
@ -220,6 +247,20 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetErrorStats: async (): Promise<ResultError | ({ status: 'OK' }& Types.ErrorStats)> => {
const auth = await params.retrieveNostrMetricsAuth()
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
const nostrRequest: NostrRequest = {}
const data = await send(params.pubDestination, {rpcName:'GetErrorStats',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.ErrorStatsValidate(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')
@ -409,6 +450,50 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUserOffer: async (request: Types.OfferId): Promise<ResultError | ({ status: 'OK' }& Types.OfferConfig)> => {
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:'GetUserOffer',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.OfferConfigValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUserOfferInvoices: async (request: Types.GetUserOfferInvoicesReq): Promise<ResultError | ({ status: 'OK' }& Types.OfferInvoices)> => {
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:'GetUserOfferInvoices',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.OfferInvoicesValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUserOffers: async (): Promise<ResultError | ({ status: 'OK' }& Types.UserOffers)> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
const data = await send(params.pubDestination, {rpcName:'GetUserOffers',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.UserOffersValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetUserOperationsResponse)> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
@ -606,6 +691,18 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
UpdateUserOffer: async (request: Types.OfferConfig): 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:'UpdateUserOffer',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' }
},
UseInviteLink: async (request: Types.UseInviteLinkRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrGuestWithPubAuth()
if (auth === null) throw new Error('retrieveNostrGuestWithPubAuth() returned null')
@ -618,14 +715,17 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
UserHealth: async (): Promise<ResultError | ({ status: 'OK' })> => {
UserHealth: async (): Promise<ResultError | ({ status: 'OK' }& Types.UserHealthState)> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
const data = await send(params.pubDestination, {rpcName:'UserHealth',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.UserHealthStateValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},

View file

@ -80,6 +80,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 'AddUserOffer':
try {
if (!methods.AddUserOffer) throw new Error('method: AddUserOffer 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.OfferConfigValidate(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.AddUserOffer({rpcName:'AddUserOffer', 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 'AuthApp':
try {
if (!methods.AuthApp) throw new Error('method: AuthApp is not implemented')
@ -175,6 +191,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'AddUserOffer':
if (!methods.AddUserOffer) {
throw new Error('method not defined: AddUserOffer')
} else {
const error = Types.OfferConfigValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
const res = await methods.AddUserOffer({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'AuthorizeDebit':
if (!methods.AuthorizeDebit) {
throw new Error('method not defined: AuthorizeDebit')
@ -211,6 +239,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'DeleteUserOffer':
if (!methods.DeleteUserOffer) {
throw new Error('method not defined: DeleteUserOffer')
} else {
const error = Types.OfferIdValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.DeleteUserOffer({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'EditDebit':
if (!methods.EditDebit) {
throw new Error('method not defined: EditDebit')
@ -297,6 +337,40 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetUserOffer':
if (!methods.GetUserOffer) {
throw new Error('method not defined: GetUserOffer')
} else {
const error = Types.OfferIdValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
const res = await methods.GetUserOffer({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetUserOfferInvoices':
if (!methods.GetUserOfferInvoices) {
throw new Error('method not defined: GetUserOfferInvoices')
} else {
const error = Types.GetUserOfferInvoicesReqValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
const res = await methods.GetUserOfferInvoices({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetUserOffers':
if (!methods.GetUserOffers) {
throw new Error('method not defined: GetUserOffers')
} else {
opStats.validate = opStats.guard
const res = await methods.GetUserOffers({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'GetUserOperations':
if (!methods.GetUserOperations) {
throw new Error('method not defined: GetUserOperations')
@ -403,12 +477,24 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'UpdateUserOffer':
if (!methods.UpdateUserOffer) {
throw new Error('method not defined: UpdateUserOffer')
} else {
const error = Types.OfferConfigValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.UpdateUserOffer({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'UserHealth':
if (!methods.UserHealth) {
throw new Error('method not defined: UserHealth')
} else {
opStats.validate = opStats.guard
await methods.UserHealth({...operation, ctx}); responses.push({ status: 'OK' })
const res = await methods.UserHealth({...operation, ctx}); responses.push({ status: 'OK', ...res })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
@ -471,6 +557,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 'DeleteUserOffer':
try {
if (!methods.DeleteUserOffer) throw new Error('method: DeleteUserOffer 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.OfferIdValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
await methods.DeleteUserOffer({rpcName:'DeleteUserOffer', 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 'EditDebit':
try {
if (!methods.EditDebit) throw new Error('method: EditDebit is not implemented')
@ -532,6 +634,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 'GetErrorStats':
try {
if (!methods.GetErrorStats) throw new Error('method: GetErrorStats is not implemented')
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
stats.validate = stats.guard
const response = await methods.GetErrorStats({rpcName:'GetErrorStats', 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')
@ -710,6 +825,51 @@ 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 'GetUserOffer':
try {
if (!methods.GetUserOffer) throw new Error('method: GetUserOffer 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.OfferIdValidate(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.GetUserOffer({rpcName:'GetUserOffer', 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 'GetUserOfferInvoices':
try {
if (!methods.GetUserOfferInvoices) throw new Error('method: GetUserOfferInvoices 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.GetUserOfferInvoicesReqValidate(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.GetUserOfferInvoices({rpcName:'GetUserOfferInvoices', 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 'GetUserOffers':
try {
if (!methods.GetUserOffers) throw new Error('method: GetUserOffers 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.GetUserOffers({rpcName:'GetUserOffers', 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 'GetUserOperations':
try {
if (!methods.GetUserOperations) throw new Error('method: GetUserOperations is not implemented')
@ -928,6 +1088,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 'UpdateUserOffer':
try {
if (!methods.UpdateUserOffer) throw new Error('method: UpdateUserOffer 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.OfferConfigValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
await methods.UpdateUserOffer({rpcName:'UpdateUserOffer', 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 'UseInviteLink':
try {
if (!methods.UseInviteLink) throw new Error('method: UseInviteLink is not implemented')
@ -951,9 +1127,9 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
stats.guard = process.hrtime.bigint()
authCtx = authContext
stats.validate = stats.guard
await methods.UserHealth({rpcName:'UserHealth', ctx:authContext })
const response = await methods.UserHealth({rpcName:'UserHealth', ctx:authContext })
stats.handle = process.hrtime.bigint()
res({status: 'OK'})
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

View file

@ -27,15 +27,15 @@ export type GuestWithPubMethodOutputs = LinkNPubThroughToken_Output | UseInviteL
export type MetricsContext = {
operator_id: string
}
export type MetricsMethodInputs = GetAppsMetrics_Input | GetLndMetrics_Input | GetUsageMetrics_Input
export type MetricsMethodOutputs = GetAppsMetrics_Output | GetLndMetrics_Output | GetUsageMetrics_Output
export type MetricsMethodInputs = GetAppsMetrics_Input | GetErrorStats_Input | GetLndMetrics_Input | GetUsageMetrics_Input
export type MetricsMethodOutputs = GetAppsMetrics_Output | GetErrorStats_Output | GetLndMetrics_Output | GetUsageMetrics_Output
export type UserContext = {
app_id: string
app_user_id: string
user_id: string
}
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 | 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 | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UserHealth_Output
export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeDebit_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | GetDebitAuthorizations_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOfferInvoices_Input | GetUserOffers_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input
export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeDebit_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | GetDebitAuthorizations_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOfferInvoices_Output | GetUserOffers_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output
export type AuthContext = AdminContext | AppContext | GuestContext | GuestWithPubContext | MetricsContext | UserContext
export type AddApp_Input = {rpcName:'AddApp', req: AddAppRequest}
@ -56,6 +56,9 @@ export type AddPeer_Output = ResultError | { status: 'OK' }
export type AddProduct_Input = {rpcName:'AddProduct', req: AddProductRequest}
export type AddProduct_Output = ResultError | ({ status: 'OK' } & Product)
export type AddUserOffer_Input = {rpcName:'AddUserOffer', req: OfferConfig}
export type AddUserOffer_Output = ResultError | ({ status: 'OK' } & OfferId)
export type AuthApp_Input = {rpcName:'AuthApp', req: AuthAppRequest}
export type AuthApp_Output = ResultError | ({ status: 'OK' } & AuthApp)
@ -80,6 +83,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 DeleteUserOffer_Input = {rpcName:'DeleteUserOffer', req: OfferId}
export type DeleteUserOffer_Output = ResultError | { status: 'OK' }
export type EditDebit_Input = {rpcName:'EditDebit', req: DebitAuthorizationRequest}
export type EditDebit_Output = ResultError | { status: 'OK' }
@ -104,6 +110,9 @@ export type GetAppsMetrics_Output = ResultError | ({ status: 'OK' } & AppsMetric
export type GetDebitAuthorizations_Input = {rpcName:'GetDebitAuthorizations'}
export type GetDebitAuthorizations_Output = ResultError | ({ status: 'OK' } & DebitAuthorizations)
export type GetErrorStats_Input = {rpcName:'GetErrorStats'}
export type GetErrorStats_Output = ResultError | ({ status: 'OK' } & ErrorStats)
export type GetHttpCreds_Input = {rpcName:'GetHttpCreds', cb:(res: HttpCreds, err:Error|null)=> void}
export type GetHttpCreds_Output = ResultError | { status: 'OK' }
@ -158,6 +167,15 @@ export type GetUsageMetrics_Output = ResultError | ({ status: 'OK' } & UsageMetr
export type GetUserInfo_Input = {rpcName:'GetUserInfo'}
export type GetUserInfo_Output = ResultError | ({ status: 'OK' } & UserInfo)
export type GetUserOffer_Input = {rpcName:'GetUserOffer', req: OfferId}
export type GetUserOffer_Output = ResultError | ({ status: 'OK' } & OfferConfig)
export type GetUserOfferInvoices_Input = {rpcName:'GetUserOfferInvoices', req: GetUserOfferInvoicesReq}
export type GetUserOfferInvoices_Output = ResultError | ({ status: 'OK' } & OfferInvoices)
export type GetUserOffers_Input = {rpcName:'GetUserOffers'}
export type GetUserOffers_Output = ResultError | ({ status: 'OK' } & UserOffers)
export type GetUserOperations_Input = {rpcName:'GetUserOperations', req: GetUserOperationsRequest}
export type GetUserOperations_Output = ResultError | ({ status: 'OK' } & GetUserOperationsResponse)
@ -252,11 +270,14 @@ export type UpdateCallbackUrl_Output = ResultError | ({ status: 'OK' } & Callbac
export type UpdateChannelPolicy_Input = {rpcName:'UpdateChannelPolicy', req: UpdateChannelPolicyRequest}
export type UpdateChannelPolicy_Output = ResultError | { status: 'OK' }
export type UpdateUserOffer_Input = {rpcName:'UpdateUserOffer', req: OfferConfig}
export type UpdateUserOffer_Output = ResultError | { status: 'OK' }
export type UseInviteLink_Input = {rpcName:'UseInviteLink', req: UseInviteLinkRequest}
export type UseInviteLink_Output = ResultError | { status: 'OK' }
export type UserHealth_Input = {rpcName:'UserHealth'}
export type UserHealth_Output = ResultError | { status: 'OK' }
export type UserHealth_Output = ResultError | ({ status: 'OK' } & UserHealthState)
export type ServerMethods = {
AddApp?: (req: AddApp_Input & {ctx: AdminContext }) => Promise<AuthApp>
@ -265,6 +286,7 @@ export type ServerMethods = {
AddAppUserInvoice?: (req: AddAppUserInvoice_Input & {ctx: AppContext }) => Promise<NewInvoiceResponse>
AddPeer?: (req: AddPeer_Input & {ctx: AdminContext }) => Promise<void>
AddProduct?: (req: AddProduct_Input & {ctx: UserContext }) => Promise<Product>
AddUserOffer?: (req: AddUserOffer_Input & {ctx: UserContext }) => Promise<OfferId>
AuthApp?: (req: AuthApp_Input & {ctx: AdminContext }) => Promise<AuthApp>
AuthorizeDebit?: (req: AuthorizeDebit_Input & {ctx: UserContext }) => Promise<DebitAuthorization>
BanDebit?: (req: BanDebit_Input & {ctx: UserContext }) => Promise<void>
@ -272,6 +294,7 @@ export type ServerMethods = {
CloseChannel?: (req: CloseChannel_Input & {ctx: AdminContext }) => Promise<CloseChannelResponse>
CreateOneTimeInviteLink?: (req: CreateOneTimeInviteLink_Input & {ctx: AdminContext }) => Promise<CreateOneTimeInviteLinkResponse>
DecodeInvoice?: (req: DecodeInvoice_Input & {ctx: UserContext }) => Promise<DecodeInvoiceResponse>
DeleteUserOffer?: (req: DeleteUserOffer_Input & {ctx: UserContext }) => Promise<void>
EditDebit?: (req: EditDebit_Input & {ctx: UserContext }) => Promise<void>
EncryptionExchange?: (req: EncryptionExchange_Input & {ctx: GuestContext }) => Promise<void>
EnrollAdminToken?: (req: EnrollAdminToken_Input & {ctx: UserContext }) => Promise<void>
@ -280,6 +303,7 @@ export type ServerMethods = {
GetAppUserLNURLInfo?: (req: GetAppUserLNURLInfo_Input & {ctx: AppContext }) => Promise<LnurlPayInfoResponse>
GetAppsMetrics?: (req: GetAppsMetrics_Input & {ctx: MetricsContext }) => Promise<AppsMetrics>
GetDebitAuthorizations?: (req: GetDebitAuthorizations_Input & {ctx: UserContext }) => Promise<DebitAuthorizations>
GetErrorStats?: (req: GetErrorStats_Input & {ctx: MetricsContext }) => Promise<ErrorStats>
GetHttpCreds?: (req: GetHttpCreds_Input & {ctx: UserContext }) => Promise<void>
GetInviteLinkState?: (req: GetInviteLinkState_Input & {ctx: AdminContext }) => Promise<GetInviteTokenStateResponse>
GetLNURLChannelLink?: (req: GetLNURLChannelLink_Input & {ctx: UserContext }) => Promise<LnurlLinkResponse>
@ -296,6 +320,9 @@ export type ServerMethods = {
GetSeed?: (req: GetSeed_Input & {ctx: AdminContext }) => Promise<LndSeed>
GetUsageMetrics?: (req: GetUsageMetrics_Input & {ctx: MetricsContext }) => Promise<UsageMetrics>
GetUserInfo?: (req: GetUserInfo_Input & {ctx: UserContext }) => Promise<UserInfo>
GetUserOffer?: (req: GetUserOffer_Input & {ctx: UserContext }) => Promise<OfferConfig>
GetUserOfferInvoices?: (req: GetUserOfferInvoices_Input & {ctx: UserContext }) => Promise<OfferInvoices>
GetUserOffers?: (req: GetUserOffers_Input & {ctx: UserContext }) => Promise<UserOffers>
GetUserOperations?: (req: GetUserOperations_Input & {ctx: UserContext }) => Promise<GetUserOperationsResponse>
HandleLnurlAddress?: (req: HandleLnurlAddress_Input & {ctx: GuestContext }) => Promise<LnurlPayInfoResponse>
HandleLnurlPay?: (req: HandleLnurlPay_Input & {ctx: GuestContext }) => Promise<HandleLnurlPayResponse>
@ -322,8 +349,9 @@ export type ServerMethods = {
SetMockInvoiceAsPaid?: (req: SetMockInvoiceAsPaid_Input & {ctx: GuestContext }) => Promise<void>
UpdateCallbackUrl?: (req: UpdateCallbackUrl_Input & {ctx: UserContext }) => Promise<CallbackUrl>
UpdateChannelPolicy?: (req: UpdateChannelPolicy_Input & {ctx: AdminContext }) => Promise<void>
UpdateUserOffer?: (req: UpdateUserOffer_Input & {ctx: UserContext }) => Promise<void>
UseInviteLink?: (req: UseInviteLink_Input & {ctx: GuestWithPubContext }) => Promise<void>
UserHealth?: (req: UserHealth_Input & {ctx: UserContext }) => Promise<void>
UserHealth?: (req: UserHealth_Input & {ctx: UserContext }) => Promise<UserHealthState>
}
export enum AddressType {
@ -344,6 +372,13 @@ export const enumCheckIntervalType = (e?: IntervalType): boolean => {
for (const v in IntervalType) if (e === v) return true
return false
}
export enum OfferDataType {
DATA_STRING = 'DATA_STRING',
}
export const enumCheckOfferDataType = (e?: OfferDataType): boolean => {
for (const v in OfferDataType) if (e === v) return true
return false
}
export enum OperationType {
CHAIN_OP = 'CHAIN_OP',
INVOICE_OP = 'INVOICE_OP',
@ -424,14 +459,19 @@ export const AddAppRequestValidate = (o?: AddAppRequest, opts: AddAppRequestOpti
export type AddAppUserInvoiceRequest = {
http_callback_url: string
invoice_req: NewInvoiceRequest
offer_string?: string
payer_data?: PayerData
payer_identifier: string
receiver_identifier: string
}
export const AddAppUserInvoiceRequestOptionalFields: [] = []
export type AddAppUserInvoiceRequestOptionalField = 'offer_string' | 'payer_data'
export const AddAppUserInvoiceRequestOptionalFields: AddAppUserInvoiceRequestOptionalField[] = ['offer_string', 'payer_data']
export type AddAppUserInvoiceRequestOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
checkOptionalsAreSet?: AddAppUserInvoiceRequestOptionalField[]
http_callback_url_CustomCheck?: (v: string) => boolean
invoice_req_Options?: NewInvoiceRequestOptions
offer_string_CustomCheck?: (v?: string) => boolean
payer_data_Options?: PayerDataOptions
payer_identifier_CustomCheck?: (v: string) => boolean
receiver_identifier_CustomCheck?: (v: string) => boolean
}
@ -446,6 +486,15 @@ export const AddAppUserInvoiceRequestValidate = (o?: AddAppUserInvoiceRequest, o
if (invoice_reqErr !== null) return invoice_reqErr
if ((o.offer_string || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('offer_string')) && typeof o.offer_string !== 'string') return new Error(`${path}.offer_string: is not a string`)
if (opts.offer_string_CustomCheck && !opts.offer_string_CustomCheck(o.offer_string)) return new Error(`${path}.offer_string: custom check failed`)
if (typeof o.payer_data === 'object' || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('payer_data')) {
const payer_dataErr = PayerDataValidate(o.payer_data, opts.payer_data_Options, `${path}.payer_data`)
if (payer_dataErr !== null) return payer_dataErr
}
if (typeof o.payer_identifier !== 'string') return new Error(`${path}.payer_identifier: is not a string`)
if (opts.payer_identifier_CustomCheck && !opts.payer_identifier_CustomCheck(o.payer_identifier)) return new Error(`${path}.payer_identifier: custom check failed`)
@ -599,6 +648,28 @@ export const AppMetricsValidate = (o?: AppMetrics, opts: AppMetricsOptions = {},
return null
}
export type AppUsageMetrics = {
app_metrics: Record<string, UsageMetricTlv>
}
export const AppUsageMetricsOptionalFields: [] = []
export type AppUsageMetricsOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
app_metrics_EntryOptions?: UsageMetricTlvOptions
app_metrics_CustomCheck?: (v: Record<string, UsageMetricTlv>) => boolean
}
export const AppUsageMetricsValidate = (o?: AppUsageMetrics, opts: AppUsageMetricsOptions = {}, path: string = 'AppUsageMetrics::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.app_metrics !== 'object' || o.app_metrics === null) return new Error(`${path}.app_metrics: is not an object or is null`)
for (const key in o.app_metrics) {
const app_metricsErr = UsageMetricTlvValidate(o.app_metrics[key], opts.app_metrics_EntryOptions, `${path}.app_metrics['${key}']`)
if (app_metricsErr !== null) return app_metricsErr
}
return null
}
export type AppUser = {
identifier: string
info: UserInfo
@ -1304,6 +1375,77 @@ export const EnrollAdminTokenRequestValidate = (o?: EnrollAdminTokenRequest, opt
return null
}
export type ErrorStat = {
errors: number
from_unix: number
total: number
}
export const ErrorStatOptionalFields: [] = []
export type ErrorStatOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
errors_CustomCheck?: (v: number) => boolean
from_unix_CustomCheck?: (v: number) => boolean
total_CustomCheck?: (v: number) => boolean
}
export const ErrorStatValidate = (o?: ErrorStat, opts: ErrorStatOptions = {}, path: string = 'ErrorStat::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.errors !== 'number') return new Error(`${path}.errors: is not a number`)
if (opts.errors_CustomCheck && !opts.errors_CustomCheck(o.errors)) return new Error(`${path}.errors: custom check failed`)
if (typeof o.from_unix !== 'number') return new Error(`${path}.from_unix: is not a number`)
if (opts.from_unix_CustomCheck && !opts.from_unix_CustomCheck(o.from_unix)) return new Error(`${path}.from_unix: custom check failed`)
if (typeof o.total !== 'number') return new Error(`${path}.total: is not a number`)
if (opts.total_CustomCheck && !opts.total_CustomCheck(o.total)) return new Error(`${path}.total: custom check failed`)
return null
}
export type ErrorStats = {
past10m: ErrorStat
past1h: ErrorStat
past1m: ErrorStat
past24h: ErrorStat
past6h: ErrorStat
}
export const ErrorStatsOptionalFields: [] = []
export type ErrorStatsOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
past10m_Options?: ErrorStatOptions
past1h_Options?: ErrorStatOptions
past1m_Options?: ErrorStatOptions
past24h_Options?: ErrorStatOptions
past6h_Options?: ErrorStatOptions
}
export const ErrorStatsValidate = (o?: ErrorStats, opts: ErrorStatsOptions = {}, path: string = 'ErrorStats::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 past10mErr = ErrorStatValidate(o.past10m, opts.past10m_Options, `${path}.past10m`)
if (past10mErr !== null) return past10mErr
const past1hErr = ErrorStatValidate(o.past1h, opts.past1h_Options, `${path}.past1h`)
if (past1hErr !== null) return past1hErr
const past1mErr = ErrorStatValidate(o.past1m, opts.past1m_Options, `${path}.past1m`)
if (past1mErr !== null) return past1mErr
const past24hErr = ErrorStatValidate(o.past24h, opts.past24h_Options, `${path}.past24h`)
if (past24hErr !== null) return past24hErr
const past6hErr = ErrorStatValidate(o.past6h, opts.past6h_Options, `${path}.past6h`)
if (past6hErr !== null) return past6hErr
return null
}
export type FrequencyRule = {
amount: number
interval: IntervalType
@ -1463,6 +1605,29 @@ export const GetProductBuyLinkResponseValidate = (o?: GetProductBuyLinkResponse,
return null
}
export type GetUserOfferInvoicesReq = {
include_unpaid: boolean
offer_id: string
}
export const GetUserOfferInvoicesReqOptionalFields: [] = []
export type GetUserOfferInvoicesReqOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
include_unpaid_CustomCheck?: (v: boolean) => boolean
offer_id_CustomCheck?: (v: string) => boolean
}
export const GetUserOfferInvoicesReqValidate = (o?: GetUserOfferInvoicesReq, opts: GetUserOfferInvoicesReqOptions = {}, path: string = 'GetUserOfferInvoicesReq::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.include_unpaid !== 'boolean') return new Error(`${path}.include_unpaid: is not a boolean`)
if (opts.include_unpaid_CustomCheck && !opts.include_unpaid_CustomCheck(o.include_unpaid)) return new Error(`${path}.include_unpaid: custom check failed`)
if (typeof o.offer_id !== 'string') return new Error(`${path}.offer_id: is not a string`)
if (opts.offer_id_CustomCheck && !opts.offer_id_CustomCheck(o.offer_id)) return new Error(`${path}.offer_id: custom check failed`)
return null
}
export type GetUserOperationsRequest = {
latestIncomingInvoice: number
latestIncomingTx: number
@ -2201,6 +2366,137 @@ export const NewInvoiceResponseValidate = (o?: NewInvoiceResponse, opts: NewInvo
return null
}
export type OfferConfig = {
callback_url: string
default_offer: boolean
expected_data: Record<string, OfferDataType>
label: string
noffer: string
offer_id: string
price_sats: number
}
export const OfferConfigOptionalFields: [] = []
export type OfferConfigOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
callback_url_CustomCheck?: (v: string) => boolean
default_offer_CustomCheck?: (v: boolean) => boolean
expected_data_CustomCheck?: (v: Record<string, OfferDataType>) => boolean
label_CustomCheck?: (v: string) => boolean
noffer_CustomCheck?: (v: string) => boolean
offer_id_CustomCheck?: (v: string) => boolean
price_sats_CustomCheck?: (v: number) => boolean
}
export const OfferConfigValidate = (o?: OfferConfig, opts: OfferConfigOptions = {}, path: string = 'OfferConfig::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.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.default_offer !== 'boolean') return new Error(`${path}.default_offer: is not a boolean`)
if (opts.default_offer_CustomCheck && !opts.default_offer_CustomCheck(o.default_offer)) return new Error(`${path}.default_offer: custom check failed`)
if (typeof o.expected_data !== 'object' || o.expected_data === null) return new Error(`${path}.expected_data: is not an object or is null`)
for (const key in o.expected_data) {
if (!enumCheckOfferDataType(o.expected_data[key])) return new Error(`${path}.expected_data['${key}']: is not a OfferDataType`)
}
if (typeof o.label !== 'string') return new Error(`${path}.label: is not a string`)
if (opts.label_CustomCheck && !opts.label_CustomCheck(o.label)) return new Error(`${path}.label: custom check failed`)
if (typeof o.noffer !== 'string') return new Error(`${path}.noffer: is not a string`)
if (opts.noffer_CustomCheck && !opts.noffer_CustomCheck(o.noffer)) return new Error(`${path}.noffer: custom check failed`)
if (typeof o.offer_id !== 'string') return new Error(`${path}.offer_id: is not a string`)
if (opts.offer_id_CustomCheck && !opts.offer_id_CustomCheck(o.offer_id)) return new Error(`${path}.offer_id: custom check failed`)
if (typeof o.price_sats !== 'number') return new Error(`${path}.price_sats: is not a number`)
if (opts.price_sats_CustomCheck && !opts.price_sats_CustomCheck(o.price_sats)) return new Error(`${path}.price_sats: custom check failed`)
return null
}
export type OfferId = {
offer_id: string
}
export const OfferIdOptionalFields: [] = []
export type OfferIdOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
offer_id_CustomCheck?: (v: string) => boolean
}
export const OfferIdValidate = (o?: OfferId, opts: OfferIdOptions = {}, path: string = 'OfferId::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.offer_id !== 'string') return new Error(`${path}.offer_id: is not a string`)
if (opts.offer_id_CustomCheck && !opts.offer_id_CustomCheck(o.offer_id)) return new Error(`${path}.offer_id: custom check failed`)
return null
}
export type OfferInvoice = {
amount: number
data: Record<string, string>
invoice: string
offer_id: string
paid_at_unix: number
}
export const OfferInvoiceOptionalFields: [] = []
export type OfferInvoiceOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
amount_CustomCheck?: (v: number) => boolean
data_CustomCheck?: (v: Record<string, string>) => boolean
invoice_CustomCheck?: (v: string) => boolean
offer_id_CustomCheck?: (v: string) => boolean
paid_at_unix_CustomCheck?: (v: number) => boolean
}
export const OfferInvoiceValidate = (o?: OfferInvoice, opts: OfferInvoiceOptions = {}, path: string = 'OfferInvoice::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 (typeof o.data !== 'object' || o.data === null) return new Error(`${path}.data: is not an object or is null`)
for (const key in o.data) {
if (typeof o.data[key] !== 'string') return new Error(`${path}.data['${key}']: is not a string`)
}
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`)
if (typeof o.offer_id !== 'string') return new Error(`${path}.offer_id: is not a string`)
if (opts.offer_id_CustomCheck && !opts.offer_id_CustomCheck(o.offer_id)) return new Error(`${path}.offer_id: custom check failed`)
if (typeof o.paid_at_unix !== 'number') return new Error(`${path}.paid_at_unix: is not a number`)
if (opts.paid_at_unix_CustomCheck && !opts.paid_at_unix_CustomCheck(o.paid_at_unix)) return new Error(`${path}.paid_at_unix: custom check failed`)
return null
}
export type OfferInvoices = {
invoices: OfferInvoice[]
}
export const OfferInvoicesOptionalFields: [] = []
export type OfferInvoicesOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
invoices_ItemOptions?: OfferInvoiceOptions
invoices_CustomCheck?: (v: OfferInvoice[]) => boolean
}
export const OfferInvoicesValidate = (o?: OfferInvoices, opts: OfferInvoicesOptions = {}, path: string = 'OfferInvoices::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.invoices)) return new Error(`${path}.invoices: is not an array`)
for (let index = 0; index < o.invoices.length; index++) {
const invoicesErr = OfferInvoiceValidate(o.invoices[index], opts.invoices_ItemOptions, `${path}.invoices[${index}]`)
if (invoicesErr !== null) return invoicesErr
}
if (opts.invoices_CustomCheck && !opts.invoices_CustomCheck(o.invoices)) return new Error(`${path}.invoices: custom check failed`)
return null
}
export type OpenChannel = {
active: boolean
capacity: number
@ -2482,6 +2778,26 @@ export const PayInvoiceResponseValidate = (o?: PayInvoiceResponse, opts: PayInvo
return null
}
export type PayerData = {
data: Record<string, string>
}
export const PayerDataOptionalFields: [] = []
export type PayerDataOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
data_CustomCheck?: (v: Record<string, string>) => boolean
}
export const PayerDataValidate = (o?: PayerData, opts: PayerDataOptions = {}, path: string = 'PayerData::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.data !== 'object' || o.data === null) return new Error(`${path}.data: is not an object or is null`)
for (const key in o.data) {
if (typeof o.data[key] !== 'string') return new Error(`${path}.data['${key}']: is not a string`)
}
return null
}
export type PaymentState = {
amount: number
network_fee: number
@ -2852,6 +3168,7 @@ export const UpdateChannelPolicyRequestValidate = (o?: UpdateChannelPolicyReques
}
export type UsageMetric = {
app_id?: string
auth_in_nano: number
batch: boolean
batch_size: number
@ -2860,11 +3177,14 @@ export type UsageMetric = {
parsed_in_nano: number
processed_at_ms: number
rpc_name: string
success: boolean
validate_in_nano: number
}
export const UsageMetricOptionalFields: [] = []
export type UsageMetricOptionalField = 'app_id'
export const UsageMetricOptionalFields: UsageMetricOptionalField[] = ['app_id']
export type UsageMetricOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
checkOptionalsAreSet?: UsageMetricOptionalField[]
app_id_CustomCheck?: (v?: string) => boolean
auth_in_nano_CustomCheck?: (v: number) => boolean
batch_CustomCheck?: (v: boolean) => boolean
batch_size_CustomCheck?: (v: number) => boolean
@ -2873,12 +3193,16 @@ export type UsageMetricOptions = OptionsBaseMessage & {
parsed_in_nano_CustomCheck?: (v: number) => boolean
processed_at_ms_CustomCheck?: (v: number) => boolean
rpc_name_CustomCheck?: (v: string) => boolean
success_CustomCheck?: (v: boolean) => boolean
validate_in_nano_CustomCheck?: (v: number) => boolean
}
export const UsageMetricValidate = (o?: UsageMetric, opts: UsageMetricOptions = {}, path: string = 'UsageMetric::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 ((o.app_id || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('app_id')) && typeof o.app_id !== 'string') return new Error(`${path}.app_id: is not a string`)
if (opts.app_id_CustomCheck && !opts.app_id_CustomCheck(o.app_id)) return new Error(`${path}.app_id: custom check failed`)
if (typeof o.auth_in_nano !== 'number') return new Error(`${path}.auth_in_nano: is not a number`)
if (opts.auth_in_nano_CustomCheck && !opts.auth_in_nano_CustomCheck(o.auth_in_nano)) return new Error(`${path}.auth_in_nano: custom check failed`)
@ -2903,31 +3227,67 @@ export const UsageMetricValidate = (o?: UsageMetric, opts: UsageMetricOptions =
if (typeof o.rpc_name !== 'string') return new Error(`${path}.rpc_name: is not a string`)
if (opts.rpc_name_CustomCheck && !opts.rpc_name_CustomCheck(o.rpc_name)) return new Error(`${path}.rpc_name: custom check failed`)
if (typeof o.success !== 'boolean') return new Error(`${path}.success: is not a boolean`)
if (opts.success_CustomCheck && !opts.success_CustomCheck(o.success)) return new Error(`${path}.success: custom check failed`)
if (typeof o.validate_in_nano !== 'number') return new Error(`${path}.validate_in_nano: is not a number`)
if (opts.validate_in_nano_CustomCheck && !opts.validate_in_nano_CustomCheck(o.validate_in_nano)) return new Error(`${path}.validate_in_nano: custom check failed`)
return null
}
export type UsageMetricTlv = {
available_chunks: number[]
base_64_tlvs: string[]
current_chunk: number
}
export const UsageMetricTlvOptionalFields: [] = []
export type UsageMetricTlvOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
available_chunks_CustomCheck?: (v: number[]) => boolean
base_64_tlvs_CustomCheck?: (v: string[]) => boolean
current_chunk_CustomCheck?: (v: number) => boolean
}
export const UsageMetricTlvValidate = (o?: UsageMetricTlv, opts: UsageMetricTlvOptions = {}, path: string = 'UsageMetricTlv::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.available_chunks)) return new Error(`${path}.available_chunks: is not an array`)
for (let index = 0; index < o.available_chunks.length; index++) {
if (typeof o.available_chunks[index] !== 'number') return new Error(`${path}.available_chunks[${index}]: is not a number`)
}
if (opts.available_chunks_CustomCheck && !opts.available_chunks_CustomCheck(o.available_chunks)) return new Error(`${path}.available_chunks: custom check failed`)
if (!Array.isArray(o.base_64_tlvs)) return new Error(`${path}.base_64_tlvs: is not an array`)
for (let index = 0; index < o.base_64_tlvs.length; index++) {
if (typeof o.base_64_tlvs[index] !== 'string') return new Error(`${path}.base_64_tlvs[${index}]: is not a string`)
}
if (opts.base_64_tlvs_CustomCheck && !opts.base_64_tlvs_CustomCheck(o.base_64_tlvs)) return new Error(`${path}.base_64_tlvs: custom check failed`)
if (typeof o.current_chunk !== 'number') return new Error(`${path}.current_chunk: is not a number`)
if (opts.current_chunk_CustomCheck && !opts.current_chunk_CustomCheck(o.current_chunk)) return new Error(`${path}.current_chunk: custom check failed`)
return null
}
export type UsageMetrics = {
metrics: UsageMetric[]
apps: Record<string, AppUsageMetrics>
}
export const UsageMetricsOptionalFields: [] = []
export type UsageMetricsOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
metrics_ItemOptions?: UsageMetricOptions
metrics_CustomCheck?: (v: UsageMetric[]) => boolean
apps_EntryOptions?: AppUsageMetricsOptions
apps_CustomCheck?: (v: Record<string, AppUsageMetrics>) => boolean
}
export const UsageMetricsValidate = (o?: UsageMetrics, opts: UsageMetricsOptions = {}, path: string = 'UsageMetrics::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.metrics)) return new Error(`${path}.metrics: is not an array`)
for (let index = 0; index < o.metrics.length; index++) {
const metricsErr = UsageMetricValidate(o.metrics[index], opts.metrics_ItemOptions, `${path}.metrics[${index}]`)
if (metricsErr !== null) return metricsErr
if (typeof o.apps !== 'object' || o.apps === null) return new Error(`${path}.apps: is not an object or is null`)
for (const key in o.apps) {
const appsErr = AppUsageMetricsValidate(o.apps[key], opts.apps_EntryOptions, `${path}.apps['${key}']`)
if (appsErr !== null) return appsErr
}
if (opts.metrics_CustomCheck && !opts.metrics_CustomCheck(o.metrics)) return new Error(`${path}.metrics: custom check failed`)
return null
}
@ -2950,6 +3310,24 @@ export const UseInviteLinkRequestValidate = (o?: UseInviteLinkRequest, opts: Use
return null
}
export type UserHealthState = {
downtime_reason: string
}
export const UserHealthStateOptionalFields: [] = []
export type UserHealthStateOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
downtime_reason_CustomCheck?: (v: string) => boolean
}
export const UserHealthStateValidate = (o?: UserHealthState, opts: UserHealthStateOptions = {}, path: string = 'UserHealthState::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.downtime_reason !== 'string') return new Error(`${path}.downtime_reason: is not a string`)
if (opts.downtime_reason_CustomCheck && !opts.downtime_reason_CustomCheck(o.downtime_reason)) return new Error(`${path}.downtime_reason: custom check failed`)
return null
}
export type UserInfo = {
balance: number
bridge_url: string
@ -3018,6 +3396,29 @@ export const UserInfoValidate = (o?: UserInfo, opts: UserInfoOptions = {}, path:
return null
}
export type UserOffers = {
offers: OfferConfig[]
}
export const UserOffersOptionalFields: [] = []
export type UserOffersOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
offers_ItemOptions?: OfferConfigOptions
offers_CustomCheck?: (v: OfferConfig[]) => boolean
}
export const UserOffersValidate = (o?: UserOffers, opts: UserOffersOptions = {}, path: string = 'UserOffers::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.offers)) return new Error(`${path}.offers: is not an array`)
for (let index = 0; index < o.offers.length; index++) {
const offersErr = OfferConfigValidate(o.offers[index], opts.offers_ItemOptions, `${path}.offers[${index}]`)
if (offersErr !== null) return offersErr
}
if (opts.offers_CustomCheck && !opts.offers_CustomCheck(o.offers)) return new Error(`${path}.offers: custom check failed`)
return null
}
export type UserOperation = {
amount: number
confirmed: boolean

View file

@ -178,6 +178,12 @@ service LightningPub {
option (http_route) = "/api/reports/usage";
option (nostr) = true;
}
rpc GetErrorStats(structs.Empty) returns (structs.ErrorStats) {
option (auth_type) = "Metrics";
option (http_method) = "post";
option (http_route) = "/api/reports/errors";
option (nostr) = true;
}
rpc GetAppsMetrics(structs.AppsMetricsRequest) returns (structs.AppsMetrics) {
option (auth_type) = "Metrics";
@ -364,7 +370,7 @@ service LightningPub {
// </App>
// <User>
rpc UserHealth(structs.Empty)returns(structs.Empty){
rpc UserHealth(structs.Empty)returns(structs.UserHealthState){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/health";
@ -467,6 +473,48 @@ service LightningPub {
option (http_route) = "/api/user/lnurl_channel/url";
option (nostr) = true;
}
rpc GetUserOffers(structs.Empty) returns (structs.UserOffers){
option (auth_type) = "User";
option (http_method) = "get";
option (http_route) = "/api/user/offers/get";
option (nostr) = true;
}
rpc GetUserOffer(structs.OfferId) returns (structs.OfferConfig){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/offer/get";
option (nostr) = true;
}
rpc GetUserOfferInvoices(structs.GetUserOfferInvoicesReq) returns (structs.OfferInvoices){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/offer/get/invoices";
option (nostr) = true;
}
rpc UpdateUserOffer(structs.OfferConfig) returns (structs.Empty){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/offer/update";
option (nostr) = true;
}
rpc DeleteUserOffer(structs.OfferId) returns (structs.Empty){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/offer/delete";
option (nostr) = true;
}
rpc AddUserOffer(structs.OfferConfig) returns (structs.OfferId){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/offer/add";
option (nostr) = true;
}
rpc GetDebitAuthorizations(structs.Empty) returns (structs.DebitAuthorizations){
option (auth_type) = "User";
option (http_method) = "get";

View file

@ -15,6 +15,24 @@ message EncryptionExchangeRequest {
string deviceId = 2;
}
message UserHealthState {
string downtime_reason = 1;
}
message ErrorStat {
int64 from_unix = 1;
int64 total = 2;
int64 errors = 3;
}
message ErrorStats {
ErrorStat past24h = 1;
ErrorStat past6h = 2;
ErrorStat past1h = 3;
ErrorStat past10m = 4;
ErrorStat past1m = 5;
}
message UsageMetric {
int64 processed_at_ms = 1;
int64 parsed_in_nano = 2;
@ -25,10 +43,23 @@ message UsageMetric {
bool batch = 7;
bool nostr = 8;
int64 batch_size = 9;
bool success = 10;
optional string app_id = 11;
}
message UsageMetricTlv {
repeated string base_64_tlvs = 1;
int64 current_chunk = 2;
repeated int64 available_chunks = 3;
}
message AppUsageMetrics {
map<string,UsageMetricTlv> app_metrics = 1;
}
message UsageMetrics {
repeated UsageMetric metrics = 1;
map<string,AppUsageMetrics> apps = 1;
}
message AppsMetricsRequest {
@ -269,6 +300,8 @@ message AddAppUserInvoiceRequest {
string payer_identifier = 2;
string http_callback_url = 3;
NewInvoiceRequest invoice_req = 4;
optional PayerData payer_data = 5;
optional string offer_string = 6;
}
message GetAppUserRequest {
@ -331,6 +364,10 @@ message PayAddressResponse{
int64 network_fee = 4;
}
message PayerData {
map<string,string> data = 1;
}
message NewInvoiceRequest{
int64 amountSats = 1;
string memo = 2;
@ -613,4 +650,43 @@ message DebitResponse {
Empty denied = 3;
string invoice = 4;
}
}
enum OfferDataType {
DATA_STRING = 0;
}
message OfferId {
string offer_id = 1;
}
message OfferConfig {
string offer_id = 1;
string label = 2;
int64 price_sats = 3;
string callback_url = 4;
map<string, OfferDataType> expected_data = 5;
string noffer = 6;
bool default_offer = 7;
}
message UserOffers {
repeated OfferConfig offers = 1;
}
message GetUserOfferInvoicesReq {
string offer_id = 1;
bool include_unpaid = 2;
}
message OfferInvoices {
repeated OfferInvoice invoices = 1;
}
message OfferInvoice {
string invoice = 1;
string offer_id = 2;
int64 paid_at_unix = 3;
int64 amount = 4;
map<string,string> data = 5;
}

View file

@ -64,11 +64,11 @@ get_log_info() {
log "Retrieving connection information..."
# Wait for either .admin_connect or app.nprofile to appear
# Wait for either admin.connect or app.nprofile to appear
START_TIME=$(date +%s)
while [ $(($(date +%s) - START_TIME)) -lt $MAX_WAIT_TIME ]; do
if [ -f "$DATA_DIR/.admin_connect" ]; then
admin_connect=$(cat "$DATA_DIR/.admin_connect")
if [ -f "$DATA_DIR/admin.connect" ]; then
admin_connect=$(cat "$DATA_DIR/admin.connect")
# Check if the admin_connect string is complete (contains both nprofile and secret)
if [[ $admin_connect == nprofile* ]] && [[ $admin_connect == *:* ]]; then
log "An admin has not yet been enrolled."
@ -87,10 +87,10 @@ get_log_info() {
sleep $WAIT_INTERVAL
done
if [ ! -f "$DATA_DIR/.admin_connect" ] && [ ! -f "$DATA_DIR/app.nprofile" ]; then
log "Neither .admin_connect nor app.nprofile file found after waiting. Please check the service status."
if [ ! -f "$DATA_DIR/admin.connect" ] && [ ! -f "$DATA_DIR/app.nprofile" ]; then
log "Neither admin.connect nor app.nprofile file found after waiting. Please check the service status."
exit 1
elif [ -f "$DATA_DIR/.admin_connect" ] && ! [[ $(cat "$DATA_DIR/.admin_connect") == nprofile1* ]] && ! [[ $(cat "$DATA_DIR/.admin_connect") == *:* ]]; then
elif [ -f "$DATA_DIR/admin.connect" ] && ! [[ $(cat "$DATA_DIR/admin.connect") == nprofile1* ]] && ! [[ $(cat "$DATA_DIR/admin.connect") == *:* ]]; then
log "Admin connect information is incomplete. Please check the service status."
exit 1
fi

View file

@ -41,7 +41,7 @@ install_lightning_pub() {
# Merge if upgrade
if [ $upgrade_status -eq 100 ]; then
rsync -a --quiet --exclude='*.sqlite' --exclude='.env' --exclude='logs' --exclude='node_modules' --exclude='.jwt_secret' --exclude='.wallet_secret' --exclude='admin.npub' --exclude='app.nprofile' --exclude='.admin_connect' --exclude='.admin_enroll' $USER_HOME/lightning_pub_temp/ $USER_HOME/lightning_pub/
rsync -a --quiet --exclude='*.sqlite' --exclude='.env' --exclude='logs' --exclude='node_modules' --exclude='.jwt_secret' --exclude='.wallet_secret' --exclude='admin.npub' --exclude='app.nprofile' --exclude='admin.connect' --exclude='admin.enroll' $USER_HOME/lightning_pub_temp/ $USER_HOME/lightning_pub/
else
mv $USER_HOME/lightning_pub_temp $USER_HOME/lightning_pub
fi

View file

@ -49,7 +49,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
}
if (event.kind === 21001) {
const offerReq = j as NofferData
mainHandler.handleNip69Noffer(offerReq, event)
mainHandler.offerManager.handleNip69Noffer(offerReq, event)
return
} else if (event.kind === 21002) {
const debitReq = j as NdebitData

View file

@ -0,0 +1,90 @@
import { bytesToHex, concatBytes } from '@noble/hashes/utils'
import * as Types from '../../../proto/autogenerated/ts/types.js'
export const utf8Decoder: TextDecoder = new TextDecoder('utf-8')
export const utf8Encoder: TextEncoder = new TextEncoder()
export const encodeListTLV = (list: Uint8Array[]): TLV => {
const tlv: TLV = {}
tlv[64] = list
return tlv
}
export const decodeListTLV = (tlv: TLV): Uint8Array[] => {
return tlv[64]
}
export const usageMetricsToTlv = (metric: Types.UsageMetric): TLV => {
const tlv: TLV = {}
tlv[2] = [integerToUint8Array(Math.ceil(metric.processed_at_ms / 1000))] // 6 -> 6
tlv[3] = [integerToUint8Array(Math.ceil(metric.parsed_in_nano / 1000))] // 6 -> 12
tlv[4] = [integerToUint8Array(Math.ceil(metric.auth_in_nano / 1000))] // 6 -> 18
tlv[5] = [integerToUint8Array(Math.ceil(metric.validate_in_nano / 1000))] // 6 -> 24
tlv[6] = [integerToUint8Array(Math.ceil(metric.handle_in_nano / 1000))] // 6 -> 30
tlv[7] = [integerToUint8Array(metric.batch_size)] // 6 -> 36
tlv[8] = [new Uint8Array([metric.batch ? 1 : 0])] // 3 -> 39
tlv[9] = [new Uint8Array([metric.nostr ? 1 : 0])] // 3 -> 42
tlv[10] = [new Uint8Array([metric.success ? 1 : 0])] // 3 -> 45
return tlv
}
export const tlvToUsageMetrics = (rpcName: string, tlv: TLV): Types.UsageMetric => {
const metric: Types.UsageMetric = {
rpc_name: rpcName,
processed_at_ms: parseInt(bytesToHex(tlv[2][0]), 16) * 1000,
parsed_in_nano: parseInt(bytesToHex(tlv[3][0]), 16) * 1000,
auth_in_nano: parseInt(bytesToHex(tlv[4][0]), 16) * 1000,
validate_in_nano: parseInt(bytesToHex(tlv[5][0]), 16) * 1000,
handle_in_nano: parseInt(bytesToHex(tlv[6][0]), 16) * 1000,
batch_size: parseInt(bytesToHex(tlv[7][0]), 16),
batch: tlv[8][0][0] === 1,
nostr: tlv[9][0][0] === 1,
success: tlv[10][0][0] === 1,
}
return metric
}
export const integerToUint8Array = (number: number): Uint8Array => {
// Create a Uint8Array with enough space to hold a 32-bit integer (4 bytes).
const uint8Array = new Uint8Array(4)
// Use bitwise operations to extract the bytes.
uint8Array[0] = (number >> 24) & 0xff // Most significant byte (MSB)
uint8Array[1] = (number >> 16) & 0xff
uint8Array[2] = (number >> 8) & 0xff
uint8Array[3] = number & 0xff // Least significant byte (LSB)
return uint8Array
}
export type TLV = { [t: number]: Uint8Array[] }
export 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 encodeTLV = (tlv: TLV): Uint8Array => {
const entries: Uint8Array[] = []
Object.entries(tlv)
.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)
}

View file

@ -193,7 +193,10 @@ export default class {
if (req.invoice_req.zap) {
zapInfo = this.paymentManager.validateZapEvent(req.invoice_req.zap, req.invoice_req.amountSats)
}
const opts: InboundOptionals = { callbackUrl: cbUrl, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app, zapInfo }
const opts: InboundOptionals = {
callbackUrl: cbUrl, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app, zapInfo,
offerId: req.offer_string, payerData: req.payer_data?.data
}
const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts)
return {
invoice: appUserInvoice.invoice

View file

@ -24,6 +24,7 @@ import { Unlocker } from "./unlocker.js"
import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
import { DebitManager } from "./debitManager.js"
import { NofferData } from "nostr-tools/lib/types/nip69.js"
import { OfferManager } from "./offerManager.js"
type UserOperationsSub = {
id: string
@ -49,6 +50,7 @@ export default class {
liquidityManager: LiquidityManager
liquidityProvider: LiquidityProvider
debitManager: DebitManager
offerManager: OfferManager
utils: Utils
rugPullTracker: RugPullTracker
unlocker: Unlocker
@ -71,6 +73,8 @@ export default class {
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)
this.offerManager = new OfferManager(this.storage, this.lnd, this.applicationManager, this.productManager)
}
Stop() {
@ -89,6 +93,7 @@ export default class {
this.nostrSend = f
this.liquidityProvider.attachNostrSend(f)
this.debitManager.attachNostrSend(f)
this.offerManager.attachNostrSend(f)
}
htlcCb: HtlcCb = (e) => {
@ -215,11 +220,15 @@ export default class {
if (fee > 0) {
await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, 'fees', tx)
}
await this.triggerPaidCallback(log, userInvoice.callbackUrl)
await this.triggerPaidCallback(log, userInvoice.callbackUrl, { invoice: paymentRequest, amount, other: userInvoice.payer_data })
const operationId = `${Types.UserOperationType.INCOMING_INVOICE}-${userInvoice.serial_id}`
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_INVOICE, identifier: userInvoice.invoice, operationId, network_fee: 0, service_fee: fee, confirmed: true, tx_hash: "", internal }
this.sendOperationToNostr(userInvoice.linkedApplication, userInvoice.user.user_id, op)
this.createZapReceipt(log, userInvoice)
try {
this.createZapReceipt(log, userInvoice)
} catch (err: any) {
log(ERROR, "cannot create zap receipt", err.message || "")
}
this.liquidityManager.afterInInvoicePaid()
this.utils.stateBundler.AddTxPoint('invoiceWasPaid', amount, { used, from: 'system', timeDiscount: true })
} catch (err: any) {
@ -229,13 +238,21 @@ export default class {
})
}
async triggerPaidCallback(log: PubLogger, url: string) {
async triggerPaidCallback(log: PubLogger, url: string, { invoice, amount, other }: { invoice: string, amount: number, other?: Record<string, string> }) {
if (!url) {
return
}
let finalUrl = url.replace(`%[invoice]`, invoice).replace(`%[amount]`, amount.toString())
if (other) {
for (const [key, value] of Object.entries(other)) {
finalUrl = finalUrl.replace(`%[${key}]`, value)
}
}
try {
const symbol = url.includes('?') ? "&" : "?"
await fetch(url + symbol + "ok=true")
const symbol = finalUrl.includes('?') ? "&" : "?"
finalUrl = finalUrl + symbol + "ok=true"
log("sending paid callback to", finalUrl)
await fetch(finalUrl)
} catch (err: any) {
log(ERROR, "error sending paid callback for invoice", err.message || "")
}
@ -286,70 +303,6 @@ export default class {
log({ unsigned: event })
this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined)
}
async getNofferInvoice(offerReq: NofferData, appId: string): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> {
try {
const { remote } = await this.lnd.ChannelBalance()
const { offer, amount } = offerReq
const split = offer.split(':')
if (split.length === 1) {
if (!amount || isNaN(amount) || amount < 10 || amount > remote) {
return { success: false, code: 5, max: remote }
}
const res = await this.applicationManager.AddAppUserInvoice(appId, {
http_callback_url: "", payer_identifier: split[0], receiver_identifier: split[0],
invoice_req: { amountSats: amount, memo: "Default NIP-69 Offer", zap: offerReq.zap }
})
return { success: true, invoice: res.invoice }
} else if (split[0] === 'p') {
const product = await this.productManager.NewProductInvoice(split[1])
return { success: true, invoice: product.invoice }
} else {
return { success: false, code: 1, max: remote }
}
} catch (e: any) {
getLogger({ component: "noffer" })(ERROR, e.message || e)
return { success: false, code: 1, max: 0 }
}
}
async handleNip69Noffer(offerReq: NofferData, event: NostrEvent) {
const offerInvoice = await this.getNofferInvoice(offerReq, event.appId)
if (!offerInvoice.success) {
const code = offerInvoice.code
const e = newNofferResponse(JSON.stringify({ code, error: codeToMessage(code), range: { min: 10, max: offerInvoice.max } }), event)
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
return
}
const e = newNofferResponse(JSON.stringify({ bolt11: offerInvoice.invoice }), event)
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
return
}
}
const codeToMessage = (code: number) => {
switch (code) {
case 1: return 'Invalid Offer'
case 2: return 'Temporary Failure'
case 3: return 'Expired Offer'
case 4: return 'Unsupported Feature'
case 5: return 'Invalid Amount'
default: throw new Error("unknown error code" + code)
}
}
const newNofferResponse = (content: string, event: NostrEvent): UnsignedEvent => {
return {
content,
created_at: Math.floor(Date.now() / 1000),
kind: 21001,
pubkey: "",
tags: [
['p', event.pub],
['e', event.id],
],
}
}

View file

@ -0,0 +1,259 @@
import crypto from 'crypto';
import * as Types from "../../../proto/autogenerated/ts/types.js";
import ApplicationManager from "./applicationManager.js";
import ProductManager from "./productManager.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';
import { NofferData } from "nostr-tools/lib/types/nip69.js"
import { UserOffer } from '../storage/entity/UserOffer.js';
import { DeepPartial } from 'typeorm';
import { nip19 } from 'nostr-tools';
import { LoadNosrtSettingsFromEnv } from '../nostr/index.js';
const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay }: { pubkey: string, relay: string }): Types.OfferConfig => {
if (offer.expected_data) {
const keys = Object.keys(offer.expected_data)
for (const key of keys) {
const v = offer.expected_data[key] as Types.OfferDataType
if (!Types.OfferDataType[v]) {
offer.expected_data[key] = Types.OfferDataType.DATA_STRING
}
}
}
const offerStr = offer.offer_id
const priceType: nip19.OfferPriceType = offer.price_sats === 0 ? nip19.OfferPriceType.Spontaneous : nip19.OfferPriceType.Fixed
const noffer = nip19.nofferEncode({ pubkey, offer: offerStr, priceType, relay, price: offer.price_sats || undefined })
return {
label: offer.label,
price_sats: offer.price_sats,
callback_url: offer.callback_url,
expected_data: (offer.expected_data || {}) as Record<string, Types.OfferDataType>,
offer_id: offer.offer_id,
noffer: noffer,
default_offer: appUserId === offer.app_user_id
}
}
export class OfferManager {
_nostrSend: NostrSend | null = null
applicationManager: ApplicationManager
productManager: ProductManager
storage: Storage
lnd: LND
logger = getLogger({ component: 'DebitManager' })
constructor(storage: Storage, lnd: LND, applicationManager: ApplicationManager, productManager: ProductManager) {
this.storage = storage
this.lnd = lnd
this.applicationManager = applicationManager
this.productManager = productManager
}
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)
}
async AddUserOffer(ctx: Types.UserContext, req: Types.OfferConfig): Promise<Types.OfferId> {
const newOffer = await this.storage.offerStorage.AddUserOffer(ctx.app_user_id, {
expected_data: req.expected_data,
label: req.label,
price_sats: req.price_sats,
callback_url: req.callback_url,
})
return {
offer_id: newOffer.offer_id
}
}
async DeleteUserOffer(ctx: Types.UserContext, req: Types.OfferId) {
await this.storage.offerStorage.DeleteUserOffer(ctx.app_user_id, req.offer_id)
}
async UpdateUserOffer(ctx: Types.UserContext, req: Types.OfferConfig) {
await this.storage.offerStorage.UpdateUserOffer(ctx.app_user_id, req.offer_id, {
expected_data: req.expected_data,
label: req.label,
price_sats: req.price_sats,
callback_url: req.callback_url,
})
}
async GetUserOfferInvoices(ctx: Types.UserContext, req: Types.GetUserOfferInvoicesReq): Promise<Types.OfferInvoices> {
const userOffer = await this.storage.offerStorage.GetUserOffer(ctx.app_user_id, req.offer_id)
if (!userOffer) {
throw new Error("Offer not found")
}
const i = await this.storage.paymentStorage.GetOfferInvoices(req.offer_id, req.include_unpaid)
return {
invoices: i.map(i => ({
invoice: i.invoice,
offer_id: i.offer_id || "",
paid_at_unix: i.paid_at_unix,
amount: i.paid_amount,
data: i.payer_data || {}
}))
}
}
async GetUserOffer(ctx: Types.UserContext, req: Types.OfferId): Promise<Types.OfferConfig> {
const app = await this.applicationManager.GetApp(ctx.app_id)
if (!app) {
throw new Error("App not found")
}
const offer = await this.storage.offerStorage.GetUserOffer(ctx.app_user_id, req.offer_id)
if (!offer) {
throw new Error("Offer not found")
}
const nostrSettings = LoadNosrtSettingsFromEnv()
return mapToOfferConfig(ctx.app_user_id, offer, { pubkey: app.npub, relay: nostrSettings.relays[0] })
}
async GetUserOffers(ctx: Types.UserContext): Promise<Types.UserOffers> {
const app = await this.applicationManager.GetApp(ctx.app_id)
if (!app) {
throw new Error("App not found")
}
const offers = await this.storage.offerStorage.GetUserOffers(ctx.app_user_id)
const defaultOffer = offers.find(o => o.app_user_id === o.offer_id)
let toAppend: UserOffer | undefined = undefined
if (!defaultOffer) {
toAppend = await this.storage.offerStorage.AddDefaultUserOffer(ctx.app_user_id)
}
if (toAppend) {
offers.push(toAppend)
}
const nostrSettings = LoadNosrtSettingsFromEnv()
return {
offers: offers.map(o => mapToOfferConfig(ctx.app_user_id, o, { pubkey: app.npub, relay: nostrSettings.relays[0] }))
}
}
ValidateExpectedData(userOffer: UserOffer, payerData: any): { passed: false, validated: undefined } | { passed: true, validated: Record<string, string> } {
const expected = userOffer.expected_data
if (!expected) {
return { passed: true, validated: {} }
}
const expectedKeys = Object.keys(expected)
if (expectedKeys.length === 0) {
return { passed: true, validated: {} }
}
if (typeof payerData !== 'object' || payerData === null) {
return { passed: false, validated: undefined }
}
const validated: Record<string, string> = {}
for (const key of expectedKeys) {
if (typeof payerData[key] !== 'string') {
return { passed: false, validated: undefined }
}
validated[key] = payerData[key]
}
return { passed: true, validated }
}
async handleNip69Noffer(offerReq: NofferData, event: NostrEvent) {
const offerInvoice = await this.getNofferInvoice(offerReq, event.appId)
if (!offerInvoice.success) {
const code = offerInvoice.code
const e = newNofferResponse(JSON.stringify({ code, error: codeToMessage(code), range: { min: 10, max: offerInvoice.max } }), event)
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
return
}
const e = newNofferResponse(JSON.stringify({ bolt11: offerInvoice.invoice }), event)
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
return
}
async HandleDefaultUserOffer(offerReq: NofferData, appId: string, remote: number): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> {
const { amount, offer } = offerReq
if (!amount || isNaN(amount) || amount < 10 || amount > remote) {
return { success: false, code: 5, max: remote }
}
const res = await this.applicationManager.AddAppUserInvoice(appId, {
http_callback_url: "", payer_identifier: offer, receiver_identifier: offer,
invoice_req: { amountSats: amount, memo: "Default NIP-69 Offer", zap: offerReq.zap },
offer_string: 'offer'
})
return { success: true, invoice: res.invoice }
}
async HandleUserOffer(offerReq: NofferData, appId: string, remote: number): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> {
const { amount, offer } = offerReq
const userOffer = await this.storage.offerStorage.GetOffer(offer)
if (!userOffer) {
return this.HandleDefaultUserOffer(offerReq, appId, remote)
}
let amt = userOffer.price_sats
if (userOffer.price_sats === 0) {
if (!amount || isNaN(amount) || amount < 10 || amount > remote) {
return { success: false, code: 5, max: remote }
}
amt = amount
}
const { passed, validated } = this.ValidateExpectedData(userOffer, offerReq.payer_data)
if (!passed) {
return { success: false, code: 1, max: remote }
}
const res = await this.applicationManager.AddAppUserInvoice(appId, {
http_callback_url: userOffer.callback_url, payer_identifier: userOffer.app_user_id, receiver_identifier: userOffer.app_user_id,
invoice_req: { amountSats: amt, memo: userOffer.label, zap: offerReq.zap },
payer_data: validated ? { data: validated } : undefined,
offer_string: offer
})
return { success: true, invoice: res.invoice }
}
async getNofferInvoice(offerReq: NofferData, appId: string): Promise<{ success: true, invoice: string } | { success: false, code: number, max: number }> {
try {
const { remote } = await this.lnd.ChannelBalance()
const split = offerReq.offer.split(':')
if (split.length === 1) {
return this.HandleUserOffer(offerReq, appId, remote)
} else if (split[0] === 'p') {
const product = await this.productManager.NewProductInvoice(split[1])
return { success: true, invoice: product.invoice }
} else {
return { success: false, code: 1, max: remote }
}
} catch (e: any) {
getLogger({ component: "noffer" })(ERROR, e.message || e)
return { success: false, code: 1, max: 0 }
}
}
}
const newNofferResponse = (content: string, event: NostrEvent): UnsignedEvent => {
return {
content,
created_at: Math.floor(Date.now() / 1000),
kind: 21001,
pubkey: "",
tags: [
['p', event.pub],
['e', event.id],
],
}
}
const codeToMessage = (code: number) => {
switch (code) {
case 1: return 'Invalid Offer'
case 2: return 'Temporary Failure'
case 3: return 'Expired Offer'
case 4: return 'Unsupported Feature'
case 5: return 'Invalid Amount'
default: throw new Error("unknown error code" + code)
}
}

View file

@ -575,7 +575,7 @@ export default class {
const e = this.parseTags("e", nostrEvent.tags)
const relays = this.parseTags("relays", nostrEvent.tags, { required: true, multiples: true })
const amount = this.parseTags("amount", nostrEvent.tags)
if (+amount !== amt) {
if (amount.length > 0 && +amount[0] !== amt) {
throw new Error("amount mismatch")
}
return { pub: p[0], eventId: e.length > 0 ? e[0] : "", relays, description: event }

View file

@ -1,3 +1,4 @@
import fs from 'fs'
import Storage from '../storage/index.js'
import * as Types from '../../../proto/autogenerated/ts/types.js'
import { Application } from '../storage/entity/Application.js'
@ -7,20 +8,27 @@ import { BalanceEvent } from '../storage/entity/BalanceEvent.js'
import { ChannelBalanceEvent } from '../storage/entity/ChannelsBalanceEvent.js'
import LND from '../lnd/lnd.js'
import HtlcTracker from './htlcTracker.js'
const maxEvents = 100_000
export default class Handler {
import { MainSettings } from '../main/settings.js'
import { getLogger } from '../helpers/logger.js'
import { encodeTLV, usageMetricsToTlv } from '../helpers/tlv.js'
import { ChannelCloseSummary_ClosureType } from '../../../proto/lnd/lightning.js'
export default class Handler {
storage: Storage
lnd: LND
htlcTracker: HtlcTracker
metrics: Types.UsageMetric[] = []
logger = getLogger({ component: "metrics" })
constructor(storage: Storage, lnd: LND) {
this.storage = storage
this.lnd = lnd
this.htlcTracker = new HtlcTracker(this.storage)
}
async HtlcCb(htlc: HtlcEvent) {
await this.htlcTracker.onHtlcEvent(htlc)
}
@ -29,7 +37,6 @@ export default class Handler {
const providers = await this.storage.liquidityStorage.GetTrackedProviders()
const channels = await this.lnd.GetChannelBalance()
let providerTotal = 0
console.log({ providers })
providers.forEach(p => {
if (p.provider_type === 'lnPub') {
providerTotal += p.latest_balance
@ -62,30 +69,94 @@ export default class Handler {
}))
}
AddMetrics(newMetrics: (Types.RequestMetric & { app_id?: string })[]) {
const parsed: Types.UsageMetric[] = newMetrics.map(m => ({
rpc_name: m.rpcName,
batch: m.batch,
nostr: m.nostr,
batch_size: m.batchSize,
parsed_in_nano: Number(m.parse - m.start),
auth_in_nano: Number(m.guard - m.parse),
validate_in_nano: Number(m.validate - m.guard),
handle_in_nano: Number(m.handle - m.validate),
success: !m.error,
app_id: m.app_id ? m.app_id : "",
processed_at_ms: m.startMs
}))
const len = this.metrics.push(...parsed)
if (len > maxEvents) {
this.metrics.splice(0, len - maxEvents)
}
}
async GetUsageMetrics(): Promise<Types.UsageMetrics> {
return {
metrics: this.metrics
}
return this.storage.metricsEventStorage.LoadLatestMetrics()
}
async GetErrorStats(): Promise<Types.ErrorStats> {
const last24h = this.storage.metricsEventStorage.getlast24hCache()
const nowUnix = Math.floor(Date.now() / 1000)
const stats: Types.ErrorStats = {
past24h: { errors: 0, total: 0, from_unix: nowUnix - 60 * 60 * 24 },
past6h: { errors: 0, total: 0, from_unix: nowUnix - 60 * 60 * 6 },
past1h: { errors: 0, total: 0, from_unix: nowUnix - 60 * 60 },
past10m: { errors: 0, total: 0, from_unix: nowUnix - 60 * 10 },
past1m: { errors: 0, total: 0, from_unix: nowUnix - 60 },
}
for (let i = last24h.length - 1; i >= 0; i--) {
const e = last24h[i]
if (e.ts < stats.past24h.from_unix) {
break
}
stats.past24h.total += e.ok + e.fail
stats.past24h.errors += e.fail
if (e.ts >= stats.past6h.from_unix) {
stats.past6h.total += e.ok + e.fail
stats.past6h.errors += e.fail
}
if (e.ts >= stats.past1h.from_unix) {
stats.past1h.total += e.ok + e.fail
stats.past1h.errors += e.fail
}
if (e.ts >= stats.past10m.from_unix) {
stats.past10m.total += e.ok + e.fail
stats.past10m.errors += e.fail
}
if (e.ts >= stats.past1m.from_unix) {
stats.past1m.total += e.ok + e.fail
stats.past1m.errors += e.fail
}
}
return stats
}
AddMetrics(newMetrics: (Types.RequestMetric & { app_id?: string })[]) {
newMetrics.forEach(m => {
const appId = m.app_id || "_root"
const um: Types.UsageMetric = {
rpc_name: m.rpcName,
batch: m.batch,
nostr: m.nostr,
batch_size: m.batchSize,
parsed_in_nano: Number(m.parse - m.start),
auth_in_nano: Number(m.guard - m.parse),
validate_in_nano: Number(m.validate - m.guard),
handle_in_nano: Number(m.handle - m.validate),
success: !m.error,
app_id: m.app_id ? m.app_id : "",
processed_at_ms: m.startMs
}
const tlv = usageMetricsToTlv(um)
this.storage.metricsEventStorage.AddMetricEvent(appId, m.rpcName, encodeTLV(tlv), !m.error)
})
}
/* addTrackedMetric = (appId: string, method: string, metric: Uint8Array) => {
if (!this.metaReady) {
throw new Error("meta metrics not ready")
}
const tlvString = Buffer.from(metric).toString("base64")
if (!this.metrics[appId]) {
this.metrics[appId] = { app_metrics: {} }
}
if (!this.metrics[appId].app_metrics[method]) {
this.metrics[appId].app_metrics[method] = { base_64_tlvs: [] }
}
const len = this.metrics[appId].app_metrics[method].base_64_tlvs.push(tlvString)
if (len > maxEvents) {
this.metrics[appId].app_metrics[method].base_64_tlvs.splice(0, len - maxEvents)
}
} */
async GetAppsMetrics(req: Types.AppsMetricsRequest): Promise<Types.AppsMetrics> {
const dbApps = await this.storage.applicationStorage.GetApplications()
const apps = await Promise.all(dbApps.map(app => this.GetAppMetrics(req, app)))
@ -251,7 +322,7 @@ export default class Handler {
externalBalance.push({ x: e.block_height, y: e.external_balance })
}
})
const closed = await Promise.all(closedChannels.map(async c => {
const closed = await Promise.all(closedChannels.filter(c => c.closeType !== ChannelCloseSummary_ClosureType.FUNDING_CANCELED).map(async c => {
const tx = await this.lnd.GetTx(c.closingTxHash)
return { capacity: Number(c.capacity), channel_id: c.chanId, closed_height: c.closeHeight, close_tx_timestamp: Number(tx.timeStamp) }
}))

View file

@ -7,6 +7,9 @@ export default (mainHandler: Main): Types.ServerMethods => {
GetUsageMetrics: async ({ ctx }) => {
return mainHandler.metricsManager.GetUsageMetrics()
},
GetErrorStats: async ({ ctx }) => {
return mainHandler.metricsManager.GetErrorStats()
},
GetAppsMetrics: async ({ ctx, req }) => {
return mainHandler.metricsManager.GetAppsMetrics(req)
},
@ -73,7 +76,10 @@ export default (mainHandler: Main): Types.ServerMethods => {
if (err != null) throw new Error(err.message)
await mainHandler.paymentManager.SetMockInvoiceAsPaid(req)
},
UserHealth: async () => { },
UserHealth: async () => {
try { await mainHandler.lnd.Health(); return { downtime_reason: "" } }
catch (e: any) { return { downtime_reason: e.message } }
},
GetUserInfo: ({ ctx }) => mainHandler.appUserManager.GetUserInfo(ctx),
UpdateCallbackUrl: async ({ ctx, req }) => {
return mainHandler.appUserManager.UpdateCallbackUrl(ctx, req)
@ -323,6 +329,40 @@ export default (mainHandler: Main): Types.ServerMethods => {
},
RespondToDebit: async ({ ctx, req }) => {
return mainHandler.debitManager.RespondToDebit(ctx, req);
},
AddUserOffer: async ({ ctx, req }) => {
const err = Types.OfferConfigValidate(req, {
label_CustomCheck: label => label !== '',
})
if (err != null) throw new Error(err.message)
return mainHandler.offerManager.AddUserOffer(ctx, req)
},
DeleteUserOffer: async ({ ctx, req }) => {
const err = Types.OfferIdValidate(req, {
offer_id_CustomCheck: id => id !== ''
})
if (err != null) throw new Error(err.message)
return mainHandler.offerManager.DeleteUserOffer(ctx, req)
},
UpdateUserOffer: async ({ ctx, req }) => {
return mainHandler.offerManager.UpdateUserOffer(ctx, req)
},
GetUserOffers: async ({ ctx }) => {
return mainHandler.offerManager.GetUserOffers(ctx)
},
GetUserOffer: async ({ ctx, req }) => {
const err = Types.OfferIdValidate(req, {
offer_id_CustomCheck: id => id !== ''
})
if (err != null) throw new Error(err.message)
return mainHandler.offerManager.GetUserOffer(ctx, req)
},
GetUserOfferInvoices: async ({ ctx, req }) => {
const err = Types.GetUserOfferInvoicesReqValidate(req, {
offer_id_CustomCheck: id => id !== ''
})
if (err != null) throw new Error(err.message)
return mainHandler.offerManager.GetUserOfferInvoices(ctx, req)
}
}
}

View file

@ -23,6 +23,7 @@ import { TrackedProvider } from "./entity/TrackedProvider.js"
import { InviteToken } from "./entity/InviteToken.js"
import { DebitAccess } from "./entity/DebitAccess.js"
import { RootOperation } from "./entity/RootOperation.js"
import { UserOffer } from "./entity/UserOffer.js"
export type DbSettings = {
@ -63,7 +64,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, DebitAccess
InviteToken, DebitAccess, UserOffer
],
//synchronize: true,
migrations

View file

@ -0,0 +1,37 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
@Entity()
export class UserOffer {
@PrimaryGeneratedColumn()
serial_id: number
@Column()
app_user_id: string
@Column({ unique: true, nullable: false })
offer_id: string
@Column()
label: string
@Column({ default: 0 })
price_sats: number
@Column({ default: "" })
callback_url: string
@Column({
nullable: true,
type: 'simple-json',
default: null
})
expected_data: Record<string, string> | null
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -58,6 +58,15 @@ export class UserReceivingInvoice {
})
zap_info?: ZapInfo
@Column({
nullable: true,
type: 'simple-json'
})
payer_data?: Record<string, string>
@Column({ default: "" })
offer_id?: string
@Column({
nullable: true,
})

View file

@ -6,11 +6,13 @@ import ApplicationStorage from './applicationStorage.js'
import UserStorage from "./userStorage.js";
import PaymentStorage from "./paymentStorage.js";
import MetricsStorage from "./metricsStorage.js";
import MetricsEventStorage from "./metricsEventStorage.js";
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"
import OfferStorage from "./offerStorage.js"
export type StorageSettings = {
dbSettings: DbSettings
eventLogPath: string
@ -28,8 +30,10 @@ export default class {
userStorage: UserStorage
paymentStorage: PaymentStorage
metricsStorage: MetricsStorage
metricsEventStorage: MetricsEventStorage
liquidityStorage: LiquidityStorage
debitStorage: DebitStorage
offerStorage: OfferStorage
eventsLog: EventsLogManager
stateBundler: StateBundler
constructor(settings: StorageSettings) {
@ -45,8 +49,10 @@ export default class {
this.applicationStorage = new ApplicationStorage(this.DB, this.userStorage, this.txQueue)
this.paymentStorage = new PaymentStorage(this.DB, this.userStorage, this.txQueue)
this.metricsStorage = new MetricsStorage(this.settings)
this.metricsEventStorage = new MetricsEventStorage(this.settings)
this.liquidityStorage = new LiquidityStorage(this.DB, this.txQueue)
this.debitStorage = new DebitStorage(this.DB, this.txQueue)
this.offerStorage = new OfferStorage(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,211 @@
import fs from 'fs'
import * as Types from '../../../proto/autogenerated/ts/types.js'
import { StorageSettings } from "./index.js";
import { decodeListTLV, encodeListTLV, encodeTLV, parseTLV } from '../helpers/tlv.js';
const chunkSizeBytes = 128 * 1024
export default class {
settings: StorageSettings
metricsPath: string
cachePath: string
metaReady = false
metricsMeta: Record<string, Record<string, { chunks: number[] }>> = {}
pendingMetrics: Record<string, Record<string, { tlvs: Uint8Array[] }>> = {}
last24hCache: { ts: number, ok: number, fail: number }[] = []
lastPersistedMetrics: number = 0
lastPersistedCache: number = 0
constructor(settings: StorageSettings) {
this.settings = settings;
this.metricsPath = [settings.dataDir, "metric_events"].join("/")
this.cachePath = [settings.dataDir, "metric_cache"].join("/")
if (!fs.existsSync(this.cachePath)) {
fs.mkdirSync(this.cachePath, { recursive: true });
}
this.initMetricsMeta()
this.loadCache()
setInterval(() => {
if (Date.now() - this.lastPersistedMetrics > 1000 * 60 * 4) {
this.persistMetrics()
}
if (Date.now() - this.lastPersistedCache > 1000 * 60 * 4) {
this.persistCache()
}
}, 1000 * 60 * 5)
process.on('exit', () => {
this.persistMetrics()
this.persistCache()
});
// catch ctrl+c event and exit normally
process.on('SIGINT', () => {
console.log('Ctrl-C...');
process.exit(2);
});
//catch uncaught exceptions, trace, then exit normally
process.on('uncaughtException', (e) => {
console.log('Uncaught Exception...');
console.log(e.stack);
process.exit(99);
});
}
getlast24hCache = () => { return this.last24hCache }
rotateCache = (nowUnix: number) => {
const yesterday = nowUnix - 60 * 60 * 24
const latest = this.last24hCache.findIndex(c => c.ts >= yesterday)
if (latest === -1) {
this.last24hCache = []
return
} else if (latest === 0) {
return
}
this.last24hCache = this.last24hCache.slice(latest)
}
pushToCache = (ok: boolean) => {
const now = Math.floor(Date.now() / 1000)
this.rotateCache(now)
if (this.last24hCache.length === 0) {
this.last24hCache.push({ ts: now, ok: ok ? 1 : 0, fail: ok ? 0 : 1 })
return
}
const last = this.last24hCache[this.last24hCache.length - 1]
if (last.ts === now) {
last.ok += ok ? 1 : 0
last.fail += ok ? 0 : 1
} else {
this.last24hCache.push({ ts: now, ok: ok ? 1 : 0, fail: ok ? 0 : 1 })
}
}
persistCache = () => {
const last24CachePath = [this.cachePath, "last24hSF.json"].join("/")
fs.writeFileSync(last24CachePath, JSON.stringify(this.last24hCache), {})
}
loadCache = () => {
const last24CachePath = [this.cachePath, "last24hSF.json"].join("/")
if (fs.existsSync(last24CachePath)) {
this.last24hCache = JSON.parse(fs.readFileSync(last24CachePath, 'utf-8'))
this.rotateCache(Math.floor(Date.now() / 1000))
}
}
AddMetricEvent = (appId: string, method: string, metric: Uint8Array, success: boolean) => {
if (!this.metaReady) {
throw new Error("meta metrics not ready")
}
if (!this.pendingMetrics[appId]) {
this.pendingMetrics[appId] = {}
}
if (!this.pendingMetrics[appId][method]) {
this.pendingMetrics[appId][method] = { tlvs: [] }
}
this.pendingMetrics[appId][method].tlvs.push(metric)
this.pushToCache(success)
}
LoadLatestMetrics = async (): Promise<Types.UsageMetrics> => {
this.persistMetrics()
const metrics: Types.UsageMetrics = { apps: {} }
this.foreachMetricMethodFile((app, method, tlvFiles) => {
if (tlvFiles.length === 0) { return }
const methodPath = [this.metricsPath, app, method].join("/")
const latest = tlvFiles[tlvFiles.length - 1]
const tlvFile = [methodPath, `${latest}.mtlv`].join("/")
const tlv = fs.readFileSync(tlvFile)
const decoded = decodeListTLV(parseTLV(tlv))
if (!metrics.apps[app]) {
metrics.apps[app] = { app_metrics: {} }
}
metrics.apps[app].app_metrics[method] = {
base_64_tlvs: decoded.map(d => Buffer.from(d).toString('base64')),
current_chunk: latest,
available_chunks: tlvFiles
}
})
return metrics
}
persistMetrics = () => {
if (!this.metaReady) {
throw new Error("meta metrics not ready")
}
this.lastPersistedMetrics = Date.now()
const tosync = this.pendingMetrics
this.pendingMetrics = {}
const apps = Object.keys(tosync)
apps.map(app => {
const appPath = [this.metricsPath, app].join("/")
if (!fs.existsSync(appPath)) {
fs.mkdirSync(appPath, { recursive: true });
}
const methods = Object.keys(tosync[app])
methods.map(methodName => {
const methodPath = [appPath, methodName].join("/")
if (!fs.existsSync(methodPath)) {
fs.mkdirSync(methodPath, { recursive: true });
}
const method = tosync[app][methodName]
const meta = this.getMetricsMeta(app, methodName)
const chunks = meta.chunks.length > 0 ? meta.chunks : [0]
const latest = chunks[chunks.length - 1]
const tlv = encodeTLV(encodeListTLV(method.tlvs))
const tlvFile = [methodPath, `${latest}.mtlv`].join("/")
fs.appendFileSync(tlvFile, Buffer.from(tlv))
if (fs.lstatSync(tlvFile).size > chunkSizeBytes) {
this.updateMetricsMeta(app, methodName, [...chunks, latest + 1])
}
})
})
}
initMetricsMeta = () => {
this.foreachMetricMethodFile((app, method, tlvFiles) => {
this.updateMetricsMeta(app, method, tlvFiles)
})
this.metaReady = true
}
updateMetricsMeta = (appId: string, method: string, sortedChunks: number[]) => {
if (!this.metricsMeta[appId]) {
this.metricsMeta[appId] = {}
}
this.metricsMeta[appId][method] = { chunks: sortedChunks }
}
getMetricsMeta = (appId: string, method: string) => {
if (!this.metricsMeta[appId]) {
return { chunks: [] }
}
return this.metricsMeta[appId][method] || { chunks: [] }
}
foreachMetricMethodFile = (cb: (appId: string, method: string, tlvFiles: number[]) => void) => {
if (!fs.existsSync(this.metricsPath)) {
fs.mkdirSync(this.metricsPath, { recursive: true });
}
const apps = fs.readdirSync(this.metricsPath)
apps.forEach(appDir => {
const appPath = [this.metricsPath, appDir].join("/")
if (!fs.lstatSync(appPath).isDirectory()) {
return
}
const methods = fs.readdirSync(appPath)
methods.forEach(methodDir => {
const methodPath = [appPath, methodDir].join("/")
if (!fs.lstatSync(methodPath).isDirectory()) {
return
}
const tlvFiles = fs.readdirSync(methodPath)
.filter(f => f.endsWith(".mtlv"))
.map(f => +f.slice(0, -".mtlv".length))
.filter(n => !isNaN(n))
.sort((a, b) => a - b)
cb(appDir, methodDir, tlvFiles)
})
})
}
}

View file

@ -0,0 +1,26 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class UserOffer1733502626042 implements MigrationInterface {
name = 'UserOffer1733502626042'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "user_offer" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_user_id" varchar NOT NULL, "offer_id" varchar NOT NULL, "label" varchar NOT NULL, "price_sats" integer NOT NULL DEFAULT (0), "callback_url" varchar NOT NULL DEFAULT (''), "expected_data" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "UQ_478f72095abd8a516d3a309a5c5" UNIQUE ("offer_id"))`);
await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`);
await queryRunner.query(`CREATE TABLE "temporary_user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer, "liquidityProvider" varchar, "payer_data" text, "offer_id" varchar NOT NULL DEFAULT (''), CONSTRAINT "FK_2c0dfb3483f3e5e7e3cdd5dc71f" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_5263bde2a519db9ea608b702ec8" FOREIGN KEY ("productProductId") REFERENCES "product" ("product_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_d4bb1e4c60e8a869f1f43ca2e31" FOREIGN KEY ("payerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_714a8b7d4f89f8a802ca181b789" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "temporary_user_receiving_invoice"("serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider") SELECT "serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider" FROM "user_receiving_invoice"`);
await queryRunner.query(`DROP TABLE "user_receiving_invoice"`);
await queryRunner.query(`ALTER TABLE "temporary_user_receiving_invoice" RENAME TO "user_receiving_invoice"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`);
await queryRunner.query(`ALTER TABLE "user_receiving_invoice" RENAME TO "temporary_user_receiving_invoice"`);
await queryRunner.query(`CREATE TABLE "user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer, "liquidityProvider" varchar, CONSTRAINT "FK_2c0dfb3483f3e5e7e3cdd5dc71f" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_5263bde2a519db9ea608b702ec8" FOREIGN KEY ("productProductId") REFERENCES "product" ("product_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_d4bb1e4c60e8a869f1f43ca2e31" FOREIGN KEY ("payerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_714a8b7d4f89f8a802ca181b789" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "user_receiving_invoice"("serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider") SELECT "serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider" FROM "temporary_user_receiving_invoice"`);
await queryRunner.query(`DROP TABLE "temporary_user_receiving_invoice"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `);
await queryRunner.query(`DROP TABLE "user_offer"`);
}
}

View file

@ -17,7 +17,8 @@ import { DebitAccessFixes1726685229264 } from './1726685229264-debit_access_fixe
import { DebitToPub1727105758354 } from './1727105758354-debit_to_pub.js'
import { UserCbUrl1727112281043 } from './1727112281043-user_cb_url.js'
import { RootOps1732566440447 } from './1732566440447-root_ops.js'
const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043]
import { UserOffer1733502626042 } from './1733502626042-user_offer.js'
const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042]
const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447]
export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
if (arg === 'fake_initial_migration') {

View file

@ -0,0 +1,49 @@
import { DataSource, EntityManager } from "typeorm"
import crypto from 'crypto';
import UserStorage from './userStorage.js';
import TransactionsQueue from "./transactionsQueue.js";
import { DebitAccess, DebitAccessRules } from "./entity/DebitAccess.js";
import { UserOffer } from "./entity/UserOffer.js";
export default class {
DB: DataSource | EntityManager
txQueue: TransactionsQueue
constructor(DB: DataSource | EntityManager, txQueue: TransactionsQueue) {
this.DB = DB
this.txQueue = txQueue
}
async AddDefaultUserOffer(appUserId: string): Promise<UserOffer> {
const newUserOffer = this.DB.getRepository(UserOffer).create({
app_user_id: appUserId,
offer_id: appUserId,
label: 'Default NIP-69 Offer',
})
return this.txQueue.PushToQueue<UserOffer>({ exec: async db => db.getRepository(UserOffer).save(newUserOffer), dbTx: false, description: `add default offer for ${appUserId}` })
}
async AddUserOffer(appUserId: string, req: Partial<UserOffer>): Promise<UserOffer> {
const newUserOffer = this.DB.getRepository(UserOffer).create({
...req,
app_user_id: appUserId,
offer_id: crypto.randomBytes(34).toString('hex')
})
return this.txQueue.PushToQueue<UserOffer>({ exec: async db => db.getRepository(UserOffer).save(newUserOffer), dbTx: false, description: `add offer for ${appUserId}: ${req.label} ` })
}
async DeleteUserOffer(appUserId: string, offerId: string, entityManager = this.DB) {
await entityManager.getRepository(UserOffer).delete({ app_user_id: appUserId, offer_id: offerId })
}
async UpdateUserOffer(app_user_id: string, offerId: string, req: Partial<UserOffer>) {
return this.DB.getRepository(UserOffer).update({ app_user_id, offer_id: offerId }, req)
}
async GetUserOffers(app_user_id: string): Promise<UserOffer[]> {
return this.DB.getRepository(UserOffer).find({ where: { app_user_id } })
}
async GetUserOffer(app_user_id: string, offer_id: string): Promise<UserOffer | null> {
return this.DB.getRepository(UserOffer).findOne({ where: { app_user_id, offer_id } })
}
async GetOffer(offer_id: string): Promise<UserOffer | null> {
return this.DB.getRepository(UserOffer).findOne({ where: { offer_id } })
}
}

View file

@ -13,7 +13,7 @@ import { UserToUserPayment } from './entity/UserToUserPayment.js';
import { Application } from './entity/Application.js';
import TransactionsQueue from "./transactionsQueue.js";
import { LoggedEvent } from './eventsLog.js';
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application, zapInfo?: ZapInfo }
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application, zapInfo?: ZapInfo, offerId?: string, payerData?: Record<string, string> }
export const defaultInvoiceExpiry = 60 * 60
export default class {
DB: DataSource | EntityManager
@ -102,7 +102,9 @@ export default class {
payer: options.expectedPayer,
linkedApplication: options.linkedApplication,
zap_info: options.zapInfo,
liquidityProvider: providerDestination
liquidityProvider: providerDestination,
offer_id: options.offerId,
payer_data: options.payerData,
})
return this.txQueue.PushToQueue<UserReceivingInvoice>({ exec: async db => db.getRepository(UserReceivingInvoice).save(newUserInvoice), dbTx: false, description: `add invoice for ${user.user_id} linked to ${options.linkedApplication?.app_id}: ${invoice} ` })
}
@ -452,4 +454,12 @@ export default class {
async GetPendingPayments(entityManager = this.DB) {
return entityManager.getRepository(UserInvoicePayment).find({ where: { paid_at_unix: 0 } })
}
async GetOfferInvoices(offerId: string, includeUnpaid: boolean, entityManager = this.DB) {
const where: { offer_id: string, paid_at_unix?: FindOperator<number> } = { offer_id: offerId }
if (!includeUnpaid) {
where.paid_at_unix = MoreThan(0)
}
return entityManager.getRepository(UserReceivingInvoice).find({ where })
}
}

View file

@ -34,7 +34,7 @@ export class StateBundler {
latestReport = Date.now()
reportLog = getLogger({ component: 'stateBundlerReport' })
constructor() {
process.on('exit', () => {
/* process.on('exit', () => {
this.Report()
});
@ -49,7 +49,7 @@ export class StateBundler {
console.log('Uncaught Exception...');
console.log(e.stack);
process.exit(99);
});
}); */
}
increment = (key: string, value: number) => {