Merge branch 'master' into npub-linking-through-expirable-tokens
This commit is contained in:
commit
0963f94989
23 changed files with 321 additions and 4551 deletions
|
|
@ -44,3 +44,9 @@ MIGRATE_DB=false
|
||||||
RECORD_PERFORMANCE=true
|
RECORD_PERFORMANCE=true
|
||||||
SKIP_SANITY_CHECK=false
|
SKIP_SANITY_CHECK=false
|
||||||
DISABLE_EXTERNAL_PAYMENTS=false
|
DISABLE_EXTERNAL_PAYMENTS=false
|
||||||
|
|
||||||
|
# Max difference between users balance and LND balance since beginning of app execution
|
||||||
|
WATCHDOG_MAX_DIFF_SATS=10000
|
||||||
|
|
||||||
|
# Max difference between users balance and LND balance after each payment
|
||||||
|
WATCHDOG_MAX_UPDATE_DIFF_SATS=1000
|
||||||
|
|
@ -1,825 +0,0 @@
|
||||||
# NOSTR API DEFINITION
|
|
||||||
|
|
||||||
|
|
||||||
A nostr request will take the same parameter and give the same response as an http request, but it will use nostr as transport, to do that it will send encrypted events to the server public key, in the event 6 thing are required:
|
|
||||||
- __rpcName__: string containing the name of the method
|
|
||||||
- __params__: a map with the all the url params for the method
|
|
||||||
- __query__: a map with the the url query for the method
|
|
||||||
- __body__: the body of the method request
|
|
||||||
- __requestId__: id of the request to be able to get a response
|
|
||||||
|
|
||||||
The nostr server will send back a message response, and inside the body there will also be a __requestId__ to identify the request this response is answering
|
|
||||||
|
|
||||||
## NOSTR Methods
|
|
||||||
### These are the nostr methods the client implements to communicate with the API via nostr
|
|
||||||
|
|
||||||
- LinkNPubThroughToken
|
|
||||||
- auth type: __User__
|
|
||||||
- input: [LinkNPubThroughTokenRequest](#LinkNPubThroughTokenRequest)
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- UserHealth
|
|
||||||
- auth type: __User__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- GetUserInfo
|
|
||||||
- auth type: __User__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [UserInfo](#UserInfo)
|
|
||||||
|
|
||||||
- AddProduct
|
|
||||||
- auth type: __User__
|
|
||||||
- input: [AddProductRequest](#AddProductRequest)
|
|
||||||
- output: [Product](#Product)
|
|
||||||
|
|
||||||
- NewProductInvoice
|
|
||||||
- auth type: __User__
|
|
||||||
- the request url __query__ can take the following string items:
|
|
||||||
- id
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [NewInvoiceResponse](#NewInvoiceResponse)
|
|
||||||
|
|
||||||
- GetUserOperations
|
|
||||||
- auth type: __User__
|
|
||||||
- input: [GetUserOperationsRequest](#GetUserOperationsRequest)
|
|
||||||
- output: [GetUserOperationsResponse](#GetUserOperationsResponse)
|
|
||||||
|
|
||||||
- NewAddress
|
|
||||||
- auth type: __User__
|
|
||||||
- input: [NewAddressRequest](#NewAddressRequest)
|
|
||||||
- output: [NewAddressResponse](#NewAddressResponse)
|
|
||||||
|
|
||||||
- PayAddress
|
|
||||||
- auth type: __User__
|
|
||||||
- input: [PayAddressRequest](#PayAddressRequest)
|
|
||||||
- output: [PayAddressResponse](#PayAddressResponse)
|
|
||||||
|
|
||||||
- NewInvoice
|
|
||||||
- auth type: __User__
|
|
||||||
- input: [NewInvoiceRequest](#NewInvoiceRequest)
|
|
||||||
- output: [NewInvoiceResponse](#NewInvoiceResponse)
|
|
||||||
|
|
||||||
- DecodeInvoice
|
|
||||||
- auth type: __User__
|
|
||||||
- input: [DecodeInvoiceRequest](#DecodeInvoiceRequest)
|
|
||||||
- output: [DecodeInvoiceResponse](#DecodeInvoiceResponse)
|
|
||||||
|
|
||||||
- PayInvoice
|
|
||||||
- auth type: __User__
|
|
||||||
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
|
||||||
- output: [PayInvoiceResponse](#PayInvoiceResponse)
|
|
||||||
|
|
||||||
- OpenChannel
|
|
||||||
- auth type: __User__
|
|
||||||
- input: [OpenChannelRequest](#OpenChannelRequest)
|
|
||||||
- output: [OpenChannelResponse](#OpenChannelResponse)
|
|
||||||
|
|
||||||
- GetLnurlWithdrawLink
|
|
||||||
- auth type: __User__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LnurlLinkResponse](#LnurlLinkResponse)
|
|
||||||
|
|
||||||
- GetLnurlPayLink
|
|
||||||
- auth type: __User__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LnurlLinkResponse](#LnurlLinkResponse)
|
|
||||||
|
|
||||||
- GetLNURLChannelLink
|
|
||||||
- auth type: __User__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LnurlLinkResponse](#LnurlLinkResponse)
|
|
||||||
|
|
||||||
- GetLiveUserOperations
|
|
||||||
- auth type: __User__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LiveUserOperation](#LiveUserOperation)
|
|
||||||
|
|
||||||
- GetMigrationUpdate
|
|
||||||
- auth type: __User__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [MigrationUpdate](#MigrationUpdate)
|
|
||||||
|
|
||||||
- BatchUser
|
|
||||||
- auth type: __User__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
# HTTP API DEFINITION
|
|
||||||
|
|
||||||
## Supported HTTP Auths
|
|
||||||
### These are the supported http auth types, to give different type of access to the API users
|
|
||||||
|
|
||||||
- __Guest__:
|
|
||||||
- expected context content
|
|
||||||
|
|
||||||
- __User__:
|
|
||||||
- expected context content
|
|
||||||
- __user_id__: _string_
|
|
||||||
- __app_id__: _string_
|
|
||||||
- __app_user_id__: _string_
|
|
||||||
|
|
||||||
- __Admin__:
|
|
||||||
- expected context content
|
|
||||||
- __admin_id__: _string_
|
|
||||||
|
|
||||||
- __Metrics__:
|
|
||||||
- expected context content
|
|
||||||
- __operator_id__: _string_
|
|
||||||
|
|
||||||
- __App__:
|
|
||||||
- expected context content
|
|
||||||
- __app_id__: _string_
|
|
||||||
|
|
||||||
## HTTP Methods
|
|
||||||
### These are the http methods the client implements to communicate with the API
|
|
||||||
|
|
||||||
- LndGetInfo
|
|
||||||
- auth type: __Admin__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/admin/lnd/getinfo__
|
|
||||||
- input: [LndGetInfoRequest](#LndGetInfoRequest)
|
|
||||||
- output: [LndGetInfoResponse](#LndGetInfoResponse)
|
|
||||||
|
|
||||||
- AddApp
|
|
||||||
- auth type: __Admin__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/admin/app/add__
|
|
||||||
- input: [AddAppRequest](#AddAppRequest)
|
|
||||||
- output: [AuthApp](#AuthApp)
|
|
||||||
|
|
||||||
- AuthApp
|
|
||||||
- auth type: __Admin__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/admin/app/auth__
|
|
||||||
- input: [AuthAppRequest](#AuthAppRequest)
|
|
||||||
- output: [AuthApp](#AuthApp)
|
|
||||||
|
|
||||||
- GetUsageMetrics
|
|
||||||
- auth type: __Metrics__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/reports/usage__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [UsageMetrics](#UsageMetrics)
|
|
||||||
|
|
||||||
- GetAppsMetrics
|
|
||||||
- auth type: __Metrics__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/reports/apps__
|
|
||||||
- input: [AppsMetricsRequest](#AppsMetricsRequest)
|
|
||||||
- output: [AppsMetrics](#AppsMetrics)
|
|
||||||
|
|
||||||
- GetLndMetrics
|
|
||||||
- auth type: __Metrics__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/reports/lnd__
|
|
||||||
- input: [LndMetricsRequest](#LndMetricsRequest)
|
|
||||||
- output: [LndMetrics](#LndMetrics)
|
|
||||||
|
|
||||||
- Health
|
|
||||||
- auth type: __Guest__
|
|
||||||
- http method: __get__
|
|
||||||
- http route: __/api/health__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- EncryptionExchange
|
|
||||||
- auth type: __Guest__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/encryption/exchange__
|
|
||||||
- input: [EncryptionExchangeRequest](#EncryptionExchangeRequest)
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- SetMockInvoiceAsPaid
|
|
||||||
- auth type: __Guest__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/lnd/mock/invoice/paid__
|
|
||||||
- input: [SetMockInvoiceAsPaidRequest](#SetMockInvoiceAsPaidRequest)
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- GetLnurlWithdrawInfo
|
|
||||||
- auth type: __Guest__
|
|
||||||
- http method: __get__
|
|
||||||
- http route: __/api/guest/lnurl_withdraw/info__
|
|
||||||
- the request url __query__ can take the following string items:
|
|
||||||
- k1
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LnurlWithdrawInfoResponse](#LnurlWithdrawInfoResponse)
|
|
||||||
|
|
||||||
- HandleLnurlWithdraw
|
|
||||||
- auth type: __Guest__
|
|
||||||
- http method: __get__
|
|
||||||
- http route: __/api/guest/lnurl_withdraw/handle__
|
|
||||||
- the request url __query__ can take the following string items:
|
|
||||||
- k1
|
|
||||||
- pr
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- GetLnurlPayInfo
|
|
||||||
- auth type: __Guest__
|
|
||||||
- http method: __get__
|
|
||||||
- http route: __/api/guest/lnurl_pay/info__
|
|
||||||
- the request url __query__ can take the following string items:
|
|
||||||
- k1
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LnurlPayInfoResponse](#LnurlPayInfoResponse)
|
|
||||||
|
|
||||||
- HandleLnurlPay
|
|
||||||
- auth type: __Guest__
|
|
||||||
- http method: __get__
|
|
||||||
- http route: __/api/guest/lnurl_pay/handle__
|
|
||||||
- the request url __query__ can take the following string items:
|
|
||||||
- k1
|
|
||||||
- amount
|
|
||||||
- nostr
|
|
||||||
- lnurl
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [HandleLnurlPayResponse](#HandleLnurlPayResponse)
|
|
||||||
|
|
||||||
- HandleLnurlAddress
|
|
||||||
- auth type: __Guest__
|
|
||||||
- http method: __get__
|
|
||||||
- http route: __/.well-known/lnurlp/:address_name__
|
|
||||||
- the request url __params__ are the following string items:
|
|
||||||
- address_name
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LnurlPayInfoResponse](#LnurlPayInfoResponse)
|
|
||||||
|
|
||||||
- LinkNPubThroughToken
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/guest/npub/link__
|
|
||||||
- input: [LinkNPubThroughTokenRequest](#LinkNPubThroughTokenRequest)
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- GetApp
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/get__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [Application](#Application)
|
|
||||||
|
|
||||||
- AddAppUser
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/user/add__
|
|
||||||
- input: [AddAppUserRequest](#AddAppUserRequest)
|
|
||||||
- output: [AppUser](#AppUser)
|
|
||||||
|
|
||||||
- AddAppInvoice
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/add/invoice__
|
|
||||||
- input: [AddAppInvoiceRequest](#AddAppInvoiceRequest)
|
|
||||||
- output: [NewInvoiceResponse](#NewInvoiceResponse)
|
|
||||||
|
|
||||||
- AddAppUserInvoice
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/user/add/invoice__
|
|
||||||
- input: [AddAppUserInvoiceRequest](#AddAppUserInvoiceRequest)
|
|
||||||
- output: [NewInvoiceResponse](#NewInvoiceResponse)
|
|
||||||
|
|
||||||
- GetAppUser
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/user/get__
|
|
||||||
- input: [GetAppUserRequest](#GetAppUserRequest)
|
|
||||||
- output: [AppUser](#AppUser)
|
|
||||||
|
|
||||||
- PayAppUserInvoice
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/invoice/pay__
|
|
||||||
- input: [PayAppUserInvoiceRequest](#PayAppUserInvoiceRequest)
|
|
||||||
- output: [PayInvoiceResponse](#PayInvoiceResponse)
|
|
||||||
|
|
||||||
- SendAppUserToAppUserPayment
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/user/internal/pay__
|
|
||||||
- input: [SendAppUserToAppUserPaymentRequest](#SendAppUserToAppUserPaymentRequest)
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- SendAppUserToAppPayment
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/internal/pay__
|
|
||||||
- input: [SendAppUserToAppPaymentRequest](#SendAppUserToAppPaymentRequest)
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- GetAppUserLNURLInfo
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/user/lnurl/pay/info__
|
|
||||||
- input: [GetAppUserLNURLInfoRequest](#GetAppUserLNURLInfoRequest)
|
|
||||||
- output: [LnurlPayInfoResponse](#LnurlPayInfoResponse)
|
|
||||||
|
|
||||||
- SetMockAppUserBalance
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/mock/user/blance/set__
|
|
||||||
- input: [SetMockAppUserBalanceRequest](#SetMockAppUserBalanceRequest)
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- SetMockAppBalance
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/mock/blance/set__
|
|
||||||
- input: [SetMockAppBalanceRequest](#SetMockAppBalanceRequest)
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- RequestNPubLinkingToken
|
|
||||||
- auth type: __App__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/app/user/npub/token__
|
|
||||||
- input: [RequestNPubLinkingTokenRequest](#RequestNPubLinkingTokenRequest)
|
|
||||||
- output: [RequestNPubLinkingTokenResponse](#RequestNPubLinkingTokenResponse)
|
|
||||||
|
|
||||||
- UserHealth
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/health__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
- GetUserInfo
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/info__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [UserInfo](#UserInfo)
|
|
||||||
|
|
||||||
- AddProduct
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/product/add__
|
|
||||||
- input: [AddProductRequest](#AddProductRequest)
|
|
||||||
- output: [Product](#Product)
|
|
||||||
|
|
||||||
- NewProductInvoice
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __get__
|
|
||||||
- http route: __/api/user/product/get/invoice__
|
|
||||||
- the request url __query__ can take the following string items:
|
|
||||||
- id
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [NewInvoiceResponse](#NewInvoiceResponse)
|
|
||||||
|
|
||||||
- GetUserOperations
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/operations__
|
|
||||||
- input: [GetUserOperationsRequest](#GetUserOperationsRequest)
|
|
||||||
- output: [GetUserOperationsResponse](#GetUserOperationsResponse)
|
|
||||||
|
|
||||||
- NewAddress
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/chain/new__
|
|
||||||
- input: [NewAddressRequest](#NewAddressRequest)
|
|
||||||
- output: [NewAddressResponse](#NewAddressResponse)
|
|
||||||
|
|
||||||
- PayAddress
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/chain/pay__
|
|
||||||
- input: [PayAddressRequest](#PayAddressRequest)
|
|
||||||
- output: [PayAddressResponse](#PayAddressResponse)
|
|
||||||
|
|
||||||
- NewInvoice
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/invoice/new__
|
|
||||||
- input: [NewInvoiceRequest](#NewInvoiceRequest)
|
|
||||||
- output: [NewInvoiceResponse](#NewInvoiceResponse)
|
|
||||||
|
|
||||||
- DecodeInvoice
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/invoice/decode__
|
|
||||||
- input: [DecodeInvoiceRequest](#DecodeInvoiceRequest)
|
|
||||||
- output: [DecodeInvoiceResponse](#DecodeInvoiceResponse)
|
|
||||||
|
|
||||||
- PayInvoice
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/invoice/pay__
|
|
||||||
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
|
||||||
- output: [PayInvoiceResponse](#PayInvoiceResponse)
|
|
||||||
|
|
||||||
- OpenChannel
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/open/channel__
|
|
||||||
- input: [OpenChannelRequest](#OpenChannelRequest)
|
|
||||||
- output: [OpenChannelResponse](#OpenChannelResponse)
|
|
||||||
|
|
||||||
- GetLnurlWithdrawLink
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __get__
|
|
||||||
- http route: __/api/user/lnurl_withdraw/link__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LnurlLinkResponse](#LnurlLinkResponse)
|
|
||||||
|
|
||||||
- GetLnurlPayLink
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __get__
|
|
||||||
- http route: __/api/user/lnurl_pay/link__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LnurlLinkResponse](#LnurlLinkResponse)
|
|
||||||
|
|
||||||
- GetLNURLChannelLink
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/lnurl_channel/url__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LnurlLinkResponse](#LnurlLinkResponse)
|
|
||||||
|
|
||||||
- GetLiveUserOperations
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/operations/sub__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [LiveUserOperation](#LiveUserOperation)
|
|
||||||
|
|
||||||
- GetMigrationUpdate
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/migrations/sub__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- output: [MigrationUpdate](#MigrationUpdate)
|
|
||||||
|
|
||||||
- BatchUser
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/batch__
|
|
||||||
- This methods has an __empty__ __request__ body
|
|
||||||
- This methods has an __empty__ __response__ body
|
|
||||||
|
|
||||||
# INPUTS AND OUTPUTS
|
|
||||||
|
|
||||||
## Messages
|
|
||||||
### The content of requests and response from the methods
|
|
||||||
|
|
||||||
### Application
|
|
||||||
- __name__: _string_
|
|
||||||
- __id__: _string_
|
|
||||||
- __balance__: _number_
|
|
||||||
- __npub__: _string_
|
|
||||||
|
|
||||||
### ClosureMigration
|
|
||||||
- __closes_at_unix__: _number_
|
|
||||||
|
|
||||||
### LinkNPubThroughTokenRequest
|
|
||||||
- __token__: _string_
|
|
||||||
- __nostr_pub__: _string_
|
|
||||||
|
|
||||||
### EncryptionExchangeRequest
|
|
||||||
- __publicKey__: _string_
|
|
||||||
- __deviceId__: _string_
|
|
||||||
|
|
||||||
### UsersInfo
|
|
||||||
- __total__: _number_
|
|
||||||
- __no_balance__: _number_
|
|
||||||
- __negative_balance__: _number_
|
|
||||||
- __always_been_inactive__: _number_
|
|
||||||
- __balance_avg__: _number_
|
|
||||||
- __balance_median__: _number_
|
|
||||||
|
|
||||||
### HandleLnurlPayResponse
|
|
||||||
- __pr__: _string_
|
|
||||||
- __routes__: ARRAY of: _[Empty](#Empty)_
|
|
||||||
|
|
||||||
### MigrationUpdate
|
|
||||||
- __closure__: _[ClosureMigration](#ClosureMigration)_ *this field is optional
|
|
||||||
- __relays__: _[RelaysMigration](#RelaysMigration)_ *this field is optional
|
|
||||||
|
|
||||||
### AppMetrics
|
|
||||||
- __app__: _[Application](#Application)_
|
|
||||||
- __users__: _[UsersInfo](#UsersInfo)_
|
|
||||||
- __received__: _number_
|
|
||||||
- __spent__: _number_
|
|
||||||
- __available__: _number_
|
|
||||||
- __fees__: _number_
|
|
||||||
- __invoices__: _number_
|
|
||||||
- __total_fees__: _number_
|
|
||||||
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
|
|
||||||
|
|
||||||
### NewInvoiceResponse
|
|
||||||
- __invoice__: _string_
|
|
||||||
|
|
||||||
### LndMetrics
|
|
||||||
- __nodes__: ARRAY of: _[LndNodeMetrics](#LndNodeMetrics)_
|
|
||||||
|
|
||||||
### AddProductRequest
|
|
||||||
- __name__: _string_
|
|
||||||
- __price_sats__: _number_
|
|
||||||
|
|
||||||
### PayAddressRequest
|
|
||||||
- __address__: _string_
|
|
||||||
- __amoutSats__: _number_
|
|
||||||
- __satsPerVByte__: _number_
|
|
||||||
|
|
||||||
### UserOperation
|
|
||||||
- __paidAtUnix__: _number_
|
|
||||||
- __type__: _[UserOperationType](#UserOperationType)_
|
|
||||||
- __inbound__: _boolean_
|
|
||||||
- __amount__: _number_
|
|
||||||
- __identifier__: _string_
|
|
||||||
- __operationId__: _string_
|
|
||||||
- __service_fee__: _number_
|
|
||||||
- __network_fee__: _number_
|
|
||||||
- __confirmed__: _boolean_
|
|
||||||
- __tx_hash__: _string_
|
|
||||||
- __internal__: _boolean_
|
|
||||||
|
|
||||||
### GetUserOperationsResponse
|
|
||||||
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
|
|
||||||
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
|
|
||||||
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
|
|
||||||
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
|
|
||||||
- __latestOutgoingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
|
|
||||||
- __latestIncomingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
|
|
||||||
|
|
||||||
### AuthAppRequest
|
|
||||||
- __name__: _string_
|
|
||||||
- __allow_user_creation__: _boolean_ *this field is optional
|
|
||||||
|
|
||||||
### AddAppUserRequest
|
|
||||||
- __identifier__: _string_
|
|
||||||
- __fail_if_exists__: _boolean_
|
|
||||||
- __balance__: _number_
|
|
||||||
|
|
||||||
### SetMockAppBalanceRequest
|
|
||||||
- __amount__: _number_
|
|
||||||
|
|
||||||
### DecodeInvoiceResponse
|
|
||||||
- __amount__: _number_
|
|
||||||
|
|
||||||
### UserOperations
|
|
||||||
- __fromIndex__: _number_
|
|
||||||
- __toIndex__: _number_
|
|
||||||
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
|
|
||||||
|
|
||||||
### RelaysMigration
|
|
||||||
- __relays__: ARRAY of: _string_
|
|
||||||
|
|
||||||
### LndNodeMetrics
|
|
||||||
- __channels_balance_events__: ARRAY of: _[ChannelBalanceEvent](#ChannelBalanceEvent)_
|
|
||||||
- __chain_balance_events__: ARRAY of: _[ChainBalanceEvent](#ChainBalanceEvent)_
|
|
||||||
- __offline_channels__: _number_
|
|
||||||
- __online_channels__: _number_
|
|
||||||
- __pending_channels__: _number_
|
|
||||||
- __closing_channels__: _number_
|
|
||||||
- __open_channels__: ARRAY of: _[OpenChannel](#OpenChannel)_
|
|
||||||
- __closed_channels__: ARRAY of: _[ClosedChannel](#ClosedChannel)_
|
|
||||||
- __channel_routing__: ARRAY of: _[ChannelRouting](#ChannelRouting)_
|
|
||||||
|
|
||||||
### AddAppUserInvoiceRequest
|
|
||||||
- __receiver_identifier__: _string_
|
|
||||||
- __payer_identifier__: _string_
|
|
||||||
- __http_callback_url__: _string_
|
|
||||||
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
|
|
||||||
|
|
||||||
### ClosedChannel
|
|
||||||
- __channel_id__: _string_
|
|
||||||
- __capacity__: _number_
|
|
||||||
- __closed_height__: _number_
|
|
||||||
|
|
||||||
### AddAppRequest
|
|
||||||
- __name__: _string_
|
|
||||||
- __allow_user_creation__: _boolean_
|
|
||||||
|
|
||||||
### LnurlLinkResponse
|
|
||||||
- __lnurl__: _string_
|
|
||||||
- __k1__: _string_
|
|
||||||
|
|
||||||
### UsageMetrics
|
|
||||||
- __metrics__: ARRAY of: _[UsageMetric](#UsageMetric)_
|
|
||||||
|
|
||||||
### ChainBalanceEvent
|
|
||||||
- __block_height__: _number_
|
|
||||||
- __confirmed_balance__: _number_
|
|
||||||
- __unconfirmed_balance__: _number_
|
|
||||||
- __total_balance__: _number_
|
|
||||||
|
|
||||||
### Product
|
|
||||||
- __id__: _string_
|
|
||||||
- __name__: _string_
|
|
||||||
- __price_sats__: _number_
|
|
||||||
|
|
||||||
### ChannelBalanceEvent
|
|
||||||
- __block_height__: _number_
|
|
||||||
- __channel_id__: _string_
|
|
||||||
- __local_balance_sats__: _number_
|
|
||||||
- __remote_balance_sats__: _number_
|
|
||||||
|
|
||||||
### AuthApp
|
|
||||||
- __app__: _[Application](#Application)_
|
|
||||||
- __auth_token__: _string_
|
|
||||||
|
|
||||||
### AppsMetrics
|
|
||||||
- __apps__: ARRAY of: _[AppMetrics](#AppMetrics)_
|
|
||||||
|
|
||||||
### RoutingEvent
|
|
||||||
- __incoming_channel_id__: _number_
|
|
||||||
- __incoming_htlc_id__: _number_
|
|
||||||
- __outgoing_channel_id__: _number_
|
|
||||||
- __outgoing_htlc_id__: _number_
|
|
||||||
- __timestamp_ns__: _number_
|
|
||||||
- __event_type__: _string_
|
|
||||||
- __incoming_amt_msat__: _number_
|
|
||||||
- __outgoing_amt_msat__: _number_
|
|
||||||
- __failure_string__: _string_
|
|
||||||
- __settled__: _boolean_
|
|
||||||
- __offchain__: _boolean_
|
|
||||||
- __forward_fail_event__: _boolean_
|
|
||||||
|
|
||||||
### AddAppInvoiceRequest
|
|
||||||
- __payer_identifier__: _string_
|
|
||||||
- __http_callback_url__: _string_
|
|
||||||
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
|
|
||||||
|
|
||||||
### LnurlWithdrawInfoResponse
|
|
||||||
- __tag__: _string_
|
|
||||||
- __callback__: _string_
|
|
||||||
- __k1__: _string_
|
|
||||||
- __defaultDescription__: _string_
|
|
||||||
- __minWithdrawable__: _number_
|
|
||||||
- __maxWithdrawable__: _number_
|
|
||||||
- __balanceCheck__: _string_
|
|
||||||
- __payLink__: _string_
|
|
||||||
|
|
||||||
### LndGetInfoRequest
|
|
||||||
- __nodeId__: _number_
|
|
||||||
|
|
||||||
### AppUser
|
|
||||||
- __identifier__: _string_
|
|
||||||
- __info__: _[UserInfo](#UserInfo)_
|
|
||||||
- __max_withdrawable__: _number_
|
|
||||||
|
|
||||||
### PayAppUserInvoiceRequest
|
|
||||||
- __user_identifier__: _string_
|
|
||||||
- __invoice__: _string_
|
|
||||||
- __amount__: _number_
|
|
||||||
|
|
||||||
### OpenChannelRequest
|
|
||||||
- __destination__: _string_
|
|
||||||
- __fundingAmount__: _number_
|
|
||||||
- __pushAmount__: _number_
|
|
||||||
- __closeAddress__: _string_
|
|
||||||
|
|
||||||
### LnurlPayInfoResponse
|
|
||||||
- __tag__: _string_
|
|
||||||
- __callback__: _string_
|
|
||||||
- __maxSendable__: _number_
|
|
||||||
- __minSendable__: _number_
|
|
||||||
- __metadata__: _string_
|
|
||||||
- __allowsNostr__: _boolean_
|
|
||||||
- __nostrPubkey__: _string_
|
|
||||||
|
|
||||||
### GetProductBuyLinkResponse
|
|
||||||
- __link__: _string_
|
|
||||||
|
|
||||||
### Empty
|
|
||||||
|
|
||||||
### ChannelRouting
|
|
||||||
- __channel_id__: _string_
|
|
||||||
- __send_errors__: _number_
|
|
||||||
- __receive_errors__: _number_
|
|
||||||
- __forward_errors_as_input__: _number_
|
|
||||||
- __forward_errors_as_output__: _number_
|
|
||||||
- __missed_forward_fee_as_input__: _number_
|
|
||||||
- __missed_forward_fee_as_output__: _number_
|
|
||||||
- __forward_fee_as_input__: _number_
|
|
||||||
- __forward_fee_as_output__: _number_
|
|
||||||
- __events_number__: _number_
|
|
||||||
|
|
||||||
### OpenChannel
|
|
||||||
- __channel_id__: _string_
|
|
||||||
- __capacity__: _number_
|
|
||||||
- __active__: _boolean_
|
|
||||||
- __lifetime__: _number_
|
|
||||||
- __local_balance__: _number_
|
|
||||||
- __remote_balance__: _number_
|
|
||||||
|
|
||||||
### RequestNPubLinkingTokenResponse
|
|
||||||
- __token__: _string_
|
|
||||||
|
|
||||||
### SendAppUserToAppPaymentRequest
|
|
||||||
- __from_user_identifier__: _string_
|
|
||||||
- __amount__: _number_
|
|
||||||
|
|
||||||
### NewInvoiceRequest
|
|
||||||
- __amountSats__: _number_
|
|
||||||
- __memo__: _string_
|
|
||||||
|
|
||||||
### DecodeInvoiceRequest
|
|
||||||
- __invoice__: _string_
|
|
||||||
|
|
||||||
### GetUserOperationsRequest
|
|
||||||
- __latestIncomingInvoice__: _number_
|
|
||||||
- __latestOutgoingInvoice__: _number_
|
|
||||||
- __latestIncomingTx__: _number_
|
|
||||||
- __latestOutgoingTx__: _number_
|
|
||||||
- __latestIncomingUserToUserPayment__: _number_
|
|
||||||
- __latestOutgoingUserToUserPayment__: _number_
|
|
||||||
- __max_size__: _number_
|
|
||||||
|
|
||||||
### LiveUserOperation
|
|
||||||
- __operation__: _[UserOperation](#UserOperation)_
|
|
||||||
|
|
||||||
### UsageMetric
|
|
||||||
- __processed_at_ms__: _number_
|
|
||||||
- __parsed_in_nano__: _number_
|
|
||||||
- __auth_in_nano__: _number_
|
|
||||||
- __validate_in_nano__: _number_
|
|
||||||
- __handle_in_nano__: _number_
|
|
||||||
- __rpc_name__: _string_
|
|
||||||
- __batch__: _boolean_
|
|
||||||
- __nostr__: _boolean_
|
|
||||||
- __batch_size__: _number_
|
|
||||||
|
|
||||||
### LndMetricsRequest
|
|
||||||
- __from_unix__: _number_ *this field is optional
|
|
||||||
- __to_unix__: _number_ *this field is optional
|
|
||||||
|
|
||||||
### GetAppUserRequest
|
|
||||||
- __user_identifier__: _string_
|
|
||||||
|
|
||||||
### SendAppUserToAppUserPaymentRequest
|
|
||||||
- __from_user_identifier__: _string_
|
|
||||||
- __to_user_identifier__: _string_
|
|
||||||
- __amount__: _number_
|
|
||||||
|
|
||||||
### PayAddressResponse
|
|
||||||
- __txId__: _string_
|
|
||||||
- __operation_id__: _string_
|
|
||||||
- __service_fee__: _number_
|
|
||||||
- __network_fee__: _number_
|
|
||||||
|
|
||||||
### OpenChannelResponse
|
|
||||||
- __channelId__: _string_
|
|
||||||
|
|
||||||
### RequestNPubLinkingTokenRequest
|
|
||||||
- __user_identifier__: _string_
|
|
||||||
|
|
||||||
### SetMockInvoiceAsPaidRequest
|
|
||||||
- __invoice__: _string_
|
|
||||||
- __amount__: _number_
|
|
||||||
|
|
||||||
### LndGetInfoResponse
|
|
||||||
- __alias__: _string_
|
|
||||||
|
|
||||||
### UserInfo
|
|
||||||
- __userId__: _string_
|
|
||||||
- __balance__: _number_
|
|
||||||
- __max_withdrawable__: _number_
|
|
||||||
- __user_identifier__: _string_
|
|
||||||
|
|
||||||
### AppsMetricsRequest
|
|
||||||
- __from_unix__: _number_ *this field is optional
|
|
||||||
- __to_unix__: _number_ *this field is optional
|
|
||||||
- __include_operations__: _boolean_ *this field is optional
|
|
||||||
|
|
||||||
### GetAppUserLNURLInfoRequest
|
|
||||||
- __user_identifier__: _string_
|
|
||||||
- __base_url_override__: _string_
|
|
||||||
|
|
||||||
### PayInvoiceRequest
|
|
||||||
- __invoice__: _string_
|
|
||||||
- __amount__: _number_
|
|
||||||
|
|
||||||
### PayInvoiceResponse
|
|
||||||
- __preimage__: _string_
|
|
||||||
- __amount_paid__: _number_
|
|
||||||
- __operation_id__: _string_
|
|
||||||
- __service_fee__: _number_
|
|
||||||
- __network_fee__: _number_
|
|
||||||
|
|
||||||
### SetMockAppUserBalanceRequest
|
|
||||||
- __user_identifier__: _string_
|
|
||||||
- __amount__: _number_
|
|
||||||
|
|
||||||
### NewAddressResponse
|
|
||||||
- __address__: _string_
|
|
||||||
|
|
||||||
### NewAddressRequest
|
|
||||||
- __addressType__: _[AddressType](#AddressType)_
|
|
||||||
## Enums
|
|
||||||
### The enumerators used in the messages
|
|
||||||
|
|
||||||
### AddressType
|
|
||||||
- __WITNESS_PUBKEY_HASH__
|
|
||||||
- __NESTED_PUBKEY_HASH__
|
|
||||||
- __TAPROOT_PUBKEY__
|
|
||||||
|
|
||||||
### UserOperationType
|
|
||||||
- __INCOMING_TX__
|
|
||||||
- __OUTGOING_TX__
|
|
||||||
- __INCOMING_INVOICE__
|
|
||||||
- __OUTGOING_INVOICE__
|
|
||||||
- __OUTGOING_USER_TO_USER__
|
|
||||||
- __INCOMING_USER_TO_USER__
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -99,6 +99,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
||||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
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 }
|
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||||
})
|
})
|
||||||
|
if (!opts.allowNotImplementedMethods && !methods.BanUser) throw new Error('method: BanUser is not implemented')
|
||||||
|
app.post('/api/admin/user/ban', async (req, res) => {
|
||||||
|
const info: Types.RequestInfo = { rpcName: 'BanUser', batch: false, nostr: false, batchSize: 0}
|
||||||
|
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.BanUser) throw new Error('method: BanUser is not implemented')
|
||||||
|
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
|
||||||
|
authCtx = authContext
|
||||||
|
stats.guard = process.hrtime.bigint()
|
||||||
|
const request = req.body
|
||||||
|
const error = Types.BanUserRequestValidate(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.BanUser({rpcName:'BanUser', 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.GetUsageMetrics) throw new Error('method: GetUsageMetrics is not implemented')
|
if (!opts.allowNotImplementedMethods && !methods.GetUsageMetrics) throw new Error('method: GetUsageMetrics is not implemented')
|
||||||
app.post('/api/reports/usage', async (req, res) => {
|
app.post('/api/reports/usage', async (req, res) => {
|
||||||
const info: Types.RequestInfo = { rpcName: 'GetUsageMetrics', batch: false, nostr: false, batchSize: 0}
|
const info: Types.RequestInfo = { rpcName: 'GetUsageMetrics', batch: false, nostr: false, batchSize: 0}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,20 @@ export default (params: ClientParams) => ({
|
||||||
}
|
}
|
||||||
return { status: 'ERROR', reason: 'invalid response' }
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
},
|
},
|
||||||
|
BanUser: async (request: Types.BanUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.BanUserResponse)> => {
|
||||||
|
const auth = await params.retrieveAdminAuth()
|
||||||
|
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||||
|
let finalRoute = '/api/admin/user/ban'
|
||||||
|
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
|
||||||
|
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||||
|
if (data.status === 'OK') {
|
||||||
|
const result = data
|
||||||
|
if(!params.checkResult) return { status: 'OK', ...result }
|
||||||
|
const error = Types.BanUserResponseValidate(result)
|
||||||
|
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||||
|
}
|
||||||
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
|
},
|
||||||
GetUsageMetrics: async (): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetrics)> => {
|
GetUsageMetrics: async (): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetrics)> => {
|
||||||
const auth = await params.retrieveMetricsAuth()
|
const auth = await params.retrieveMetricsAuth()
|
||||||
if (auth === null) throw new Error('retrieveMetricsAuth() returned null')
|
if (auth === null) throw new Error('retrieveMetricsAuth() returned null')
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,12 @@ service LightningPub {
|
||||||
option (http_route) = "/api/admin/app/auth";
|
option (http_route) = "/api/admin/app/auth";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpc BanUser(structs.BanUserRequest) returns (structs.BanUserResponse) {
|
||||||
|
option (auth_type) = "Admin";
|
||||||
|
option (http_method) = "post";
|
||||||
|
option (http_route) = "/api/admin/user/ban";
|
||||||
|
}
|
||||||
|
|
||||||
rpc GetUsageMetrics(structs.Empty) returns (structs.UsageMetrics) {
|
rpc GetUsageMetrics(structs.Empty) returns (structs.UsageMetrics) {
|
||||||
option (auth_type) = "Metrics";
|
option (auth_type) = "Metrics";
|
||||||
option (http_method) = "post";
|
option (http_method) = "post";
|
||||||
|
|
@ -123,6 +129,8 @@ service LightningPub {
|
||||||
option (http_method) = "post";
|
option (http_method) = "post";
|
||||||
option (http_route) = "/api/reports/lnd";
|
option (http_route) = "/api/reports/lnd";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// </Admin>
|
// </Admin>
|
||||||
|
|
||||||
// <Guest>
|
// <Guest>
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,21 @@ message LndGetInfoResponse {
|
||||||
string alias = 1;
|
string alias = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message BanUserRequest {
|
||||||
|
string user_id = 1;
|
||||||
|
}
|
||||||
|
message BannedAppUser {
|
||||||
|
string app_name = 1;
|
||||||
|
string app_id = 2;
|
||||||
|
string user_identifier = 3;
|
||||||
|
string nostr_pub = 4;
|
||||||
|
|
||||||
|
}
|
||||||
|
message BanUserResponse {
|
||||||
|
int64 balance_sats = 1;
|
||||||
|
repeated BannedAppUser banned_app_users = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message AddAppRequest {
|
message AddAppRequest {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
bool allow_user_creation = 2;
|
bool allow_user_creation = 2;
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@ import 'dotenv/config'
|
||||||
import NewServer from '../proto/autogenerated/ts/express_server.js'
|
import NewServer from '../proto/autogenerated/ts/express_server.js'
|
||||||
import GetServerMethods from './services/serverMethods/index.js'
|
import GetServerMethods from './services/serverMethods/index.js'
|
||||||
import serverOptions from './auth.js';
|
import serverOptions from './auth.js';
|
||||||
import Storage from './services/storage/index.js'
|
|
||||||
import { LoadNosrtSettingsFromEnv } from './services/nostr/index.js'
|
import { LoadNosrtSettingsFromEnv } from './services/nostr/index.js'
|
||||||
import nostrMiddleware from './nostrMiddleware.js'
|
import nostrMiddleware from './nostrMiddleware.js'
|
||||||
import { TypeOrmMigrationRunner } from './services/storage/migrations/runner.js';
|
|
||||||
import { getLogger } from './services/helpers/logger.js';
|
import { getLogger } from './services/helpers/logger.js';
|
||||||
import { initMainHandler } from './services/main/init.js';
|
import { initMainHandler } from './services/main/init.js';
|
||||||
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
|
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
||||||
const nostrTransport = NewNostrTransport(serverMethods, {
|
const nostrTransport = NewNostrTransport(serverMethods, {
|
||||||
NostrUserAuthGuard: async (appId, pub) => {
|
NostrUserAuthGuard: async (appId, pub) => {
|
||||||
const app = await mainHandler.storage.applicationStorage.GetApplication(appId || "")
|
const app = await mainHandler.storage.applicationStorage.GetApplication(appId || "")
|
||||||
let nostrUser = await mainHandler.storage.applicationStorage.GetOrCreateNostrAppUser(app, pub || "")
|
const nostrUser = await mainHandler.storage.applicationStorage.GetOrCreateNostrAppUser(app, pub || "")
|
||||||
return { user_id: nostrUser.user.user_id, app_user_id: nostrUser.identifier, app_id: appId || "" }
|
return { user_id: nostrUser.user.user_id, app_user_id: nostrUser.identifier, app_id: appId || "" }
|
||||||
},
|
},
|
||||||
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,17 @@ export const EnvMustBeInteger = (name: string): number => {
|
||||||
}
|
}
|
||||||
return +env
|
return +env
|
||||||
}
|
}
|
||||||
|
export const EnvCanBeInteger = (name: string, defaultValue = 0): number => {
|
||||||
|
const env = process.env[name]
|
||||||
|
if (!env) {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
const envNum = +env
|
||||||
|
if (isNaN(envNum) || !Number.isInteger(envNum)) {
|
||||||
|
throw new Error(`${name} ENV must be an integer number or nothing`);
|
||||||
|
}
|
||||||
|
return envNum
|
||||||
|
}
|
||||||
export const EnvCanBeBoolean = (name: string): boolean => {
|
export const EnvCanBeBoolean = (name: string): boolean => {
|
||||||
const env = process.env[name]
|
const env = process.env[name]
|
||||||
if (!env) return false
|
if (!env) return false
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ export interface LightningHandler {
|
||||||
GetForwardingHistory(indexOffset: number): Promise<{ fee: number, chanIdIn: string, chanIdOut: string, timestampNs: number, offset: number }[]>
|
GetForwardingHistory(indexOffset: number): Promise<{ fee: number, chanIdIn: string, chanIdOut: string, timestampNs: number, offset: number }[]>
|
||||||
GetAllPaidInvoices(max: number): Promise<ListInvoiceResponse>
|
GetAllPaidInvoices(max: number): Promise<ListInvoiceResponse>
|
||||||
GetAllPayments(max: number): Promise<ListPaymentsResponse>
|
GetAllPayments(max: number): Promise<ListPaymentsResponse>
|
||||||
|
LockOutgoingOperations(): void
|
||||||
|
UnlockOutgoingOperations(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (settings: LndSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb): LightningHandler => {
|
export default (settings: LndSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb): LightningHandler => {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ export default class {
|
||||||
newBlockCb: NewBlockCb
|
newBlockCb: NewBlockCb
|
||||||
htlcCb: HtlcCb
|
htlcCb: HtlcCb
|
||||||
log = getLogger({ appName: 'lndManager' })
|
log = getLogger({ appName: 'lndManager' })
|
||||||
|
outgoingOpsLocked = false
|
||||||
constructor(settings: LndSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb) {
|
constructor(settings: LndSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.addressPaidCb = addressPaidCb
|
this.addressPaidCb = addressPaidCb
|
||||||
|
|
@ -60,6 +61,14 @@ export default class {
|
||||||
this.router = new RouterClient(transport)
|
this.router = new RouterClient(transport)
|
||||||
this.chainNotifier = new ChainNotifierClient(transport)
|
this.chainNotifier = new ChainNotifierClient(transport)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LockOutgoingOperations(): void {
|
||||||
|
this.outgoingOpsLocked = true
|
||||||
|
}
|
||||||
|
UnlockOutgoingOperations(): void {
|
||||||
|
this.outgoingOpsLocked = false
|
||||||
|
}
|
||||||
|
|
||||||
SetMockInvoiceAsPaid(invoice: string, amount: number): Promise<void> {
|
SetMockInvoiceAsPaid(invoice: string, amount: number): Promise<void> {
|
||||||
throw new Error("SetMockInvoiceAsPaid only available in mock mode")
|
throw new Error("SetMockInvoiceAsPaid only available in mock mode")
|
||||||
}
|
}
|
||||||
|
|
@ -251,6 +260,10 @@ export default class {
|
||||||
return { local: r.localBalance ? Number(r.localBalance.sat) : 0, remote: r.remoteBalance ? Number(r.remoteBalance.sat) : 0 }
|
return { local: r.localBalance ? Number(r.localBalance.sat) : 0, remote: r.remoteBalance ? Number(r.remoteBalance.sat) : 0 }
|
||||||
}
|
}
|
||||||
async PayInvoice(invoice: string, amount: number, feeLimit: number): Promise<PaidInvoice> {
|
async PayInvoice(invoice: string, amount: number, feeLimit: number): Promise<PaidInvoice> {
|
||||||
|
if (this.outgoingOpsLocked) {
|
||||||
|
this.log("outgoing ops locked, rejecting payment request")
|
||||||
|
throw new Error("lnd node is currently out of sync")
|
||||||
|
}
|
||||||
await this.Health()
|
await this.Health()
|
||||||
this.log("paying invoice", invoice, "for", amount, "sats")
|
this.log("paying invoice", invoice, "for", amount, "sats")
|
||||||
const abortController = new AbortController()
|
const abortController = new AbortController()
|
||||||
|
|
@ -287,6 +300,10 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async PayAddress(address: string, amount: number, satPerVByte: number, label = ""): Promise<SendCoinsResponse> {
|
async PayAddress(address: string, amount: number, satPerVByte: number, label = ""): Promise<SendCoinsResponse> {
|
||||||
|
if (this.outgoingOpsLocked) {
|
||||||
|
this.log("outgoing ops locked, rejecting payment request")
|
||||||
|
throw new Error("lnd node is currently out of sync")
|
||||||
|
}
|
||||||
await this.Health()
|
await this.Health()
|
||||||
this.log("sending chain TX for", amount, "sats", "to", address)
|
this.log("sending chain TX for", amount, "sats", "to", address)
|
||||||
const res = await this.lightning.sendCoins(SendCoinsReq(address, amount, satPerVByte, label), DeadLineMetadata())
|
const res = await this.lightning.sendCoins(SendCoinsReq(address, amount, satPerVByte, label), DeadLineMetadata())
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,12 @@ export default class {
|
||||||
async GetAllPayments(max: number): Promise<ListPaymentsResponse> {
|
async GetAllPayments(max: number): Promise<ListPaymentsResponse> {
|
||||||
throw new Error("not implemented")
|
throw new Error("not implemented")
|
||||||
}
|
}
|
||||||
|
LockOutgoingOperations() {
|
||||||
|
throw new Error("not implemented")
|
||||||
|
}
|
||||||
|
UnlockOutgoingOperations() {
|
||||||
|
throw new Error("not implemented")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
117
src/services/lnd/watchdog.ts
Normal file
117
src/services/lnd/watchdog.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { EnvCanBeInteger } from "../helpers/envParser.js";
|
||||||
|
import { getLogger } from "../helpers/logger.js";
|
||||||
|
import { LightningHandler } from "./index.js";
|
||||||
|
export type WatchdogSettings = {
|
||||||
|
maxDiffSats: number
|
||||||
|
}
|
||||||
|
export const LoadWatchdogSettingsFromEnv = (test = false): WatchdogSettings => {
|
||||||
|
return {
|
||||||
|
maxDiffSats: EnvCanBeInteger("WATCHDOG_MAX_DIFF_SATS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Watchdog {
|
||||||
|
initialLndBalance: number;
|
||||||
|
initialUsersBalance: number;
|
||||||
|
lnd: LightningHandler;
|
||||||
|
settings: WatchdogSettings;
|
||||||
|
log = getLogger({ appName: "watchdog" })
|
||||||
|
enabled = false
|
||||||
|
constructor(settings: WatchdogSettings, lnd: LightningHandler) {
|
||||||
|
this.lnd = lnd;
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
SeedLndBalance = async (totalUsersBalance: number) => {
|
||||||
|
this.initialLndBalance = await this.getTotalLndBalance()
|
||||||
|
this.initialUsersBalance = totalUsersBalance
|
||||||
|
this.enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotalLndBalance = async () => {
|
||||||
|
const { channelsBalance, confirmedBalance } = await this.lnd.GetBalance()
|
||||||
|
return confirmedBalance + channelsBalance.reduce((acc, { localBalanceSats }) => acc + localBalanceSats, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkBalanceUpdate = (deltaLnd: number, deltaUsers: number) => {
|
||||||
|
this.log("LND balance update:", deltaLnd, "sats since app startup")
|
||||||
|
this.log("Users balance update:", deltaUsers, "sats since app startup")
|
||||||
|
|
||||||
|
const result = this.checkDeltas(deltaLnd, deltaUsers)
|
||||||
|
switch (result.type) {
|
||||||
|
case 'mismatch':
|
||||||
|
if (deltaLnd < 0) {
|
||||||
|
this.log("WARNING! LND balance decreased while users balance increased creating a difference of", result.absoluteDiff, "sats")
|
||||||
|
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||||
|
this.log("Difference is too big for an update, locking outgoing operations")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.log("LND balance increased while users balance decreased creating a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'negative':
|
||||||
|
if (Math.abs(deltaLnd) > Math.abs(deltaUsers)) {
|
||||||
|
this.log("WARNING! LND balance decreased more than users balance with a difference of", result.absoluteDiff, "sats")
|
||||||
|
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||||
|
this.log("Difference is too big for an update, locking outgoing operations")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.log("LND balance decreased less than users balance with a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'positive':
|
||||||
|
if (deltaLnd < deltaUsers) {
|
||||||
|
this.log("WARNING! LND balance increased less than users balance with a difference of", result.absoluteDiff, "sats")
|
||||||
|
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||||
|
this.log("Difference is too big for an update, locking outgoing operations")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.log("LND balance increased more than users balance with a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
PaymentRequested = async (totalUsersBalance: number) => {
|
||||||
|
this.log("Payment requested, checking balance")
|
||||||
|
if (!this.enabled) {
|
||||||
|
this.log("WARNING! Watchdog not enabled, skipping balance check")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const totalLndBalance = await this.getTotalLndBalance()
|
||||||
|
const deltaLnd = totalLndBalance - this.initialLndBalance
|
||||||
|
const deltaUsers = totalUsersBalance - this.initialUsersBalance
|
||||||
|
const deny = this.checkBalanceUpdate(deltaLnd, deltaUsers)
|
||||||
|
if (deny) {
|
||||||
|
this.log("Balance mismatch detected in absolute update, locking outgoing operations")
|
||||||
|
this.lnd.LockOutgoingOperations()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDeltas = (deltaLnd: number, deltaUsers: number): DeltaCheckResult => {
|
||||||
|
if (deltaLnd < 0) {
|
||||||
|
if (deltaUsers < 0) {
|
||||||
|
const diff = Math.abs(deltaLnd - deltaUsers)
|
||||||
|
return { type: 'negative', absoluteDiff: diff, relativeDiff: diff / Math.max(deltaLnd, deltaUsers) }
|
||||||
|
} else {
|
||||||
|
const diff = Math.abs(deltaLnd) + deltaUsers
|
||||||
|
return { type: 'mismatch', absoluteDiff: diff }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (deltaUsers < 0) {
|
||||||
|
const diff = deltaLnd + Math.abs(deltaUsers)
|
||||||
|
return { type: 'mismatch', absoluteDiff: diff }
|
||||||
|
} else {
|
||||||
|
const diff = Math.abs(deltaLnd - deltaUsers)
|
||||||
|
return { type: 'positive', absoluteDiff: diff, relativeDiff: diff / Math.max(deltaLnd, deltaUsers) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type DeltaCheckResult = { type: 'negative' | 'positive', absoluteDiff: number, relativeDiff: number } | { type: 'mismatch', absoluteDiff: number }
|
||||||
|
|
@ -31,9 +31,23 @@ export default class {
|
||||||
return decoded
|
return decoded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async BanUser(userId: string): Promise<Types.BanUserResponse> {
|
||||||
|
const banned = await this.storage.userStorage.BanUser(userId)
|
||||||
|
const appUsers = await this.storage.applicationStorage.GetAllAppUsersFromUser(userId)
|
||||||
|
return {
|
||||||
|
balance_sats: banned.balance_sats,
|
||||||
|
banned_app_users: appUsers.map(appUser => ({
|
||||||
|
app_id: appUser.application.app_id,
|
||||||
|
app_name: appUser.application.name,
|
||||||
|
user_identifier: appUser.identifier,
|
||||||
|
nostr_pub: appUser.nostr_public_key || ""
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async GetUserInfo(ctx: Types.UserContext): Promise<Types.UserInfo> {
|
async GetUserInfo(ctx: Types.UserContext): Promise<Types.UserInfo> {
|
||||||
const user = await this.storage.userStorage.GetUser(ctx.user_id)
|
const user = await this.storage.userStorage.GetUser(ctx.user_id)
|
||||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id);
|
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||||
const appUser = await this.storage.applicationStorage.GetAppUserFromUser(app, user.user_id)
|
const appUser = await this.storage.applicationStorage.GetAppUserFromUser(app, user.user_id)
|
||||||
if (!appUser) {
|
if (!appUser) {
|
||||||
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
|
throw new Error(`app user ${ctx.user_id} not found`) // TODO: fix logs doxing
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,10 @@ export default class {
|
||||||
await Promise.all(confirmed.map(async c => {
|
await Promise.all(confirmed.map(async c => {
|
||||||
if (c.type === 'outgoing') {
|
if (c.type === 'outgoing') {
|
||||||
await this.storage.paymentStorage.UpdateUserTransactionPayment(c.tx.serial_id, { confs: c.confs })
|
await this.storage.paymentStorage.UpdateUserTransactionPayment(c.tx.serial_id, { confs: c.confs })
|
||||||
|
const { linkedApplication, user, address, paid_amount: amount, service_fees: serviceFee, serial_id: serialId, chain_fees } = c.tx;
|
||||||
|
const operationId = `${Types.UserOperationType.OUTGOING_TX}-${serialId}`
|
||||||
|
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: false, type: Types.UserOperationType.OUTGOING_TX, identifier: address, operationId, network_fee: chain_fees, service_fee: serviceFee, confirmed: true, tx_hash: c.tx.tx_hash, internal: c.tx.internal }
|
||||||
|
this.sendOperationToNostr(linkedApplication!, user.user_id, op)
|
||||||
} else {
|
} else {
|
||||||
this.storage.StartTransaction(async tx => {
|
this.storage.StartTransaction(async tx => {
|
||||||
const { user_address: userAddress, paid_amount: amount, service_fee: serviceFee, serial_id: serialId, tx_hash } = c.tx
|
const { user_address: userAddress, paid_amount: amount, service_fee: serviceFee, serial_id: serialId, tx_hash } = c.tx
|
||||||
|
|
@ -90,12 +94,12 @@ export default class {
|
||||||
if (!updateResult.affected) {
|
if (!updateResult.affected) {
|
||||||
throw new Error("unable to flag chain transaction as paid")
|
throw new Error("unable to flag chain transaction as paid")
|
||||||
}
|
}
|
||||||
await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, amount - serviceFee, userAddress.address, tx)
|
const addressData = `${userAddress.address}:${tx_hash}`
|
||||||
|
this.storage.eventsLog.LogEvent({ type: 'address_paid', userId: userAddress.user.user_id, appId: userAddress.linkedApplication.app_id, appUserId: "", balance: userAddress.user.balance_sats, data: addressData, amount })
|
||||||
|
await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, amount - serviceFee, addressData, tx)
|
||||||
if (serviceFee > 0) {
|
if (serviceFee > 0) {
|
||||||
await this.storage.userStorage.IncrementUserBalance(userAddress.linkedApplication.owner.user_id, serviceFee, 'fees', tx)
|
await this.storage.userStorage.IncrementUserBalance(userAddress.linkedApplication.owner.user_id, serviceFee, 'fees', tx)
|
||||||
}
|
}
|
||||||
const addressData = `${userAddress.address}:${tx_hash}`
|
|
||||||
this.storage.eventsLog.LogEvent({ type: 'address_paid', userId: userAddress.user.user_id, appId: userAddress.linkedApplication.app_id, appUserId: "", balance: userAddress.user.balance_sats, data: addressData, amount })
|
|
||||||
const operationId = `${Types.UserOperationType.INCOMING_TX}-${serialId}`
|
const operationId = `${Types.UserOperationType.INCOMING_TX}-${serialId}`
|
||||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: serviceFee, confirmed: true, tx_hash: c.tx.tx_hash, internal: c.tx.internal }
|
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: serviceFee, confirmed: true, tx_hash: c.tx.tx_hash, internal: c.tx.internal }
|
||||||
this.sendOperationToNostr(userAddress.linkedApplication!, userAddress.user.user_id, op)
|
this.sendOperationToNostr(userAddress.linkedApplication!, userAddress.user.user_id, op)
|
||||||
|
|
@ -125,12 +129,13 @@ export default class {
|
||||||
// This call will fail if the transaction is already registered
|
// This call will fail if the transaction is already registered
|
||||||
const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, internal, blockHeight, tx)
|
const addedTx = await this.storage.paymentStorage.AddAddressReceivingTransaction(userAddress, txOutput.hash, txOutput.index, amount, fee, internal, blockHeight, tx)
|
||||||
if (internal) {
|
if (internal) {
|
||||||
await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, addedTx.paid_amount - fee, userAddress.address, tx)
|
const addressData = `${address}:${txOutput.hash}`
|
||||||
|
this.storage.eventsLog.LogEvent({ type: 'address_paid', userId: userAddress.user.user_id, appId: userAddress.linkedApplication.app_id, appUserId: "", balance: userAddress.user.balance_sats, data: addressData, amount })
|
||||||
|
await this.storage.userStorage.IncrementUserBalance(userAddress.user.user_id, addedTx.paid_amount - fee, addressData, tx)
|
||||||
if (fee > 0) {
|
if (fee > 0) {
|
||||||
await this.storage.userStorage.IncrementUserBalance(userAddress.linkedApplication.owner.user_id, fee, 'fees', tx)
|
await this.storage.userStorage.IncrementUserBalance(userAddress.linkedApplication.owner.user_id, fee, 'fees', tx)
|
||||||
}
|
}
|
||||||
const addressData = `${address}:${txOutput.hash}`
|
|
||||||
this.storage.eventsLog.LogEvent({ type: 'address_paid', userId: userAddress.user.user_id, appId: userAddress.linkedApplication.app_id, appUserId: "", balance: userAddress.user.balance_sats, data: addressData, amount })
|
|
||||||
}
|
}
|
||||||
const operationId = `${Types.UserOperationType.INCOMING_TX}-${addedTx.serial_id}`
|
const operationId = `${Types.UserOperationType.INCOMING_TX}-${addedTx.serial_id}`
|
||||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: fee, confirmed: internal, tx_hash: txOutput.hash, internal: false }
|
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: fee, confirmed: internal, tx_hash: txOutput.hash, internal: false }
|
||||||
|
|
@ -160,11 +165,11 @@ export default class {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, internal, tx)
|
await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, internal, tx)
|
||||||
|
this.storage.eventsLog.LogEvent({ type: 'invoice_paid', userId: userInvoice.user.user_id, appId: userInvoice.linkedApplication.app_id, appUserId: "", balance: userInvoice.user.balance_sats, data: paymentRequest, amount })
|
||||||
await this.storage.userStorage.IncrementUserBalance(userInvoice.user.user_id, amount - fee, userInvoice.invoice, tx)
|
await this.storage.userStorage.IncrementUserBalance(userInvoice.user.user_id, amount - fee, userInvoice.invoice, tx)
|
||||||
if (fee > 0) {
|
if (fee > 0) {
|
||||||
await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, 'fees', tx)
|
await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, 'fees', tx)
|
||||||
}
|
}
|
||||||
this.storage.eventsLog.LogEvent({ type: 'invoice_paid', userId: userInvoice.user.user_id, appId: userInvoice.linkedApplication.app_id, appUserId: "", balance: userInvoice.user.balance_sats, data: paymentRequest, amount })
|
|
||||||
await this.triggerPaidCallback(log, userInvoice.callbackUrl)
|
await this.triggerPaidCallback(log, userInvoice.callbackUrl)
|
||||||
const operationId = `${Types.UserOperationType.INCOMING_INVOICE}-${userInvoice.serial_id}`
|
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 }
|
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 }
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,13 @@ export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings
|
||||||
if (manualMigration) {
|
if (manualMigration) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!mainSettings.skipSanityCheck) {
|
|
||||||
await storageManager.VerifyEventsLog()
|
|
||||||
}
|
|
||||||
const mainHandler = new Main(mainSettings, storageManager)
|
const mainHandler = new Main(mainSettings, storageManager)
|
||||||
await mainHandler.lnd.Warmup()
|
await mainHandler.lnd.Warmup()
|
||||||
|
if (!mainSettings.skipSanityCheck) {
|
||||||
|
await mainHandler.VerifyEventsLog()
|
||||||
|
}
|
||||||
|
const totalUsersBalance = await mainHandler.storage.paymentStorage.GetTotalUsersBalance()
|
||||||
|
await mainHandler.paymentManager.watchDog.SeedLndBalance(totalUsersBalance || 0)
|
||||||
const appsData = await mainHandler.storage.applicationStorage.GetApplications()
|
const appsData = await mainHandler.storage.applicationStorage.GetApplications()
|
||||||
const existingWalletApp = await appsData.find(app => app.name === 'wallet' || app.name === 'wallet-test')
|
const existingWalletApp = await appsData.find(app => app.name === 'wallet' || app.name === 'wallet-test')
|
||||||
if (!existingWalletApp) {
|
if (!existingWalletApp) {
|
||||||
|
|
@ -49,7 +51,7 @@ const processArgs = async (mainHandler: Main) => {
|
||||||
getLogger({ userId: process.argv[3] })(`user balance updated correctly`)
|
getLogger({ userId: process.argv[3] })(`user balance updated correctly`)
|
||||||
return false
|
return false
|
||||||
case 'unlock':
|
case 'unlock':
|
||||||
await mainHandler.storage.userStorage.UnlockUser(process.argv[3])
|
await mainHandler.storage.userStorage.UnbanUser(process.argv[3])
|
||||||
getLogger({ userId: process.argv[3] })(`user unlocked`)
|
getLogger({ userId: process.argv[3] })(`user unlocked`)
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { SendCoinsResponse } from '../../../proto/lnd/lightning.js'
|
||||||
import { Event, verifiedSymbol, verifySignature } from '../nostr/tools/event.js'
|
import { Event, verifiedSymbol, verifySignature } from '../nostr/tools/event.js'
|
||||||
import { AddressReceivingTransaction } from '../storage/entity/AddressReceivingTransaction.js'
|
import { AddressReceivingTransaction } from '../storage/entity/AddressReceivingTransaction.js'
|
||||||
import { UserTransactionPayment } from '../storage/entity/UserTransactionPayment.js'
|
import { UserTransactionPayment } from '../storage/entity/UserTransactionPayment.js'
|
||||||
|
import { Watchdog } from '../lnd/watchdog.js'
|
||||||
interface UserOperationInfo {
|
interface UserOperationInfo {
|
||||||
serial_id: number
|
serial_id: number
|
||||||
paid_amount: number
|
paid_amount: number
|
||||||
|
|
@ -45,10 +46,12 @@ export default class {
|
||||||
addressPaidCb: AddressPaidCb
|
addressPaidCb: AddressPaidCb
|
||||||
invoicePaidCb: InvoicePaidCb
|
invoicePaidCb: InvoicePaidCb
|
||||||
log = getLogger({ appName: "PaymentManager" })
|
log = getLogger({ appName: "PaymentManager" })
|
||||||
|
watchDog: Watchdog
|
||||||
constructor(storage: Storage, lnd: LightningHandler, settings: MainSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
constructor(storage: Storage, lnd: LightningHandler, settings: MainSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.lnd = lnd
|
this.lnd = lnd
|
||||||
|
this.watchDog = new Watchdog(settings.watchDogSettings, lnd)
|
||||||
this.addressPaidCb = addressPaidCb
|
this.addressPaidCb = addressPaidCb
|
||||||
this.invoicePaidCb = invoicePaidCb
|
this.invoicePaidCb = invoicePaidCb
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +100,9 @@ export default class {
|
||||||
async NewAddress(ctx: Types.UserContext, req: Types.NewAddressRequest): Promise<Types.NewAddressResponse> {
|
async NewAddress(ctx: Types.UserContext, req: Types.NewAddressRequest): Promise<Types.NewAddressResponse> {
|
||||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||||
const user = await this.storage.userStorage.GetUser(ctx.user_id)
|
const user = await this.storage.userStorage.GetUser(ctx.user_id)
|
||||||
|
if (user.locked) {
|
||||||
|
throw new Error("user is banned, cannot generate address")
|
||||||
|
}
|
||||||
const existingAddress = await this.storage.paymentStorage.GetExistingUserAddress(ctx.user_id, app)
|
const existingAddress = await this.storage.paymentStorage.GetExistingUserAddress(ctx.user_id, app)
|
||||||
if (existingAddress) {
|
if (existingAddress) {
|
||||||
return { address: existingAddress.address }
|
return { address: existingAddress.address }
|
||||||
|
|
@ -109,6 +115,9 @@ export default class {
|
||||||
|
|
||||||
async NewInvoice(userId: string, req: Types.NewInvoiceRequest, options: InboundOptionals = { expiry: defaultInvoiceExpiry }): Promise<Types.NewInvoiceResponse> {
|
async NewInvoice(userId: string, req: Types.NewInvoiceRequest, options: InboundOptionals = { expiry: defaultInvoiceExpiry }): Promise<Types.NewInvoiceResponse> {
|
||||||
const user = await this.storage.userStorage.GetUser(userId)
|
const user = await this.storage.userStorage.GetUser(userId)
|
||||||
|
if (user.locked) {
|
||||||
|
throw new Error("user is banned, cannot generate invoice")
|
||||||
|
}
|
||||||
const res = await this.lnd.NewInvoice(req.amountSats, req.memo, options.expiry)
|
const res = await this.lnd.NewInvoice(req.amountSats, req.memo, options.expiry)
|
||||||
const userInvoice = await this.storage.paymentStorage.AddUserInvoice(user, res.payRequest, options)
|
const userInvoice = await this.storage.paymentStorage.AddUserInvoice(user, res.payRequest, options)
|
||||||
const appId = options.linkedApplication ? options.linkedApplication.app_id : ""
|
const appId = options.linkedApplication ? options.linkedApplication.app_id : ""
|
||||||
|
|
@ -134,8 +143,18 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async WatchdogCheck() {
|
||||||
|
const total = await this.storage.paymentStorage.GetTotalUsersBalance()
|
||||||
|
await this.watchDog.PaymentRequested(total || 0)
|
||||||
|
}
|
||||||
|
|
||||||
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application): Promise<Types.PayInvoiceResponse> {
|
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application): Promise<Types.PayInvoiceResponse> {
|
||||||
this.log("paying invoice", req.invoice, "for user", userId, "with amount", req.amount)
|
this.log("paying invoice", req.invoice, "for user", userId, "with amount", req.amount)
|
||||||
|
await this.WatchdogCheck()
|
||||||
|
const maybeBanned = await this.storage.userStorage.GetUser(userId)
|
||||||
|
if (maybeBanned.locked) {
|
||||||
|
throw new Error("user is banned, cannot send payment")
|
||||||
|
}
|
||||||
const decoded = await this.lnd.DecodeInvoice(req.invoice)
|
const decoded = await this.lnd.DecodeInvoice(req.invoice)
|
||||||
if (decoded.numSatoshis !== 0 && req.amount !== 0) {
|
if (decoded.numSatoshis !== 0 && req.amount !== 0) {
|
||||||
throw new Error("invoice has value, do not provide amount the the request")
|
throw new Error("invoice has value, do not provide amount the the request")
|
||||||
|
|
@ -192,6 +211,12 @@ export default class {
|
||||||
|
|
||||||
async PayAddress(ctx: Types.UserContext, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
|
async PayAddress(ctx: Types.UserContext, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
|
||||||
throw new Error("address payment currently disabled, use Lightning instead")
|
throw new Error("address payment currently disabled, use Lightning instead")
|
||||||
|
await this.WatchdogCheck()
|
||||||
|
this.log("paying address", req.address, "for user", ctx.user_id, "with amount", req.amoutSats)
|
||||||
|
const maybeBanned = await this.storage.userStorage.GetUser(ctx.user_id)
|
||||||
|
if (maybeBanned.locked) {
|
||||||
|
throw new Error("user is banned, cannot send chain tx")
|
||||||
|
}
|
||||||
const { blockHeight } = await this.lnd.GetInfo()
|
const { blockHeight } = await this.lnd.GetInfo()
|
||||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||||
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_TX, req.amoutSats, false)
|
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_TX, req.amoutSats, false)
|
||||||
|
|
@ -459,6 +484,10 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetUserOperations(userId: string, req: Types.GetUserOperationsRequest): Promise<Types.GetUserOperationsResponse> {
|
async GetUserOperations(userId: string, req: Types.GetUserOperationsRequest): Promise<Types.GetUserOperationsResponse> {
|
||||||
|
const user = await this.storage.userStorage.GetUser(userId)
|
||||||
|
if (user.locked) {
|
||||||
|
throw new Error("user is banned, cannot retrieve operations")
|
||||||
|
}
|
||||||
const [outgoingInvoices, outgoingTransactions, incomingInvoices, incomingTransactions, incomingUserToUser, outgoingUserToUser] = await Promise.all([
|
const [outgoingInvoices, outgoingTransactions, incomingInvoices, incomingTransactions, incomingUserToUser, outgoingUserToUser] = await Promise.all([
|
||||||
this.storage.paymentStorage.GetUserInvoicePayments(userId, req.latestOutgoingInvoice, req.max_size),
|
this.storage.paymentStorage.GetUserInvoicePayments(userId, req.latestOutgoingInvoice, req.max_size),
|
||||||
this.storage.paymentStorage.GetUserTransactionPayments(userId, req.latestOutgoingTx, req.max_size),
|
this.storage.paymentStorage.GetUserTransactionPayments(userId, req.latestOutgoingTx, req.max_size),
|
||||||
|
|
@ -482,6 +511,9 @@ export default class {
|
||||||
await this.storage.StartTransaction(async tx => {
|
await this.storage.StartTransaction(async tx => {
|
||||||
const fromUser = await this.storage.userStorage.GetUser(fromUserId, tx)
|
const fromUser = await this.storage.userStorage.GetUser(fromUserId, tx)
|
||||||
const toUser = await this.storage.userStorage.GetUser(toUserId, tx)
|
const toUser = await this.storage.userStorage.GetUser(toUserId, tx)
|
||||||
|
if (fromUser.locked || toUser.locked) {
|
||||||
|
throw new Error("one of the users is banned, cannot send payment")
|
||||||
|
}
|
||||||
if (fromUser.balance_sats < amount) {
|
if (fromUser.balance_sats < amount) {
|
||||||
throw new Error("not enough balance to send payment")
|
throw new Error("not enough balance to send payment")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import { LoadStorageSettingsFromEnv, StorageSettings } from '../storage/index.js'
|
import { LoadStorageSettingsFromEnv, StorageSettings } from '../storage/index.js'
|
||||||
import { LndSettings } from '../lnd/settings.js'
|
import { LndSettings } from '../lnd/settings.js'
|
||||||
|
import { LoadWatchdogSettingsFromEnv, WatchdogSettings } from '../lnd/watchdog.js'
|
||||||
import { LoadLndSettingsFromEnv } from '../lnd/index.js'
|
import { LoadLndSettingsFromEnv } from '../lnd/index.js'
|
||||||
import { EnvMustBeInteger, EnvMustBeNonEmptyString } from '../helpers/envParser.js'
|
import { EnvMustBeInteger, EnvMustBeNonEmptyString } from '../helpers/envParser.js'
|
||||||
export type MainSettings = {
|
export type MainSettings = {
|
||||||
storageSettings: StorageSettings,
|
storageSettings: StorageSettings,
|
||||||
lndSettings: LndSettings,
|
lndSettings: LndSettings,
|
||||||
|
watchDogSettings: WatchdogSettings,
|
||||||
jwtSecret: string
|
jwtSecret: string
|
||||||
incomingTxFee: number
|
incomingTxFee: number
|
||||||
outgoingTxFee: number
|
outgoingTxFee: number
|
||||||
|
|
@ -22,6 +24,7 @@ export type MainSettings = {
|
||||||
}
|
}
|
||||||
export const LoadMainSettingsFromEnv = (): MainSettings => {
|
export const LoadMainSettingsFromEnv = (): MainSettings => {
|
||||||
return {
|
return {
|
||||||
|
watchDogSettings: LoadWatchdogSettingsFromEnv(),
|
||||||
lndSettings: LoadLndSettingsFromEnv(),
|
lndSettings: LoadLndSettingsFromEnv(),
|
||||||
storageSettings: LoadStorageSettingsFromEnv(),
|
storageSettings: LoadStorageSettingsFromEnv(),
|
||||||
jwtSecret: EnvMustBeNonEmptyString("JWT_SECRET"),
|
jwtSecret: EnvMustBeNonEmptyString("JWT_SECRET"),
|
||||||
|
|
@ -37,7 +40,8 @@ export const LoadMainSettingsFromEnv = (): MainSettings => {
|
||||||
servicePort: EnvMustBeInteger("PORT"),
|
servicePort: EnvMustBeInteger("PORT"),
|
||||||
recordPerformance: process.env.RECORD_PERFORMANCE === 'true' || false,
|
recordPerformance: process.env.RECORD_PERFORMANCE === 'true' || false,
|
||||||
skipSanityCheck: process.env.SKIP_SANITY_CHECK === 'true' || false,
|
skipSanityCheck: process.env.SKIP_SANITY_CHECK === 'true' || false,
|
||||||
disableExternalPayments: process.env.DISABLE_EXTERNAL_PAYMENTS === 'true' || false
|
disableExternalPayments: process.env.DISABLE_EXTERNAL_PAYMENTS === 'true' || false,
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,13 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
||||||
const info = await mainHandler.lnd.GetInfo()
|
const info = await mainHandler.lnd.GetInfo()
|
||||||
return { alias: info.alias }
|
return { alias: info.alias }
|
||||||
},
|
},
|
||||||
|
BanUser: async ({ ctx, req }) => {
|
||||||
|
const err = Types.BanUserRequestValidate(req, {
|
||||||
|
user_id_CustomCheck: id => id !== ''
|
||||||
|
})
|
||||||
|
if (err != null) throw new Error(err.message)
|
||||||
|
return mainHandler.appUserManager.BanUser(req.user_id)
|
||||||
|
},
|
||||||
SetMockInvoiceAsPaid: async ({ ctx, req }) => {
|
SetMockInvoiceAsPaid: async ({ ctx, req }) => {
|
||||||
const err = Types.SetMockInvoiceAsPaidRequestValidate(req, {
|
const err = Types.SetMockInvoiceAsPaidRequestValidate(req, {
|
||||||
invoice_CustomCheck: invoice => invoice !== '',
|
invoice_CustomCheck: invoice => invoice !== '',
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,11 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetAppUserFromUser(application: Application, userId: string, entityManager = this.DB): Promise<ApplicationUser | null> {
|
async GetAppUserFromUser(application: Application, userId: string, entityManager = this.DB): Promise<ApplicationUser | null> {
|
||||||
return await entityManager.getRepository(ApplicationUser).findOne({ where: { user: { user_id: userId }, application: { app_id: application.app_id } } })
|
return entityManager.getRepository(ApplicationUser).findOne({ where: { user: { user_id: userId }, application: { app_id: application.app_id } } })
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetAllAppUsersFromUser(userId: string, entityManager = this.DB): Promise<ApplicationUser[]> {
|
||||||
|
return entityManager.getRepository(ApplicationUser).find({ where: { user: { user_id: userId } } })
|
||||||
}
|
}
|
||||||
|
|
||||||
async IsApplicationOwner(userId: string, entityManager = this.DB) {
|
async IsApplicationOwner(userId: string, entityManager = this.DB) {
|
||||||
|
|
|
||||||
|
|
@ -360,4 +360,8 @@ export default class {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async GetTotalUsersBalance(entityManager = this.DB) {
|
||||||
|
return entityManager.getRepository(User).sum("balance_sats")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ export default class {
|
||||||
this.txQueue = txQueue
|
this.txQueue = txQueue
|
||||||
this.eventsLog = eventsLog
|
this.eventsLog = eventsLog
|
||||||
}
|
}
|
||||||
|
|
||||||
async AddUser(balance: number, dbTx: DataSource | EntityManager): Promise<User> {
|
async AddUser(balance: number, dbTx: DataSource | EntityManager): Promise<User> {
|
||||||
if (balance && process.env.ALLOW_BALANCE_MIGRATION !== 'true') {
|
if (balance && process.env.ALLOW_BALANCE_MIGRATION !== 'true') {
|
||||||
throw new Error("balance migration is not allowed")
|
throw new Error("balance migration is not allowed")
|
||||||
|
|
@ -53,15 +54,7 @@ export default class {
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
async LockUser(userId: string, entityManager = this.DB) {
|
async UnbanUser(userId: string, entityManager = this.DB) {
|
||||||
const res = await entityManager.getRepository(User).update({
|
|
||||||
user_id: userId
|
|
||||||
}, { locked: true })
|
|
||||||
if (!res.affected) {
|
|
||||||
throw new Error("unaffected user lock for " + userId) // TODO: fix logs doxing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async UnlockUser(userId: string, entityManager = this.DB) {
|
|
||||||
const res = await entityManager.getRepository(User).update({
|
const res = await entityManager.getRepository(User).update({
|
||||||
user_id: userId
|
user_id: userId
|
||||||
}, { locked: false })
|
}, { locked: false })
|
||||||
|
|
@ -69,6 +62,20 @@ export default class {
|
||||||
throw new Error("unaffected user unlock for " + userId) // TODO: fix logs doxing
|
throw new Error("unaffected user unlock for " + userId) // TODO: fix logs doxing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async BanUser(userId: string, entityManager = this.DB) {
|
||||||
|
const user = await this.GetUser(userId, entityManager)
|
||||||
|
const res = await entityManager.getRepository(User).update({
|
||||||
|
user_id: userId
|
||||||
|
}, { balance_sats: 0, locked: true })
|
||||||
|
if (!res.affected) {
|
||||||
|
throw new Error("unaffected ban user for " + userId) // TODO: fix logs doxing
|
||||||
|
}
|
||||||
|
if (user.balance_sats > 0) {
|
||||||
|
this.eventsLog.LogEvent({ type: 'balance_decrement', userId, appId: "", appUserId: "", balance: user.balance_sats, data: 'ban', amount: user.balance_sats })
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
async IncrementUserBalance(userId: string, increment: number, reason: string, entityManager = this.DB) {
|
async IncrementUserBalance(userId: string, increment: number, reason: string, entityManager = this.DB) {
|
||||||
const user = await this.GetUser(userId, entityManager)
|
const user = await this.GetUser(userId, entityManager)
|
||||||
const res = await entityManager.getRepository(User).increment({
|
const res = await entityManager.getRepository(User).increment({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue