fix payment stream
This commit is contained in:
parent
e4033d4159
commit
f7c26ee38a
17 changed files with 90 additions and 327 deletions
|
|
@ -275,11 +275,6 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
||||||
- output: [PayInvoiceResponse](#PayInvoiceResponse)
|
- output: [PayInvoiceResponse](#PayInvoiceResponse)
|
||||||
|
|
||||||
- PayInvoiceStream
|
|
||||||
- auth type: __User__
|
|
||||||
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
|
||||||
- output: [InvoicePaymentStream](#InvoicePaymentStream)
|
|
||||||
|
|
||||||
- PingSubProcesses
|
- PingSubProcesses
|
||||||
- auth type: __Metrics__
|
- auth type: __Metrics__
|
||||||
- This methods has an __empty__ __request__ body
|
- This methods has an __empty__ __request__ body
|
||||||
|
|
@ -865,13 +860,6 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
||||||
- output: [PayInvoiceResponse](#PayInvoiceResponse)
|
- output: [PayInvoiceResponse](#PayInvoiceResponse)
|
||||||
|
|
||||||
- PayInvoiceStream
|
|
||||||
- auth type: __User__
|
|
||||||
- http method: __post__
|
|
||||||
- http route: __/api/user/invoice/pay/stream__
|
|
||||||
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
|
||||||
- output: [InvoicePaymentStream](#InvoicePaymentStream)
|
|
||||||
|
|
||||||
- PingSubProcesses
|
- PingSubProcesses
|
||||||
- auth type: __Metrics__
|
- auth type: __Metrics__
|
||||||
- http method: __post__
|
- http method: __post__
|
||||||
|
|
@ -1280,9 +1268,6 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- __token__: _string_
|
- __token__: _string_
|
||||||
- __url__: _string_
|
- __url__: _string_
|
||||||
|
|
||||||
### InvoicePaymentStream
|
|
||||||
- __update__: _[InvoicePaymentStream_update](#InvoicePaymentStream_update)_
|
|
||||||
|
|
||||||
### LatestBundleMetricReq
|
### LatestBundleMetricReq
|
||||||
- __limit__: _number_ *this field is optional
|
- __limit__: _number_ *this field is optional
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,6 @@ type Client struct {
|
||||||
PayAddress func(req PayAddressRequest) (*PayAddressResponse, error)
|
PayAddress func(req PayAddressRequest) (*PayAddressResponse, error)
|
||||||
PayAppUserInvoice func(req PayAppUserInvoiceRequest) (*PayInvoiceResponse, error)
|
PayAppUserInvoice func(req PayAppUserInvoiceRequest) (*PayInvoiceResponse, error)
|
||||||
PayInvoice func(req PayInvoiceRequest) (*PayInvoiceResponse, error)
|
PayInvoice func(req PayInvoiceRequest) (*PayInvoiceResponse, error)
|
||||||
PayInvoiceStream func(req PayInvoiceRequest) (*InvoicePaymentStream, error)
|
|
||||||
PingSubProcesses func() error
|
PingSubProcesses func() error
|
||||||
RequestNPubLinkingToken func(req RequestNPubLinkingTokenRequest) (*RequestNPubLinkingTokenResponse, error)
|
RequestNPubLinkingToken func(req RequestNPubLinkingTokenRequest) (*RequestNPubLinkingTokenResponse, error)
|
||||||
ResetDebit func(req DebitOperation) error
|
ResetDebit func(req DebitOperation) error
|
||||||
|
|
@ -1836,7 +1835,6 @@ func NewClient(params ClientParams) *Client {
|
||||||
}
|
}
|
||||||
return &res, nil
|
return &res, nil
|
||||||
},
|
},
|
||||||
// server streaming method: PayInvoiceStream not implemented
|
|
||||||
PingSubProcesses: func() error {
|
PingSubProcesses: func() error {
|
||||||
auth, err := params.RetrieveMetricsAuth()
|
auth, err := params.RetrieveMetricsAuth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -353,9 +353,6 @@ type HttpCreds struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
}
|
}
|
||||||
type InvoicePaymentStream struct {
|
|
||||||
Update *InvoicePaymentStream_update `json:"update"`
|
|
||||||
}
|
|
||||||
type LatestBundleMetricReq struct {
|
type LatestBundleMetricReq struct {
|
||||||
Limit int64 `json:"limit"`
|
Limit int64 `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
@ -770,18 +767,6 @@ type DebitRule_rule struct {
|
||||||
Expiration_rule *DebitExpirationRule `json:"expiration_rule"`
|
Expiration_rule *DebitExpirationRule `json:"expiration_rule"`
|
||||||
Frequency_rule *FrequencyRule `json:"frequency_rule"`
|
Frequency_rule *FrequencyRule `json:"frequency_rule"`
|
||||||
}
|
}
|
||||||
type InvoicePaymentStream_update_type string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ACK InvoicePaymentStream_update_type = "ack"
|
|
||||||
DONE InvoicePaymentStream_update_type = "done"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InvoicePaymentStream_update struct {
|
|
||||||
Type InvoicePaymentStream_update_type `json:"type"`
|
|
||||||
Ack *Empty `json:"ack"`
|
|
||||||
Done *PayInvoiceResponse `json:"done"`
|
|
||||||
}
|
|
||||||
type LiveDebitRequest_debit_type string
|
type LiveDebitRequest_debit_type string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
|
|
@ -881,7 +881,6 @@ export default (params: ClientParams) => ({
|
||||||
}
|
}
|
||||||
return { status: 'ERROR', reason: 'invalid response' }
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
},
|
},
|
||||||
PayInvoiceStream: async (request: Types.PayInvoiceRequest, cb: (v:ResultError | ({ status: 'OK' }& Types.InvoicePaymentStream)) => void): Promise<void> => { throw new Error('http streams are not supported')},
|
|
||||||
PingSubProcesses: async (): Promise<ResultError | ({ status: 'OK' })> => {
|
PingSubProcesses: async (): Promise<ResultError | ({ status: 'OK' })> => {
|
||||||
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')
|
||||||
|
|
|
||||||
|
|
@ -755,22 +755,6 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
||||||
}
|
}
|
||||||
return { status: 'ERROR', reason: 'invalid response' }
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
},
|
},
|
||||||
PayInvoiceStream: async (request: Types.PayInvoiceRequest, cb: (res:ResultError | ({ status: 'OK' }& Types.InvoicePaymentStream)) => void): Promise<void> => {
|
|
||||||
const auth = await params.retrieveNostrUserAuth()
|
|
||||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
|
||||||
const nostrRequest: NostrRequest = {}
|
|
||||||
nostrRequest.body = request
|
|
||||||
subscribe(params.pubDestination, {rpcName:'PayInvoiceStream',authIdentifier:auth, ...nostrRequest }, (data) => {
|
|
||||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return cb(data)
|
|
||||||
if (data.status === 'OK') {
|
|
||||||
const result = data
|
|
||||||
if(!params.checkResult) return cb({ status: 'OK', ...result })
|
|
||||||
const error = Types.InvoicePaymentStreamValidate(result)
|
|
||||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
|
||||||
}
|
|
||||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
PingSubProcesses: async (): Promise<ResultError | ({ status: 'OK' })> => {
|
PingSubProcesses: async (): Promise<ResultError | ({ status: 'OK' })> => {
|
||||||
const auth = await params.retrieveNostrMetricsAuth()
|
const auth = await params.retrieveNostrMetricsAuth()
|
||||||
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
|
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
|
||||||
|
|
|
||||||
|
|
@ -1190,22 +1190,6 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
||||||
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 }
|
||||||
break
|
break
|
||||||
case 'PayInvoiceStream':
|
|
||||||
try {
|
|
||||||
if (!methods.PayInvoiceStream) throw new Error('method: PayInvoiceStream is not implemented')
|
|
||||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
|
||||||
stats.guard = process.hrtime.bigint()
|
|
||||||
authCtx = authContext
|
|
||||||
const request = req.body
|
|
||||||
const error = Types.PayInvoiceRequestValidate(request)
|
|
||||||
stats.validate = process.hrtime.bigint()
|
|
||||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
|
||||||
methods.PayInvoiceStream({rpcName:'PayInvoiceStream', ctx:authContext , req: request ,cb: (response, err) => {
|
|
||||||
stats.handle = process.hrtime.bigint()
|
|
||||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)} else { res({status: 'OK', ...response});opts.metricsCallback([{ ...info, ...stats, ...authContext }])}
|
|
||||||
}})
|
|
||||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
|
||||||
break
|
|
||||||
case 'PingSubProcesses':
|
case 'PingSubProcesses':
|
||||||
try {
|
try {
|
||||||
if (!methods.PingSubProcesses) throw new Error('method: PingSubProcesses is not implemented')
|
if (!methods.PingSubProcesses) throw new Error('method: PingSubProcesses is not implemented')
|
||||||
|
|
|
||||||
|
|
@ -262,9 +262,6 @@ export type PayAppUserInvoice_Output = ResultError | ({ status: 'OK' } & PayInvo
|
||||||
export type PayInvoice_Input = {rpcName:'PayInvoice', req: PayInvoiceRequest}
|
export type PayInvoice_Input = {rpcName:'PayInvoice', req: PayInvoiceRequest}
|
||||||
export type PayInvoice_Output = ResultError | ({ status: 'OK' } & PayInvoiceResponse)
|
export type PayInvoice_Output = ResultError | ({ status: 'OK' } & PayInvoiceResponse)
|
||||||
|
|
||||||
export type PayInvoiceStream_Input = {rpcName:'PayInvoiceStream', req: PayInvoiceRequest, cb:(res: InvoicePaymentStream, err:Error|null)=> void}
|
|
||||||
export type PayInvoiceStream_Output = ResultError | { status: 'OK' }
|
|
||||||
|
|
||||||
export type PingSubProcesses_Input = {rpcName:'PingSubProcesses'}
|
export type PingSubProcesses_Input = {rpcName:'PingSubProcesses'}
|
||||||
export type PingSubProcesses_Output = ResultError | { status: 'OK' }
|
export type PingSubProcesses_Output = ResultError | { status: 'OK' }
|
||||||
|
|
||||||
|
|
@ -392,7 +389,6 @@ export type ServerMethods = {
|
||||||
PayAddress?: (req: PayAddress_Input & {ctx: UserContext }) => Promise<PayAddressResponse>
|
PayAddress?: (req: PayAddress_Input & {ctx: UserContext }) => Promise<PayAddressResponse>
|
||||||
PayAppUserInvoice?: (req: PayAppUserInvoice_Input & {ctx: AppContext }) => Promise<PayInvoiceResponse>
|
PayAppUserInvoice?: (req: PayAppUserInvoice_Input & {ctx: AppContext }) => Promise<PayInvoiceResponse>
|
||||||
PayInvoice?: (req: PayInvoice_Input & {ctx: UserContext }) => Promise<PayInvoiceResponse>
|
PayInvoice?: (req: PayInvoice_Input & {ctx: UserContext }) => Promise<PayInvoiceResponse>
|
||||||
PayInvoiceStream?: (req: PayInvoiceStream_Input & {ctx: UserContext }) => Promise<void>
|
|
||||||
PingSubProcesses?: (req: PingSubProcesses_Input & {ctx: MetricsContext }) => Promise<void>
|
PingSubProcesses?: (req: PingSubProcesses_Input & {ctx: MetricsContext }) => Promise<void>
|
||||||
RequestNPubLinkingToken?: (req: RequestNPubLinkingToken_Input & {ctx: AppContext }) => Promise<RequestNPubLinkingTokenResponse>
|
RequestNPubLinkingToken?: (req: RequestNPubLinkingToken_Input & {ctx: AppContext }) => Promise<RequestNPubLinkingTokenResponse>
|
||||||
ResetDebit?: (req: ResetDebit_Input & {ctx: UserContext }) => Promise<void>
|
ResetDebit?: (req: ResetDebit_Input & {ctx: UserContext }) => Promise<void>
|
||||||
|
|
@ -2054,25 +2050,6 @@ export const HttpCredsValidate = (o?: HttpCreds, opts: HttpCredsOptions = {}, pa
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InvoicePaymentStream = {
|
|
||||||
update: InvoicePaymentStream_update
|
|
||||||
}
|
|
||||||
export const InvoicePaymentStreamOptionalFields: [] = []
|
|
||||||
export type InvoicePaymentStreamOptions = OptionsBaseMessage & {
|
|
||||||
checkOptionalsAreSet?: []
|
|
||||||
update_Options?: InvoicePaymentStream_updateOptions
|
|
||||||
}
|
|
||||||
export const InvoicePaymentStreamValidate = (o?: InvoicePaymentStream, opts: InvoicePaymentStreamOptions = {}, path: string = 'InvoicePaymentStream::root.'): Error | null => {
|
|
||||||
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
|
|
||||||
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
|
|
||||||
|
|
||||||
const updateErr = InvoicePaymentStream_updateValidate(o.update, opts.update_Options, `${path}.update`)
|
|
||||||
if (updateErr !== null) return updateErr
|
|
||||||
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LatestBundleMetricReq = {
|
export type LatestBundleMetricReq = {
|
||||||
limit?: number
|
limit?: number
|
||||||
}
|
}
|
||||||
|
|
@ -4435,43 +4412,6 @@ export const DebitRule_ruleValidate = (o?: DebitRule_rule, opts:DebitRule_ruleOp
|
||||||
if (frequency_ruleErr !== null) return frequency_ruleErr
|
if (frequency_ruleErr !== null) return frequency_ruleErr
|
||||||
|
|
||||||
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return new Error(path + ': unknown type '+ stringType)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
export enum InvoicePaymentStream_update_type {
|
|
||||||
ACK = 'ack',
|
|
||||||
DONE = 'done',
|
|
||||||
}
|
|
||||||
export const enumCheckInvoicePaymentStream_update_type = (e?: InvoicePaymentStream_update_type): boolean => {
|
|
||||||
for (const v in InvoicePaymentStream_update_type) if (e === v) return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
export type InvoicePaymentStream_update =
|
|
||||||
{type:InvoicePaymentStream_update_type.ACK, ack:Empty}|
|
|
||||||
{type:InvoicePaymentStream_update_type.DONE, done:PayInvoiceResponse}
|
|
||||||
|
|
||||||
export type InvoicePaymentStream_updateOptions = {
|
|
||||||
ack_Options?: EmptyOptions
|
|
||||||
done_Options?: PayInvoiceResponseOptions
|
|
||||||
}
|
|
||||||
export const InvoicePaymentStream_updateValidate = (o?: InvoicePaymentStream_update, opts:InvoicePaymentStream_updateOptions = {}, path: string = 'InvoicePaymentStream_update::root.'): Error | null => {
|
|
||||||
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
|
|
||||||
const stringType: string = o.type
|
|
||||||
switch (o.type) {
|
|
||||||
case InvoicePaymentStream_update_type.ACK:
|
|
||||||
const ackErr = EmptyValidate(o.ack, opts.ack_Options, `${path}.ack`)
|
|
||||||
if (ackErr !== null) return ackErr
|
|
||||||
|
|
||||||
|
|
||||||
break
|
|
||||||
case InvoicePaymentStream_update_type.DONE:
|
|
||||||
const doneErr = PayInvoiceResponseValidate(o.done, opts.done_Options, `${path}.done`)
|
|
||||||
if (doneErr !== null) return doneErr
|
|
||||||
|
|
||||||
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return new Error(path + ': unknown type '+ stringType)
|
return new Error(path + ': unknown type '+ stringType)
|
||||||
|
|
|
||||||
|
|
@ -517,13 +517,6 @@ service LightningPub {
|
||||||
option (nostr) = true;
|
option (nostr) = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc PayInvoiceStream(structs.PayInvoiceRequest) returns (stream structs.InvoicePaymentStream){
|
|
||||||
option (auth_type) = "User";
|
|
||||||
option (http_method) = "post";
|
|
||||||
option (http_route) = "/api/user/invoice/pay/stream";
|
|
||||||
option (nostr) = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
rpc GetPaymentState(structs.GetPaymentStateRequest) returns (structs.PaymentState){
|
rpc GetPaymentState(structs.GetPaymentStateRequest) returns (structs.PaymentState){
|
||||||
option (auth_type) = "User";
|
option (auth_type) = "User";
|
||||||
option (http_method) = "post";
|
option (http_method) = "post";
|
||||||
|
|
|
||||||
|
|
@ -479,13 +479,6 @@ message PayInvoiceResponse{
|
||||||
int64 latest_balance = 6;
|
int64 latest_balance = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message InvoicePaymentStream {
|
|
||||||
oneof update {
|
|
||||||
Empty ack = 1;
|
|
||||||
PayInvoiceResponse done = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetPaymentStateRequest{
|
message GetPaymentStateRequest{
|
||||||
string invoice = 1;
|
string invoice = 1;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,23 +111,6 @@ export default class {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async PayInvoiceStream(ctx: Types.UserContext, req: Types.PayInvoiceRequest, cb: (res: Types.InvoicePaymentStream, err: Error | null) => void) {
|
|
||||||
return await this.applicationManager.PayAppUserInvoiceStream(ctx.app_id, {
|
|
||||||
amount: req.amount,
|
|
||||||
invoice: req.invoice,
|
|
||||||
user_identifier: ctx.app_user_id,
|
|
||||||
debit_npub: req.debit_npub,
|
|
||||||
fee_limit_sats: req.fee_limit_sats
|
|
||||||
}, cb)
|
|
||||||
}
|
|
||||||
async PayAddress(ctx: Types.UserContext, req: Types.PayInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
|
||||||
return this.applicationManager.PayAppUserInvoice(ctx.app_id, {
|
|
||||||
amount: req.amount,
|
|
||||||
invoice: req.invoice,
|
|
||||||
user_identifier: ctx.app_user_id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async EnrollMessagingToken(ctx: Types.UserContext, req: Types.MessagingToken): Promise<void> {
|
async EnrollMessagingToken(ctx: Types.UserContext, req: Types.MessagingToken): Promise<void> {
|
||||||
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.applicationStorage.GetApplicationUser(app, ctx.app_user_id);
|
const user = await this.storage.applicationStorage.GetApplicationUser(app, ctx.app_user_id);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { Application } from '../storage/entity/Application.js'
|
||||||
import { ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
|
import { ZapInfo } from '../storage/entity/UserReceivingInvoice.js'
|
||||||
import { nofferEncode, ndebitEncode, OfferPriceType, nmanageEncode } from '@shocknet/clink-sdk'
|
import { nofferEncode, ndebitEncode, OfferPriceType, nmanageEncode } from '@shocknet/clink-sdk'
|
||||||
import SettingsManager from './settingsManager.js'
|
import SettingsManager from './settingsManager.js'
|
||||||
|
import { NostrSend, SendData, SendInitiator } from '../nostr/handler.js'
|
||||||
const TOKEN_EXPIRY_TIME = 2 * 60 * 1000 // 2 minutes, in milliseconds
|
const TOKEN_EXPIRY_TIME = 2 * 60 * 1000 // 2 minutes, in milliseconds
|
||||||
|
|
||||||
type NsecLinkingData = {
|
type NsecLinkingData = {
|
||||||
|
|
@ -17,7 +18,7 @@ type NsecLinkingData = {
|
||||||
expiry: number
|
expiry: number
|
||||||
}
|
}
|
||||||
export default class {
|
export default class {
|
||||||
|
_nostrSend: NostrSend | null = null
|
||||||
storage: Storage
|
storage: Storage
|
||||||
settings: SettingsManager
|
settings: SettingsManager
|
||||||
paymentManager: PaymentManager
|
paymentManager: PaymentManager
|
||||||
|
|
@ -33,6 +34,17 @@ export default class {
|
||||||
this.StartLinkingTokenInterval()
|
this.StartLinkingTokenInterval()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachNostrSend = (nostrSend: NostrSend) => {
|
||||||
|
this._nostrSend = nostrSend
|
||||||
|
}
|
||||||
|
|
||||||
|
nostrSend: NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
|
||||||
|
if (!this._nostrSend) {
|
||||||
|
throw new Error("No nostrSend attached")
|
||||||
|
}
|
||||||
|
this._nostrSend(initiator, data, relays)
|
||||||
|
}
|
||||||
|
|
||||||
StartLinkingTokenInterval() {
|
StartLinkingTokenInterval() {
|
||||||
this.linkingTokenInterval = setInterval(() => {
|
this.linkingTokenInterval = setInterval(() => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
@ -234,15 +246,21 @@ export default class {
|
||||||
async PayAppUserInvoice(appId: string, req: Types.PayAppUserInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
async PayAppUserInvoice(appId: string, req: Types.PayAppUserInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
||||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
||||||
const paid = await this.paymentManager.PayInvoice(appUser.user.user_id, req, app)
|
const paid = await this.paymentManager.PayInvoice(appUser.user.user_id, req, app, pendingOp => {
|
||||||
|
this.notifyAppUserPayment(appUser, pendingOp)
|
||||||
|
})
|
||||||
|
this.notifyAppUserPayment(appUser, paid.operation)
|
||||||
getLogger({ appName: app.name })(appUser.identifier, "invoice paid", paid.amount_paid, "sats")
|
getLogger({ appName: app.name })(appUser.identifier, "invoice paid", paid.amount_paid, "sats")
|
||||||
return paid
|
return paid
|
||||||
}
|
}
|
||||||
|
|
||||||
async PayAppUserInvoiceStream(appId: string, req: Types.PayAppUserInvoiceRequest, cb: (res: Types.InvoicePaymentStream, err: Error | null) => void) {
|
notifyAppUserPayment = (appUser: ApplicationUser, op: Types.UserOperation) => {
|
||||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
const balance = appUser.user.balance_sats
|
||||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier)
|
const message: Types.LiveUserOperation & { requestId: string, status: 'OK' } =
|
||||||
return this.paymentManager.PayInvoiceStream(appUser.user.user_id, req, app, cb)
|
{ operation: op, requestId: "GetLiveUserOperations", status: 'OK', latest_balance: balance }
|
||||||
|
if (appUser.nostr_public_key) { // TODO - fix before support for http streams
|
||||||
|
this.nostrSend({ type: 'app', appId: appUser.application.app_id }, { type: 'content', content: JSON.stringify(message), pub: appUser.nostr_public_key })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async SendAppUserToAppUserPayment(appId: string, req: Types.SendAppUserToAppUserPaymentRequest): Promise<void> {
|
async SendAppUserToAppUserPayment(appId: string, req: Types.SendAppUserToAppUserPaymentRequest): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export class DebitManager {
|
||||||
attachNostrSend = (nostrSend: NostrSend) => {
|
attachNostrSend = (nostrSend: NostrSend) => {
|
||||||
this._nostrSend = nostrSend
|
this._nostrSend = nostrSend
|
||||||
}
|
}
|
||||||
|
|
||||||
nostrSend: NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
|
nostrSend: NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
|
||||||
if (!this._nostrSend) {
|
if (!this._nostrSend) {
|
||||||
throw new Error("No nostrSend attached")
|
throw new Error("No nostrSend attached")
|
||||||
|
|
@ -87,11 +88,9 @@ 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 { payment } = await this.sendDebitPayment(ctx.app_id, ctx.app_user_id, npub, invoice)
|
||||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, ctx.app_user_id)
|
|
||||||
const { op, payment } = await this.sendDebitPayment(ctx.app_id, ctx.app_user_id, npub, invoice)
|
|
||||||
const debitRes: NdebitSuccess = { res: 'ok', preimage: payment.preimage }
|
const debitRes: NdebitSuccess = { res: 'ok', preimage: payment.preimage }
|
||||||
this.notifyPaymentSuccess(appUser, debitRes, op, { appId: ctx.app_id, pub: npub, id: request_id })
|
this.notifyPaymentSuccess(debitRes, { appId: ctx.app_id, pub: npub, id: request_id })
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.logger("❌ [DEBIT REQUEST] Error in single invoice payment")
|
this.logger("❌ [DEBIT REQUEST] Error in single invoice payment")
|
||||||
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: npub, id: request_id, appId: ctx.app_id })
|
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: npub, id: request_id, appId: ctx.app_id })
|
||||||
|
|
@ -124,9 +123,9 @@ export class DebitManager {
|
||||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, ctx.app_user_id)
|
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, ctx.app_user_id)
|
||||||
this.validateAccessRules(access, app, appUser)
|
this.validateAccessRules(access, app, appUser)
|
||||||
this.logger("🔍 [DEBIT REQUEST] Sending debit payment")
|
this.logger("🔍 [DEBIT REQUEST] Sending debit payment")
|
||||||
const { op, payment } = await this.sendDebitPayment(ctx.app_id, ctx.app_user_id, npub, invoice)
|
const { payment } = await this.sendDebitPayment(ctx.app_id, ctx.app_user_id, npub, invoice)
|
||||||
const debitRes: NdebitSuccess = { res: 'ok', preimage: payment.preimage }
|
const debitRes: NdebitSuccess = { res: 'ok', preimage: payment.preimage }
|
||||||
this.notifyPaymentSuccess(appUser, debitRes, op, { appId: ctx.app_id, pub: npub, id: request_id })
|
this.notifyPaymentSuccess(debitRes, { appId: ctx.app_id, pub: npub, id: request_id })
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.logger("❌ [DEBIT REQUEST] Error in debit authorization")
|
this.logger("❌ [DEBIT REQUEST] Error in debit authorization")
|
||||||
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: npub, id: request_id, appId: ctx.app_id })
|
this.sendDebitResponse({ res: 'GFY', error: nofferErrors[1], code: 1 }, { pub: npub, id: request_id, appId: ctx.app_id })
|
||||||
|
|
@ -157,8 +156,8 @@ export class DebitManager {
|
||||||
this.handleAuthRequired(pointerdata, event, res)
|
this.handleAuthRequired(pointerdata, event, res)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { op, debitRes } = res
|
const { debitRes } = res
|
||||||
this.notifyPaymentSuccess(appUser, debitRes, op, event)
|
this.notifyPaymentSuccess(debitRes, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAuthRequired = (data: NdebitData, event: NostrEvent, res: AuthRequiredRes) => {
|
handleAuthRequired = (data: NdebitData, event: NostrEvent, res: AuthRequiredRes) => {
|
||||||
|
|
@ -170,13 +169,7 @@ export class DebitManager {
|
||||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'content', content: JSON.stringify(message), pub: res.appUser.nostr_public_key })
|
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'content', content: JSON.stringify(message), pub: res.appUser.nostr_public_key })
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyPaymentSuccess = (appUser: ApplicationUser, debitRes: NdebitSuccess, op: Types.UserOperation, event: { pub: string, id: string, appId: string }) => {
|
notifyPaymentSuccess = (debitRes: NdebitSuccess, event: { pub: string, id: string, appId: string }) => {
|
||||||
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
|
|
||||||
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'content', content: JSON.stringify(message), pub: appUser.nostr_public_key })
|
|
||||||
}
|
|
||||||
this.sendDebitResponse(debitRes, event)
|
this.sendDebitResponse(debitRes, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,15 +283,14 @@ export class DebitManager {
|
||||||
}
|
}
|
||||||
await this.validateAccessRules(authorization, app, appUser)
|
await this.validateAccessRules(authorization, app, appUser)
|
||||||
this.logger("🔍 [DEBIT REQUEST] Sending requested debit payment")
|
this.logger("🔍 [DEBIT REQUEST] Sending requested debit payment")
|
||||||
const { op, payment } = await this.sendDebitPayment(appId, appUserId, requestorPub, bolt11)
|
const { payment } = await this.sendDebitPayment(appId, appUserId, requestorPub, bolt11)
|
||||||
return { status: 'invoicePaid', op, app, appUser, debitRes: { res: 'ok', preimage: payment.preimage } }
|
return { status: 'invoicePaid', app, appUser, debitRes: { res: 'ok', preimage: payment.preimage } }
|
||||||
}
|
}
|
||||||
|
|
||||||
sendDebitPayment = async (appId: string, appUserId: string, requestorPub: string, bolt11: string) => {
|
sendDebitPayment = async (appId: string, appUserId: string, requestorPub: string, bolt11: string) => {
|
||||||
const payment = await this.applicationManager.PayAppUserInvoice(appId, { amount: 0, invoice: bolt11, user_identifier: appUserId, debit_npub: requestorPub })
|
const payment = await this.applicationManager.PayAppUserInvoice(appId, { amount: 0, invoice: bolt11, user_identifier: appUserId, debit_npub: requestorPub })
|
||||||
await this.storage.debitStorage.IncrementDebitAccess(appUserId, requestorPub, payment.amount_paid + payment.service_fee + payment.network_fee)
|
await this.storage.debitStorage.IncrementDebitAccess(appUserId, requestorPub, payment.amount_paid + payment.service_fee + payment.network_fee)
|
||||||
const op = this.newPaymentOperation(payment, bolt11)
|
return { payment }
|
||||||
return { payment, op }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validateAccessRules = async (access: DebitAccess, app: Application, appUser: ApplicationUser): Promise<boolean> => {
|
validateAccessRules = async (access: DebitAccess, app: Application, appUser: ApplicationUser): Promise<boolean> => {
|
||||||
|
|
@ -329,21 +321,5 @@ export class DebitManager {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
newPaymentOperation = (payment: Types.PayInvoiceResponse, bolt11: string) => {
|
|
||||||
return {
|
|
||||||
amount: payment.amount_paid,
|
|
||||||
paidAtUnix: Math.floor(Date.now() / 1000),
|
|
||||||
inbound: false,
|
|
||||||
type: Types.UserOperationType.OUTGOING_INVOICE,
|
|
||||||
identifier: bolt11,
|
|
||||||
operationId: payment.operation_id,
|
|
||||||
network_fee: payment.network_fee,
|
|
||||||
service_fee: payment.service_fee,
|
|
||||||
confirmed: true,
|
|
||||||
tx_hash: "",
|
|
||||||
internal: payment.network_fee === 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import * as Types from "../../../proto/autogenerated/ts/types.js";
|
import * as Types from "../../../proto/autogenerated/ts/types.js";
|
||||||
import { DebitAccessRules } from '../storage/entity/DebitAccess.js';
|
import { DebitAccessRules } from '../storage/entity/DebitAccess.js';
|
||||||
import { Application } from '../storage/entity/Application.js';
|
import { Application } from '../storage/entity/Application.js';
|
||||||
import { ApplicationUser } from '../storage/entity/ApplicationUser.js';
|
import { ApplicationUser } from '../storage/entity/ApplicationUser.js';
|
||||||
import { UnsignedEvent } from 'nostr-tools';
|
import { UnsignedEvent } from 'nostr-tools';
|
||||||
import { NdebitFailure, NdebitSuccess, RecurringDebitTimeUnit } from "@shocknet/clink-sdk";
|
import { NdebitFailure, NdebitSuccess, RecurringDebitTimeUnit } from "@shocknet/clink-sdk";
|
||||||
|
|
||||||
export const expirationRuleName = 'expiration'
|
export const expirationRuleName = 'expiration'
|
||||||
export const frequencyRuleName = 'frequency'
|
export const frequencyRuleName = 'frequency'
|
||||||
|
|
@ -96,7 +96,7 @@ export const nofferErrors = {
|
||||||
}
|
}
|
||||||
export type AuthRequiredRes = { status: 'authRequired', liveDebitReq: Types.LiveDebitRequest, app: Application, appUser: ApplicationUser }
|
export type AuthRequiredRes = { status: 'authRequired', liveDebitReq: Types.LiveDebitRequest, app: Application, appUser: ApplicationUser }
|
||||||
export type HandleNdebitRes = { status: 'fail', debitRes: NdebitFailure }
|
export type HandleNdebitRes = { status: 'fail', debitRes: NdebitFailure }
|
||||||
| { status: 'invoicePaid', op: Types.UserOperation, app: Application, appUser: ApplicationUser, debitRes: NdebitSuccess }
|
| { status: 'invoicePaid', app: Application, appUser: ApplicationUser, debitRes: NdebitSuccess }
|
||||||
| AuthRequiredRes
|
| AuthRequiredRes
|
||||||
| { status: 'authOk', debitRes: NdebitSuccess }
|
| { status: 'authOk', debitRes: NdebitSuccess }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ export default class {
|
||||||
this.offerManager.attachNostrSend(f)
|
this.offerManager.attachNostrSend(f)
|
||||||
this.managementManager.attachNostrSend(f)
|
this.managementManager.attachNostrSend(f)
|
||||||
this.utils.attachNostrSend(f)
|
this.utils.attachNostrSend(f)
|
||||||
|
this.applicationManager.attachNostrSend(f)
|
||||||
//this.webRTC.attachNostrSend(f)
|
//this.webRTC.attachNostrSend(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,6 @@ 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
|
||||||
|
|
@ -34,12 +28,10 @@ export class LiquidityProvider {
|
||||||
utils: Utils
|
utils: Utils
|
||||||
pendingPayments: Record<string, number> = {}
|
pendingPayments: Record<string, number> = {}
|
||||||
feesCache: { networkFeeBps: number, networkFeeFixed: number, serviceFeeBps: number } | null = null
|
feesCache: { networkFeeBps: number, networkFeeFixed: number, serviceFeeBps: number } | null = null
|
||||||
// unreachableSince: number | null = null
|
|
||||||
// reconnecting = false
|
|
||||||
lastSeenBeacon = 0
|
lastSeenBeacon = 0
|
||||||
latestReceivedBalance = 0
|
latestReceivedBalance = 0
|
||||||
incrementProviderBalance: (balance: number) => Promise<void>
|
incrementProviderBalance: (balance: number) => Promise<void>
|
||||||
// rand = Math.random()
|
pendingPaymentsAck: Record<string, boolean> = {}
|
||||||
// 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>) {
|
||||||
this.utils = utils
|
this.utils = utils
|
||||||
|
|
@ -79,10 +71,6 @@ export class LiquidityProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
IsReady = () => {
|
IsReady = () => {
|
||||||
/* const elapsed = this.unreachableSince ? Date.now() - this.unreachableSince : 0
|
|
||||||
if (!this.reconnecting && elapsed > 1000 * 60 * 5) {
|
|
||||||
this.GetUserState().then(() => this.reconnecting = false)
|
|
||||||
} */
|
|
||||||
const seenInPast2Minutes = Date.now() - this.lastSeenBeacon < 1000 * 60 * 2
|
const seenInPast2Minutes = Date.now() - this.lastSeenBeacon < 1000 * 60 * 2
|
||||||
return this.ready && !this.getSettings().disableLiquidityProvider && seenInPast2Minutes
|
return this.ready && !this.getSettings().disableLiquidityProvider && seenInPast2Minutes
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +113,9 @@ export class LiquidityProvider {
|
||||||
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
|
this.latestReceivedBalance = res.latest_balance
|
||||||
|
if (!res.operation.inbound && !res.operation.confirmed) {
|
||||||
|
delete this.pendingPaymentsAck[res.operation.identifier]
|
||||||
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this.log("error processing incoming invoice", err.message)
|
this.log("error processing incoming invoice", err.message)
|
||||||
}
|
}
|
||||||
|
|
@ -168,69 +159,36 @@ export class LiquidityProvider {
|
||||||
return Math.floor((this.latestReceivedBalance - networkFeeFixed) / div)
|
return Math.floor((this.latestReceivedBalance - networkFeeFixed) / div)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* GetLatestMaxWithdrawable = async () => {
|
|
||||||
if (!this.IsReady()) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
const res = await this.GetUserState()
|
|
||||||
if (res.status === 'ERROR') {
|
|
||||||
this.log("error getting user info", res.reason)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return res.max_withdrawable
|
|
||||||
} */
|
|
||||||
|
|
||||||
GetLatestBalance = () => {
|
GetLatestBalance = () => {
|
||||||
if (!this.IsReady()) {
|
if (!this.IsReady()) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return this.latestReceivedBalance
|
return this.latestReceivedBalance
|
||||||
/* const res = await this.GetUserState()
|
|
||||||
if (res.status === 'ERROR') {
|
|
||||||
this.log("error getting user info", res.reason)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return res.balance */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GetPendingBalance = async () => {
|
GetPendingBalance = async () => {
|
||||||
return Object.values(this.pendingPayments).reduce((a, b) => a + b, 0)
|
return Object.values(this.pendingPayments).reduce((a, b) => a + b, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
CalculateExpectedFeeLimit = (amount: number, info: Types.UserInfo) => {
|
CalculateExpectedFeeLimit = (amount: number) => {
|
||||||
const serviceFeeRate = info.service_fee_bps / 10000
|
const fees = this.GetFees()
|
||||||
|
const serviceFeeRate = fees.serviceFeeBps / 10000
|
||||||
const serviceFee = Math.ceil(serviceFeeRate * amount)
|
const serviceFee = Math.ceil(serviceFeeRate * amount)
|
||||||
const networkMaxFeeRate = info.network_max_fee_bps / 10000
|
const networkMaxFeeRate = fees.networkFeeBps / 10000
|
||||||
const networkFeeLimit = Math.ceil(amount * networkMaxFeeRate + info.network_max_fee_fixed)
|
const networkFeeLimit = Math.ceil(amount * networkMaxFeeRate + fees.networkFeeFixed)
|
||||||
return serviceFee + networkFeeLimit
|
return serviceFee + networkFeeLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
GetExpectedFeeLimit = async (amount: number) => {
|
CanProviderHandle = async (req: LiquidityRequest): Promise<boolean> => {
|
||||||
if (!this.IsReady()) {
|
|
||||||
throw new Error("liquidity provider is not ready yet, disabled or unreachable")
|
|
||||||
}
|
|
||||||
const state = await this.GetUserState()
|
|
||||||
if (state.status === 'ERROR') {
|
|
||||||
throw new Error(state.reason)
|
|
||||||
}
|
|
||||||
return this.CalculateExpectedFeeLimit(amount, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
CanProviderHandle = async (req: LiquidityRequest): Promise<false | Types.UserInfo> => {
|
|
||||||
if (!this.IsReady()) {
|
if (!this.IsReady()) {
|
||||||
this.log("provider is not ready")
|
this.log("provider is not ready")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const state = await this.GetUserState()
|
const maxW = this.GetMaxWithdrawable()
|
||||||
if (state.status === 'ERROR') {
|
|
||||||
this.log("error getting user state", state.reason)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const maxW = state.max_withdrawable
|
|
||||||
if (req.action === 'spend' && maxW < req.amount) {
|
if (req.action === 'spend' && maxW < req.amount) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return state
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
AddInvoice = async (amount: number, memo: string, from: 'user' | 'system', expiry: number) => {
|
AddInvoice = async (amount: number, memo: string, from: 'user' | 'system', expiry: number) => {
|
||||||
|
|
@ -257,38 +215,22 @@ export class LiquidityProvider {
|
||||||
if (!this.IsReady()) {
|
if (!this.IsReady()) {
|
||||||
throw new Error("liquidity provider is not ready yet, disabled or unreachable")
|
throw new Error("liquidity provider is not ready yet, disabled or unreachable")
|
||||||
}
|
}
|
||||||
/* const userInfo = await this.GetUserState()
|
const feeLimitToUse = feeLimit ? feeLimit : this.CalculateExpectedFeeLimit(decodedAmount)
|
||||||
if (userInfo.status === 'ERROR') {
|
this.pendingPayments[invoice] = decodedAmount + feeLimitToUse
|
||||||
throw new Error(userInfo.reason)
|
|
||||||
} */
|
|
||||||
const feeLimitToUse = feeLimit ? feeLimit : await this.GetExpectedFeeLimit(decodedAmount)
|
|
||||||
this.pendingPayments[invoice] = decodedAmount + feeLimitToUse //this.CalculateExpectedFeeLimit(decodedAmount, userInfo)
|
|
||||||
let acked = false
|
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
this.log("10 seconds passed, still waiting for ack")
|
if (!this.pendingPaymentsAck[invoice]) {
|
||||||
this.GetUserState()
|
return
|
||||||
|
}
|
||||||
|
this.log("10 seconds passed without a payment ack, locking provider until the next beacon")
|
||||||
|
this.lastSeenBeacon = 0
|
||||||
}, 1000 * 10)
|
}, 1000 * 10)
|
||||||
const res = await new Promise<Types.PayInvoiceResponse>((resolve, reject) => {
|
this.pendingPaymentsAck[invoice] = true
|
||||||
this.client.PayInvoiceStream({ invoice, amount: 0, fee_limit_sats: feeLimitToUse }, (resp) => {
|
const res = await this.client.PayInvoice({ invoice, amount: 0, fee_limit_sats: feeLimitToUse })
|
||||||
if (resp.status === 'ERROR') {
|
delete this.pendingPaymentsAck[invoice]
|
||||||
this.log("error paying invoice", resp.reason)
|
if (res.status === 'ERROR') {
|
||||||
reject(new Error(resp.reason))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (resp.update.type === Types.InvoicePaymentStream_update_type.ACK) {
|
|
||||||
this.log("acked")
|
|
||||||
clearTimeout(timeout)
|
|
||||||
acked = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resolve(resp.update.done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
//const res = await this.client.PayInvoice({ invoice, amount: 0, fee_limit_sats: feeLimitToUse })
|
|
||||||
/* if (res.status === 'ERROR') {
|
|
||||||
this.log("error paying invoice", res.reason)
|
this.log("error paying invoice", res.reason)
|
||||||
throw new Error(res.reason)
|
throw new Error(res.reason)
|
||||||
} */
|
}
|
||||||
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.latestReceivedBalance = res.latest_balance
|
||||||
|
|
|
||||||
|
|
@ -251,27 +251,6 @@ export default class {
|
||||||
const div = 1 + (totalBps / 10000)
|
const div = 1 + (totalBps / 10000)
|
||||||
const max = Math.floor((balance - networkFeeFixed) / div)
|
const max = Math.floor((balance - networkFeeFixed) / div)
|
||||||
return { max, serviceFeeBps, networkFeeBps, networkFeeFixed }
|
return { max, serviceFeeBps, networkFeeBps, networkFeeFixed }
|
||||||
|
|
||||||
/* if (this.lnd.liquidProvider.IsReady()) {
|
|
||||||
const fees = this.lnd.liquidProvider.GetFees()
|
|
||||||
const providerServiceFee = fees.serviceFeeBps / 10000
|
|
||||||
const providerNetworkFee = fees.networkFeeBps / 10000
|
|
||||||
const div = 1 + serviceFee + providerServiceFee + providerNetworkFee
|
|
||||||
const max = Math.floor((balance - fees.networkFeeFixed) / div)
|
|
||||||
const networkFeeBps = fees.networkFeeBps + fees.serviceFeeBps
|
|
||||||
return { max, serviceFeeBps: outgoingAppUserInvoiceFeeBps, networkFeeBps, networkFeeFixed: fees.networkFeeFixed }
|
|
||||||
}
|
|
||||||
const { feeFixedLimit, feeRateLimit, feeRateBps } = this.settings.getSettings().lndSettings
|
|
||||||
const div = 1 + serviceFee + feeRateLimit
|
|
||||||
const max = Math.floor((balance - feeFixedLimit) / div)
|
|
||||||
return { max, serviceFeeBps: outgoingAppUserInvoiceFeeBps, networkFeeBps: feeRateBps, networkFeeFixed: feeFixedLimit } */
|
|
||||||
/* let maxWithinServiceFee = 0
|
|
||||||
if (appUser) {
|
|
||||||
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.getSettings().serviceFeeSettings.outgoingAppUserInvoiceFee)))
|
|
||||||
} else {
|
|
||||||
maxWithinServiceFee = Math.max(0, Math.floor(balance * (1 - this.settings.getSettings().serviceFeeSettings.outgoingAppInvoiceFee)))
|
|
||||||
}
|
|
||||||
return this.lnd.GetMaxWithinLimit(maxWithinServiceFee) */
|
|
||||||
}
|
}
|
||||||
async DecodeInvoice(req: Types.DecodeInvoiceRequest): Promise<Types.DecodeInvoiceResponse> {
|
async DecodeInvoice(req: Types.DecodeInvoiceRequest): Promise<Types.DecodeInvoiceResponse> {
|
||||||
const decoded = await this.lnd.DecodeInvoice(req.invoice)
|
const decoded = await this.lnd.DecodeInvoice(req.invoice)
|
||||||
|
|
@ -280,17 +259,7 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async PayInvoiceStream(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application, cb: (res: Types.InvoicePaymentStream, err: Error | null) => void) {
|
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application, ack?: (op: Types.UserOperation) => void): Promise<Types.PayInvoiceResponse & { operation: Types.UserOperation }> {
|
||||||
const ack = () => cb({ update: { type: Types.InvoicePaymentStream_update_type.ACK, ack: {} } }, null)
|
|
||||||
try {
|
|
||||||
const paid = await this.PayInvoice(userId, req, linkedApplication, ack)
|
|
||||||
cb({ update: { type: Types.InvoicePaymentStream_update_type.DONE, done: paid } }, null)
|
|
||||||
} catch (err: any) {
|
|
||||||
cb({ update: { type: Types.InvoicePaymentStream_update_type.ACK, ack: {} } }, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application, ack?: () => void): Promise<Types.PayInvoiceResponse> {
|
|
||||||
await this.watchDog.PaymentRequested()
|
await this.watchDog.PaymentRequested()
|
||||||
const maybeBanned = await this.storage.userStorage.GetUser(userId)
|
const maybeBanned = await this.storage.userStorage.GetUser(userId)
|
||||||
if (maybeBanned.locked) {
|
if (maybeBanned.locked) {
|
||||||
|
|
@ -328,13 +297,16 @@ export default class {
|
||||||
}
|
}
|
||||||
const user = await this.storage.userStorage.GetUser(userId)
|
const user = await this.storage.userStorage.GetUser(userId)
|
||||||
this.storage.eventsLog.LogEvent({ type: 'invoice_payment', userId, appId: linkedApplication.app_id, appUserId: "", balance: user.balance_sats, data: req.invoice, amount: payAmount })
|
this.storage.eventsLog.LogEvent({ type: 'invoice_payment', userId, appId: linkedApplication.app_id, appUserId: "", balance: user.balance_sats, data: req.invoice, amount: payAmount })
|
||||||
|
const opId = `${Types.UserOperationType.OUTGOING_INVOICE}-${paymentInfo.serialId}`
|
||||||
|
const operation = this.newInvoicePaymentOperation({ invoice: req.invoice, opId, amount: paymentInfo.amtPaid, networkFee: paymentInfo.networkFee, serviceFee: serviceFee, confirmed: true })
|
||||||
return {
|
return {
|
||||||
preimage: paymentInfo.preimage,
|
preimage: paymentInfo.preimage,
|
||||||
amount_paid: paymentInfo.amtPaid,
|
amount_paid: paymentInfo.amtPaid,
|
||||||
operation_id: `${Types.UserOperationType.OUTGOING_INVOICE}-${paymentInfo.serialId}`,
|
operation_id: opId,
|
||||||
network_fee: paymentInfo.networkFee,
|
network_fee: paymentInfo.networkFee,
|
||||||
service_fee: serviceFee,
|
service_fee: serviceFee,
|
||||||
latest_balance: user.balance_sats
|
latest_balance: user.balance_sats,
|
||||||
|
operation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -353,7 +325,7 @@ export default class {
|
||||||
return { use: 'provider', feeLimit: inputLimit || use.feeLimit }
|
return { use: 'provider', feeLimit: inputLimit || use.feeLimit }
|
||||||
}
|
}
|
||||||
|
|
||||||
async PayExternalInvoice(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, amountForLnd: number, feeLimit?: number }, linkedApplication: Application, debitNpub?: string, ack?: () => void) {
|
async PayExternalInvoice(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, amountForLnd: number, feeLimit?: number }, linkedApplication: Application, debitNpub?: string, ack?: (op: Types.UserOperation) => void) {
|
||||||
if (this.settings.getSettings().serviceSettings.disableExternalPayments) {
|
if (this.settings.getSettings().serviceSettings.disableExternalPayments) {
|
||||||
throw new Error("something went wrong sending payment, please try again later")
|
throw new Error("something went wrong sending payment, please try again later")
|
||||||
}
|
}
|
||||||
|
|
@ -379,7 +351,9 @@ export default class {
|
||||||
return await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, { payAmount, serviceFee, networkFee: routingFeeLimit }, linkedApplication, provider, tx, debitNpub)
|
return await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, { payAmount, serviceFee, networkFee: routingFeeLimit }, linkedApplication, provider, tx, debitNpub)
|
||||||
}, "payment started")
|
}, "payment started")
|
||||||
this.log("ready to pay")
|
this.log("ready to pay")
|
||||||
ack?.()
|
const opId = `${Types.UserOperationType.OUTGOING_INVOICE}-${pendingPayment.serial_id}`
|
||||||
|
const op = this.newInvoicePaymentOperation({ invoice, opId, amount: payAmount, networkFee: routingFeeLimit, serviceFee: serviceFee, confirmed: false })
|
||||||
|
ack?.(op)
|
||||||
try {
|
try {
|
||||||
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit, payAmount, { useProvider: use === 'provider', from: 'user' }, index => {
|
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit, payAmount, { useProvider: use === 'provider', from: 'user' }, index => {
|
||||||
this.storage.paymentStorage.SetExternalPaymentIndex(pendingPayment.serial_id, index)
|
this.storage.paymentStorage.SetExternalPaymentIndex(pendingPayment.serial_id, index)
|
||||||
|
|
@ -414,10 +388,8 @@ export default class {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await this.storage.userStorage.IncrementUserBalance(userId, totalAmountToDecrement, "internal_payment_refund:" + internalInvoice.invoice)
|
await this.storage.userStorage.IncrementUserBalance(userId, totalAmountToDecrement, "internal_payment_refund:" + internalInvoice.invoice)
|
||||||
this.utils.stateBundler.AddTxPointFailed('paidAnInvoice', payAmount, { used: 'internal', from: 'user' }, linkedApplication.app_id)
|
this.utils.stateBundler.AddTxPointFailed('paidAnInvoice', payAmount, { used: 'internal', from: 'user' }, linkedApplication.app_id)
|
||||||
|
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -732,6 +704,23 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newInvoicePaymentOperation = (opInfo: { invoice: string, opId: string, amount: number, networkFee: number, serviceFee: number, confirmed: boolean }): Types.UserOperation => {
|
||||||
|
const { invoice, opId, amount, networkFee, serviceFee, confirmed } = opInfo
|
||||||
|
return {
|
||||||
|
amount: amount,
|
||||||
|
paidAtUnix: Math.floor(Date.now() / 1000),
|
||||||
|
inbound: false,
|
||||||
|
type: Types.UserOperationType.OUTGOING_INVOICE,
|
||||||
|
identifier: invoice,
|
||||||
|
operationId: opId,
|
||||||
|
network_fee: networkFee,
|
||||||
|
service_fee: serviceFee,
|
||||||
|
confirmed,
|
||||||
|
tx_hash: "",
|
||||||
|
internal: networkFee === 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async GetPaymentState(userId: string, req: Types.GetPaymentStateRequest): Promise<Types.PaymentState> {
|
async GetPaymentState(userId: string, req: Types.GetPaymentStateRequest): Promise<Types.PaymentState> {
|
||||||
const user = await this.storage.userStorage.GetUser(userId)
|
const user = await this.storage.userStorage.GetUser(userId)
|
||||||
if (user.locked) {
|
if (user.locked) {
|
||||||
|
|
|
||||||
|
|
@ -158,13 +158,6 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
||||||
if (err != null) throw new Error(err.message)
|
if (err != null) throw new Error(err.message)
|
||||||
return mainHandler.appUserManager.PayInvoice(ctx, req)
|
return mainHandler.appUserManager.PayInvoice(ctx, req)
|
||||||
},
|
},
|
||||||
PayInvoiceStream: async ({ ctx, req, cb }) => {
|
|
||||||
const err = Types.PayInvoiceRequestValidate(req, {
|
|
||||||
invoice_CustomCheck: invoice => invoice !== ''
|
|
||||||
})
|
|
||||||
if (err != null) throw new Error(err.message)
|
|
||||||
mainHandler.appUserManager.PayInvoiceStream(ctx, req, cb)
|
|
||||||
},
|
|
||||||
GetLnurlWithdrawLink: ({ ctx }) => mainHandler.paymentManager.GetLnurlWithdrawLink(ctx),
|
GetLnurlWithdrawLink: ({ ctx }) => mainHandler.paymentManager.GetLnurlWithdrawLink(ctx),
|
||||||
GetLnurlWithdrawInfo: async ({ ctx, query }) => {
|
GetLnurlWithdrawInfo: async ({ ctx, query }) => {
|
||||||
if (!query.k1) {
|
if (!query.k1) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue