provider: wire beacon + return balance + use cached balance
This commit is contained in:
parent
c8ede119d6
commit
8e4a8b2a2a
17 changed files with 307 additions and 92 deletions
|
|
@ -1104,6 +1104,13 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- __nostr_pub__: _string_
|
- __nostr_pub__: _string_
|
||||||
- __user_identifier__: _string_
|
- __user_identifier__: _string_
|
||||||
|
|
||||||
|
### BeaconData
|
||||||
|
- __avatarUrl__: _string_ *this field is optional
|
||||||
|
- __fees__: _[CumulativeFees](#CumulativeFees)_ *this field is optional
|
||||||
|
- __name__: _string_
|
||||||
|
- __nextRelay__: _string_ *this field is optional
|
||||||
|
- __type__: _string_
|
||||||
|
|
||||||
### BundleData
|
### BundleData
|
||||||
- __available_chunks__: ARRAY of: _number_
|
- __available_chunks__: ARRAY of: _number_
|
||||||
- __base_64_data__: ARRAY of: _string_
|
- __base_64_data__: ARRAY of: _string_
|
||||||
|
|
@ -1149,6 +1156,11 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
### CreateOneTimeInviteLinkResponse
|
### CreateOneTimeInviteLinkResponse
|
||||||
- __invitation_link__: _string_
|
- __invitation_link__: _string_
|
||||||
|
|
||||||
|
### CumulativeFees
|
||||||
|
- __networkFeeBps__: _number_
|
||||||
|
- __networkFeeFixed__: _number_
|
||||||
|
- __serviceFeeBps__: _number_
|
||||||
|
|
||||||
### DebitAuthorization
|
### DebitAuthorization
|
||||||
- __authorized__: _boolean_
|
- __authorized__: _boolean_
|
||||||
- __debit_id__: _string_
|
- __debit_id__: _string_
|
||||||
|
|
@ -1290,6 +1302,7 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- __request_id__: _string_
|
- __request_id__: _string_
|
||||||
|
|
||||||
### LiveUserOperation
|
### LiveUserOperation
|
||||||
|
- __latest_balance__: _number_
|
||||||
- __operation__: _[UserOperation](#UserOperation)_
|
- __operation__: _[UserOperation](#UserOperation)_
|
||||||
|
|
||||||
### LndChannels
|
### LndChannels
|
||||||
|
|
@ -1487,6 +1500,7 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
|
|
||||||
### PayInvoiceResponse
|
### PayInvoiceResponse
|
||||||
- __amount_paid__: _number_
|
- __amount_paid__: _number_
|
||||||
|
- __latest_balance__: _number_
|
||||||
- __network_fee__: _number_
|
- __network_fee__: _number_
|
||||||
- __operation_id__: _string_
|
- __operation_id__: _string_
|
||||||
- __preimage__: _string_
|
- __preimage__: _string_
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,13 @@ type BannedAppUser struct {
|
||||||
Nostr_pub string `json:"nostr_pub"`
|
Nostr_pub string `json:"nostr_pub"`
|
||||||
User_identifier string `json:"user_identifier"`
|
User_identifier string `json:"user_identifier"`
|
||||||
}
|
}
|
||||||
|
type BeaconData struct {
|
||||||
|
Avatarurl string `json:"avatarUrl"`
|
||||||
|
Fees *CumulativeFees `json:"fees"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Nextrelay string `json:"nextRelay"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
type BundleData struct {
|
type BundleData struct {
|
||||||
Available_chunks []int64 `json:"available_chunks"`
|
Available_chunks []int64 `json:"available_chunks"`
|
||||||
Base_64_data []string `json:"base_64_data"`
|
Base_64_data []string `json:"base_64_data"`
|
||||||
|
|
@ -222,6 +229,11 @@ type CreateOneTimeInviteLinkRequest struct {
|
||||||
type CreateOneTimeInviteLinkResponse struct {
|
type CreateOneTimeInviteLinkResponse struct {
|
||||||
Invitation_link string `json:"invitation_link"`
|
Invitation_link string `json:"invitation_link"`
|
||||||
}
|
}
|
||||||
|
type CumulativeFees struct {
|
||||||
|
Networkfeebps int64 `json:"networkFeeBps"`
|
||||||
|
Networkfeefixed int64 `json:"networkFeeFixed"`
|
||||||
|
Servicefeebps int64 `json:"serviceFeeBps"`
|
||||||
|
}
|
||||||
type DebitAuthorization struct {
|
type DebitAuthorization struct {
|
||||||
Authorized bool `json:"authorized"`
|
Authorized bool `json:"authorized"`
|
||||||
Debit_id string `json:"debit_id"`
|
Debit_id string `json:"debit_id"`
|
||||||
|
|
@ -363,6 +375,7 @@ type LiveManageRequest struct {
|
||||||
Request_id string `json:"request_id"`
|
Request_id string `json:"request_id"`
|
||||||
}
|
}
|
||||||
type LiveUserOperation struct {
|
type LiveUserOperation struct {
|
||||||
|
Latest_balance int64 `json:"latest_balance"`
|
||||||
Operation *UserOperation `json:"operation"`
|
Operation *UserOperation `json:"operation"`
|
||||||
}
|
}
|
||||||
type LndChannels struct {
|
type LndChannels struct {
|
||||||
|
|
@ -560,6 +573,7 @@ type PayInvoiceRequest struct {
|
||||||
}
|
}
|
||||||
type PayInvoiceResponse struct {
|
type PayInvoiceResponse struct {
|
||||||
Amount_paid int64 `json:"amount_paid"`
|
Amount_paid int64 `json:"amount_paid"`
|
||||||
|
Latest_balance int64 `json:"latest_balance"`
|
||||||
Network_fee int64 `json:"network_fee"`
|
Network_fee int64 `json:"network_fee"`
|
||||||
Operation_id string `json:"operation_id"`
|
Operation_id string `json:"operation_id"`
|
||||||
Preimage string `json:"preimage"`
|
Preimage string `json:"preimage"`
|
||||||
|
|
|
||||||
|
|
@ -983,6 +983,48 @@ export const BannedAppUserValidate = (o?: BannedAppUser, opts: BannedAppUserOpti
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type BeaconData = {
|
||||||
|
avatarUrl?: string
|
||||||
|
fees?: CumulativeFees
|
||||||
|
name: string
|
||||||
|
nextRelay?: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
export type BeaconDataOptionalField = 'avatarUrl' | 'fees' | 'nextRelay'
|
||||||
|
export const BeaconDataOptionalFields: BeaconDataOptionalField[] = ['avatarUrl', 'fees', 'nextRelay']
|
||||||
|
export type BeaconDataOptions = OptionsBaseMessage & {
|
||||||
|
checkOptionalsAreSet?: BeaconDataOptionalField[]
|
||||||
|
avatarUrl_CustomCheck?: (v?: string) => boolean
|
||||||
|
fees_Options?: CumulativeFeesOptions
|
||||||
|
name_CustomCheck?: (v: string) => boolean
|
||||||
|
nextRelay_CustomCheck?: (v?: string) => boolean
|
||||||
|
type_CustomCheck?: (v: string) => boolean
|
||||||
|
}
|
||||||
|
export const BeaconDataValidate = (o?: BeaconData, opts: BeaconDataOptions = {}, path: string = 'BeaconData::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.avatarUrl || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('avatarUrl')) && typeof o.avatarUrl !== 'string') return new Error(`${path}.avatarUrl: is not a string`)
|
||||||
|
if (opts.avatarUrl_CustomCheck && !opts.avatarUrl_CustomCheck(o.avatarUrl)) return new Error(`${path}.avatarUrl: custom check failed`)
|
||||||
|
|
||||||
|
if (typeof o.fees === 'object' || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('fees')) {
|
||||||
|
const feesErr = CumulativeFeesValidate(o.fees, opts.fees_Options, `${path}.fees`)
|
||||||
|
if (feesErr !== null) return feesErr
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (typeof o.name !== 'string') return new Error(`${path}.name: is not a string`)
|
||||||
|
if (opts.name_CustomCheck && !opts.name_CustomCheck(o.name)) return new Error(`${path}.name: custom check failed`)
|
||||||
|
|
||||||
|
if ((o.nextRelay || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('nextRelay')) && typeof o.nextRelay !== 'string') return new Error(`${path}.nextRelay: is not a string`)
|
||||||
|
if (opts.nextRelay_CustomCheck && !opts.nextRelay_CustomCheck(o.nextRelay)) return new Error(`${path}.nextRelay: custom check failed`)
|
||||||
|
|
||||||
|
if (typeof o.type !== 'string') return new Error(`${path}.type: is not a string`)
|
||||||
|
if (opts.type_CustomCheck && !opts.type_CustomCheck(o.type)) return new Error(`${path}.type: custom check failed`)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export type BundleData = {
|
export type BundleData = {
|
||||||
available_chunks: number[]
|
available_chunks: number[]
|
||||||
base_64_data: string[]
|
base_64_data: string[]
|
||||||
|
|
@ -1256,6 +1298,34 @@ export const CreateOneTimeInviteLinkResponseValidate = (o?: CreateOneTimeInviteL
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CumulativeFees = {
|
||||||
|
networkFeeBps: number
|
||||||
|
networkFeeFixed: number
|
||||||
|
serviceFeeBps: number
|
||||||
|
}
|
||||||
|
export const CumulativeFeesOptionalFields: [] = []
|
||||||
|
export type CumulativeFeesOptions = OptionsBaseMessage & {
|
||||||
|
checkOptionalsAreSet?: []
|
||||||
|
networkFeeBps_CustomCheck?: (v: number) => boolean
|
||||||
|
networkFeeFixed_CustomCheck?: (v: number) => boolean
|
||||||
|
serviceFeeBps_CustomCheck?: (v: number) => boolean
|
||||||
|
}
|
||||||
|
export const CumulativeFeesValidate = (o?: CumulativeFees, opts: CumulativeFeesOptions = {}, path: string = 'CumulativeFees::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.networkFeeBps !== 'number') return new Error(`${path}.networkFeeBps: is not a number`)
|
||||||
|
if (opts.networkFeeBps_CustomCheck && !opts.networkFeeBps_CustomCheck(o.networkFeeBps)) return new Error(`${path}.networkFeeBps: custom check failed`)
|
||||||
|
|
||||||
|
if (typeof o.networkFeeFixed !== 'number') return new Error(`${path}.networkFeeFixed: is not a number`)
|
||||||
|
if (opts.networkFeeFixed_CustomCheck && !opts.networkFeeFixed_CustomCheck(o.networkFeeFixed)) return new Error(`${path}.networkFeeFixed: custom check failed`)
|
||||||
|
|
||||||
|
if (typeof o.serviceFeeBps !== 'number') return new Error(`${path}.serviceFeeBps: is not a number`)
|
||||||
|
if (opts.serviceFeeBps_CustomCheck && !opts.serviceFeeBps_CustomCheck(o.serviceFeeBps)) return new Error(`${path}.serviceFeeBps: custom check failed`)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export type DebitAuthorization = {
|
export type DebitAuthorization = {
|
||||||
authorized: boolean
|
authorized: boolean
|
||||||
debit_id: string
|
debit_id: string
|
||||||
|
|
@ -2112,17 +2182,22 @@ export const LiveManageRequestValidate = (o?: LiveManageRequest, opts: LiveManag
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LiveUserOperation = {
|
export type LiveUserOperation = {
|
||||||
|
latest_balance: number
|
||||||
operation: UserOperation
|
operation: UserOperation
|
||||||
}
|
}
|
||||||
export const LiveUserOperationOptionalFields: [] = []
|
export const LiveUserOperationOptionalFields: [] = []
|
||||||
export type LiveUserOperationOptions = OptionsBaseMessage & {
|
export type LiveUserOperationOptions = OptionsBaseMessage & {
|
||||||
checkOptionalsAreSet?: []
|
checkOptionalsAreSet?: []
|
||||||
|
latest_balance_CustomCheck?: (v: number) => boolean
|
||||||
operation_Options?: UserOperationOptions
|
operation_Options?: UserOperationOptions
|
||||||
}
|
}
|
||||||
export const LiveUserOperationValidate = (o?: LiveUserOperation, opts: LiveUserOperationOptions = {}, path: string = 'LiveUserOperation::root.'): Error | null => {
|
export const LiveUserOperationValidate = (o?: LiveUserOperation, opts: LiveUserOperationOptions = {}, path: string = 'LiveUserOperation::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 (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 !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
|
||||||
|
|
||||||
|
if (typeof o.latest_balance !== 'number') return new Error(`${path}.latest_balance: is not a number`)
|
||||||
|
if (opts.latest_balance_CustomCheck && !opts.latest_balance_CustomCheck(o.latest_balance)) return new Error(`${path}.latest_balance: custom check failed`)
|
||||||
|
|
||||||
const operationErr = UserOperationValidate(o.operation, opts.operation_Options, `${path}.operation`)
|
const operationErr = UserOperationValidate(o.operation, opts.operation_Options, `${path}.operation`)
|
||||||
if (operationErr !== null) return operationErr
|
if (operationErr !== null) return operationErr
|
||||||
|
|
||||||
|
|
@ -3287,6 +3362,7 @@ export const PayInvoiceRequestValidate = (o?: PayInvoiceRequest, opts: PayInvoic
|
||||||
|
|
||||||
export type PayInvoiceResponse = {
|
export type PayInvoiceResponse = {
|
||||||
amount_paid: number
|
amount_paid: number
|
||||||
|
latest_balance: number
|
||||||
network_fee: number
|
network_fee: number
|
||||||
operation_id: string
|
operation_id: string
|
||||||
preimage: string
|
preimage: string
|
||||||
|
|
@ -3296,6 +3372,7 @@ export const PayInvoiceResponseOptionalFields: [] = []
|
||||||
export type PayInvoiceResponseOptions = OptionsBaseMessage & {
|
export type PayInvoiceResponseOptions = OptionsBaseMessage & {
|
||||||
checkOptionalsAreSet?: []
|
checkOptionalsAreSet?: []
|
||||||
amount_paid_CustomCheck?: (v: number) => boolean
|
amount_paid_CustomCheck?: (v: number) => boolean
|
||||||
|
latest_balance_CustomCheck?: (v: number) => boolean
|
||||||
network_fee_CustomCheck?: (v: number) => boolean
|
network_fee_CustomCheck?: (v: number) => boolean
|
||||||
operation_id_CustomCheck?: (v: string) => boolean
|
operation_id_CustomCheck?: (v: string) => boolean
|
||||||
preimage_CustomCheck?: (v: string) => boolean
|
preimage_CustomCheck?: (v: string) => boolean
|
||||||
|
|
@ -3308,6 +3385,9 @@ export const PayInvoiceResponseValidate = (o?: PayInvoiceResponse, opts: PayInvo
|
||||||
if (typeof o.amount_paid !== 'number') return new Error(`${path}.amount_paid: is not a number`)
|
if (typeof o.amount_paid !== 'number') return new Error(`${path}.amount_paid: is not a number`)
|
||||||
if (opts.amount_paid_CustomCheck && !opts.amount_paid_CustomCheck(o.amount_paid)) return new Error(`${path}.amount_paid: custom check failed`)
|
if (opts.amount_paid_CustomCheck && !opts.amount_paid_CustomCheck(o.amount_paid)) return new Error(`${path}.amount_paid: custom check failed`)
|
||||||
|
|
||||||
|
if (typeof o.latest_balance !== 'number') return new Error(`${path}.latest_balance: is not a number`)
|
||||||
|
if (opts.latest_balance_CustomCheck && !opts.latest_balance_CustomCheck(o.latest_balance)) return new Error(`${path}.latest_balance: custom check failed`)
|
||||||
|
|
||||||
if (typeof o.network_fee !== 'number') return new Error(`${path}.network_fee: is not a number`)
|
if (typeof o.network_fee !== 'number') return new Error(`${path}.network_fee: is not a number`)
|
||||||
if (opts.network_fee_CustomCheck && !opts.network_fee_CustomCheck(o.network_fee)) return new Error(`${path}.network_fee: custom check failed`)
|
if (opts.network_fee_CustomCheck && !opts.network_fee_CustomCheck(o.network_fee)) return new Error(`${path}.network_fee: custom check failed`)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -476,6 +476,7 @@ message PayInvoiceResponse{
|
||||||
string operation_id = 3;
|
string operation_id = 3;
|
||||||
int64 service_fee = 4;
|
int64 service_fee = 4;
|
||||||
int64 network_fee = 5;
|
int64 network_fee = 5;
|
||||||
|
int64 latest_balance = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message InvoicePaymentStream {
|
message InvoicePaymentStream {
|
||||||
|
|
@ -613,6 +614,7 @@ message GetProductBuyLinkResponse {
|
||||||
|
|
||||||
message LiveUserOperation {
|
message LiveUserOperation {
|
||||||
UserOperation operation = 1;
|
UserOperation operation = 1;
|
||||||
|
int64 latest_balance = 2;
|
||||||
}
|
}
|
||||||
message MigrationUpdate {
|
message MigrationUpdate {
|
||||||
optional ClosureMigration closure = 1;
|
optional ClosureMigration closure = 1;
|
||||||
|
|
@ -833,3 +835,18 @@ message MessagingToken {
|
||||||
string device_id = 1;
|
string device_id = 1;
|
||||||
string firebase_messaging_token = 2;
|
string firebase_messaging_token = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message CumulativeFees {
|
||||||
|
int64 networkFeeBps = 1;
|
||||||
|
int64 networkFeeFixed = 2;
|
||||||
|
int64 serviceFeeBps = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BeaconData {
|
||||||
|
string type = 1;
|
||||||
|
string name = 2;
|
||||||
|
optional string avatarUrl = 3;
|
||||||
|
optional string nextRelay = 4;
|
||||||
|
optional CumulativeFees fees = 5;
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,10 @@ const start = async () => {
|
||||||
const nostrSettings = settingsManager.getSettings().nostrRelaySettings
|
const nostrSettings = settingsManager.getSettings().nostrRelaySettings
|
||||||
log("initializing nostr middleware")
|
log("initializing nostr middleware")
|
||||||
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
||||||
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
{
|
||||||
|
...nostrSettings, apps, clients: [liquidityProviderInfo],
|
||||||
|
providerDestinationPub: settingsManager.getSettings().liquiditySettings.liquidityProviderPub
|
||||||
|
},
|
||||||
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
||||||
)
|
)
|
||||||
log("starting server")
|
log("starting server")
|
||||||
|
|
|
||||||
11
src/index.ts
11
src/index.ts
|
|
@ -25,10 +25,13 @@ const start = async () => {
|
||||||
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
const { apps, mainHandler, liquidityProviderInfo, wizard, adminManager } = keepOn
|
||||||
const serverMethods = GetServerMethods(mainHandler)
|
const serverMethods = GetServerMethods(mainHandler)
|
||||||
log("initializing nostr middleware")
|
log("initializing nostr middleware")
|
||||||
const relays = mainHandler.settings.getSettings().nostrRelaySettings.relays
|
const relays = settingsManager.getSettings().nostrRelaySettings.relays
|
||||||
const maxEventContentLength = mainHandler.settings.getSettings().nostrRelaySettings.maxEventContentLength
|
const maxEventContentLength = settingsManager.getSettings().nostrRelaySettings.maxEventContentLength
|
||||||
const { Send, Stop, Ping, Reset } = nostrMiddleware(serverMethods, mainHandler,
|
const { Send, Stop, Ping, Reset } = nostrMiddleware(serverMethods, mainHandler,
|
||||||
{ relays, maxEventContentLength, apps, clients: [liquidityProviderInfo] },
|
{
|
||||||
|
relays, maxEventContentLength, apps, clients: [liquidityProviderInfo],
|
||||||
|
providerDestinationPub: settingsManager.getSettings().liquiditySettings.liquidityProviderPub
|
||||||
|
},
|
||||||
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
||||||
)
|
)
|
||||||
exitHandler(() => { Stop(); mainHandler.Stop() })
|
exitHandler(() => { Stop(); mainHandler.Stop() })
|
||||||
|
|
@ -43,7 +46,7 @@ const start = async () => {
|
||||||
}
|
}
|
||||||
adminManager.setAppNprofile(appNprofile)
|
adminManager.setAppNprofile(appNprofile)
|
||||||
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
const Server = NewServer(serverMethods, serverOptions(mainHandler))
|
||||||
Server.Listen(mainHandler.settings.getSettings().serviceSettings.servicePort)
|
Server.Listen(settingsManager.getSettings().serviceSettings.servicePort)
|
||||||
}
|
}
|
||||||
start()
|
start()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
||||||
nostrTransport({ ...j, appId: event.appId }, res => {
|
nostrTransport({ ...j, appId: event.appId }, res => {
|
||||||
nostr.Send({ type: 'app', appId: event.appId }, { type: 'content', pub: event.pub, content: JSON.stringify({ ...res, requestId: j.requestId }) })
|
nostr.Send({ type: 'app', appId: event.appId }, { type: 'content', pub: event.pub, content: JSON.stringify({ ...res, requestId: j.requestId }) })
|
||||||
}, event.startAtNano, event.startAtMs)
|
}, event.startAtNano, event.startAtMs)
|
||||||
})
|
}, beacon => mainHandler.liquidityProvider.onBeaconEvent(beacon))
|
||||||
|
|
||||||
// Mark nostr connected/ready after initial subscription tick
|
// Mark nostr connected/ready after initial subscription tick
|
||||||
mainHandler.adminManager.setNostrConnected(true)
|
mainHandler.adminManager.setNostrConnected(true)
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export default class {
|
||||||
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
|
||||||
}
|
}
|
||||||
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||||
const { max, networkFeeBps, networkFeeFixed, serviceFeeBps } = this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats, true)
|
const { max, networkFeeBps, networkFeeFixed, serviceFeeBps } = this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats)
|
||||||
return {
|
return {
|
||||||
userId: ctx.user_id,
|
userId: ctx.user_id,
|
||||||
balance: user.balance_sats,
|
balance: user.balance_sats,
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,13 @@ export default class {
|
||||||
}, 60 * 1000); // 1 minute
|
}, 60 * 1000); // 1 minute
|
||||||
}
|
}
|
||||||
|
|
||||||
async StartAppsServiceBeacon(publishBeacon: (app: Application) => void) {
|
async StartAppsServiceBeacon(publishBeacon: (app: Application, fees: Types.CumulativeFees) => void) {
|
||||||
this.serviceBeaconInterval = setInterval(async () => {
|
this.serviceBeaconInterval = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
|
const fees = this.paymentManager.GetAllFees()
|
||||||
const apps = await this.storage.applicationStorage.GetApplications()
|
const apps = await this.storage.applicationStorage.GetApplications()
|
||||||
apps.forEach(app => {
|
apps.forEach(app => {
|
||||||
publishBeacon(app)
|
publishBeacon(app, fees)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.log("error in beacon", (e as any).message)
|
this.log("error in beacon", (e as any).message)
|
||||||
|
|
@ -154,7 +155,7 @@ export default class {
|
||||||
|
|
||||||
const ndebitString = ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] })
|
const ndebitString = ndebitEncode({ pubkey: app.nostr_public_key!, pointer: u.identifier, relay: nostrSettings.relays[0] })
|
||||||
log("🔗 [DEBUG] Generated ndebit for user", { userId: u.user.user_id, ndebit: ndebitString })
|
log("🔗 [DEBUG] Generated ndebit for user", { userId: u.user.user_id, ndebit: ndebitString })
|
||||||
const { max, networkFeeBps, networkFeeFixed, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true)
|
const { max, networkFeeBps, networkFeeFixed, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats)
|
||||||
return {
|
return {
|
||||||
identifier: u.identifier,
|
identifier: u.identifier,
|
||||||
info: {
|
info: {
|
||||||
|
|
@ -212,7 +213,7 @@ export default class {
|
||||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||||
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||||
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
const nostrSettings = this.settings.getSettings().nostrRelaySettings
|
||||||
const { max, networkFeeBps, networkFeeFixed, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true)
|
const { max, networkFeeBps, networkFeeFixed, serviceFeeBps } = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats)
|
||||||
return {
|
return {
|
||||||
max_withdrawable: max, identifier: req.user_identifier, info: {
|
max_withdrawable: max, identifier: req.user_identifier, info: {
|
||||||
userId: user.user.user_id, balance: user.user.balance_sats,
|
userId: user.user.user_id, balance: user.user.balance_sats,
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ import { ApplicationUser } from '../storage/entity/ApplicationUser.js';
|
||||||
import { NostrEvent, NostrSend, SendData, SendInitiator } from '../nostr/handler.js';
|
import { NostrEvent, NostrSend, SendData, SendInitiator } from '../nostr/handler.js';
|
||||||
import { UnsignedEvent } from 'nostr-tools';
|
import { UnsignedEvent } from 'nostr-tools';
|
||||||
import { Ndebit, NdebitData, NdebitFailure, NdebitSuccess, RecurringDebitTimeUnit } from "@shocknet/clink-sdk";
|
import { Ndebit, NdebitData, NdebitFailure, NdebitSuccess, RecurringDebitTimeUnit } from "@shocknet/clink-sdk";
|
||||||
import { debitAccessRulesToDebitRules, newNdebitResponse,debitRulesToDebitAccessRules,
|
import {
|
||||||
|
debitAccessRulesToDebitRules, newNdebitResponse, debitRulesToDebitAccessRules,
|
||||||
nofferErrors, AuthRequiredRes, HandleNdebitRes, expirationRuleName,
|
nofferErrors, AuthRequiredRes, HandleNdebitRes, expirationRuleName,
|
||||||
frequencyRuleName,IntervalTypeToSeconds,unitToIntervalType } from "./debitTypes.js";
|
frequencyRuleName, IntervalTypeToSeconds, unitToIntervalType
|
||||||
|
} from "./debitTypes.js";
|
||||||
|
|
||||||
export class DebitManager {
|
export class DebitManager {
|
||||||
|
|
||||||
|
|
@ -72,7 +74,7 @@ export class DebitManager {
|
||||||
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: req.npub, id: req.request_id, appId: ctx.app_id })
|
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: req.npub, id: req.request_id, appId: ctx.app_id })
|
||||||
return
|
return
|
||||||
case Types.DebitResponse_response_type.INVOICE:
|
case Types.DebitResponse_response_type.INVOICE:
|
||||||
await this.paySingleInvoice(ctx, {invoice: req.response.invoice, npub: req.npub, request_id: req.request_id})
|
await this.paySingleInvoice(ctx, { invoice: req.response.invoice, npub: req.npub, request_id: req.request_id })
|
||||||
return
|
return
|
||||||
case Types.DebitResponse_response_type.AUTHORIZE:
|
case Types.DebitResponse_response_type.AUTHORIZE:
|
||||||
await this.handleAuthorization(ctx, req.response.authorize, { npub: req.npub, request_id: req.request_id })
|
await this.handleAuthorization(ctx, req.response.authorize, { npub: req.npub, request_id: req.request_id })
|
||||||
|
|
@ -82,7 +84,7 @@ export class DebitManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
paySingleInvoice = async (ctx: Types.UserContext, {invoice,npub,request_id}:{invoice:string, npub:string, request_id:string}) => {
|
paySingleInvoice = async (ctx: Types.UserContext, { invoice, npub, request_id }: { invoice: string, npub: string, request_id: string }) => {
|
||||||
try {
|
try {
|
||||||
this.logger("🔍 [DEBIT REQUEST] Paying single invoice")
|
this.logger("🔍 [DEBIT REQUEST] Paying single invoice")
|
||||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||||
|
|
@ -97,7 +99,7 @@ export class DebitManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAuthorization = async (ctx: Types.UserContext,debit:Types.DebitToAuthorize, {npub,request_id}:{ npub:string, request_id:string})=>{
|
handleAuthorization = async (ctx: Types.UserContext, debit: Types.DebitToAuthorize, { npub, request_id }: { npub: string, request_id: string }) => {
|
||||||
this.logger("🔍 [DEBIT REQUEST] Handling authorization", {
|
this.logger("🔍 [DEBIT REQUEST] Handling authorization", {
|
||||||
npub,
|
npub,
|
||||||
request_id,
|
request_id,
|
||||||
|
|
@ -144,7 +146,7 @@ export class DebitManager {
|
||||||
pointerdata
|
pointerdata
|
||||||
})
|
})
|
||||||
const res = await this.payNdebitInvoice(event, pointerdata)
|
const res = await this.payNdebitInvoice(event, pointerdata)
|
||||||
this.logger("🔍 [DEBIT REQUEST] Sending ",res.status," response")
|
this.logger("🔍 [DEBIT REQUEST] Sending ", res.status, " response")
|
||||||
if (res.status === 'fail' || res.status === 'authOk') {
|
if (res.status === 'fail' || res.status === 'authOk') {
|
||||||
const e = newNdebitResponse(JSON.stringify(res.debitRes), event)
|
const e = newNdebitResponse(JSON.stringify(res.debitRes), event)
|
||||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
|
||||||
|
|
@ -159,7 +161,7 @@ export class DebitManager {
|
||||||
this.notifyPaymentSuccess(appUser, debitRes, op, event)
|
this.notifyPaymentSuccess(appUser, debitRes, op, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAuthRequired = (data:NdebitData, event: NostrEvent, res: AuthRequiredRes) => {
|
handleAuthRequired = (data: NdebitData, event: NostrEvent, res: AuthRequiredRes) => {
|
||||||
if (!res.appUser.nostr_public_key) {
|
if (!res.appUser.nostr_public_key) {
|
||||||
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: event.pub, id: event.id, appId: event.appId })
|
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: event.pub, id: event.id, appId: event.appId })
|
||||||
return
|
return
|
||||||
|
|
@ -169,7 +171,9 @@ export class DebitManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyPaymentSuccess = (appUser: ApplicationUser, debitRes: NdebitSuccess, op: Types.UserOperation, event: { pub: string, id: string, appId: string }) => {
|
notifyPaymentSuccess = (appUser: ApplicationUser, debitRes: NdebitSuccess, op: Types.UserOperation, event: { pub: string, id: string, appId: string }) => {
|
||||||
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } = { operation: op, requestId: "GetLiveUserOperations", status: 'OK' }
|
const balance = appUser.user.balance_sats
|
||||||
|
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } =
|
||||||
|
{ operation: op, requestId: "GetLiveUserOperations", status: 'OK', latest_balance: balance }
|
||||||
if (appUser.nostr_public_key) { // TODO - fix before support for http streams
|
if (appUser.nostr_public_key) { // TODO - fix before support for http streams
|
||||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'content', content: JSON.stringify(message), pub: appUser.nostr_public_key })
|
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'content', content: JSON.stringify(message), pub: appUser.nostr_public_key })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,8 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
StartBeacons() {
|
StartBeacons() {
|
||||||
this.applicationManager.StartAppsServiceBeacon(app => {
|
this.applicationManager.StartAppsServiceBeacon((app, fees) => {
|
||||||
this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url })
|
this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, fees })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -373,8 +373,9 @@ export default class {
|
||||||
getLogger({ appName: app.name })("cannot notify user, not a nostr user")
|
getLogger({ appName: app.name })("cannot notify user, not a nostr user")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const balance = user.user.balance_sats
|
||||||
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } = { operation: op, requestId: "GetLiveUserOperations", status: 'OK' }
|
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } =
|
||||||
|
{ operation: op, requestId: "GetLiveUserOperations", status: 'OK', latest_balance: balance }
|
||||||
const j = JSON.stringify(message)
|
const j = JSON.stringify(message)
|
||||||
this.nostrSend({ type: 'app', appId: app.app_id }, { type: 'content', content: j, pub: user.nostr_public_key })
|
this.nostrSend({ type: 'app', appId: app.app_id }, { type: 'content', content: j, pub: user.nostr_public_key })
|
||||||
this.SendEncryptedNotification(app, user, op)
|
this.SendEncryptedNotification(app, user, op)
|
||||||
|
|
@ -396,7 +397,7 @@ export default class {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async UpdateBeacon(app: Application, content: { type: 'service', name: string, avatarUrl?: string, nextRelay?: string }) {
|
async UpdateBeacon(app: Application, content: Types.BeaconData) {
|
||||||
if (!app.nostr_public_key) {
|
if (!app.nostr_public_key) {
|
||||||
getLogger({ appName: app.name })("cannot update beacon, public key not set")
|
getLogger({ appName: app.name })("cannot update beacon, public key not set")
|
||||||
return
|
return
|
||||||
|
|
@ -435,8 +436,9 @@ export default class {
|
||||||
async ResetNostr() {
|
async ResetNostr() {
|
||||||
const apps = await this.storage.applicationStorage.GetApplications()
|
const apps = await this.storage.applicationStorage.GetApplications()
|
||||||
const nextRelay = this.settings.getSettings().nostrRelaySettings.relays[0]
|
const nextRelay = this.settings.getSettings().nostrRelaySettings.relays[0]
|
||||||
|
const fees = this.paymentManager.GetAllFees()
|
||||||
for (const app of apps) {
|
for (const app of apps) {
|
||||||
await this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, nextRelay })
|
await this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, nextRelay, fees })
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultNames = ['wallet', 'wallet-test', this.settings.getSettings().serviceSettings.defaultAppName]
|
const defaultNames = ['wallet', 'wallet-test', this.settings.getSettings().serviceSettings.defaultAppName]
|
||||||
|
|
@ -453,7 +455,8 @@ export default class {
|
||||||
apps: apps.map(a => ({ appId: a.app_id, name: a.name, privateKey: a.nostr_private_key || "", publicKey: a.nostr_public_key || "" })),
|
apps: apps.map(a => ({ appId: a.app_id, name: a.name, privateKey: a.nostr_private_key || "", publicKey: a.nostr_public_key || "" })),
|
||||||
relays: this.settings.getSettings().nostrRelaySettings.relays,
|
relays: this.settings.getSettings().nostrRelaySettings.relays,
|
||||||
maxEventContentLength: this.settings.getSettings().nostrRelaySettings.maxEventContentLength,
|
maxEventContentLength: this.settings.getSettings().nostrRelaySettings.maxEventContentLength,
|
||||||
clients: [liquidityProviderInfo]
|
clients: [liquidityProviderInfo],
|
||||||
|
providerDestinationPub: this.settings.getSettings().liquiditySettings.liquidityProviderPub
|
||||||
}
|
}
|
||||||
this.nostrReset(s)
|
this.nostrReset(s)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ export class LiquidityManager {
|
||||||
afterOutInvoicePaid = async () => { }
|
afterOutInvoicePaid = async () => { }
|
||||||
|
|
||||||
shouldDrainProvider = async () => {
|
shouldDrainProvider = async () => {
|
||||||
const maxW = await this.liquidityProvider.GetLatestMaxWithdrawable()
|
const maxW = await this.liquidityProvider.GetMaxWithdrawable()
|
||||||
const { remote } = await this.lnd.ChannelBalance()
|
const { remote } = await this.lnd.ChannelBalance()
|
||||||
const drainable = Math.min(maxW, remote)
|
const drainable = Math.min(maxW, remote)
|
||||||
if (drainable < 500) {
|
if (drainable < 500) {
|
||||||
|
|
@ -173,7 +173,7 @@ export class LiquidityManager {
|
||||||
if (pendingChannels.pendingOpenChannels.length > 0) {
|
if (pendingChannels.pendingOpenChannels.length > 0) {
|
||||||
return { shouldOpen: false }
|
return { shouldOpen: false }
|
||||||
}
|
}
|
||||||
const maxW = await this.liquidityProvider.GetLatestMaxWithdrawable()
|
const maxW = await this.liquidityProvider.GetMaxWithdrawable()
|
||||||
if (maxW < threshold) {
|
if (maxW < threshold) {
|
||||||
return { shouldOpen: false }
|
return { shouldOpen: false }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import newNostrClient from '../../../proto/autogenerated/ts/nostr_client.js'
|
import newNostrClient from '../../../proto/autogenerated/ts/nostr_client.js'
|
||||||
import { NostrRequest } from '../../../proto/autogenerated/ts/nostr_transport.js'
|
import { NostrRequest } from '../../../proto/autogenerated/ts/nostr_transport.js'
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
import { getLogger } from '../helpers/logger.js'
|
import { ERROR, getLogger } from '../helpers/logger.js'
|
||||||
import { Utils } from '../helpers/utilsWrapper.js'
|
import { Utils } from '../helpers/utilsWrapper.js'
|
||||||
import { NostrEvent, NostrSend } from '../nostr/handler.js'
|
import { NostrEvent, NostrSend } from '../nostr/handler.js'
|
||||||
import { InvoicePaidCb } from '../lnd/settings.js'
|
import { InvoicePaidCb } from '../lnd/settings.js'
|
||||||
|
|
@ -9,7 +9,12 @@ import Storage from '../storage/index.js'
|
||||||
import SettingsManager from './settingsManager.js'
|
import SettingsManager from './settingsManager.js'
|
||||||
import { LiquiditySettings } from './settings.js'
|
import { LiquiditySettings } from './settings.js'
|
||||||
export type LiquidityRequest = { action: 'spend' | 'receive', amount: number }
|
export type LiquidityRequest = { action: 'spend' | 'receive', amount: number }
|
||||||
|
/* export type CumulativeFees = {
|
||||||
|
networkFeeBps: number;
|
||||||
|
networkFeeFixed: number;
|
||||||
|
serviceFeeBps: number;
|
||||||
|
}
|
||||||
|
export type BeaconData = { type: 'service', name: string, avatarUrl?: string, nextRelay?: string, fees?: CumulativeFees } */
|
||||||
export type nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
export type nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
||||||
export class LiquidityProvider {
|
export class LiquidityProvider {
|
||||||
getSettings: () => LiquiditySettings
|
getSettings: () => LiquiditySettings
|
||||||
|
|
@ -28,9 +33,11 @@ export class LiquidityProvider {
|
||||||
queue: ((state: 'ready') => void)[] = []
|
queue: ((state: 'ready') => void)[] = []
|
||||||
utils: Utils
|
utils: Utils
|
||||||
pendingPayments: Record<string, number> = {}
|
pendingPayments: Record<string, number> = {}
|
||||||
stateCache: Types.UserInfo | null = null
|
feesCache: { networkFeeBps: number, networkFeeFixed: number, serviceFeeBps: number } | null = null
|
||||||
unreachableSince: number | null = null
|
// unreachableSince: number | null = null
|
||||||
reconnecting = false
|
// reconnecting = false
|
||||||
|
lastSeenBeacon = 0
|
||||||
|
latestReceivedBalance = 0
|
||||||
incrementProviderBalance: (balance: number) => Promise<void>
|
incrementProviderBalance: (balance: number) => Promise<void>
|
||||||
// make the sub process accept client
|
// make the sub process accept client
|
||||||
constructor(getSettings: () => LiquiditySettings, utils: Utils, invoicePaidCb: InvoicePaidCb, incrementProviderBalance: (balance: number) => Promise<any>) {
|
constructor(getSettings: () => LiquiditySettings, utils: Utils, invoicePaidCb: InvoicePaidCb, incrementProviderBalance: (balance: number) => Promise<any>) {
|
||||||
|
|
@ -71,11 +78,12 @@ export class LiquidityProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
IsReady = () => {
|
IsReady = () => {
|
||||||
const elapsed = this.unreachableSince ? Date.now() - this.unreachableSince : 0
|
/* const elapsed = this.unreachableSince ? Date.now() - this.unreachableSince : 0
|
||||||
if (!this.reconnecting && elapsed > 1000 * 60 * 5) {
|
if (!this.reconnecting && elapsed > 1000 * 60 * 5) {
|
||||||
this.GetUserState().then(() => this.reconnecting = false)
|
this.GetUserState().then(() => this.reconnecting = false)
|
||||||
}
|
} */
|
||||||
return this.ready && !this.getSettings().disableLiquidityProvider && !this.unreachableSince
|
const seenInPast2Minutes = Date.now() - this.lastSeenBeacon < 1000 * 60 * 2
|
||||||
|
return this.ready && !this.getSettings().disableLiquidityProvider && seenInPast2Minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
AwaitProviderReady = async (): Promise<'inactive' | 'ready'> => {
|
AwaitProviderReady = async (): Promise<'inactive' | 'ready'> => {
|
||||||
|
|
@ -114,6 +122,7 @@ export class LiquidityProvider {
|
||||||
try {
|
try {
|
||||||
await this.invoicePaidCb(res.operation.identifier, res.operation.amount, 'provider')
|
await this.invoicePaidCb(res.operation.identifier, res.operation.amount, 'provider')
|
||||||
this.incrementProviderBalance(res.operation.amount)
|
this.incrementProviderBalance(res.operation.amount)
|
||||||
|
this.latestReceivedBalance = res.latest_balance
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this.log("error processing incoming invoice", err.message)
|
this.log("error processing incoming invoice", err.message)
|
||||||
}
|
}
|
||||||
|
|
@ -126,30 +135,38 @@ export class LiquidityProvider {
|
||||||
if (res.status === 'ERROR') {
|
if (res.status === 'ERROR') {
|
||||||
if (res.reason !== 'timeout') {
|
if (res.reason !== 'timeout') {
|
||||||
this.log("error getting user info", res.reason)
|
this.log("error getting user info", res.reason)
|
||||||
if (!this.unreachableSince) this.unreachableSince = Date.now()
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
this.unreachableSince = null
|
this.feesCache = {
|
||||||
this.stateCache = res
|
networkFeeBps: res.network_max_fee_bps,
|
||||||
|
networkFeeFixed: res.network_max_fee_fixed,
|
||||||
|
serviceFeeBps: res.service_fee_bps
|
||||||
|
}
|
||||||
|
this.latestReceivedBalance = res.balance
|
||||||
this.utils.stateBundler.AddBalancePoint('providerBalance', res.balance)
|
this.utils.stateBundler.AddBalancePoint('providerBalance', res.balance)
|
||||||
this.utils.stateBundler.AddBalancePoint('providerMaxWithdrawable', res.max_withdrawable)
|
this.utils.stateBundler.AddBalancePoint('providerMaxWithdrawable', res.max_withdrawable)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
GetFees = () => {
|
GetFees = () => {
|
||||||
if (!this.stateCache) {
|
if (!this.feesCache) {
|
||||||
throw new Error("user state not cached")
|
throw new Error("fees not cached")
|
||||||
}
|
|
||||||
return {
|
|
||||||
serviceFeeBps: this.stateCache.service_fee_bps,
|
|
||||||
networkFeeBps: this.stateCache.network_max_fee_bps,
|
|
||||||
networkFeeFixed: this.stateCache.network_max_fee_fixed,
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return this.feesCache
|
||||||
}
|
}
|
||||||
|
|
||||||
GetLatestMaxWithdrawable = async () => {
|
GetMaxWithdrawable = () => {
|
||||||
|
if (!this.IsReady() || !this.feesCache) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const { networkFeeBps, networkFeeFixed, serviceFeeBps } = this.feesCache
|
||||||
|
const totalBps = networkFeeBps + serviceFeeBps
|
||||||
|
const div = 1 + (totalBps / 10000)
|
||||||
|
return Math.floor((this.latestReceivedBalance - networkFeeFixed) / div)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GetLatestMaxWithdrawable = async () => {
|
||||||
if (!this.IsReady()) {
|
if (!this.IsReady()) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
@ -159,18 +176,19 @@ export class LiquidityProvider {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return res.max_withdrawable
|
return res.max_withdrawable
|
||||||
}
|
} */
|
||||||
|
|
||||||
GetLatestBalance = async () => {
|
GetLatestBalance = () => {
|
||||||
if (!this.IsReady()) {
|
if (!this.IsReady()) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
const res = await this.GetUserState()
|
return this.latestReceivedBalance
|
||||||
|
/* const res = await this.GetUserState()
|
||||||
if (res.status === 'ERROR') {
|
if (res.status === 'ERROR') {
|
||||||
this.log("error getting user info", res.reason)
|
this.log("error getting user info", res.reason)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return res.balance
|
return res.balance */
|
||||||
}
|
}
|
||||||
|
|
||||||
GetPendingBalance = async () => {
|
GetPendingBalance = async () => {
|
||||||
|
|
@ -270,6 +288,7 @@ export class LiquidityProvider {
|
||||||
} */
|
} */
|
||||||
const totalPaid = res.amount_paid + res.network_fee + res.service_fee
|
const totalPaid = res.amount_paid + res.network_fee + res.service_fee
|
||||||
this.incrementProviderBalance(-totalPaid).then(() => { delete this.pendingPayments[invoice] })
|
this.incrementProviderBalance(-totalPaid).then(() => { delete this.pendingPayments[invoice] })
|
||||||
|
this.latestReceivedBalance = res.latest_balance
|
||||||
this.utils.stateBundler.AddTxPoint('paidAnInvoice', decodedAmount, { used: 'provider', from, timeDiscount: true })
|
this.utils.stateBundler.AddTxPoint('paidAnInvoice', decodedAmount, { used: 'provider', from, timeDiscount: true })
|
||||||
return res
|
return res
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -328,6 +347,26 @@ export class LiquidityProvider {
|
||||||
this.log("configured to send to ", this.pubDestination)
|
this.log("configured to send to ", this.pubDestination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// fees: { networkFeeBps: number, networkFeeFixed: number, serviceFeeBps: number }
|
||||||
|
onBeaconEvent = async (beaconData: { content: string, pub: string }) => {
|
||||||
|
if (beaconData.pub !== this.pubDestination) {
|
||||||
|
this.log(ERROR, "got beacon from invalid pub", beaconData.pub, this.pubDestination)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const beacon = JSON.parse(beaconData.content) as Types.BeaconData
|
||||||
|
const err = Types.BeaconDataValidate(beacon)
|
||||||
|
if (err) {
|
||||||
|
this.log(ERROR, "error validating beacon data", err.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (beacon.type !== 'service') {
|
||||||
|
this.log(ERROR, "got beacon from invalid type", beacon.type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (beacon.fees) {
|
||||||
|
this.feesCache = beacon.fees
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onEvent = async (res: { requestId: string }, fromPub: string) => {
|
onEvent = async (res: { requestId: string }, fromPub: string) => {
|
||||||
if (fromPub !== this.pubDestination) {
|
if (fromPub !== this.pubDestination) {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ interface UserOperationInfo {
|
||||||
};
|
};
|
||||||
internal?: boolean;
|
internal?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type PendingTx = { type: 'incoming', tx: AddressReceivingTransaction } | { type: 'outgoing', tx: UserTransactionPayment }
|
export type PendingTx = { type: 'incoming', tx: AddressReceivingTransaction } | { type: 'outgoing', tx: UserTransactionPayment }
|
||||||
const defaultLnurlPayMetadata = (text: string) => `[["text/plain", "${text}"]]`
|
const defaultLnurlPayMetadata = (text: string) => `[["text/plain", "${text}"]]`
|
||||||
const defaultLnAddressMetadata = (text: string, id: string) => `[["text/plain", "${text}"],["text/identifier", "${id}"]]`
|
const defaultLnAddressMetadata = (text: string, id: string) => `[["text/plain", "${text}"],["text/identifier", "${id}"]]`
|
||||||
|
|
@ -232,10 +234,25 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GetMaxPayableInvoice(balance: number, appUser: boolean): { max: number, serviceFeeBps: number, networkFeeBps: number, networkFeeFixed: number } {
|
GetAllFees = (): Types.CumulativeFees => {
|
||||||
const { outgoingAppInvoiceFee, outgoingAppUserInvoiceFee, outgoingAppUserInvoiceFeeBps } = this.settings.getSettings().serviceFeeSettings
|
const { outgoingAppUserInvoiceFeeBps } = this.settings.getSettings().serviceFeeSettings
|
||||||
const serviceFee = appUser ? outgoingAppUserInvoiceFee : outgoingAppInvoiceFee
|
|
||||||
if (this.lnd.liquidProvider.IsReady()) {
|
if (this.lnd.liquidProvider.IsReady()) {
|
||||||
|
const fees = this.lnd.liquidProvider.GetFees()
|
||||||
|
const networkFeeBps = fees.networkFeeBps + fees.serviceFeeBps
|
||||||
|
return { networkFeeBps, networkFeeFixed: fees.networkFeeFixed, serviceFeeBps: outgoingAppUserInvoiceFeeBps }
|
||||||
|
}
|
||||||
|
const { feeFixedLimit, feeRateBps } = this.settings.getSettings().lndSettings
|
||||||
|
return { networkFeeBps: feeRateBps, networkFeeFixed: feeFixedLimit, serviceFeeBps: outgoingAppUserInvoiceFeeBps }
|
||||||
|
}
|
||||||
|
|
||||||
|
GetMaxPayableInvoice(balance: number): Types.CumulativeFees & { max: number } {
|
||||||
|
const { networkFeeBps, networkFeeFixed, serviceFeeBps } = this.GetAllFees()
|
||||||
|
const totalBps = networkFeeBps + serviceFeeBps
|
||||||
|
const div = 1 + (totalBps / 10000)
|
||||||
|
const max = Math.floor((balance - networkFeeFixed) / div)
|
||||||
|
return { max, serviceFeeBps, networkFeeBps, networkFeeFixed }
|
||||||
|
|
||||||
|
/* if (this.lnd.liquidProvider.IsReady()) {
|
||||||
const fees = this.lnd.liquidProvider.GetFees()
|
const fees = this.lnd.liquidProvider.GetFees()
|
||||||
const providerServiceFee = fees.serviceFeeBps / 10000
|
const providerServiceFee = fees.serviceFeeBps / 10000
|
||||||
const providerNetworkFee = fees.networkFeeBps / 10000
|
const providerNetworkFee = fees.networkFeeBps / 10000
|
||||||
|
|
@ -247,7 +264,7 @@ export default class {
|
||||||
const { feeFixedLimit, feeRateLimit, feeRateBps } = this.settings.getSettings().lndSettings
|
const { feeFixedLimit, feeRateLimit, feeRateBps } = this.settings.getSettings().lndSettings
|
||||||
const div = 1 + serviceFee + feeRateLimit
|
const div = 1 + serviceFee + feeRateLimit
|
||||||
const max = Math.floor((balance - feeFixedLimit) / div)
|
const max = Math.floor((balance - feeFixedLimit) / div)
|
||||||
return { max, serviceFeeBps: outgoingAppUserInvoiceFeeBps, networkFeeBps: feeRateBps, networkFeeFixed: feeFixedLimit }
|
return { max, serviceFeeBps: outgoingAppUserInvoiceFeeBps, networkFeeBps: feeRateBps, networkFeeFixed: feeFixedLimit } */
|
||||||
/* let maxWithinServiceFee = 0
|
/* let maxWithinServiceFee = 0
|
||||||
if (appUser) {
|
if (appUser) {
|
||||||
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFee)))
|
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFee)))
|
||||||
|
|
@ -317,6 +334,7 @@ export default class {
|
||||||
operation_id: `${Types.UserOperationType.OUTGOING_INVOICE}-${paymentInfo.serialId}`,
|
operation_id: `${Types.UserOperationType.OUTGOING_INVOICE}-${paymentInfo.serialId}`,
|
||||||
network_fee: paymentInfo.networkFee,
|
network_fee: paymentInfo.networkFee,
|
||||||
service_fee: serviceFee,
|
service_fee: serviceFee,
|
||||||
|
latest_balance: user.balance_sats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export class RugPullTracker {
|
||||||
const providerTracker = await this.storage.liquidityStorage.GetTrackedProvider('lnPub', pubDst)
|
const providerTracker = await this.storage.liquidityStorage.GetTrackedProvider('lnPub', pubDst)
|
||||||
const ready = this.liquidProvider.IsReady()
|
const ready = this.liquidProvider.IsReady()
|
||||||
if (ready) {
|
if (ready) {
|
||||||
const balance = await this.liquidProvider.GetLatestBalance()
|
const balance = this.liquidProvider.GetLatestBalance()
|
||||||
const pendingBalance = await this.liquidProvider.GetPendingBalance()
|
const pendingBalance = await this.liquidProvider.GetPendingBalance()
|
||||||
const trackedBalance = balance + pendingBalance
|
const trackedBalance = balance + pendingBalance
|
||||||
if (!providerTracker) {
|
if (!providerTracker) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import WebSocket from 'ws'
|
import WebSocket from 'ws'
|
||||||
Object.assign(global, { WebSocket: WebSocket });
|
Object.assign(global, { WebSocket: WebSocket });
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import { SimplePool, Event, UnsignedEvent, finalizeEvent, Relay, nip44 } from 'nostr-tools'
|
import { SimplePool, Event, UnsignedEvent, finalizeEvent, Relay, nip44, Filter } from 'nostr-tools'
|
||||||
import { ERROR, getLogger } from '../helpers/logger.js'
|
import { ERROR, getLogger } from '../helpers/logger.js'
|
||||||
import { nip19 } from 'nostr-tools'
|
import { nip19 } from 'nostr-tools'
|
||||||
import { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.js'
|
import { encrypt as encryptV1, decrypt as decryptV1, getSharedSecret as getConversationKeyV1 } from './nip44v1.js'
|
||||||
|
|
@ -26,6 +26,7 @@ export type NostrSettings = {
|
||||||
relays: string[]
|
relays: string[]
|
||||||
clients: ClientInfo[]
|
clients: ClientInfo[]
|
||||||
maxEventContentLength: number
|
maxEventContentLength: number
|
||||||
|
providerDestinationPub: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NostrEvent = {
|
export type NostrEvent = {
|
||||||
|
|
@ -69,9 +70,14 @@ type ProcessMetricsResponse = {
|
||||||
type: 'processMetrics'
|
type: 'processMetrics'
|
||||||
metrics: ProcessMetrics
|
metrics: ProcessMetrics
|
||||||
}
|
}
|
||||||
|
type BeaconResponse = {
|
||||||
|
type: 'beacon'
|
||||||
|
content: string
|
||||||
|
pub: string
|
||||||
|
}
|
||||||
|
|
||||||
export type ChildProcessRequest = SettingsRequest | SendRequest | PingRequest
|
export type ChildProcessRequest = SettingsRequest | SendRequest | PingRequest
|
||||||
export type ChildProcessResponse = ReadyResponse | EventResponse | ProcessMetricsResponse | PingResponse
|
export type ChildProcessResponse = ReadyResponse | EventResponse | ProcessMetricsResponse | PingResponse | BeaconResponse
|
||||||
const send = (message: ChildProcessResponse) => {
|
const send = (message: ChildProcessResponse) => {
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
process.send(message, undefined, undefined, err => {
|
process.send(message, undefined, undefined, err => {
|
||||||
|
|
@ -218,18 +224,28 @@ export default class Handler {
|
||||||
appIds: appIds,
|
appIds: appIds,
|
||||||
listeningForPubkeys: appIds
|
listeningForPubkeys: appIds
|
||||||
})
|
})
|
||||||
|
const subs: Filter[] = [
|
||||||
return relay.subscribe([
|
|
||||||
{
|
{
|
||||||
since: Math.ceil(Date.now() / 1000),
|
since: Math.ceil(Date.now() / 1000),
|
||||||
kinds: supportedKinds,
|
kinds: supportedKinds,
|
||||||
'#p': appIds,
|
'#p': appIds,
|
||||||
}
|
}
|
||||||
], {
|
]
|
||||||
|
if (this.settings.providerDestinationPub) {
|
||||||
|
subs.push({
|
||||||
|
kinds: [30078], '#d': ['Lightning.Pub'],
|
||||||
|
authors: [this.settings.providerDestinationPub]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return relay.subscribe(subs, {
|
||||||
oneose: () => {
|
oneose: () => {
|
||||||
this.log("up to date with nostr events")
|
this.log("up to date with nostr events")
|
||||||
},
|
},
|
||||||
onevent: async (e) => {
|
onevent: async (e) => {
|
||||||
|
if (e.kind === 30078 && e.pubkey === this.settings.providerDestinationPub) {
|
||||||
|
send({ type: 'beacon', content: e.content, pub: e.pubkey })
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!supportedKinds.includes(e.kind) || !e.pubkey) {
|
if (!supportedKinds.includes(e.kind) || !e.pubkey) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse, S
|
||||||
import { Utils } from '../helpers/utilsWrapper.js'
|
import { Utils } from '../helpers/utilsWrapper.js'
|
||||||
import { getLogger, ERROR } from '../helpers/logger.js'
|
import { getLogger, ERROR } from '../helpers/logger.js'
|
||||||
type EventCallback = (event: NostrEvent) => void
|
type EventCallback = (event: NostrEvent) => void
|
||||||
|
type BeaconCallback = (beacon: { content: string, pub: string }) => void
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ export default class NostrSubprocess {
|
||||||
utils: Utils
|
utils: Utils
|
||||||
awaitingPongs: (() => void)[] = []
|
awaitingPongs: (() => void)[] = []
|
||||||
log = getLogger({})
|
log = getLogger({})
|
||||||
constructor(settings: NostrSettings, utils: Utils, eventCallback: EventCallback) {
|
constructor(settings: NostrSettings, utils: Utils, eventCallback: EventCallback, beaconCallback: BeaconCallback) {
|
||||||
this.utils = utils
|
this.utils = utils
|
||||||
this.childProcess = fork("./build/src/services/nostr/handler")
|
this.childProcess = fork("./build/src/services/nostr/handler")
|
||||||
this.childProcess.on("error", (error) => {
|
this.childProcess.on("error", (error) => {
|
||||||
|
|
@ -43,6 +43,9 @@ export default class NostrSubprocess {
|
||||||
this.awaitingPongs.forEach(resolve => resolve())
|
this.awaitingPongs.forEach(resolve => resolve())
|
||||||
this.awaitingPongs = []
|
this.awaitingPongs = []
|
||||||
break
|
break
|
||||||
|
case 'beacon':
|
||||||
|
beaconCallback({ content: message.content, pub: message.pub })
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
console.error("unknown nostr event response", message)
|
console.error("unknown nostr event response", message)
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue