logging, drain logic, watchdog, rugpull
This commit is contained in:
parent
32dbd20a76
commit
3a8fdf89f5
46 changed files with 10441 additions and 9936 deletions
|
|
@ -13,16 +13,19 @@ import { UserToUserPayment } from "./build/src/services/storage/entity/UserToUse
|
|||
import { UserTransactionPayment } from "./build/src/services/storage/entity/UserTransactionPayment.js"
|
||||
import { LspOrder } from "./build/src/services/storage/entity/LspOrder.js"
|
||||
import { LndNodeInfo } from "./build/src/services/storage/entity/LndNodeInfo.js"
|
||||
import { TrackedProvider } from "./build/src/services/storage/entity/TrackedProvider.js"
|
||||
|
||||
import { Initial1703170309875 } from './build/src/services/storage/migrations/1703170309875-initial.js'
|
||||
import { LspOrder1718387847693 } from './build/src/services/storage/migrations/1718387847693-lsp_order.js'
|
||||
import { LndNodeInfo1720187506189 } from './build/src/services/storage/migrations/1720187506189-lnd_node_info.js'
|
||||
import { LiquidityProvider1719335699480 } from './build/src/services/storage/migrations/1719335699480-liquidity_provider.js'
|
||||
export default new DataSource({
|
||||
type: "sqlite",
|
||||
database: "db.sqlite",
|
||||
// logging: true,
|
||||
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480],
|
||||
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189],
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo],
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider],
|
||||
// synchronize: true,
|
||||
})
|
||||
//npx typeorm migration:generate ./src/services/storage/migrations/lnd_node_info -d ./datasource.js
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
create lnd classes: `npx protoc -I ./others --ts_out=./lnd others/*`
|
||||
create server classes: `npx protoc -I ./service --pub_out=. service/*`
|
||||
create server classes: `npx protoc -I ./service --pub_out=. service/*`
|
||||
|
||||
export PATH=$PATH:~/Lightning.Pub/proto
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,282 +1,282 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
import { NostrRequest } from './nostr_transport.js'
|
||||
import * as Types from './types.js'
|
||||
export type ResultError = { status: 'ERROR', reason: string }
|
||||
|
||||
export type NostrClientParams = {
|
||||
pubDestination: string
|
||||
retrieveNostrUserAuth: () => Promise<string | null>
|
||||
checkResult?: true
|
||||
}
|
||||
export default (params: NostrClientParams, send: (to:string, message: NostrRequest) => Promise<any>, subscribe: (to:string, message: NostrRequest, cb:(res:any)=> void) => void) => ({
|
||||
LinkNPubThroughToken: async (request: Types.LinkNPubThroughTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'LinkNPubThroughToken',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
UserHealth: async (): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'UserHealth',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetUserInfo: async (): Promise<ResultError | ({ status: 'OK' }& Types.UserInfo)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetUserInfo',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.UserInfoValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
AddProduct: async (request: Types.AddProductRequest): Promise<ResultError | ({ status: 'OK' }& Types.Product)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'AddProduct',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.ProductValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewProductInvoice: async (query: Types.NewProductInvoice_Query): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.query = query
|
||||
const data = await send(params.pubDestination, {rpcName:'NewProductInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.NewInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetUserOperationsResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'GetUserOperations',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.GetUserOperationsResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewAddress: async (request: Types.NewAddressRequest): Promise<ResultError | ({ status: 'OK' }& Types.NewAddressResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'NewAddress',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.NewAddressResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayAddress: async (request: Types.PayAddressRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayAddressResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'PayAddress',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.PayAddressResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewInvoice: async (request: Types.NewInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'NewInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.NewInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
DecodeInvoice: async (request: Types.DecodeInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.DecodeInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'DecodeInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.DecodeInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayInvoice: async (request: Types.PayInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'PayInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.PayInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
OpenChannel: async (request: Types.OpenChannelRequest): Promise<ResultError | ({ status: 'OK' }& Types.OpenChannelResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'OpenChannel',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.OpenChannelResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLnurlWithdrawLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLnurlWithdrawLink',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLnurlPayLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLnurlPayLink',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLNURLChannelLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLNURLChannelLink',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLiveUserOperations: async (cb: (res:ResultError | ({ status: 'OK' }& Types.LiveUserOperation)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetLiveUserOperations',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.LiveUserOperationValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
GetMigrationUpdate: async (cb: (res:ResultError | ({ status: 'OK' }& Types.MigrationUpdate)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetMigrationUpdate',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.MigrationUpdateValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
GetHttpCreds: async (cb: (res:ResultError | ({ status: 'OK' }& Types.HttpCreds)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetHttpCreds',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.HttpCredsValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
BatchUser: async (requests:Types.UserMethodInputs[]): Promise<ResultError | ({ status: 'OK', responses:(Types.UserMethodOutputs)[] })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {body:{requests}}
|
||||
const data = await send(params.pubDestination, {rpcName:'BatchUser',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
}
|
||||
})
|
||||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
import { NostrRequest } from './nostr_transport.js'
|
||||
import * as Types from './types.js'
|
||||
export type ResultError = { status: 'ERROR', reason: string }
|
||||
|
||||
export type NostrClientParams = {
|
||||
pubDestination: string
|
||||
retrieveNostrUserAuth: () => Promise<string | null>
|
||||
checkResult?: true
|
||||
}
|
||||
export default (params: NostrClientParams, send: (to:string, message: NostrRequest) => Promise<any>, subscribe: (to:string, message: NostrRequest, cb:(res:any)=> void) => void) => ({
|
||||
LinkNPubThroughToken: async (request: Types.LinkNPubThroughTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'LinkNPubThroughToken',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
UserHealth: async (): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'UserHealth',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetUserInfo: async (): Promise<ResultError | ({ status: 'OK' }& Types.UserInfo)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetUserInfo',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.UserInfoValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
AddProduct: async (request: Types.AddProductRequest): Promise<ResultError | ({ status: 'OK' }& Types.Product)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'AddProduct',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.ProductValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewProductInvoice: async (query: Types.NewProductInvoice_Query): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.query = query
|
||||
const data = await send(params.pubDestination, {rpcName:'NewProductInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.NewInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetUserOperations: async (request: Types.GetUserOperationsRequest): Promise<ResultError | ({ status: 'OK' }& Types.GetUserOperationsResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'GetUserOperations',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.GetUserOperationsResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewAddress: async (request: Types.NewAddressRequest): Promise<ResultError | ({ status: 'OK' }& Types.NewAddressResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'NewAddress',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.NewAddressResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayAddress: async (request: Types.PayAddressRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayAddressResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'PayAddress',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.PayAddressResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
NewInvoice: async (request: Types.NewInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.NewInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'NewInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.NewInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
DecodeInvoice: async (request: Types.DecodeInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.DecodeInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'DecodeInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.DecodeInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
PayInvoice: async (request: Types.PayInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayInvoiceResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'PayInvoice',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.PayInvoiceResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
OpenChannel: async (request: Types.OpenChannelRequest): Promise<ResultError | ({ status: 'OK' }& Types.OpenChannelResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
nostrRequest.body = request
|
||||
const data = await send(params.pubDestination, {rpcName:'OpenChannel',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.OpenChannelResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLnurlWithdrawLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLnurlWithdrawLink',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLnurlPayLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLnurlPayLink',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLNURLChannelLink: async (): Promise<ResultError | ({ status: 'OK' }& Types.LnurlLinkResponse)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'GetLNURLChannelLink',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.LnurlLinkResponseValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetLiveUserOperations: async (cb: (res:ResultError | ({ status: 'OK' }& Types.LiveUserOperation)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetLiveUserOperations',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.LiveUserOperationValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
GetMigrationUpdate: async (cb: (res:ResultError | ({ status: 'OK' }& Types.MigrationUpdate)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetMigrationUpdate',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.MigrationUpdateValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
GetHttpCreds: async (cb: (res:ResultError | ({ status: 'OK' }& Types.HttpCreds)) => void): Promise<void> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
subscribe(params.pubDestination, {rpcName:'GetHttpCreds',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.HttpCredsValidate(result)
|
||||
if (error === null) { return cb({ status: 'OK', ...result }) } else return cb({ status: 'ERROR', reason: error.message })
|
||||
}
|
||||
return cb({ status: 'ERROR', reason: 'invalid response' })
|
||||
})
|
||||
},
|
||||
BatchUser: async (requests:Types.UserMethodInputs[]): Promise<ResultError | ({ status: 'OK', responses:(Types.UserMethodOutputs)[] })> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {body:{requests}}
|
||||
const data = await send(params.pubDestination, {rpcName:'BatchUser',authIdentifier:auth, ...nostrRequest })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,499 +1,493 @@
|
|||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
|
||||
import * as Types from './types.js'
|
||||
export type Logger = { log: (v: any) => void, error: (v: any) => void }
|
||||
type NostrResponse = (message: object) => void
|
||||
export type NostrRequest = {
|
||||
rpcName?: string
|
||||
params?: Record<string, string>
|
||||
query?: Record<string, string>
|
||||
body?: any
|
||||
authIdentifier?: string
|
||||
requestId?: string
|
||||
appId?: string
|
||||
}
|
||||
export type NostrOptions = {
|
||||
logger?: Logger
|
||||
throwErrors?: true
|
||||
metricsCallback: (metrics: Types.RequestMetric[]) => void
|
||||
NostrUserAuthGuard: (appId?: string, identifier?: string) => Promise<Types.UserContext>
|
||||
}
|
||||
const logErrorAndReturnResponse = (error: Error, response: string, res: NostrResponse, logger: Logger, metric: Types.RequestMetric, metricsCallback: (metrics: Types.RequestMetric[]) => void) => {
|
||||
logger.error(error.message || error); metricsCallback([{ ...metric, error: response }]); res({ status: 'ERROR', reason: response })
|
||||
}
|
||||
export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
||||
const logger = opts.logger || { log: console.log, error: console.error }
|
||||
return async (req: NostrRequest, res: NostrResponse, startString: string, startMs: number) => {
|
||||
const startTime = BigInt(startString)
|
||||
const info: Types.RequestInfo = { rpcName: req.rpcName || 'unkown', batch: false, nostr: true, batchSize: 0 }
|
||||
const stats: Types.RequestStats = { startMs, start: startTime, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
|
||||
let authCtx: Types.AuthContext = {}
|
||||
switch (req.rpcName) {
|
||||
case 'LinkNPubThroughToken':
|
||||
try {
|
||||
if (!methods.LinkNPubThroughToken) throw new Error('method: LinkNPubThroughToken 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.LinkNPubThroughTokenRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
await methods.LinkNPubThroughToken({ rpcName: 'LinkNPubThroughToken', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK' })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'UserHealth':
|
||||
try {
|
||||
if (!methods.UserHealth) throw new Error('method: UserHealth is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
await methods.UserHealth({ rpcName: 'UserHealth', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK' })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetUserInfo':
|
||||
try {
|
||||
if (!methods.GetUserInfo) throw new Error('method: GetUserInfo is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetUserInfo({ rpcName: 'GetUserInfo', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'AddProduct':
|
||||
try {
|
||||
if (!methods.AddProduct) throw new Error('method: AddProduct 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.AddProductRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.AddProduct({ rpcName: 'AddProduct', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewProductInvoice':
|
||||
try {
|
||||
if (!methods.NewProductInvoice) throw new Error('method: NewProductInvoice is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.NewProductInvoice({ rpcName: 'NewProductInvoice', ctx: authContext, query: req.query || {} })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetUserOperations':
|
||||
try {
|
||||
if (!methods.GetUserOperations) throw new Error('method: GetUserOperations is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.GetUserOperationsRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.GetUserOperations({ rpcName: 'GetUserOperations', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewAddress':
|
||||
try {
|
||||
if (!methods.NewAddress) throw new Error('method: NewAddress 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.NewAddressRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.NewAddress({ rpcName: 'NewAddress', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'PayAddress':
|
||||
try {
|
||||
if (!methods.PayAddress) throw new Error('method: PayAddress 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.PayAddressRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.PayAddress({ rpcName: 'PayAddress', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewInvoice':
|
||||
try {
|
||||
if (!methods.NewInvoice) throw new Error('method: NewInvoice 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.NewInvoiceRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.NewInvoice({ rpcName: 'NewInvoice', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'DecodeInvoice':
|
||||
try {
|
||||
if (!methods.DecodeInvoice) throw new Error('method: DecodeInvoice 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.DecodeInvoiceRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.DecodeInvoice({ rpcName: 'DecodeInvoice', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'PayInvoice':
|
||||
try {
|
||||
if (!methods.PayInvoice) throw new Error('method: PayInvoice 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)
|
||||
const response = await methods.PayInvoice({ rpcName: 'PayInvoice', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'OpenChannel':
|
||||
try {
|
||||
if (!methods.OpenChannel) throw new Error('method: OpenChannel 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.OpenChannelRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.OpenChannel({ rpcName: 'OpenChannel', ctx: authContext, req: request })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLnurlWithdrawLink':
|
||||
try {
|
||||
if (!methods.GetLnurlWithdrawLink) throw new Error('method: GetLnurlWithdrawLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLnurlWithdrawLink({ rpcName: 'GetLnurlWithdrawLink', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLnurlPayLink':
|
||||
try {
|
||||
if (!methods.GetLnurlPayLink) throw new Error('method: GetLnurlPayLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLnurlPayLink({ rpcName: 'GetLnurlPayLink', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLNURLChannelLink':
|
||||
try {
|
||||
if (!methods.GetLNURLChannelLink) throw new Error('method: GetLNURLChannelLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLNURLChannelLink({ rpcName: 'GetLNURLChannelLink', ctx: authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', ...response })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLiveUserOperations':
|
||||
try {
|
||||
if (!methods.GetLiveUserOperations) throw new Error('method: GetLiveUserOperations is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetLiveUserOperations({
|
||||
rpcName: 'GetLiveUserOperations', ctx: authContext, cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) } else { res({ status: 'OK', ...response }); opts.metricsCallback([{ ...info, ...stats, ...authContext }]) }
|
||||
}
|
||||
})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetMigrationUpdate':
|
||||
try {
|
||||
if (!methods.GetMigrationUpdate) throw new Error('method: GetMigrationUpdate is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetMigrationUpdate({
|
||||
rpcName: 'GetMigrationUpdate', ctx: authContext, cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) } else { res({ status: 'OK', ...response }); opts.metricsCallback([{ ...info, ...stats, ...authContext }]) }
|
||||
}
|
||||
})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetHttpCreds':
|
||||
try {
|
||||
if (!methods.GetHttpCreds) throw new Error('method: GetHttpCreds is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetHttpCreds({
|
||||
rpcName: 'GetHttpCreds', ctx: authContext, cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) } else { res({ status: 'OK', ...response }); opts.metricsCallback([{ ...info, ...stats, ...authContext }]) }
|
||||
}
|
||||
})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'BatchUser':
|
||||
try {
|
||||
info.batch = true
|
||||
const requests = req.body.requests as Types.UserMethodInputs[]
|
||||
if (!Array.isArray(requests)) throw new Error('invalid body, is not an array')
|
||||
info.batchSize = requests.length
|
||||
if (requests.length > 10) throw new Error('too many requests in the batch')
|
||||
const ctx = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = ctx
|
||||
stats.validate = stats.guard
|
||||
const responses = []
|
||||
const callsMetrics: Types.RequestMetric[] = []
|
||||
for (let i = 0; i < requests.length; i++) {
|
||||
const operation = requests[i]
|
||||
const opInfo: Types.RequestInfo = { rpcName: operation.rpcName, batch: true, nostr: true, batchSize: 0 }
|
||||
const opStats: Types.RequestStats = { startMs, start: startTime, parse: stats.parse, guard: stats.guard, validate: 0n, handle: 0n }
|
||||
try {
|
||||
switch (operation.rpcName) {
|
||||
case 'LinkNPubThroughToken':
|
||||
if (!methods.LinkNPubThroughToken) {
|
||||
throw new Error('method not defined: LinkNPubThroughToken')
|
||||
} else {
|
||||
const error = Types.LinkNPubThroughTokenRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
await methods.LinkNPubThroughToken({ ...operation, ctx }); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'UserHealth':
|
||||
if (!methods.UserHealth) {
|
||||
throw new Error('method not defined: UserHealth')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
await methods.UserHealth({ ...operation, ctx }); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetUserInfo':
|
||||
if (!methods.GetUserInfo) {
|
||||
throw new Error('method not defined: GetUserInfo')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetUserInfo({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'AddProduct':
|
||||
if (!methods.AddProduct) {
|
||||
throw new Error('method not defined: AddProduct')
|
||||
} else {
|
||||
const error = Types.AddProductRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.AddProduct({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewProductInvoice':
|
||||
if (!methods.NewProductInvoice) {
|
||||
throw new Error('method not defined: NewProductInvoice')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.NewProductInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetUserOperations':
|
||||
if (!methods.GetUserOperations) {
|
||||
throw new Error('method not defined: GetUserOperations')
|
||||
} else {
|
||||
const error = Types.GetUserOperationsRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.GetUserOperations({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewAddress':
|
||||
if (!methods.NewAddress) {
|
||||
throw new Error('method not defined: NewAddress')
|
||||
} else {
|
||||
const error = Types.NewAddressRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.NewAddress({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'PayAddress':
|
||||
if (!methods.PayAddress) {
|
||||
throw new Error('method not defined: PayAddress')
|
||||
} else {
|
||||
const error = Types.PayAddressRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.PayAddress({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewInvoice':
|
||||
if (!methods.NewInvoice) {
|
||||
throw new Error('method not defined: NewInvoice')
|
||||
} else {
|
||||
const error = Types.NewInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.NewInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'DecodeInvoice':
|
||||
if (!methods.DecodeInvoice) {
|
||||
throw new Error('method not defined: DecodeInvoice')
|
||||
} else {
|
||||
const error = Types.DecodeInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.DecodeInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'PayInvoice':
|
||||
if (!methods.PayInvoice) {
|
||||
throw new Error('method not defined: PayInvoice')
|
||||
} else {
|
||||
const error = Types.PayInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.PayInvoice({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'OpenChannel':
|
||||
if (!methods.OpenChannel) {
|
||||
throw new Error('method not defined: OpenChannel')
|
||||
} else {
|
||||
const error = Types.OpenChannelRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.OpenChannel({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLnurlWithdrawLink':
|
||||
if (!methods.GetLnurlWithdrawLink) {
|
||||
throw new Error('method not defined: GetLnurlWithdrawLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLnurlWithdrawLink({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLnurlPayLink':
|
||||
if (!methods.GetLnurlPayLink) {
|
||||
throw new Error('method not defined: GetLnurlPayLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLnurlPayLink({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLNURLChannelLink':
|
||||
if (!methods.GetLNURLChannelLink) {
|
||||
throw new Error('method not defined: GetLNURLChannelLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLNURLChannelLink({ ...operation, ctx }); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error('unkown rpcName')
|
||||
}
|
||||
} catch (ex) { const e = ex as any; logger.error(e.message || e); callsMetrics.push({ ...opInfo, ...opStats, ...ctx, error: e.message }); responses.push({ status: 'ERROR', reason: e.message || e }) }
|
||||
}
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', responses })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...ctx }, ...callsMetrics])
|
||||
} 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
|
||||
default: logger.error('unknown rpc call name from nostr event:' + req.rpcName)
|
||||
}
|
||||
}
|
||||
}
|
||||
// This file was autogenerated from a .proto file, DO NOT EDIT!
|
||||
|
||||
import * as Types from './types.js'
|
||||
export type Logger = { log: (v: any) => void, error: (v: any) => void }
|
||||
type NostrResponse = (message: object) => void
|
||||
export type NostrRequest = {
|
||||
rpcName?: string
|
||||
params?: Record<string, string>
|
||||
query?: Record<string, string>
|
||||
body?: any
|
||||
authIdentifier?: string
|
||||
requestId?: string
|
||||
appId?: string
|
||||
}
|
||||
export type NostrOptions = {
|
||||
logger?: Logger
|
||||
throwErrors?: true
|
||||
metricsCallback: (metrics: Types.RequestMetric[]) => void
|
||||
NostrUserAuthGuard: (appId?:string, identifier?: string) => Promise<Types.UserContext>
|
||||
}
|
||||
const logErrorAndReturnResponse = (error: Error, response: string, res: NostrResponse, logger: Logger, metric: Types.RequestMetric, metricsCallback: (metrics: Types.RequestMetric[]) => void) => {
|
||||
logger.error(error.message || error); metricsCallback([{ ...metric, error: response }]); res({ status: 'ERROR', reason: response })
|
||||
}
|
||||
export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
||||
const logger = opts.logger || { log: console.log, error: console.error }
|
||||
return async (req: NostrRequest, res: NostrResponse, startString: string, startMs: number) => {
|
||||
const startTime = BigInt(startString)
|
||||
const info: Types.RequestInfo = { rpcName: req.rpcName || 'unkown', batch: false, nostr: true, batchSize: 0 }
|
||||
const stats: Types.RequestStats = { startMs, start: startTime, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
|
||||
let authCtx: Types.AuthContext = {}
|
||||
switch (req.rpcName) {
|
||||
case 'LinkNPubThroughToken':
|
||||
try {
|
||||
if (!methods.LinkNPubThroughToken) throw new Error('method: LinkNPubThroughToken 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.LinkNPubThroughTokenRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
await methods.LinkNPubThroughToken({rpcName:'LinkNPubThroughToken', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK'})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'UserHealth':
|
||||
try {
|
||||
if (!methods.UserHealth) throw new Error('method: UserHealth is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
await methods.UserHealth({rpcName:'UserHealth', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK'})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetUserInfo':
|
||||
try {
|
||||
if (!methods.GetUserInfo) throw new Error('method: GetUserInfo is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetUserInfo({rpcName:'GetUserInfo', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'AddProduct':
|
||||
try {
|
||||
if (!methods.AddProduct) throw new Error('method: AddProduct 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.AddProductRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.AddProduct({rpcName:'AddProduct', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewProductInvoice':
|
||||
try {
|
||||
if (!methods.NewProductInvoice) throw new Error('method: NewProductInvoice is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.NewProductInvoice({rpcName:'NewProductInvoice', ctx:authContext ,query: req.query||{}})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetUserOperations':
|
||||
try {
|
||||
if (!methods.GetUserOperations) throw new Error('method: GetUserOperations is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
const request = req.body
|
||||
const error = Types.GetUserOperationsRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.GetUserOperations({rpcName:'GetUserOperations', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewAddress':
|
||||
try {
|
||||
if (!methods.NewAddress) throw new Error('method: NewAddress 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.NewAddressRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.NewAddress({rpcName:'NewAddress', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'PayAddress':
|
||||
try {
|
||||
if (!methods.PayAddress) throw new Error('method: PayAddress 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.PayAddressRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.PayAddress({rpcName:'PayAddress', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'NewInvoice':
|
||||
try {
|
||||
if (!methods.NewInvoice) throw new Error('method: NewInvoice 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.NewInvoiceRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.NewInvoice({rpcName:'NewInvoice', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'DecodeInvoice':
|
||||
try {
|
||||
if (!methods.DecodeInvoice) throw new Error('method: DecodeInvoice 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.DecodeInvoiceRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.DecodeInvoice({rpcName:'DecodeInvoice', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'PayInvoice':
|
||||
try {
|
||||
if (!methods.PayInvoice) throw new Error('method: PayInvoice 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)
|
||||
const response = await methods.PayInvoice({rpcName:'PayInvoice', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'OpenChannel':
|
||||
try {
|
||||
if (!methods.OpenChannel) throw new Error('method: OpenChannel 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.OpenChannelRequestValidate(request)
|
||||
stats.validate = process.hrtime.bigint()
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
|
||||
const response = await methods.OpenChannel({rpcName:'OpenChannel', ctx:authContext , req: request})
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLnurlWithdrawLink':
|
||||
try {
|
||||
if (!methods.GetLnurlWithdrawLink) throw new Error('method: GetLnurlWithdrawLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLnurlWithdrawLink({rpcName:'GetLnurlWithdrawLink', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLnurlPayLink':
|
||||
try {
|
||||
if (!methods.GetLnurlPayLink) throw new Error('method: GetLnurlPayLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLnurlPayLink({rpcName:'GetLnurlPayLink', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLNURLChannelLink':
|
||||
try {
|
||||
if (!methods.GetLNURLChannelLink) throw new Error('method: GetLNURLChannelLink is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
const response = await methods.GetLNURLChannelLink({rpcName:'GetLNURLChannelLink', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({status: 'OK', ...response})
|
||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetLiveUserOperations':
|
||||
try {
|
||||
if (!methods.GetLiveUserOperations) throw new Error('method: GetLiveUserOperations is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetLiveUserOperations({rpcName:'GetLiveUserOperations', ctx:authContext ,cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)} else { res({status: 'OK', ...response});opts.metricsCallback([{ ...info, ...stats, ...authContext }])}
|
||||
}})
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetMigrationUpdate':
|
||||
try {
|
||||
if (!methods.GetMigrationUpdate) throw new Error('method: GetMigrationUpdate is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetMigrationUpdate({rpcName:'GetMigrationUpdate', ctx:authContext ,cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)} else { res({status: 'OK', ...response});opts.metricsCallback([{ ...info, ...stats, ...authContext }])}
|
||||
}})
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'GetHttpCreds':
|
||||
try {
|
||||
if (!methods.GetHttpCreds) throw new Error('method: GetHttpCreds is not implemented')
|
||||
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = authContext
|
||||
stats.validate = stats.guard
|
||||
methods.GetHttpCreds({rpcName:'GetHttpCreds', ctx:authContext ,cb: (response, err) => {
|
||||
stats.handle = process.hrtime.bigint()
|
||||
if (err) { logErrorAndReturnResponse(err, err.message, res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)} else { res({status: 'OK', ...response});opts.metricsCallback([{ ...info, ...stats, ...authContext }])}
|
||||
}})
|
||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||
break
|
||||
case 'BatchUser':
|
||||
try {
|
||||
info.batch = true
|
||||
const requests = req.body.requests as Types.UserMethodInputs[]
|
||||
if (!Array.isArray(requests))throw new Error('invalid body, is not an array')
|
||||
info.batchSize = requests.length
|
||||
if (requests.length > 10) throw new Error('too many requests in the batch')
|
||||
const ctx = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
|
||||
stats.guard = process.hrtime.bigint()
|
||||
authCtx = ctx
|
||||
stats.validate = stats.guard
|
||||
const responses = []
|
||||
const callsMetrics: Types.RequestMetric[] = []
|
||||
for (let i = 0; i < requests.length; i++) {
|
||||
const operation = requests[i]
|
||||
const opInfo: Types.RequestInfo = { rpcName: operation.rpcName, batch: true, nostr: true, batchSize: 0 }
|
||||
const opStats: Types.RequestStats = { startMs, start: startTime, parse: stats.parse, guard: stats.guard, validate: 0n, handle: 0n }
|
||||
try {
|
||||
switch(operation.rpcName) {
|
||||
case 'LinkNPubThroughToken':
|
||||
if (!methods.LinkNPubThroughToken) {
|
||||
throw new Error('method not defined: LinkNPubThroughToken')
|
||||
} else {
|
||||
const error = Types.LinkNPubThroughTokenRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
await methods.LinkNPubThroughToken({...operation, ctx}); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'UserHealth':
|
||||
if (!methods.UserHealth) {
|
||||
throw new Error('method not defined: UserHealth')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
await methods.UserHealth({...operation, ctx}); responses.push({ status: 'OK' })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetUserInfo':
|
||||
if (!methods.GetUserInfo) {
|
||||
throw new Error('method not defined: GetUserInfo')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetUserInfo({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'AddProduct':
|
||||
if (!methods.AddProduct) {
|
||||
throw new Error('method not defined: AddProduct')
|
||||
} else {
|
||||
const error = Types.AddProductRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.AddProduct({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewProductInvoice':
|
||||
if (!methods.NewProductInvoice) {
|
||||
throw new Error('method not defined: NewProductInvoice')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.NewProductInvoice({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetUserOperations':
|
||||
if (!methods.GetUserOperations) {
|
||||
throw new Error('method not defined: GetUserOperations')
|
||||
} else {
|
||||
const error = Types.GetUserOperationsRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.GetUserOperations({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewAddress':
|
||||
if (!methods.NewAddress) {
|
||||
throw new Error('method not defined: NewAddress')
|
||||
} else {
|
||||
const error = Types.NewAddressRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.NewAddress({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'PayAddress':
|
||||
if (!methods.PayAddress) {
|
||||
throw new Error('method not defined: PayAddress')
|
||||
} else {
|
||||
const error = Types.PayAddressRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.PayAddress({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'NewInvoice':
|
||||
if (!methods.NewInvoice) {
|
||||
throw new Error('method not defined: NewInvoice')
|
||||
} else {
|
||||
const error = Types.NewInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.NewInvoice({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'DecodeInvoice':
|
||||
if (!methods.DecodeInvoice) {
|
||||
throw new Error('method not defined: DecodeInvoice')
|
||||
} else {
|
||||
const error = Types.DecodeInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.DecodeInvoice({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'PayInvoice':
|
||||
if (!methods.PayInvoice) {
|
||||
throw new Error('method not defined: PayInvoice')
|
||||
} else {
|
||||
const error = Types.PayInvoiceRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.PayInvoice({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'OpenChannel':
|
||||
if (!methods.OpenChannel) {
|
||||
throw new Error('method not defined: OpenChannel')
|
||||
} else {
|
||||
const error = Types.OpenChannelRequestValidate(operation.req)
|
||||
opStats.validate = process.hrtime.bigint()
|
||||
if (error !== null) throw error
|
||||
const res = await methods.OpenChannel({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLnurlWithdrawLink':
|
||||
if (!methods.GetLnurlWithdrawLink) {
|
||||
throw new Error('method not defined: GetLnurlWithdrawLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLnurlWithdrawLink({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLnurlPayLink':
|
||||
if (!methods.GetLnurlPayLink) {
|
||||
throw new Error('method not defined: GetLnurlPayLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLnurlPayLink({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'GetLNURLChannelLink':
|
||||
if (!methods.GetLNURLChannelLink) {
|
||||
throw new Error('method not defined: GetLNURLChannelLink')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.GetLNURLChannelLink({...operation, ctx}); responses.push({ status: 'OK', ...res })
|
||||
opStats.handle = process.hrtime.bigint()
|
||||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error('unkown rpcName')
|
||||
}
|
||||
} catch(ex) {const e = ex as any; logger.error(e.message || e); callsMetrics.push({ ...opInfo, ...opStats, ...ctx, error: e.message }); responses.push({ status: 'ERROR', reason: e.message || e })}
|
||||
}
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res({ status: 'OK', responses })
|
||||
opts.metricsCallback([{ ...info, ...stats, ...ctx }, ...callsMetrics])
|
||||
}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
|
||||
default: logger.error('unknown rpc call name from nostr event:'+req.rpcName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -349,6 +349,9 @@ message UserInfo{
|
|||
int64 balance = 2;
|
||||
int64 max_withdrawable = 3;
|
||||
string user_identifier = 4;
|
||||
int64 service_fee_bps = 5;
|
||||
int64 network_max_fee_bps = 6;
|
||||
int64 network_max_fee_fixed = 7;
|
||||
}
|
||||
|
||||
message GetUserOperationsRequest{
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const start = async () => {
|
|||
log("initializing nostr middleware")
|
||||
const { Send } = nostrMiddleware(serverMethods, mainHandler,
|
||||
{ ...nostrSettings, apps, clients: [liquidityProviderInfo] },
|
||||
(e, p) => mainHandler.liquidProvider.onEvent(e, p)
|
||||
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
|
||||
)
|
||||
log("starting server")
|
||||
mainHandler.attachNostrSend(Send)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
|
|||
let j: NostrRequest
|
||||
try {
|
||||
j = JSON.parse(event.content)
|
||||
log("nostr event", j.rpcName || 'no rpc name')
|
||||
//log("nostr event", j.rpcName || 'no rpc name')
|
||||
} catch {
|
||||
log(ERROR, "invalid json event received", event.content)
|
||||
return
|
||||
|
|
|
|||
11
src/services/helpers/utilsWrapper.ts
Normal file
11
src/services/helpers/utilsWrapper.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { MainSettings } from "../main/settings.js";
|
||||
import { StateBundler } from "../storage/stateBundler.js";
|
||||
|
||||
export class Utils {
|
||||
stateBundler: StateBundler
|
||||
settings: MainSettings
|
||||
constructor(settings: MainSettings) {
|
||||
this.settings = settings
|
||||
this.stateBundler = new StateBundler()
|
||||
}
|
||||
}
|
||||
|
|
@ -11,11 +11,12 @@ const resolveHome = (filepath: string) => {
|
|||
}
|
||||
|
||||
export const LoadLndSettingsFromEnv = (): LndSettings => {
|
||||
const lndAddr = process.env.LND_ADDRESS || "127.0.0.1:10009"
|
||||
const lndCertPath = process.env.LND_CERT_PATH || resolveHome("~/.lnd/tls.cert")
|
||||
const lndMacaroonPath = process.env.LND_MACAROON_PATH || resolveHome("~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon")
|
||||
const feeRateLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_BPS", 60) / 10000
|
||||
const feeFixedLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS", 100)
|
||||
const mockLnd = EnvCanBeBoolean("MOCK_LND")
|
||||
return { mainNode: { lndAddr, lndCertPath, lndMacaroonPath }, feeRateLimit, feeFixedLimit, mockLnd }
|
||||
const lndAddr = process.env.LND_ADDRESS || "127.0.0.1:10009"
|
||||
const lndCertPath = process.env.LND_CERT_PATH || resolveHome("~/.lnd/tls.cert")
|
||||
const lndMacaroonPath = process.env.LND_MACAROON_PATH || resolveHome("~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon")
|
||||
const feeRateBps = EnvCanBeInteger("OUTBOUND_MAX_FEE_BPS", 60)
|
||||
const feeRateLimit = feeRateBps / 10000
|
||||
const feeFixedLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS", 100)
|
||||
const mockLnd = EnvCanBeBoolean("MOCK_LND")
|
||||
return { mainNode: { lndAddr, lndCertPath, lndMacaroonPath }, feeRateLimit, feeFixedLimit, feeRateBps, mockLnd }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,12 @@ import { SendCoinsReq } from './sendCoinsReq.js';
|
|||
import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice, NewBlockCb, HtlcCb, BalanceInfo } from './settings.js';
|
||||
import { getLogger } from '../helpers/logger.js';
|
||||
import { HtlcEvent_EventType } from '../../../proto/lnd/router.js';
|
||||
import { LiquidityProvider, LiquidityRequest } from './liquidityProvider.js';
|
||||
import { LiquidityProvider, LiquidityRequest } from '../main/liquidityProvider.js';
|
||||
import { Utils } from '../helpers/utilsWrapper.js';
|
||||
import { TxPointSettings } from '../storage/stateBundler.js';
|
||||
const DeadLineMetadata = (deadline = 10 * 1000) => ({ deadline: Date.now() + deadline })
|
||||
const deadLndRetrySeconds = 5
|
||||
type TxActionOptions = { useProvider: boolean, from: 'user' | 'system' }
|
||||
export default class {
|
||||
lightning: LightningClient
|
||||
invoices: InvoicesClient
|
||||
|
|
@ -36,8 +39,10 @@ export default class {
|
|||
log = getLogger({ component: 'lndManager' })
|
||||
outgoingOpsLocked = false
|
||||
liquidProvider: LiquidityProvider
|
||||
constructor(settings: LndSettings, liquidProvider: LiquidityProvider, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb) {
|
||||
utils: Utils
|
||||
constructor(settings: LndSettings, liquidProvider: LiquidityProvider, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb, newBlockCb: NewBlockCb, htlcCb: HtlcCb) {
|
||||
this.settings = settings
|
||||
this.utils = utils
|
||||
this.addressPaidCb = addressPaidCb
|
||||
this.invoicePaidCb = invoicePaidCb
|
||||
this.newBlockCb = newBlockCb
|
||||
|
|
@ -196,8 +201,7 @@ export default class {
|
|||
if (tx.numConfirmations === 0) { // only process pending transactions, confirmed transaction are processed by the newBlock CB
|
||||
tx.outputDetails.forEach(output => {
|
||||
if (output.isOurAddress) {
|
||||
this.log("received chan TX", Number(output.amount), "sats", "for", output.address)
|
||||
this.addressPaidCb({ hash: tx.txHash, index: Number(output.outputIndex) }, output.address, Number(output.amount), false)
|
||||
this.addressPaidCb({ hash: tx.txHash, index: Number(output.outputIndex) }, output.address, Number(output.amount), 'lnd')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -217,9 +221,8 @@ export default class {
|
|||
}, { abort: this.abortController.signal })
|
||||
stream.responses.onMessage(invoice => {
|
||||
if (invoice.state === Invoice_InvoiceState.SETTLED) {
|
||||
this.log("An invoice was paid for", Number(invoice.amtPaidSat), "sats", invoice.paymentRequest)
|
||||
this.latestKnownSettleIndex = Number(invoice.settleIndex)
|
||||
this.invoicePaidCb(invoice.paymentRequest, Number(invoice.amtPaidSat), false)
|
||||
this.invoicePaidCb(invoice.paymentRequest, Number(invoice.amtPaidSat), 'lnd')
|
||||
}
|
||||
})
|
||||
stream.responses.onError(error => {
|
||||
|
|
@ -231,8 +234,8 @@ export default class {
|
|||
})
|
||||
}
|
||||
|
||||
async NewAddress(addressType: Types.AddressType): Promise<NewAddressResponse> {
|
||||
this.log("generating new address")
|
||||
async NewAddress(addressType: Types.AddressType, { useProvider, from }: TxActionOptions): Promise<NewAddressResponse> {
|
||||
|
||||
await this.Health()
|
||||
let lndAddressType: AddressType
|
||||
switch (addressType) {
|
||||
|
|
@ -248,22 +251,34 @@ export default class {
|
|||
default:
|
||||
throw new Error("unknown address type " + addressType)
|
||||
}
|
||||
const res = await this.lightning.newAddress({ account: "", type: lndAddressType }, DeadLineMetadata())
|
||||
this.log("new address", res.response.address)
|
||||
return res.response
|
||||
if (useProvider) {
|
||||
throw new Error("provider payments not support chain payments yet")
|
||||
}
|
||||
try {
|
||||
const res = await this.lightning.newAddress({ account: "", type: lndAddressType }, DeadLineMetadata())
|
||||
this.utils.stateBundler.AddTxPoint('addedAddress', 1, { from, used: 'lnd' })
|
||||
return res.response
|
||||
} catch (err) {
|
||||
this.utils.stateBundler.AddTxPointFailed('addedAddress', 1, { from, used: 'lnd' })
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async NewInvoice(value: number, memo: string, expiry: number, useProvider = false): Promise<Invoice> {
|
||||
this.log("generating new invoice for", value, "sats")
|
||||
async NewInvoice(value: number, memo: string, expiry: number, { useProvider, from }: TxActionOptions): Promise<Invoice> {
|
||||
await this.Health()
|
||||
if (useProvider) {
|
||||
const invoice = await this.liquidProvider.AddInvoice(value, memo)
|
||||
const invoice = await this.liquidProvider.AddInvoice(value, memo, from)
|
||||
const providerDst = this.liquidProvider.GetProviderDestination()
|
||||
return { payRequest: invoice, providerDst }
|
||||
}
|
||||
const res = await this.lightning.addInvoice(AddInvoiceReq(value, expiry, true, memo), DeadLineMetadata())
|
||||
this.log("new invoice", res.response.paymentRequest)
|
||||
return { payRequest: res.response.paymentRequest }
|
||||
try {
|
||||
const res = await this.lightning.addInvoice(AddInvoiceReq(value, expiry, true, memo), DeadLineMetadata())
|
||||
this.utils.stateBundler.AddTxPoint('addedInvoice', value, { from, used: 'lnd' })
|
||||
return { payRequest: res.response.paymentRequest }
|
||||
} catch (err) {
|
||||
this.utils.stateBundler.AddTxPointFailed('addedInvoice', value, { from, used: 'lnd' })
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async DecodeInvoice(paymentRequest: string): Promise<DecodedInvoice> {
|
||||
|
|
@ -284,42 +299,45 @@ export default class {
|
|||
const r = res.response
|
||||
return { local: r.localBalance ? Number(r.localBalance.sat) : 0, remote: r.remoteBalance ? Number(r.remoteBalance.sat) : 0 }
|
||||
}
|
||||
async PayInvoice(invoice: string, amount: number, feeLimit: number, useProvider = false): Promise<PaidInvoice> {
|
||||
async PayInvoice(invoice: string, amount: number, feeLimit: number, decodedAmount: number, { useProvider, from }: TxActionOptions): Promise<PaidInvoice> {
|
||||
if (this.outgoingOpsLocked) {
|
||||
this.log("outgoing ops locked, rejecting payment request")
|
||||
throw new Error("lnd node is currently out of sync")
|
||||
}
|
||||
await this.Health()
|
||||
this.log("paying invoice", invoice, "for", amount, "sats with", useProvider ? 'provider' : 'lnd')
|
||||
if (useProvider) {
|
||||
const res = await this.liquidProvider.PayInvoice(invoice)
|
||||
const res = await this.liquidProvider.PayInvoice(invoice, decodedAmount, from)
|
||||
const providerDst = this.liquidProvider.GetProviderDestination()
|
||||
return { feeSat: res.network_fee + res.service_fee, valueSat: res.amount_paid, paymentPreimage: res.preimage, providerDst }
|
||||
}
|
||||
const abortController = new AbortController()
|
||||
const req = PayInvoiceReq(invoice, amount, feeLimit)
|
||||
const stream = this.router.sendPaymentV2(req, { abort: abortController.signal })
|
||||
return new Promise((res, rej) => {
|
||||
stream.responses.onError(error => {
|
||||
this.log("invoice payment failed", error)
|
||||
rej(error)
|
||||
try {
|
||||
const abortController = new AbortController()
|
||||
const req = PayInvoiceReq(invoice, amount, feeLimit)
|
||||
const stream = this.router.sendPaymentV2(req, { abort: abortController.signal })
|
||||
return new Promise((res, rej) => {
|
||||
stream.responses.onError(error => {
|
||||
this.log("invoice payment failed", error)
|
||||
rej(error)
|
||||
})
|
||||
stream.responses.onMessage(payment => {
|
||||
switch (payment.status) {
|
||||
case Payment_PaymentStatus.FAILED:
|
||||
console.log(payment)
|
||||
this.log("invoice payment failed", payment.failureReason)
|
||||
rej(PaymentFailureReason[payment.failureReason])
|
||||
return
|
||||
case Payment_PaymentStatus.SUCCEEDED:
|
||||
this.utils.stateBundler.AddTxPoint('paidAnInvoice', Number(payment.valueSat), { from, used: 'lnd', timeDiscount: true })
|
||||
res({ feeSat: Math.ceil(Number(payment.feeMsat) / 1000), valueSat: Number(payment.valueSat), paymentPreimage: payment.paymentPreimage })
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
stream.responses.onMessage(payment => {
|
||||
switch (payment.status) {
|
||||
case Payment_PaymentStatus.FAILED:
|
||||
console.log(payment)
|
||||
this.log("invoice payment failed", payment.failureReason)
|
||||
rej(PaymentFailureReason[payment.failureReason])
|
||||
return
|
||||
case Payment_PaymentStatus.SUCCEEDED:
|
||||
this.log("invoice payment succeded", Number(payment.valueSat))
|
||||
res({ feeSat: Math.ceil(Number(payment.feeMsat) / 1000), valueSat: Number(payment.valueSat), paymentPreimage: payment.paymentPreimage })
|
||||
return
|
||||
default:
|
||||
this.log("inflight payment update index", Number(payment.paymentIndex), Payment_PaymentStatus[payment.status])
|
||||
}
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
this.utils.stateBundler.AddTxPointFailed('paidAnInvoice', decodedAmount, { from, used: 'lnd' })
|
||||
throw err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async EstimateChainFees(address: string, amount: number, targetConf: number): Promise<EstimateFeeResponse> {
|
||||
|
|
@ -333,16 +351,23 @@ export default class {
|
|||
return res.response
|
||||
}
|
||||
|
||||
async PayAddress(address: string, amount: number, satPerVByte: number, label = ""): Promise<SendCoinsResponse> {
|
||||
async PayAddress(address: string, amount: number, satPerVByte: number, label = "", { useProvider, from }: TxActionOptions): Promise<SendCoinsResponse> {
|
||||
if (this.outgoingOpsLocked) {
|
||||
this.log("outgoing ops locked, rejecting payment request")
|
||||
throw new Error("lnd node is currently out of sync")
|
||||
}
|
||||
await this.Health()
|
||||
this.log("sending chain TX for", amount, "sats", "to", address)
|
||||
const res = await this.lightning.sendCoins(SendCoinsReq(address, amount, satPerVByte, label), DeadLineMetadata())
|
||||
this.log("sent chain TX for", amount, "sats", "to", address)
|
||||
return res.response
|
||||
if (useProvider) {
|
||||
throw new Error("provider payments not support chain payments yet")
|
||||
}
|
||||
try {
|
||||
await this.Health()
|
||||
const res = await this.lightning.sendCoins(SendCoinsReq(address, amount, satPerVByte, label), DeadLineMetadata())
|
||||
this.utils.stateBundler.AddTxPoint('paidAnAddress', amount, { from, used: 'lnd', timeDiscount: true })
|
||||
return res.response
|
||||
} catch (err) {
|
||||
this.utils.stateBundler.AddTxPointFailed('paidAnAddress', amount, { from, used: 'lnd' })
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async GetTransactions(startHeight: number): Promise<TransactionDetails> {
|
||||
|
|
@ -361,7 +386,20 @@ export default class {
|
|||
return res.response
|
||||
}
|
||||
|
||||
async GetBalance(): Promise<BalanceInfo> {
|
||||
async GetTotalBalace() {
|
||||
const walletBalance = await this.GetWalletBalance()
|
||||
const confirmedWalletBalance = Number(walletBalance.confirmedBalance)
|
||||
this.utils.stateBundler.AddBalancePoint('walletBalance', confirmedWalletBalance)
|
||||
const channelsBalance = await this.GetChannelBalance()
|
||||
const totalLightningBalanceMsats = (channelsBalance.localBalance?.msat || 0n) + (channelsBalance.unsettledLocalBalance?.msat || 0n)
|
||||
const totalLightningBalance = Math.ceil(Number(totalLightningBalanceMsats) / 1000)
|
||||
this.utils.stateBundler.AddBalancePoint('channelBalance', totalLightningBalance)
|
||||
const totalLndBalance = confirmedWalletBalance + totalLightningBalance
|
||||
this.utils.stateBundler.AddBalancePoint('totalLndBalance', totalLndBalance)
|
||||
return totalLndBalance
|
||||
}
|
||||
|
||||
async GetBalance(): Promise<BalanceInfo> { // TODO: remove this
|
||||
const wRes = await this.lightning.walletBalance({}, DeadLineMetadata())
|
||||
const { confirmedBalance, unconfirmedBalance, totalBalance } = wRes.response
|
||||
const { response } = await this.lightning.listChannels({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import fetch from "node-fetch"
|
||||
import { LiquidityProvider } from "./liquidityProvider.js"
|
||||
import { LiquidityProvider } from "../main/liquidityProvider.js"
|
||||
import { getLogger, PubLogger } from '../helpers/logger.js'
|
||||
import LND from "./lnd.js"
|
||||
import { AddressType } from "../../../proto/autogenerated/ts/types.js"
|
||||
|
|
@ -113,7 +113,7 @@ export class FlashsatsLSP extends LSP {
|
|||
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee)
|
||||
return null
|
||||
}
|
||||
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11_invoice)
|
||||
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11_invoice, decoded.numSatoshis, 'system')
|
||||
const fees = +order.payment.fee_total_sat + res.network_fee + res.service_fee
|
||||
this.log("paid", res.amount_paid, "to open channel, and a fee of", fees)
|
||||
return { orderId: order.order_id, invoice: order.payment.bolt11_invoice, totalSats: +order.payment.order_total_sat, fees }
|
||||
|
|
@ -163,7 +163,7 @@ export class OlympusLSP extends LSP {
|
|||
await this.addPeer(servicePub, host)
|
||||
const lndInfo = await this.lnd.GetInfo()
|
||||
const myPub = lndInfo.identityPubkey
|
||||
const refundAddr = await this.lnd.NewAddress(AddressType.WITNESS_PUBKEY_HASH)
|
||||
const refundAddr = await this.lnd.NewAddress(AddressType.WITNESS_PUBKEY_HASH, { useProvider: false, from: 'system' })
|
||||
const channelSize = Math.floor(maxSpendable * (1 - this.settings.maxRelativeFee)) * 2
|
||||
const lspBalance = channelSize.toString()
|
||||
const chanExpiryBlocks = serviceInfo.max_channel_expiry_blocks
|
||||
|
|
@ -186,7 +186,7 @@ export class OlympusLSP extends LSP {
|
|||
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee)
|
||||
return null
|
||||
}
|
||||
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11.invoice)
|
||||
const res = await this.liquidityProvider.PayInvoice(order.payment.bolt11.invoice, decoded.numSatoshis, 'system')
|
||||
const fees = +order.payment.bolt11.fee_total_sat + res.network_fee + res.service_fee
|
||||
this.log("paid", res.amount_paid, "to open channel, and a fee of", fees)
|
||||
return { orderId: order.order_id, invoice: order.payment.bolt11.invoice, totalSats: +order.payment.bolt11.order_total_sat, fees }
|
||||
|
|
@ -275,7 +275,7 @@ export class VoltageLSP extends LSP {
|
|||
}
|
||||
await this.addPeer(info.pubkey, `${ipv4.address}:${ipv4.port}`)
|
||||
|
||||
const invoice = await this.lnd.NewInvoice(amtSats, "open channel", 60 * 60)
|
||||
const invoice = await this.lnd.NewInvoice(amtSats, "open channel", 60 * 60, { from: 'system', useProvider: false })
|
||||
const proposalRes = await this.proposal(invoice.payRequest, fee.id)
|
||||
this.log("proposal res", proposalRes, fee.id)
|
||||
const decoded = await this.lnd.DecodeInvoice(proposalRes.jit_bolt11)
|
||||
|
|
@ -287,7 +287,7 @@ export class VoltageLSP extends LSP {
|
|||
this.log("invoice of amount", decoded.numSatoshis, "exceeds user balance of", maxSpendable)
|
||||
return null
|
||||
}
|
||||
const res = await this.liquidityProvider.PayInvoice(proposalRes.jit_bolt11)
|
||||
const res = await this.liquidityProvider.PayInvoice(proposalRes.jit_bolt11, decoded.numSatoshis, 'system')
|
||||
const fees = feeSats + res.network_fee + res.service_fee
|
||||
this.log("paid", res.amount_paid, "to open channel, and a fee of", fees)
|
||||
return { orderId: fee.id, invoice: proposalRes.jit_bolt11, totalSats: decoded.numSatoshis, fees }
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export type LndSettings = {
|
|||
mainNode: NodeSettings
|
||||
feeRateLimit: number
|
||||
feeFixedLimit: number
|
||||
feeRateBps: number
|
||||
mockLnd: boolean
|
||||
|
||||
otherNode?: NodeSettings
|
||||
|
|
@ -30,8 +31,8 @@ export type BalanceInfo = {
|
|||
channelsBalance: ChannelBalance[];
|
||||
}
|
||||
|
||||
export type AddressPaidCb = (txOutput: TxOutput, address: string, amount: number, internal: boolean) => void
|
||||
export type InvoicePaidCb = (paymentRequest: string, amount: number, internal: boolean) => void
|
||||
export type AddressPaidCb = (txOutput: TxOutput, address: string, amount: number, used: 'lnd' | 'provider' | 'internal') => Promise<void>
|
||||
export type InvoicePaidCb = (paymentRequest: string, amount: number, used: 'lnd' | 'provider' | 'internal') => Promise<void>
|
||||
export type NewBlockCb = (height: number) => void
|
||||
export type HtlcCb = (event: HtlcEvent) => void
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,10 @@ export default class {
|
|||
userId: ctx.user_id,
|
||||
balance: user.balance_sats,
|
||||
max_withdrawable: this.applicationManager.paymentManager.GetMaxPayableInvoice(user.balance_sats, true),
|
||||
user_identifier: appUser.identifier
|
||||
user_identifier: appUser.identifier,
|
||||
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
|
||||
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,11 @@ export default class {
|
|||
userId: u.user.user_id,
|
||||
balance: u.user.balance_sats,
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true),
|
||||
user_identifier: u.identifier
|
||||
user_identifier: u.identifier,
|
||||
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
|
||||
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps
|
||||
|
||||
},
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(u.user.balance_sats, true)
|
||||
}
|
||||
|
|
@ -191,7 +195,10 @@ export default class {
|
|||
max_withdrawable: max, identifier: req.user_identifier, info: {
|
||||
userId: user.user.user_id, balance: user.user.balance_sats,
|
||||
max_withdrawable: this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats, true),
|
||||
user_identifier: user.identifier
|
||||
user_identifier: user.identifier,
|
||||
network_max_fee_bps: this.settings.lndSettings.feeRateBps,
|
||||
network_max_fee_fixed: this.settings.lndSettings.feeFixedLimit,
|
||||
service_fee_bps: this.settings.outgoingAppUserInvoiceFeeBps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ import { UnsignedEvent } from '../nostr/tools/event.js'
|
|||
import { NostrSend } from '../nostr/handler.js'
|
||||
import MetricsManager from '../metrics/index.js'
|
||||
import { LoggedEvent } from '../storage/eventsLog.js'
|
||||
import { LiquidityProvider } from "../lnd/liquidityProvider.js"
|
||||
import { LiquidityProvider } from "./liquidityProvider.js"
|
||||
import { LiquidityManager } from "./liquidityManager.js"
|
||||
import { Utils } from "../helpers/utilsWrapper.js"
|
||||
import { RugPullTracker } from "./rugPullTracker.js"
|
||||
|
||||
type UserOperationsSub = {
|
||||
id: string
|
||||
|
|
@ -37,18 +39,22 @@ export default class {
|
|||
paymentManager: PaymentManager
|
||||
paymentSubs: Record<string, ((op: Types.UserOperation) => void) | null> = {}
|
||||
metricsManager: MetricsManager
|
||||
liquidProvider: LiquidityProvider
|
||||
liquidityProvider: LiquidityProvider
|
||||
liquidityManager: LiquidityManager
|
||||
utils: Utils
|
||||
rugPullTracker: RugPullTracker
|
||||
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
|
||||
constructor(settings: MainSettings, storage: Storage) {
|
||||
constructor(settings: MainSettings, storage: Storage, utils: Utils) {
|
||||
this.settings = settings
|
||||
this.storage = storage
|
||||
this.liquidProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.invoicePaidCb)
|
||||
this.lnd = new LND(settings.lndSettings, this.liquidProvider, this.addressPaidCb, this.invoicePaidCb, this.newBlockCb, this.htlcCb)
|
||||
this.liquidityManager = new LiquidityManager(this.settings.liquiditySettings, this.storage, this.liquidProvider, this.lnd)
|
||||
this.utils = utils
|
||||
this.liquidityProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.utils, this.invoicePaidCb)
|
||||
this.rugPullTracker = new RugPullTracker(this.storage, this.liquidityProvider)
|
||||
this.lnd = new LND(settings.lndSettings, this.liquidityProvider, this.utils, this.addressPaidCb, this.invoicePaidCb, this.newBlockCb, this.htlcCb)
|
||||
this.liquidityManager = new LiquidityManager(this.settings.liquiditySettings, this.storage, this.utils, this.liquidityProvider, this.lnd, this.rugPullTracker)
|
||||
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
||||
|
||||
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.liquidityManager, this.addressPaidCb, this.invoicePaidCb)
|
||||
this.paymentManager = new PaymentManager(this.storage, this.lnd, this.settings, this.liquidityManager, this.utils, this.addressPaidCb, this.invoicePaidCb)
|
||||
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
|
||||
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager)
|
||||
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
|
||||
|
|
@ -68,7 +74,7 @@ export default class {
|
|||
|
||||
attachNostrSend(f: NostrSend) {
|
||||
this.nostrSend = f
|
||||
this.liquidProvider.attachNostrSend(f)
|
||||
this.liquidityProvider.attachNostrSend(f)
|
||||
}
|
||||
|
||||
htlcCb: HtlcCb = (e) => {
|
||||
|
|
@ -124,11 +130,12 @@ export default class {
|
|||
}))
|
||||
}
|
||||
|
||||
addressPaidCb: AddressPaidCb = (txOutput, address, amount, internal) => {
|
||||
this.storage.StartTransaction(async tx => {
|
||||
addressPaidCb: AddressPaidCb = (txOutput, address, amount, used) => {
|
||||
return this.storage.StartTransaction(async tx => {
|
||||
const { blockHeight } = await this.lnd.GetInfo()
|
||||
const userAddress = await this.storage.paymentStorage.GetAddressOwner(address, tx)
|
||||
if (!userAddress) { return }
|
||||
const internal = used === 'internal'
|
||||
let log = getLogger({})
|
||||
if (!userAddress.linkedApplication) {
|
||||
log(ERROR, "an address was paid, that has no linked application")
|
||||
|
|
@ -155,17 +162,20 @@ export default class {
|
|||
const operationId = `${Types.UserOperationType.INCOMING_TX}-${addedTx.serial_id}`
|
||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_TX, identifier: userAddress.address, operationId, network_fee: 0, service_fee: fee, confirmed: internal, tx_hash: txOutput.hash, internal: false }
|
||||
this.sendOperationToNostr(userAddress.linkedApplication, userAddress.user.user_id, op)
|
||||
} catch {
|
||||
this.utils.stateBundler.AddTxPoint('addressWasPaid', amount, { used, from: 'system', timeDiscount: true })
|
||||
} catch (err: any) {
|
||||
this.utils.stateBundler.AddTxPointFailed('addressWasPaid', amount, { used, from: 'system' })
|
||||
log(ERROR, "cannot process address paid transaction, already registered")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
invoicePaidCb: InvoicePaidCb = (paymentRequest, amount, internal) => {
|
||||
this.storage.StartTransaction(async tx => {
|
||||
invoicePaidCb: InvoicePaidCb = (paymentRequest, amount, used) => {
|
||||
return this.storage.StartTransaction(async tx => {
|
||||
let log = getLogger({})
|
||||
const userInvoice = await this.storage.paymentStorage.GetInvoiceOwner(paymentRequest, tx)
|
||||
if (!userInvoice) { return }
|
||||
const internal = used === 'internal'
|
||||
if (userInvoice.paid_at_unix > 0 && internal) { log("cannot pay internally, invoice already paid"); return }
|
||||
if (userInvoice.paid_at_unix > 0 && !internal && userInvoice.paidByLnd) { log("invoice already paid by lnd"); return }
|
||||
if (!userInvoice.linkedApplication) {
|
||||
|
|
@ -190,9 +200,10 @@ export default class {
|
|||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_INVOICE, identifier: userInvoice.invoice, operationId, network_fee: 0, service_fee: fee, confirmed: true, tx_hash: "", internal }
|
||||
this.sendOperationToNostr(userInvoice.linkedApplication, userInvoice.user.user_id, op)
|
||||
this.createZapReceipt(log, userInvoice)
|
||||
log("paid invoice processed successfully")
|
||||
this.liquidityManager.afterInInvoicePaid()
|
||||
this.utils.stateBundler.AddTxPoint('invoiceWasPaid', amount, { used, from: 'system', timeDiscount: true })
|
||||
} catch (err: any) {
|
||||
this.utils.stateBundler.AddTxPointFailed('invoiceWasPaid', amount, { used, from: 'system' })
|
||||
log(ERROR, "cannot process paid invoice", err.message || "")
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { PubLogger, getLogger } from "../helpers/logger.js"
|
||||
import { LiquidityProvider } from "../lnd/liquidityProvider.js"
|
||||
import { LiquidityProvider } from "./liquidityProvider.js"
|
||||
import { Unlocker } from "./unlocker.js"
|
||||
import Storage from "../storage/index.js"
|
||||
import { TypeOrmMigrationRunner } from "../storage/migrations/runner.js"
|
||||
import Main from "./index.js"
|
||||
import SanityChecker from "./sanityChecker.js"
|
||||
import { MainSettings } from "./settings.js"
|
||||
import { Utils } from "../helpers/utilsWrapper.js"
|
||||
export type AppData = {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
|
|
@ -13,6 +14,7 @@ export type AppData = {
|
|||
name: string;
|
||||
}
|
||||
export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings) => {
|
||||
const utils = new Utils(mainSettings)
|
||||
const storageManager = new Storage(mainSettings.storageSettings)
|
||||
const manualMigration = await TypeOrmMigrationRunner(log, storageManager, mainSettings.storageSettings.dbSettings, process.argv[2])
|
||||
if (manualMigration) {
|
||||
|
|
@ -21,7 +23,7 @@ export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings
|
|||
const unlocker = new Unlocker(mainSettings, storageManager)
|
||||
await unlocker.Unlock()
|
||||
|
||||
const mainHandler = new Main(mainSettings, storageManager)
|
||||
const mainHandler = new Main(mainSettings, storageManager, utils)
|
||||
await mainHandler.lnd.Warmup()
|
||||
if (!mainSettings.skipSanityCheck) {
|
||||
const sanityChecker = new SanityChecker(storageManager, mainHandler.lnd)
|
||||
|
|
@ -51,7 +53,7 @@ export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings
|
|||
publicKey: liquidityProviderApp.publicKey,
|
||||
name: "liquidity_provider", clientId: `client_${liquidityProviderApp.appId}`
|
||||
}
|
||||
mainHandler.liquidProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey })
|
||||
mainHandler.liquidityProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey })
|
||||
const stop = await processArgs(mainHandler)
|
||||
if (stop) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { getLogger } from "../helpers/logger.js"
|
||||
import { LiquidityProvider } from "../lnd/liquidityProvider.js"
|
||||
import { Utils } from "../helpers/utilsWrapper.js"
|
||||
import { LiquidityProvider } from "./liquidityProvider.js"
|
||||
import LND from "../lnd/lnd.js"
|
||||
import { FlashsatsLSP, LoadLSPSettingsFromEnv, LSPSettings, OlympusLSP, VoltageLSP } from "../lnd/lsp.js"
|
||||
import Storage from '../storage/index.js'
|
||||
import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
|
||||
import { RugPullTracker } from "./rugPullTracker.js"
|
||||
export type LiquiditySettings = {
|
||||
lspSettings: LSPSettings
|
||||
liquidityProviderPub: string
|
||||
|
|
@ -18,6 +20,7 @@ export class LiquidityManager {
|
|||
settings: LiquiditySettings
|
||||
storage: Storage
|
||||
liquidityProvider: LiquidityProvider
|
||||
rugPullTracker: RugPullTracker
|
||||
lnd: LND
|
||||
olympusLSP: OlympusLSP
|
||||
voltageLSP: VoltageLSP
|
||||
|
|
@ -26,31 +29,28 @@ export class LiquidityManager {
|
|||
channelRequested = false
|
||||
channelRequesting = false
|
||||
feesPaid = 0
|
||||
constructor(settings: LiquiditySettings, storage: Storage, liquidityProvider: LiquidityProvider, lnd: LND) {
|
||||
utils: Utils
|
||||
latestDrain: ({ success: true, amt: number } | { success: false, amt: number, attempt: number, at: Date }) = { success: true, amt: 0 }
|
||||
drainsSkipped = 0
|
||||
constructor(settings: LiquiditySettings, storage: Storage, utils: Utils, liquidityProvider: LiquidityProvider, lnd: LND, rugPullTracker: RugPullTracker) {
|
||||
this.settings = settings
|
||||
this.storage = storage
|
||||
this.liquidityProvider = liquidityProvider
|
||||
this.lnd = lnd
|
||||
this.rugPullTracker = rugPullTracker
|
||||
this.utils = utils
|
||||
this.olympusLSP = new OlympusLSP(settings.lspSettings, lnd, liquidityProvider)
|
||||
this.voltageLSP = new VoltageLSP(settings.lspSettings, lnd, liquidityProvider)
|
||||
this.flashsatsLSP = new FlashsatsLSP(settings.lspSettings, lnd, liquidityProvider)
|
||||
}
|
||||
|
||||
GetPaidFees = () => {
|
||||
this.utils.stateBundler.AddBalancePoint('feesPaidForLiquidity', this.feesPaid)
|
||||
return this.feesPaid
|
||||
}
|
||||
|
||||
onNewBlock = async () => {
|
||||
const balance = await this.liquidityProvider.GetLatestMaxWithdrawable()
|
||||
const { remote } = await this.lnd.ChannelBalance()
|
||||
if (remote > balance && balance > 0) {
|
||||
this.log("draining provider balance to channel")
|
||||
const invoice = await this.lnd.NewInvoice(balance, "liqudity provider drain", defaultInvoiceExpiry)
|
||||
const res = await this.liquidityProvider.PayInvoice(invoice.payRequest)
|
||||
const fees = res.network_fee + res.service_fee
|
||||
this.log("drained provider balance to channel", res.amount_paid, "fees paid:", fees)
|
||||
this.feesPaid += fees
|
||||
}
|
||||
await this.shouldDrainProvider()
|
||||
}
|
||||
|
||||
beforeInvoiceCreation = async (amount: number): Promise<'lnd' | 'provider'> => {
|
||||
|
|
@ -58,17 +58,18 @@ export class LiquidityManager {
|
|||
return 'provider'
|
||||
}
|
||||
|
||||
if (this.rugPullTracker.HasProviderRugPulled()) {
|
||||
return 'lnd'
|
||||
}
|
||||
|
||||
const { remote } = await this.lnd.ChannelBalance()
|
||||
if (remote > amount) {
|
||||
this.log("channel has enough balance for invoice")
|
||||
return 'lnd'
|
||||
}
|
||||
const providerCanHandle = this.liquidityProvider.CanProviderHandle({ action: 'receive', amount })
|
||||
if (!providerCanHandle) {
|
||||
return 'lnd'
|
||||
}
|
||||
|
||||
this.log("channel does not have enough balance for invoice,suggesting provider")
|
||||
return 'provider'
|
||||
}
|
||||
|
||||
|
|
@ -80,6 +81,75 @@ export class LiquidityManager {
|
|||
}
|
||||
}
|
||||
|
||||
beforeOutInvoicePayment = async (amount: number): Promise<'lnd' | 'provider'> => {
|
||||
if (this.settings.useOnlyLiquidityProvider) {
|
||||
return 'provider'
|
||||
}
|
||||
const canHandle = await this.liquidityProvider.CanProviderHandle({ action: 'spend', amount })
|
||||
if (canHandle) {
|
||||
return 'provider'
|
||||
}
|
||||
return 'lnd'
|
||||
}
|
||||
afterOutInvoicePaid = async () => { }
|
||||
|
||||
shouldDrainProvider = async () => {
|
||||
const maxW = await this.liquidityProvider.GetLatestMaxWithdrawable()
|
||||
const { remote } = await this.lnd.ChannelBalance()
|
||||
const drainable = Math.min(maxW, remote)
|
||||
if (drainable < 500) {
|
||||
return
|
||||
}
|
||||
if (this.latestDrain.success) {
|
||||
if (this.latestDrain.amt === 0) {
|
||||
await this.drainProvider(drainable)
|
||||
} else {
|
||||
await this.drainProvider(Math.min(drainable, this.latestDrain.amt * 2))
|
||||
}
|
||||
} else if (this.latestDrain.attempt * 10 < this.drainsSkipped) {
|
||||
const drain = Math.min(drainable, Math.ceil(this.latestDrain.amt / 2))
|
||||
this.drainsSkipped = 0
|
||||
if (drain < 500) {
|
||||
this.log("drain attempt went below 500 sats, will start again")
|
||||
this.updateLatestDrain(true, 0)
|
||||
} else {
|
||||
await this.drainProvider(drain)
|
||||
}
|
||||
} else {
|
||||
this.drainsSkipped += 1
|
||||
}
|
||||
}
|
||||
|
||||
drainProvider = async (amt: number) => {
|
||||
try {
|
||||
const invoice = await this.lnd.NewInvoice(amt, "liqudity provider drain", defaultInvoiceExpiry, { from: 'system', useProvider: false })
|
||||
const res = await this.liquidityProvider.PayInvoice(invoice.payRequest, amt, 'system')
|
||||
const fees = res.network_fee + res.service_fee
|
||||
this.feesPaid += fees
|
||||
this.updateLatestDrain(true, amt)
|
||||
} catch (err: any) {
|
||||
this.log("error draining provider balance", err.message || err)
|
||||
this.updateLatestDrain(false, amt)
|
||||
}
|
||||
}
|
||||
|
||||
updateLatestDrain = (success: boolean, amt: number) => {
|
||||
if (this.latestDrain.success) {
|
||||
if (success) {
|
||||
this.latestDrain = { success: true, amt }
|
||||
} else {
|
||||
this.latestDrain = { success: false, amt, attempt: 1, at: new Date() }
|
||||
}
|
||||
} else {
|
||||
if (success) {
|
||||
this.latestDrain = { success: true, amt }
|
||||
} else {
|
||||
this.latestDrain = { success: false, amt, attempt: this.latestDrain.attempt + 1, at: new Date() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
shouldOpenChannel = async (): Promise<{ shouldOpen: false } | { shouldOpen: true, maxSpendable: number }> => {
|
||||
const threshold = this.settings.lspSettings.channelThreshold
|
||||
if (threshold === 0) {
|
||||
|
|
@ -96,12 +166,12 @@ export class LiquidityManager {
|
|||
this.log("pending open channels detected, liquidiity might be on the way")
|
||||
return { shouldOpen: false }
|
||||
}
|
||||
const userState = await this.liquidityProvider.CheckUserState()
|
||||
if (!userState || userState.max_withdrawable < threshold) {
|
||||
this.log("balance of", userState?.max_withdrawable || 0, "is lower than channel threshold of", threshold)
|
||||
const maxW = await this.liquidityProvider.GetLatestMaxWithdrawable()
|
||||
if (maxW < threshold) {
|
||||
this.log("max withdrawable of", maxW, "is lower than channel threshold of", threshold)
|
||||
return { shouldOpen: false }
|
||||
}
|
||||
return { shouldOpen: true, maxSpendable: userState.max_withdrawable }
|
||||
return { shouldOpen: true, maxSpendable: maxW }
|
||||
}
|
||||
|
||||
orderChannelIfNeeded = async () => {
|
||||
|
|
@ -150,19 +220,4 @@ export class LiquidityManager {
|
|||
this.channelRequesting = false
|
||||
this.log("no channel requested")
|
||||
}
|
||||
|
||||
beforeOutInvoicePayment = async (amount: number): Promise<'lnd' | 'provider'> => {
|
||||
if (this.settings.useOnlyLiquidityProvider) {
|
||||
return 'provider'
|
||||
}
|
||||
|
||||
const balance = await this.liquidityProvider.GetLatestMaxWithdrawable(true)
|
||||
if (balance > amount) {
|
||||
this.log("provider has enough balance for payment")
|
||||
return 'provider'
|
||||
}
|
||||
this.log("provider does not have enough balance for payment, suggesting lnd")
|
||||
return 'lnd'
|
||||
}
|
||||
afterOutInvoicePaid = async () => { }
|
||||
}
|
||||
|
|
@ -3,9 +3,11 @@ import { NostrRequest } from '../../../proto/autogenerated/ts/nostr_transport.js
|
|||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||
import { decodeNprofile } from '../../custom-nip19.js'
|
||||
import { getLogger } from '../helpers/logger.js'
|
||||
import { Utils } from '../helpers/utilsWrapper.js'
|
||||
import { NostrEvent, NostrSend } from '../nostr/handler.js'
|
||||
import { relayInit } from '../nostr/tools/relay.js'
|
||||
import { InvoicePaidCb } from './settings.js'
|
||||
import { InvoicePaidCb } from '../lnd/settings.js'
|
||||
import Storage from '../storage/index.js'
|
||||
export type LiquidityRequest = { action: 'spend' | 'receive', amount: number }
|
||||
|
||||
export type nostrCallback<T> = { startedAtMillis: number, type: 'single' | 'stream', f: (res: T) => void }
|
||||
|
|
@ -17,16 +19,18 @@ export class LiquidityProvider {
|
|||
myPub: string = ""
|
||||
log = getLogger({ component: 'liquidityProvider' })
|
||||
nostrSend: NostrSend | null = null
|
||||
ready = false
|
||||
configured = false
|
||||
pubDestination: string
|
||||
latestMaxWithdrawable: number | null = null
|
||||
latestBalance: number | null = null
|
||||
ready: boolean
|
||||
invoicePaidCb: InvoicePaidCb
|
||||
connecting = false
|
||||
readyInterval: NodeJS.Timeout
|
||||
configuredInterval: NodeJS.Timeout
|
||||
queue: ((state: 'ready') => void)[] = []
|
||||
utils: Utils
|
||||
pendingPayments: Record<string, number> = {}
|
||||
// make the sub process accept client
|
||||
constructor(pubDestination: string, invoicePaidCb: InvoicePaidCb) {
|
||||
constructor(pubDestination: string, utils: Utils, invoicePaidCb: InvoicePaidCb) {
|
||||
this.utils = utils
|
||||
if (!pubDestination) {
|
||||
this.log("No pub provider to liquidity provider, will not be initialized")
|
||||
return
|
||||
|
|
@ -39,9 +43,9 @@ export class LiquidityProvider {
|
|||
retrieveNostrUserAuth: async () => this.myPub,
|
||||
}, this.clientSend, this.clientSub)
|
||||
|
||||
this.readyInterval = setInterval(() => {
|
||||
if (this.ready) {
|
||||
clearInterval(this.readyInterval)
|
||||
this.configuredInterval = setInterval(() => {
|
||||
if (this.configured) {
|
||||
clearInterval(this.configuredInterval)
|
||||
this.Connect()
|
||||
}
|
||||
}, 1000)
|
||||
|
|
@ -51,11 +55,15 @@ export class LiquidityProvider {
|
|||
return this.pubDestination
|
||||
}
|
||||
|
||||
IsReady = () => {
|
||||
return this.ready
|
||||
}
|
||||
|
||||
AwaitProviderReady = async (): Promise<'inactive' | 'ready'> => {
|
||||
if (!this.pubDestination) {
|
||||
return 'inactive'
|
||||
}
|
||||
if (this.latestMaxWithdrawable !== null) {
|
||||
if (this.ready) {
|
||||
return 'ready'
|
||||
}
|
||||
return new Promise<'ready'>(res => {
|
||||
|
|
@ -64,16 +72,17 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
Stop = () => {
|
||||
clearInterval(this.readyInterval)
|
||||
clearInterval(this.configuredInterval)
|
||||
}
|
||||
|
||||
Connect = async () => {
|
||||
await new Promise(res => setTimeout(res, 2000))
|
||||
this.log("ready")
|
||||
await this.CheckUserState()
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
const res = await this.GetUserState()
|
||||
if (res.status === 'ERROR') {
|
||||
return
|
||||
}
|
||||
this.ready = true
|
||||
this.queue.forEach(q => q('ready'))
|
||||
this.log("subbing to user operations")
|
||||
this.client.GetLiveUserOperations(res => {
|
||||
|
|
@ -82,94 +91,117 @@ export class LiquidityProvider {
|
|||
this.log("error getting user operations", res.reason)
|
||||
return
|
||||
}
|
||||
this.log("got user operation", res.operation)
|
||||
//this.log("got user operation", res.operation)
|
||||
if (res.operation.type === Types.UserOperationType.INCOMING_INVOICE) {
|
||||
this.log("invoice was paid", res.operation.identifier)
|
||||
this.invoicePaidCb(res.operation.identifier, res.operation.amount, false)
|
||||
this.invoicePaidCb(res.operation.identifier, res.operation.amount, 'provider')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
GetLatestMaxWithdrawable = async (fetch = false) => {
|
||||
if (!this.pubDestination) {
|
||||
return 0
|
||||
}
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
this.log("liquidity provider is not ready yet")
|
||||
return 0
|
||||
}
|
||||
if (fetch) {
|
||||
await this.CheckUserState()
|
||||
}
|
||||
return this.latestMaxWithdrawable || 0
|
||||
}
|
||||
|
||||
GetLatestBalance = async (fetch = false) => {
|
||||
if (!this.pubDestination) {
|
||||
return 0
|
||||
}
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
this.log("liquidity provider is not ready yet")
|
||||
return 0
|
||||
}
|
||||
if (fetch) {
|
||||
await this.CheckUserState()
|
||||
}
|
||||
return this.latestBalance || 0
|
||||
}
|
||||
|
||||
CheckUserState = async () => {
|
||||
GetUserState = async () => {
|
||||
const res = await this.client.GetUserInfo()
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error getting user info", res)
|
||||
return
|
||||
return res
|
||||
}
|
||||
this.latestMaxWithdrawable = res.max_withdrawable
|
||||
this.latestBalance = res.balance
|
||||
this.log("latest provider balance:", res.balance, "latest max withdrawable:", res.max_withdrawable)
|
||||
this.utils.stateBundler.AddBalancePoint('providerBalance', res.balance)
|
||||
this.utils.stateBundler.AddBalancePoint('providerMaxWithdrawable', res.max_withdrawable)
|
||||
return res
|
||||
}
|
||||
|
||||
CanProviderHandle = (req: LiquidityRequest) => {
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
GetLatestMaxWithdrawable = async () => {
|
||||
if (!this.ready) {
|
||||
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 = async () => {
|
||||
if (!this.ready) {
|
||||
return 0
|
||||
}
|
||||
const res = await this.GetUserState()
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error getting user info", res.reason)
|
||||
return 0
|
||||
}
|
||||
return res.balance
|
||||
}
|
||||
|
||||
GetPendingBalance = async () => {
|
||||
return Object.values(this.pendingPayments).reduce((a, b) => a + b, 0)
|
||||
}
|
||||
|
||||
CalculateExpectedFeeLimit = (amount: number, info: Types.UserInfo) => {
|
||||
const serviceFeeRate = info.service_fee_bps / 10000
|
||||
const serviceFee = Math.ceil(serviceFeeRate * amount)
|
||||
const networkMaxFeeRate = info.network_max_fee_bps / 10000
|
||||
const networkFeeLimit = Math.ceil(amount * networkMaxFeeRate + info.network_max_fee_fixed)
|
||||
return serviceFee + networkFeeLimit
|
||||
}
|
||||
|
||||
CanProviderHandle = async (req: LiquidityRequest) => {
|
||||
if (!this.ready) {
|
||||
return false
|
||||
}
|
||||
const maxW = await this.GetLatestMaxWithdrawable()
|
||||
if (req.action === 'spend') {
|
||||
return this.latestMaxWithdrawable > req.amount
|
||||
return maxW > req.amount
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
AddInvoice = async (amount: number, memo: string) => {
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
throw new Error("liquidity provider is not ready yet")
|
||||
AddInvoice = async (amount: number, memo: string, from: 'user' | 'system') => {
|
||||
try {
|
||||
if (!this.ready) {
|
||||
throw new Error("liquidity provider is not ready yet")
|
||||
}
|
||||
const res = await this.client.NewInvoice({ amountSats: amount, memo })
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error creating invoice", res.reason)
|
||||
throw new Error(res.reason)
|
||||
}
|
||||
this.utils.stateBundler.AddTxPoint('addedInvoice', amount, { used: 'provider', from })
|
||||
return res.invoice
|
||||
} catch (err) {
|
||||
this.utils.stateBundler.AddTxPointFailed('addedInvoice', amount, { used: 'provider', from })
|
||||
throw err
|
||||
}
|
||||
const res = await this.client.NewInvoice({ amountSats: amount, memo })
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error creating invoice", res.reason)
|
||||
throw new Error(res.reason)
|
||||
}
|
||||
this.log("new invoice", res.invoice)
|
||||
this.CheckUserState()
|
||||
return res.invoice
|
||||
|
||||
}
|
||||
|
||||
PayInvoice = async (invoice: string) => {
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
throw new Error("liquidity provider is not ready yet")
|
||||
PayInvoice = async (invoice: string, decodedAmount: number, from: 'user' | 'system') => {
|
||||
try {
|
||||
if (!this.ready) {
|
||||
throw new Error("liquidity provider is not ready yet")
|
||||
}
|
||||
const userInfo = await this.GetUserState()
|
||||
if (userInfo.status === 'ERROR') {
|
||||
throw new Error(userInfo.reason)
|
||||
}
|
||||
this.pendingPayments[invoice] = decodedAmount + this.CalculateExpectedFeeLimit(decodedAmount, userInfo)
|
||||
const res = await this.client.PayInvoice({ invoice, amount: 0 })
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error paying invoice", res.reason)
|
||||
throw new Error(res.reason)
|
||||
}
|
||||
delete this.pendingPayments[invoice]
|
||||
this.utils.stateBundler.AddTxPoint('paidAnInvoice', decodedAmount, { used: 'provider', from, timeDiscount: true })
|
||||
return res
|
||||
} catch (err) {
|
||||
delete this.pendingPayments[invoice]
|
||||
this.utils.stateBundler.AddTxPointFailed('paidAnInvoice', decodedAmount, { used: 'provider', from })
|
||||
throw err
|
||||
}
|
||||
const res = await this.client.PayInvoice({ invoice, amount: 0 })
|
||||
if (res.status === 'ERROR') {
|
||||
this.log("error paying invoice", res.reason)
|
||||
throw new Error(res.reason)
|
||||
}
|
||||
this.log("paid invoice", res)
|
||||
this.CheckUserState()
|
||||
return res
|
||||
}
|
||||
|
||||
GetOperations = async () => {
|
||||
if (this.latestMaxWithdrawable === null) {
|
||||
if (!this.ready) {
|
||||
throw new Error("liquidity provider is not ready yet")
|
||||
}
|
||||
const res = await this.client.GetUserOperations({
|
||||
|
|
@ -188,7 +220,7 @@ export class LiquidityProvider {
|
|||
this.log("setting nostr info")
|
||||
this.clientId = clientId
|
||||
this.myPub = myPub
|
||||
this.setSetIfReady()
|
||||
this.setSetIfConfigured()
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -196,13 +228,13 @@ export class LiquidityProvider {
|
|||
attachNostrSend(f: NostrSend) {
|
||||
this.log("attaching nostrSend action")
|
||||
this.nostrSend = f
|
||||
this.setSetIfReady()
|
||||
this.setSetIfConfigured()
|
||||
}
|
||||
|
||||
setSetIfReady = () => {
|
||||
setSetIfConfigured = () => {
|
||||
if (this.nostrSend && !!this.pubDestination && !!this.clientId && !!this.myPub) {
|
||||
this.ready = true
|
||||
this.log("ready to send to ", this.pubDestination)
|
||||
this.configured = true
|
||||
this.log("configured to send to ", this.pubDestination)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -213,10 +245,11 @@ export class LiquidityProvider {
|
|||
}
|
||||
if (this.clientCbs[res.requestId]) {
|
||||
const cb = this.clientCbs[res.requestId]
|
||||
|
||||
cb.f(res)
|
||||
if (cb.type === 'single') {
|
||||
delete this.clientCbs[res.requestId]
|
||||
this.log(this.getSingleSubs(), "single subs left")
|
||||
this.utils.stateBundler.AddMaxPoint('maxProviderRespTime', Date.now() - cb.startedAtMillis)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -224,7 +257,7 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
clientSend = (to: string, message: NostrRequest): Promise<any> => {
|
||||
if (!this.ready || !this.nostrSend) {
|
||||
if (!this.configured || !this.nostrSend) {
|
||||
throw new Error("liquidity provider not initialized")
|
||||
}
|
||||
if (!message.requestId) {
|
||||
|
|
@ -242,7 +275,7 @@ export class LiquidityProvider {
|
|||
|
||||
//this.nostrSend(this.relays, to, JSON.stringify(message), this.settings)
|
||||
|
||||
this.log("subbing to single send", reqId, message.rpcName || 'no rpc name')
|
||||
// this.log("subbing to single send", reqId, message.rpcName || 'no rpc name')
|
||||
return new Promise(res => {
|
||||
this.clientCbs[reqId] = {
|
||||
startedAtMillis: Date.now(),
|
||||
|
|
@ -253,7 +286,7 @@ export class LiquidityProvider {
|
|||
}
|
||||
|
||||
clientSub = (to: string, message: NostrRequest, cb: (res: any) => void): void => {
|
||||
if (!this.ready || !this.nostrSend) {
|
||||
if (!this.configured || !this.nostrSend) {
|
||||
throw new Error("liquidity provider not initialized")
|
||||
}
|
||||
if (!message.requestId) {
|
||||
|
|
@ -15,8 +15,9 @@ import { Event, verifiedSymbol, verifySignature } from '../nostr/tools/event.js'
|
|||
import { AddressReceivingTransaction } from '../storage/entity/AddressReceivingTransaction.js'
|
||||
import { UserTransactionPayment } from '../storage/entity/UserTransactionPayment.js'
|
||||
import { Watchdog } from './watchdog.js'
|
||||
import { LiquidityProvider } from '../lnd/liquidityProvider.js'
|
||||
import { LiquidityProvider } from './liquidityProvider.js'
|
||||
import { LiquidityManager } from './liquidityManager.js'
|
||||
import { Utils } from '../helpers/utilsWrapper.js'
|
||||
interface UserOperationInfo {
|
||||
serial_id: number
|
||||
paid_amount: number
|
||||
|
|
@ -49,12 +50,14 @@ export default class {
|
|||
log = getLogger({ component: "PaymentManager" })
|
||||
watchDog: Watchdog
|
||||
liquidityManager: LiquidityManager
|
||||
constructor(storage: Storage, lnd: LND, settings: MainSettings, liquidityManager: LiquidityManager, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
||||
utils: Utils
|
||||
constructor(storage: Storage, lnd: LND, settings: MainSettings, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
||||
this.storage = storage
|
||||
this.settings = settings
|
||||
this.lnd = lnd
|
||||
this.liquidityManager = liquidityManager
|
||||
this.watchDog = new Watchdog(settings.watchDogSettings, this.liquidityManager, lnd, storage)
|
||||
this.utils = utils
|
||||
this.watchDog = new Watchdog(settings.watchDogSettings, this.liquidityManager, this.lnd, this.storage, this.utils, this.liquidityManager.rugPullTracker)
|
||||
this.addressPaidCb = addressPaidCb
|
||||
this.invoicePaidCb = invoicePaidCb
|
||||
}
|
||||
|
|
@ -113,7 +116,7 @@ export default class {
|
|||
if (existingAddress) {
|
||||
return { address: existingAddress.address }
|
||||
}
|
||||
const res = await this.lnd.NewAddress(req.addressType)
|
||||
const res = await this.lnd.NewAddress(req.addressType, { useProvider: false, from: 'user' })
|
||||
const userAddress = await this.storage.paymentStorage.AddUserAddress(user, res.address, { linkedApplication: app })
|
||||
this.storage.eventsLog.LogEvent({ type: 'new_address', userId: user.user_id, appUserId: "", appId: app.app_id, balance: user.balance_sats, data: res.address, amount: 0 })
|
||||
return { address: userAddress.address }
|
||||
|
|
@ -125,7 +128,7 @@ export default class {
|
|||
throw new Error("user is banned, cannot generate invoice")
|
||||
}
|
||||
const use = await this.liquidityManager.beforeInvoiceCreation(req.amountSats)
|
||||
const res = await this.lnd.NewInvoice(req.amountSats, req.memo, options.expiry, use === 'provider')
|
||||
const res = await this.lnd.NewInvoice(req.amountSats, req.memo, options.expiry, { useProvider: use === 'provider', from: 'user' })
|
||||
const userInvoice = await this.storage.paymentStorage.AddUserInvoice(user, res.payRequest, options, res.providerDst)
|
||||
const appId = options.linkedApplication ? options.linkedApplication.app_id : ""
|
||||
this.storage.eventsLog.LogEvent({ type: 'new_invoice', userId: user.user_id, appUserId: "", appId, balance: user.balance_sats, data: userInvoice.invoice, amount: req.amountSats })
|
||||
|
|
@ -151,7 +154,6 @@ export default class {
|
|||
}
|
||||
|
||||
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication: Application): Promise<Types.PayInvoiceResponse> {
|
||||
this.log("paying invoice", req.invoice, "for user", userId, "with amount", req.amount)
|
||||
await this.watchDog.PaymentRequested()
|
||||
const maybeBanned = await this.storage.userStorage.GetUser(userId)
|
||||
if (maybeBanned.locked) {
|
||||
|
|
@ -201,14 +203,12 @@ export default class {
|
|||
}
|
||||
const { amountForLnd, payAmount, serviceFee } = amounts
|
||||
const totalAmountToDecrement = payAmount + serviceFee
|
||||
this.log("paying external invoice", invoice)
|
||||
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
|
||||
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + routingFeeLimit, invoice)
|
||||
const pendingPayment = await this.storage.paymentStorage.AddPendingExternalPayment(userId, invoice, payAmount, linkedApplication)
|
||||
const use = await this.liquidityManager.beforeOutInvoicePayment(payAmount)
|
||||
try {
|
||||
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit, use === 'provider')
|
||||
|
||||
const payment = await this.lnd.PayInvoice(invoice, amountForLnd, routingFeeLimit, payAmount, { useProvider: use === 'provider', from: 'user' })
|
||||
if (routingFeeLimit - payment.feeSat > 0) {
|
||||
this.log("refund routing fee", routingFeeLimit, payment.feeSat, "sats")
|
||||
await this.storage.userStorage.IncrementUserBalance(userId, routingFeeLimit - payment.feeSat, "routing_fee_refund:" + invoice)
|
||||
|
|
@ -225,16 +225,24 @@ export default class {
|
|||
}
|
||||
|
||||
async PayInternalInvoice(userId: string, internalInvoice: UserReceivingInvoice, amounts: { payAmount: number, serviceFee: number }, linkedApplication: Application) {
|
||||
this.log("paying internal invoice", internalInvoice.invoice)
|
||||
if (internalInvoice.paid_at_unix > 0) {
|
||||
throw new Error("this invoice was already paid")
|
||||
}
|
||||
const { payAmount, serviceFee } = amounts
|
||||
const totalAmountToDecrement = payAmount + serviceFee
|
||||
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement, internalInvoice.invoice)
|
||||
this.invoicePaidCb(internalInvoice.invoice, payAmount, true)
|
||||
const newPayment = await this.storage.paymentStorage.AddInternalPayment(userId, internalInvoice.invoice, payAmount, serviceFee, linkedApplication)
|
||||
return { preimage: "", amtPaid: payAmount, networkFee: 0, serialId: newPayment.serial_id }
|
||||
try {
|
||||
await this.invoicePaidCb(internalInvoice.invoice, payAmount, 'internal')
|
||||
const newPayment = await this.storage.paymentStorage.AddInternalPayment(userId, internalInvoice.invoice, payAmount, serviceFee, linkedApplication)
|
||||
this.utils.stateBundler.AddTxPoint('paidAnInvoice', payAmount, { used: 'internal', from: 'user' })
|
||||
return { preimage: "", amtPaid: payAmount, networkFee: 0, serialId: newPayment.serial_id }
|
||||
} catch (err) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userId, totalAmountToDecrement, "internal_payment_refund:" + internalInvoice.invoice)
|
||||
this.utils.stateBundler.AddTxPointFailed('paidAnInvoice', payAmount, { used: 'internal', from: 'user' })
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -262,7 +270,7 @@ export default class {
|
|||
// WARNING, before re-enabling this, make sure to add the tx_hash to the DecrementUserBalance "reason"!!
|
||||
this.storage.userStorage.DecrementUserBalance(ctx.user_id, total + serviceFee, req.address)
|
||||
try {
|
||||
const payment = await this.lnd.PayAddress(req.address, req.amoutSats, req.satsPerVByte)
|
||||
const payment = await this.lnd.PayAddress(req.address, req.amoutSats, req.satsPerVByte, "", { useProvider: false, from: 'user' })
|
||||
txId = payment.txid
|
||||
} catch (err) {
|
||||
// WARNING, before re-enabling this, make sure to add the tx_hash to the IncrementUserBalance "reason"!!
|
||||
|
|
@ -274,7 +282,7 @@ export default class {
|
|||
txId = crypto.randomBytes(32).toString("hex")
|
||||
const addressData = `${req.address}:${txId}`
|
||||
await this.storage.userStorage.DecrementUserBalance(ctx.user_id, req.amoutSats + serviceFee, addressData)
|
||||
this.addressPaidCb({ hash: txId, index: 0 }, req.address, req.amoutSats, true)
|
||||
this.addressPaidCb({ hash: txId, index: 0 }, req.address, req.amoutSats, 'internal')
|
||||
}
|
||||
|
||||
if (isAppUserPayment && serviceFee > 0) {
|
||||
|
|
@ -360,11 +368,9 @@ export default class {
|
|||
if (this.isDefaultServiceUrl()) {
|
||||
throw new Error("Lnurl not enabled. Make sure to set SERVICE_URL env variable")
|
||||
}
|
||||
getLogger({})("getting lnurl pay link")
|
||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||
const key = await this.storage.paymentStorage.AddUserEphemeralKey(ctx.user_id, 'pay', app)
|
||||
const lnurl = this.encodeLnurl(this.lnurlPayUrl(key.key))
|
||||
getLogger({})("got lnurl pay link: ", lnurl)
|
||||
return {
|
||||
lnurl,
|
||||
k1: key.key
|
||||
|
|
|
|||
80
src/services/main/rugPullTracker.ts
Normal file
80
src/services/main/rugPullTracker.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import FunctionQueue from "../helpers/functionQueue.js";
|
||||
import { getLogger } from "../helpers/logger.js";
|
||||
import { Utils } from "../helpers/utilsWrapper.js";
|
||||
import { LiquidityProvider } from "./liquidityProvider.js";
|
||||
import { TrackedProvider } from "../storage/entity/TrackedProvider.js";
|
||||
import Storage from "../storage/index.js";
|
||||
|
||||
export class RugPullTracker {
|
||||
liquidProvider: LiquidityProvider
|
||||
storage: Storage
|
||||
log = getLogger({ component: "rugPullTracker" })
|
||||
rugPulled = false
|
||||
constructor(storage: Storage, liquidProvider: LiquidityProvider) {
|
||||
this.liquidProvider = liquidProvider
|
||||
this.storage = storage
|
||||
}
|
||||
|
||||
HasProviderRugPulled = () => {
|
||||
return this.rugPulled
|
||||
}
|
||||
|
||||
CheckProviderBalance = async (): Promise<{ balance: number, prevBalance?: number }> => {
|
||||
const pubDst = this.liquidProvider.GetProviderDestination()
|
||||
if (!pubDst) {
|
||||
return { balance: 0 }
|
||||
}
|
||||
const fetchedBalance = await this.liquidProvider.GetLatestBalance()
|
||||
const pendingBalance = await this.liquidProvider.GetPendingBalance()
|
||||
const providerTracker = await this.storage.liquidityStorage.GetTrackedProvider('lnPub', pubDst)
|
||||
const balance = this.liquidProvider.IsReady() ? fetchedBalance : providerTracker?.latest_balance || 0
|
||||
const trackedBalance = balance + pendingBalance
|
||||
if (!providerTracker) {
|
||||
this.log("starting to track provider", this.liquidProvider.GetProviderDestination())
|
||||
await this.storage.liquidityStorage.CreateTrackedProvider('lnPub', pubDst, trackedBalance)
|
||||
return { balance: trackedBalance }
|
||||
}
|
||||
if (providerTracker.latest_balance !== trackedBalance) {
|
||||
return this.handleBalanceMismatch(pubDst, trackedBalance, providerTracker)
|
||||
}
|
||||
this.rugPulled = false
|
||||
return { balance: trackedBalance }
|
||||
}
|
||||
|
||||
handleBalanceMismatch = async (pubDst: string, trackedBalance: number, providerTracker: TrackedProvider) => {
|
||||
const diff = trackedBalance - providerTracker.latest_balance
|
||||
if (diff < 0) {
|
||||
getLogger({ component: 'rugPull' })(pubDst, "provider balance changed from", providerTracker.latest_balance, "to", trackedBalance, "losing", diff)
|
||||
this.rugPulled = true
|
||||
if (providerTracker.latest_distruption_at_unix === 0) {
|
||||
await this.storage.liquidityStorage.UpdateTrackedProviderDisruption('lnPub', pubDst, Math.floor(Date.now() / 1000))
|
||||
}
|
||||
} else {
|
||||
getLogger({ component: 'rugPush' })(pubDst, "provider balance changed from", providerTracker.latest_balance, "to", trackedBalance, "gaining", diff)
|
||||
this.rugPulled = false
|
||||
if (providerTracker.latest_distruption_at_unix !== 0) {
|
||||
await this.storage.liquidityStorage.UpdateTrackedProviderDisruption('lnPub', pubDst, 0)
|
||||
}
|
||||
}
|
||||
return { balance: trackedBalance, prevBalance: providerTracker.latest_balance }
|
||||
}
|
||||
|
||||
updateDisruption = async (pubDst: string, trackedBalance: number, providerTracker: TrackedProvider, diff: number) => {
|
||||
if (diff < 0) {
|
||||
if (providerTracker.latest_distruption_at_unix === 0) {
|
||||
await this.storage.liquidityStorage.UpdateTrackedProviderDisruption('lnPub', pubDst, Math.floor(Date.now() / 1000))
|
||||
getLogger({ component: 'rugPull' })("detected rugpull from: ", pubDst, "provider balance changed from", providerTracker.latest_balance, "to", trackedBalance, "losing", diff)
|
||||
} else {
|
||||
getLogger({ component: 'rugPull' })("ongoing rugpull from: ", pubDst, "provider balance changed from", providerTracker.latest_balance, "to", trackedBalance, "losing", diff)
|
||||
}
|
||||
} else {
|
||||
if (providerTracker.latest_distruption_at_unix !== 0) {
|
||||
await this.storage.liquidityStorage.UpdateTrackedProviderDisruption('lnPub', pubDst, 0)
|
||||
getLogger({ component: 'rugPull' })("rugpull from: ", pubDst, "cleared after: ", (Date.now() / 1000) - providerTracker.latest_distruption_at_unix, "seconds")
|
||||
}
|
||||
if (diff > 0) {
|
||||
this.log("detected excees from: ", pubDst, "provider balance changed from", providerTracker.latest_balance, "to", trackedBalance, "gaining", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ export type MainSettings = {
|
|||
incomingAppUserInvoiceFee: number
|
||||
outgoingAppInvoiceFee: number
|
||||
outgoingAppUserInvoiceFee: number
|
||||
outgoingAppUserInvoiceFeeBps: number
|
||||
userToUserFee: number
|
||||
appToUserFee: number
|
||||
serviceUrl: string
|
||||
|
|
@ -39,6 +40,7 @@ export type BitcoinCoreSettings = {
|
|||
export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettings, thirdNode: NodeSettings, fourthNode: NodeSettings }, bitcoinCoreSettings: BitcoinCoreSettings }
|
||||
export const LoadMainSettingsFromEnv = (): MainSettings => {
|
||||
const storageSettings = LoadStorageSettingsFromEnv()
|
||||
const outgoingAppUserInvoiceFeeBps = EnvCanBeInteger("OUTGOING_INVOICE_FEE_USER_BPS", 0)
|
||||
return {
|
||||
watchDogSettings: LoadWatchdogSettingsFromEnv(),
|
||||
lndSettings: LoadLndSettingsFromEnv(),
|
||||
|
|
@ -52,7 +54,8 @@ export const LoadMainSettingsFromEnv = (): MainSettings => {
|
|||
incomingAppInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_ROOT_BPS", 0) / 10000,
|
||||
outgoingAppInvoiceFee: EnvCanBeInteger("OUTGOING_INVOICE_FEE_ROOT_BPS", 60) / 10000,
|
||||
incomingAppUserInvoiceFee: EnvCanBeInteger("INCOMING_INVOICE_FEE_USER_BPS", 0) / 10000,
|
||||
outgoingAppUserInvoiceFee: EnvCanBeInteger("OUTGOING_INVOICE_FEE_USER_BPS", 0) / 10000,
|
||||
outgoingAppUserInvoiceFeeBps,
|
||||
outgoingAppUserInvoiceFee: outgoingAppUserInvoiceFeeBps / 10000,
|
||||
userToUserFee: EnvCanBeInteger("TX_FEE_INTERNAL_USER_BPS", 0) / 10000,
|
||||
appToUserFee: EnvCanBeInteger("TX_FEE_INTERNAL_ROOT_BPS", 0) / 10000,
|
||||
serviceUrl: process.env.SERVICE_URL || `http://localhost:${EnvCanBeInteger("PORT", 1776)}`,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export class Unlocker {
|
|||
await unlocker.unlockWallet({ walletPassword, recoveryWindow: 0, statelessInit: false, channelBackups: undefined }, DeadLineMetadata())
|
||||
const infoAfter = await this.GetLndInfo(ln)
|
||||
if (!infoAfter.ok) {
|
||||
throw new Error("failed to init lnd wallet " + infoAfter.failure)
|
||||
throw new Error("failed to unlock lnd wallet " + infoAfter.failure)
|
||||
}
|
||||
this.log("unlocked wallet with pub:", infoAfter.pub)
|
||||
return { ln, pub: infoAfter.pub }
|
||||
|
|
@ -74,8 +74,6 @@ export class Unlocker {
|
|||
aezeedPassphrase: Buffer.alloc(0),
|
||||
seedEntropy: entropy
|
||||
}, DeadLineMetadata())
|
||||
console.log(seedRes.response.cipherSeedMnemonic)
|
||||
console.log(seedRes.response.encipheredSeed)
|
||||
this.log("seed created, encrypting and saving...")
|
||||
const { encryptedData } = this.EncryptWalletSeed(seedRes.response.cipherSeedMnemonic)
|
||||
const walletPw = this.GetWalletPassword()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { EnvCanBeInteger } from "../helpers/envParser.js";
|
||||
import FunctionQueue from "../helpers/functionQueue.js";
|
||||
import { getLogger } from "../helpers/logger.js";
|
||||
import { LiquidityProvider } from "../lnd/liquidityProvider.js";
|
||||
import { Utils } from "../helpers/utilsWrapper.js";
|
||||
import { LiquidityProvider } from "./liquidityProvider.js";
|
||||
import LND from "../lnd/lnd.js";
|
||||
import { ChannelBalance } from "../lnd/settings.js";
|
||||
import Storage from '../storage/index.js'
|
||||
import { LiquidityManager } from "./liquidityManager.js";
|
||||
import { RugPullTracker } from "./rugPullTracker.js";
|
||||
export type WatchdogSettings = {
|
||||
maxDiffSats: number
|
||||
}
|
||||
|
|
@ -26,16 +28,21 @@ export class Watchdog {
|
|||
liquidityManager: LiquidityManager;
|
||||
settings: WatchdogSettings;
|
||||
storage: Storage;
|
||||
rugPullTracker: RugPullTracker
|
||||
utils: Utils
|
||||
latestCheckStart = 0
|
||||
log = getLogger({ component: "watchdog" })
|
||||
ready = false
|
||||
interval: NodeJS.Timer;
|
||||
constructor(settings: WatchdogSettings, liquidityManager: LiquidityManager, lnd: LND, storage: Storage) {
|
||||
lndPubKey: string;
|
||||
constructor(settings: WatchdogSettings, liquidityManager: LiquidityManager, lnd: LND, storage: Storage, utils: Utils, rugPullTracker: RugPullTracker) {
|
||||
this.lnd = lnd;
|
||||
this.settings = settings;
|
||||
this.storage = storage;
|
||||
this.liquidProvider = lnd.liquidProvider
|
||||
this.liquidityManager = liquidityManager
|
||||
this.utils = utils
|
||||
this.rugPullTracker = rugPullTracker
|
||||
this.queue = new FunctionQueue("watchdog_queue", () => this.StartCheck())
|
||||
}
|
||||
|
||||
|
|
@ -45,33 +52,22 @@ export class Watchdog {
|
|||
}
|
||||
}
|
||||
Start = async () => {
|
||||
const result = await Promise.race([
|
||||
this.liquidProvider.AwaitProviderReady(),
|
||||
new Promise<'failed'>((res, rej) => {
|
||||
setTimeout(() => {
|
||||
this.log("Provider did not become ready in time, starting without it")
|
||||
res('failed')
|
||||
}, 3 * 60 * 1000)
|
||||
})
|
||||
])
|
||||
|
||||
let providerBalance = 0
|
||||
if (result === 'ready') {
|
||||
providerBalance = await this.liquidProvider.GetLatestBalance()
|
||||
}
|
||||
try {
|
||||
await this.StartWatching(providerBalance)
|
||||
await this.StartWatching()
|
||||
} catch (err: any) {
|
||||
this.log("Failed to start watchdog", err.message || err)
|
||||
throw err
|
||||
}
|
||||
|
||||
}
|
||||
StartWatching = async (providerBalance: number) => {
|
||||
StartWatching = async () => {
|
||||
this.log("Starting watchdog")
|
||||
this.startedAtUnix = Math.floor(Date.now() / 1000)
|
||||
const info = await this.lnd.GetInfo()
|
||||
this.lndPubKey = info.identityPubkey
|
||||
await this.getTracker()
|
||||
const totalUsersBalance = await this.storage.paymentStorage.GetTotalUsersBalance()
|
||||
this.initialLndBalance = await this.getTotalLndBalance(totalUsersBalance, providerBalance)
|
||||
this.utils.stateBundler.AddBalancePoint('usersBalance', totalUsersBalance)
|
||||
this.initialLndBalance = await this.getAggregatedExternalBalance()
|
||||
this.initialUsersBalance = totalUsersBalance
|
||||
const fwEvents = await this.lnd.GetForwardingHistory(0, this.startedAtUnix)
|
||||
this.latestIndexOffset = fwEvents.lastOffsetIndex
|
||||
|
|
@ -79,7 +75,6 @@ export class Watchdog {
|
|||
|
||||
this.interval = setInterval(() => {
|
||||
if (this.latestCheckStart + (1000 * 60) < Date.now()) {
|
||||
this.log("No balance check was made in the last minute, checking now")
|
||||
this.PaymentRequested()
|
||||
}
|
||||
}, 1000 * 60)
|
||||
|
|
@ -96,49 +91,42 @@ export class Watchdog {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
getTotalLndBalance = async (usersTotal: number, providerBalance: number) => {
|
||||
const walletBalance = await this.lnd.GetWalletBalance()
|
||||
this.log(Number(walletBalance.confirmedBalance), "sats in chain wallet")
|
||||
const channelsBalance = await this.lnd.GetChannelBalance()
|
||||
// getLogger({ component: "debugLndBalancev3" })({ w: walletBalance, c: channelsBalance, u: usersTotal, f: this.accumulatedHtlcFees })
|
||||
const totalLightningBalanceMsats = (channelsBalance.localBalance?.msat || 0n) + (channelsBalance.unsettledLocalBalance?.msat || 0n)
|
||||
const totalLightningBalance = Math.ceil(Number(totalLightningBalanceMsats) / 1000)
|
||||
getAggregatedExternalBalance = async () => {
|
||||
const totalLndBalance = await this.lnd.GetTotalBalace()
|
||||
const feesPaidForLiquidity = this.liquidityManager.GetPaidFees()
|
||||
return Number(walletBalance.confirmedBalance) + totalLightningBalance + providerBalance + feesPaidForLiquidity
|
||||
const pb = await this.rugPullTracker.CheckProviderBalance()
|
||||
const providerBalance = pb.prevBalance || pb.balance
|
||||
return totalLndBalance + providerBalance + feesPaidForLiquidity
|
||||
}
|
||||
|
||||
checkBalanceUpdate = (deltaLnd: number, deltaUsers: number) => {
|
||||
this.log("LND balance update:", deltaLnd, "sats since app startup")
|
||||
this.log("Users balance update:", deltaUsers, "sats since app startup")
|
||||
checkBalanceUpdate = async (deltaLnd: number, deltaUsers: number) => {
|
||||
this.utils.stateBundler.AddBalancePoint('deltaLnd', deltaLnd)
|
||||
this.utils.stateBundler.AddBalancePoint('deltaUsers', deltaUsers)
|
||||
|
||||
const result = this.checkDeltas(deltaLnd, deltaUsers)
|
||||
switch (result.type) {
|
||||
case 'mismatch':
|
||||
if (deltaLnd < 0) {
|
||||
this.log("WARNING! LND balance decreased while users balance increased creating a difference of", result.absoluteDiff, "sats")
|
||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||
this.log("Difference is too big for an update, locking outgoing operations")
|
||||
await this.updateDisruption(true, result.absoluteDiff)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
this.log("LND balance increased while users balance decreased creating a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||
this.updateDisruption(false, result.absoluteDiff)
|
||||
return false
|
||||
}
|
||||
break
|
||||
case 'negative':
|
||||
if (Math.abs(deltaLnd) > Math.abs(deltaUsers)) {
|
||||
this.log("WARNING! LND balance decreased more than users balance with a difference of", result.absoluteDiff, "sats")
|
||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||
this.log("Difference is too big for an update, locking outgoing operations")
|
||||
await this.updateDisruption(true, result.absoluteDiff)
|
||||
return true
|
||||
}
|
||||
} else if (deltaLnd === deltaUsers) {
|
||||
this.log("LND and users balance went both DOWN consistently")
|
||||
await this.updateDisruption(false, 0)
|
||||
return false
|
||||
} else {
|
||||
this.log("LND balance decreased less than users balance with a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||
await this.updateDisruption(false, result.absoluteDiff)
|
||||
return false
|
||||
}
|
||||
break
|
||||
|
|
@ -146,29 +134,49 @@ export class Watchdog {
|
|||
if (deltaLnd < deltaUsers) {
|
||||
this.log("WARNING! LND balance increased less than users balance with a difference of", result.absoluteDiff, "sats")
|
||||
if (result.absoluteDiff > this.settings.maxDiffSats) {
|
||||
this.log("Difference is too big for an update, locking outgoing operations")
|
||||
await this.updateDisruption(true, result.absoluteDiff)
|
||||
return true
|
||||
}
|
||||
} else if (deltaLnd === deltaUsers) {
|
||||
this.log("LND and users balance went both UP consistently")
|
||||
await this.updateDisruption(false, 0)
|
||||
return false
|
||||
} else {
|
||||
this.log("LND balance increased more than users balance with a difference of", result.absoluteDiff, "sats, could be caused by data loss, or liquidity injection")
|
||||
await this.updateDisruption(false, result.absoluteDiff)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
updateDisruption = async (isDisrupted: boolean, absoluteDiff: number) => {
|
||||
const tracker = await this.getTracker()
|
||||
if (isDisrupted) {
|
||||
if (tracker.latest_distruption_at_unix === 0) {
|
||||
await this.storage.liquidityStorage.UpdateTrackedProviderDisruption('lnd', this.lndPubKey, Math.floor(Date.now() / 1000))
|
||||
getLogger({ component: 'bark' })("detected lnd loss of", absoluteDiff, "sats,", absoluteDiff - this.settings.maxDiffSats, "above the max allowed")
|
||||
} else {
|
||||
getLogger({ component: 'bark' })("ongoing lnd loss of", absoluteDiff, "sats,", absoluteDiff - this.settings.maxDiffSats, "above the max allowed")
|
||||
}
|
||||
} else {
|
||||
if (tracker.latest_distruption_at_unix !== 0) {
|
||||
await this.storage.liquidityStorage.UpdateTrackedProviderDisruption('lnd', this.lndPubKey, 0)
|
||||
getLogger({ component: 'bark' })("loss cleared after: ", (Date.now() / 1000) - tracker.latest_distruption_at_unix, "seconds")
|
||||
} else if (absoluteDiff > 0) {
|
||||
this.log("lnd balance increased more than users balance by", absoluteDiff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StartCheck = async () => {
|
||||
this.latestCheckStart = Date.now()
|
||||
await this.updateAccumulatedHtlcFees()
|
||||
const totalUsersBalance = await this.storage.paymentStorage.GetTotalUsersBalance()
|
||||
const providerBalance = await this.liquidProvider.GetLatestBalance()
|
||||
const totalLndBalance = await this.getTotalLndBalance(totalUsersBalance, providerBalance)
|
||||
this.utils.stateBundler.AddBalancePoint('usersBalance', totalUsersBalance)
|
||||
const totalLndBalance = await this.getAggregatedExternalBalance()
|
||||
this.utils.stateBundler.AddBalancePoint('accumulatedHtlcFees', this.accumulatedHtlcFees)
|
||||
const deltaLnd = totalLndBalance - (this.initialLndBalance + this.accumulatedHtlcFees)
|
||||
const deltaUsers = totalUsersBalance - this.initialUsersBalance
|
||||
const deny = this.checkBalanceUpdate(deltaLnd, deltaUsers)
|
||||
const deny = await this.checkBalanceUpdate(deltaLnd, deltaUsers)
|
||||
if (deny) {
|
||||
this.log("Balance mismatch detected in absolute update, locking outgoing operations")
|
||||
this.lnd.LockOutgoingOperations()
|
||||
|
|
@ -178,7 +186,6 @@ export class Watchdog {
|
|||
}
|
||||
|
||||
PaymentRequested = async () => {
|
||||
this.log("Payment requested, checking balance")
|
||||
if (!this.ready) {
|
||||
throw new Error("Watchdog not ready")
|
||||
}
|
||||
|
|
@ -206,5 +213,13 @@ export class Watchdog {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTracker = async () => {
|
||||
const tracker = await this.storage.liquidityStorage.GetTrackedProvider('lnd', this.lndPubKey)
|
||||
if (!tracker) {
|
||||
return this.storage.liquidityStorage.CreateTrackedProvider('lnd', this.lndPubKey, 0)
|
||||
}
|
||||
return tracker
|
||||
}
|
||||
}
|
||||
type DeltaCheckResult = { type: 'negative' | 'positive', absoluteDiff: number, relativeDiff: number } | { type: 'mismatch', absoluteDiff: number }
|
||||
|
|
@ -18,10 +18,9 @@ export default class HtlcTracker {
|
|||
}
|
||||
log = getLogger({ component: 'htlcTracker' })
|
||||
onHtlcEvent = async (htlc: HtlcEvent) => {
|
||||
getLogger({ component: 'debugHtlcs' })(htlc)
|
||||
//getLogger({ component: 'debugHtlcs' })(htlc)
|
||||
const htlcEvent = htlc.event
|
||||
if (htlcEvent.oneofKind === 'subscribedEvent') {
|
||||
this.log("htlc subscribed")
|
||||
return
|
||||
}
|
||||
const outgoingHtlcId = Number(htlc.outgoingHtlcId)
|
||||
|
|
@ -45,12 +44,11 @@ export default class HtlcTracker {
|
|||
case 'settleEvent':
|
||||
return this.handleSuccess(info)
|
||||
default:
|
||||
this.log("unknown htlc event type")
|
||||
//this.log("unknown htlc event type")
|
||||
}
|
||||
}
|
||||
|
||||
handleForward = (fwe: ForwardEvent, { eventType, outgoingHtlcId, incomingHtlcId }: EventInfo) => {
|
||||
this.log("new forward event, currently tracked htlcs: (s,r,f)", this.pendingSendHtlcs.size, this.pendingReceiveHtlcs.size, this.pendingForwardHtlcs.size)
|
||||
const { info } = fwe
|
||||
const incomingAmtMsat = info ? Number(info.incomingAmtMsat) : 0
|
||||
const outgoingAmtMsat = info ? Number(info.outgoingAmtMsat) : 0
|
||||
|
|
@ -60,8 +58,6 @@ export default class HtlcTracker {
|
|||
this.pendingReceiveHtlcs.set(incomingHtlcId, incomingAmtMsat - outgoingAmtMsat)
|
||||
} else if (eventType === HtlcEvent_EventType.FORWARD) {
|
||||
this.pendingForwardHtlcs.set(outgoingHtlcId, outgoingAmtMsat - incomingAmtMsat)
|
||||
} else {
|
||||
this.log("unknown htlc event type for forward event")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +86,6 @@ export default class HtlcTracker {
|
|||
return this.incrementReceiveFailures(incomingChannelId)
|
||||
}
|
||||
}
|
||||
this.log("unknown htlc event type for failure event", eventType)
|
||||
}
|
||||
|
||||
handleSuccess = ({ eventType, outgoingHtlcId, incomingHtlcId }: EventInfo) => {
|
||||
|
|
@ -104,8 +99,6 @@ export default class HtlcTracker {
|
|||
if (this.deleteMapEntry(outgoingHtlcId, this.pendingSendHtlcs) !== null) return
|
||||
if (this.deleteMapEntry(incomingHtlcId, this.pendingReceiveHtlcs) !== null) return
|
||||
if (this.deleteMapEntry(outgoingHtlcId, this.pendingForwardHtlcs) !== null) return
|
||||
} else {
|
||||
this.log("unknown htlc event type for success event", eventType)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ const send = (message: ChildProcessResponse) => {
|
|||
process.send(message, undefined, undefined, err => {
|
||||
if (err) {
|
||||
getLogger({ component: "nostrMiddleware" })(ERROR, "failed to send message to parent process", err, "message:", message)
|
||||
throw new Error("failed to send message to parent process")
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { ChannelRouting } from "./entity/ChannelRouting.js"
|
|||
import { LspOrder } from "./entity/LspOrder.js"
|
||||
import { Product } from "./entity/Product.js"
|
||||
import { LndNodeInfo } from "./entity/LndNodeInfo.js"
|
||||
import { TrackedProvider } from "./entity/TrackedProvider.js"
|
||||
|
||||
|
||||
export type DbSettings = {
|
||||
|
|
@ -58,7 +59,7 @@ export default async (settings: DbSettings, migrations: Function[]): Promise<{ s
|
|||
database: settings.databaseFile,
|
||||
// logging: true,
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo],
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider],
|
||||
//synchronize: true,
|
||||
migrations
|
||||
}).initialize()
|
||||
|
|
|
|||
26
src/services/storage/entity/TrackedProvider.ts
Normal file
26
src/services/storage/entity/TrackedProvider.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index } from "typeorm"
|
||||
|
||||
@Entity()
|
||||
@Index("tracked_provider_unique", ["provider_type", "provider_pubkey"], { unique: true })
|
||||
export class TrackedProvider {
|
||||
@PrimaryGeneratedColumn()
|
||||
serial_id: number
|
||||
|
||||
@Column()
|
||||
provider_type: 'lnd' | 'lnPub'
|
||||
|
||||
@Column()
|
||||
provider_pubkey: string
|
||||
|
||||
@Column()
|
||||
latest_balance: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
latest_distruption_at_unix: number
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ export default class EventsLogManager {
|
|||
|
||||
|
||||
LogEvent = (e: Omit<LoggedEvent, 'timestampMs'>) => {
|
||||
this.log(e.type, "->", e.userId, "->", e.appId, "->", e.appUserId, "->", e.balance, "->", e.data, "->", e.amount)
|
||||
//this.log(e.type, "->", e.userId, "->", e.appId, "->", e.appUserId, "->", e.balance, "->", e.data, "->", e.amount)
|
||||
this.write([Date.now(), e.userId, e.appUserId, e.appId, e.balance, e.type, e.data, e.amount])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import MetricsStorage from "./metricsStorage.js";
|
|||
import TransactionsQueue, { TX } from "./transactionsQueue.js";
|
||||
import EventsLogManager from "./eventsLog.js";
|
||||
import { LiquidityStorage } from "./liquidityStorage.js";
|
||||
import { StateBundler } from "./stateBundler.js";
|
||||
export type StorageSettings = {
|
||||
dbSettings: DbSettings
|
||||
eventLogPath: string
|
||||
|
|
@ -27,6 +28,7 @@ export default class {
|
|||
metricsStorage: MetricsStorage
|
||||
liquidityStorage: LiquidityStorage
|
||||
eventsLog: EventsLogManager
|
||||
stateBundler: StateBundler
|
||||
constructor(settings: StorageSettings) {
|
||||
this.settings = settings
|
||||
this.eventsLog = new EventsLogManager(settings.eventLogPath)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { DataSource, EntityManager, MoreThan } from "typeorm"
|
|||
import { LspOrder } from "./entity/LspOrder.js";
|
||||
import TransactionsQueue, { TX } from "./transactionsQueue.js";
|
||||
import { LndNodeInfo } from "./entity/LndNodeInfo.js";
|
||||
import { TrackedProvider } from "./entity/TrackedProvider.js";
|
||||
export class LiquidityStorage {
|
||||
DB: DataSource | EntityManager
|
||||
txQueue: TransactionsQueue
|
||||
|
|
@ -37,4 +38,18 @@ export class LiquidityStorage {
|
|||
const entry = this.DB.getRepository(LndNodeInfo).create({ pubkey, backup })
|
||||
await this.txQueue.PushToQueue<LndNodeInfo>({ exec: async db => db.getRepository(LndNodeInfo).save(entry), dbTx: false })
|
||||
}
|
||||
|
||||
async GetTrackedProvider(providerType: 'lnd' | 'lnPub', pub: string) {
|
||||
return this.DB.getRepository(TrackedProvider).findOne({ where: { provider_pubkey: pub, provider_type: providerType } })
|
||||
}
|
||||
async CreateTrackedProvider(providerType: 'lnd' | 'lnPub', pub: string, latestBalance = 0) {
|
||||
const entry = this.DB.getRepository(TrackedProvider).create({ provider_pubkey: pub, provider_type: providerType, latest_balance: latestBalance })
|
||||
return this.txQueue.PushToQueue<TrackedProvider>({ exec: async db => db.getRepository(TrackedProvider).save(entry), dbTx: false })
|
||||
}
|
||||
async UpdateTrackedProviderBalance(providerType: 'lnd' | 'lnPub', pub: string, latestBalance: number) {
|
||||
return this.DB.getRepository(TrackedProvider).update({ provider_pubkey: pub, provider_type: providerType }, { latest_balance: latestBalance })
|
||||
}
|
||||
async UpdateTrackedProviderDisruption(providerType: 'lnd' | 'lnPub', pub: string, latestDisruptionAtUnix: number) {
|
||||
return this.DB.getRepository(TrackedProvider).update({ provider_pubkey: pub, provider_type: providerType }, { latest_distruption_at_unix: latestDisruptionAtUnix })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TrackedProvider1720814323679 implements MigrationInterface {
|
||||
name = 'TrackedProvider1720814323679'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "tracked_provider" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "provider_type" varchar NOT NULL, "provider_pubkey" varchar NOT NULL, "latest_balance" integer NOT NULL, "latest_distruption_at_unix" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "tracked_provider_unique" ON "tracked_provider" ("provider_type", "provider_pubkey") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "tracked_provider_unique"`);
|
||||
await queryRunner.query(`DROP TABLE "tracked_provider"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,7 +7,8 @@ import { ChannelRouting1709316653538 } from './1709316653538-channel_routing.js'
|
|||
import { LspOrder1718387847693 } from './1718387847693-lsp_order.js'
|
||||
import { LiquidityProvider1719335699480 } from './1719335699480-liquidity_provider.js'
|
||||
import { LndNodeInfo1720187506189 } from './1720187506189-lnd_node_info.js'
|
||||
const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189]
|
||||
import { TrackedProvider1720814323679 } from './1720814323679-tracked_provider.js'
|
||||
const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, TrackedProvider1720814323679]
|
||||
const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538]
|
||||
export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
|
||||
if (arg === 'fake_initial_migration') {
|
||||
|
|
|
|||
142
src/services/storage/stateBundler.ts
Normal file
142
src/services/storage/stateBundler.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import { getLogger } from "../helpers/logger.js"
|
||||
|
||||
const transactionStatePointTypes = ['addedInvoice', 'invoiceWasPaid', 'paidAnInvoice', 'addedAddress', 'addressWasPaid', 'paidAnAddress', 'user2user'] as const
|
||||
const balanceStatePointTypes = ['providerBalance', 'providerMaxWithdrawable', 'walletBalance', 'channelBalance', 'usersBalance', 'feesPaidForLiquidity', 'totalLndBalance', 'accumulatedHtlcFees', 'deltaUsers', 'deltaLnd'] as const
|
||||
const maxStatePointTypes = ['maxProviderRespTime'] as const
|
||||
export type TransactionStatePointType = typeof transactionStatePointTypes[number]
|
||||
export type BalanceStatePointType = typeof balanceStatePointTypes[number]
|
||||
export type MaxStatePointType = typeof maxStatePointTypes[number]
|
||||
/*export type TransactionStatePoint = {
|
||||
type: typeof TransactionStatePointTypes[number]
|
||||
with: 'lnd' | 'internal' | 'provider'
|
||||
by: 'user' | 'system'
|
||||
amount: number
|
||||
success: boolean
|
||||
networkFee?: number
|
||||
serviceFee?: number
|
||||
liquidtyFee?: number
|
||||
}*/
|
||||
|
||||
type StateBundle = Record<string, number>
|
||||
export type TxPointSettings = {
|
||||
used: 'lnd' | 'internal' | 'provider' | 'unknown'
|
||||
from: 'user' | 'system'
|
||||
meta?: string[]
|
||||
timeDiscount?: true
|
||||
}
|
||||
export class StateBundler {
|
||||
sinceStart: StateBundle = {}
|
||||
lastReport: StateBundle = {}
|
||||
sinceLatestReport: StateBundle = {}
|
||||
reportPeriod = 1000 * 60 * 60 * 12 //12h
|
||||
satsPer1SecondDiscount = 1
|
||||
totalSatsForDiscount = 0
|
||||
latestReport = Date.now()
|
||||
reportLog = getLogger({ component: 'stateBundlerReport' })
|
||||
constructor() {
|
||||
process.on('exit', () => {
|
||||
this.Report()
|
||||
});
|
||||
|
||||
// catch ctrl+c event and exit normally
|
||||
process.on('SIGINT', () => {
|
||||
console.log('Ctrl-C...');
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
//catch uncaught exceptions, trace, then exit normally
|
||||
process.on('uncaughtException', (e) => {
|
||||
console.log('Uncaught Exception...');
|
||||
console.log(e.stack);
|
||||
process.exit(99);
|
||||
});
|
||||
}
|
||||
|
||||
increment = (key: string, value: number) => {
|
||||
this.sinceStart[key] = (this.sinceStart[key] || 0) + value
|
||||
this.sinceLatestReport[key] = (this.sinceLatestReport[key] || 0) + value
|
||||
this.triggerReportCheck()
|
||||
}
|
||||
set = (key: string, value: number) => {
|
||||
this.sinceStart[key] = value
|
||||
this.sinceLatestReport[key] = value
|
||||
this.triggerReportCheck()
|
||||
}
|
||||
max = (key: string, value: number) => {
|
||||
this.sinceStart[key] = Math.max(this.sinceStart[key] || 0, value)
|
||||
this.sinceLatestReport[key] = Math.max(this.sinceLatestReport[key] || 0, value)
|
||||
this.triggerReportCheck()
|
||||
}
|
||||
|
||||
AddTxPoint = (actionName: TransactionStatePointType, v: number, settings: TxPointSettings) => {
|
||||
const { used, from, timeDiscount } = settings
|
||||
const meta = settings.meta || []
|
||||
const key = `${actionName}_${from}_${used}_${meta.join('_')}`
|
||||
this.increment(key, v)
|
||||
if (timeDiscount) {
|
||||
this.totalSatsForDiscount += v
|
||||
}
|
||||
this.smallLogEvent(actionName, from)
|
||||
}
|
||||
|
||||
AddTxPointFailed = (actionName: TransactionStatePointType, v: number, settings: TxPointSettings) => {
|
||||
const { used, from } = settings
|
||||
const meta = settings.meta || []
|
||||
const key = `${actionName}_${from}_${used}_${meta.join('_')}_failed`
|
||||
this.increment(key, v)
|
||||
}
|
||||
|
||||
AddBalancePoint = (actionName: BalanceStatePointType, v: number, meta = []) => {
|
||||
const key = `${actionName}_${meta.join('_')}`
|
||||
this.set(key, v)
|
||||
}
|
||||
|
||||
AddMaxPoint = (actionName: MaxStatePointType, v: number, meta = []) => {
|
||||
const key = `${actionName}_${meta.join('_')}`
|
||||
this.max(key, v)
|
||||
}
|
||||
|
||||
triggerReportCheck = () => {
|
||||
const discountSeconds = Math.floor(this.totalSatsForDiscount / this.satsPer1SecondDiscount)
|
||||
const totalElapsed = Date.now() - this.latestReport
|
||||
const elapsedWithDiscount = totalElapsed + discountSeconds * 1000
|
||||
if (elapsedWithDiscount > this.reportPeriod) {
|
||||
this.Report()
|
||||
}
|
||||
}
|
||||
|
||||
smallLogEvent(event: TransactionStatePointType, from: 'user' | 'system') {
|
||||
const char = from === 'user' ? 'U' : 'S'
|
||||
switch (event) {
|
||||
case 'addedAddress':
|
||||
case 'addedInvoice':
|
||||
process.stdout.write(`${char}+,`)
|
||||
return
|
||||
case 'addressWasPaid':
|
||||
case 'invoiceWasPaid':
|
||||
process.stdout.write(`${char}>,`)
|
||||
return
|
||||
case 'paidAnAddress':
|
||||
case 'paidAnInvoice':
|
||||
process.stdout.write(`${char}<,`)
|
||||
return
|
||||
case 'user2user':
|
||||
process.stdout.write(`UU`)
|
||||
}
|
||||
}
|
||||
|
||||
Report = () => {
|
||||
this.totalSatsForDiscount = 0
|
||||
this.latestReport = Date.now()
|
||||
this.reportLog("+++++ since last report:")
|
||||
Object.entries(this.sinceLatestReport).forEach(([key, value]) => {
|
||||
this.reportLog(key, value)
|
||||
})
|
||||
this.reportLog("+++++ since start:")
|
||||
Object.entries(this.sinceStart).forEach(([key, value]) => {
|
||||
this.reportLog(key, value)
|
||||
})
|
||||
this.lastReport = { ...this.sinceLatestReport }
|
||||
this.sinceLatestReport = {}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ export default async (T: TestBase) => {
|
|||
const testSuccessfulExternalPayment = async (T: TestBase) => {
|
||||
T.d("starting testSuccessfulExternalPayment")
|
||||
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
|
||||
const invoice = await T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry)
|
||||
const invoice = await T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry, { from: 'system', useProvider: false })
|
||||
expect(invoice.payRequest).to.startWith("lnbcrt5u")
|
||||
T.d("generated 500 sats invoice for external node")
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ const testSuccessfulExternalPayment = async (T: TestBase) => {
|
|||
const testFailedExternalPayment = async (T: TestBase) => {
|
||||
T.d("starting testFailedExternalPayment")
|
||||
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
|
||||
const invoice = await T.externalAccessToOtherLnd.NewInvoice(1500, "test", defaultInvoiceExpiry)
|
||||
const invoice = await T.externalAccessToOtherLnd.NewInvoice(1500, "test", defaultInvoiceExpiry, { from: 'system', useProvider: false })
|
||||
expect(invoice.payRequest).to.startWith("lnbcrt15u")
|
||||
T.d("generated 1500 sats invoice for external node")
|
||||
|
||||
|
|
|
|||
|
|
@ -23,16 +23,13 @@ const testInboundPaymentFromProvider = async (T: TestBase, bootstrapped: Main, b
|
|||
T.d("starting testInboundPaymentFromProvider")
|
||||
const invoiceRes = await bootstrapped.appUserManager.NewInvoice({ app_id: bUser.appId, user_id: bUser.userId, app_user_id: bUser.appUserIdentifier }, { amountSats: 2000, memo: "liquidityTest" })
|
||||
|
||||
await T.externalAccessToOtherLnd.PayInvoice(invoiceRes.invoice, 0, 100)
|
||||
await T.externalAccessToOtherLnd.PayInvoice(invoiceRes.invoice, 0, 100, 2000, { from: 'system', useProvider: false })
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
const userBalance = await bootstrapped.appUserManager.GetUserInfo({ app_id: bUser.appId, user_id: bUser.userId, app_user_id: bUser.appUserIdentifier })
|
||||
T.expect(userBalance.balance).to.equal(2000)
|
||||
T.d("user balance is 2000")
|
||||
const providerBalance = await bootstrapped.liquidProvider.CheckUserState()
|
||||
if (!providerBalance) {
|
||||
throw new Error("provider balance not found")
|
||||
}
|
||||
T.expect(providerBalance.balance).to.equal(2000)
|
||||
const providerBalance = await bootstrapped.liquidityProvider.GetLatestBalance()
|
||||
T.expect(providerBalance).to.equal(2000)
|
||||
T.d("provider balance is 2000")
|
||||
T.d("testInboundPaymentFromProvider done")
|
||||
}
|
||||
|
|
@ -40,17 +37,14 @@ const testInboundPaymentFromProvider = async (T: TestBase, bootstrapped: Main, b
|
|||
const testOutboundPaymentFromProvider = async (T: TestBase, bootstrapped: Main, bootstrappedUser: TestUserData) => {
|
||||
T.d("starting testOutboundPaymentFromProvider")
|
||||
|
||||
const invoice = await T.externalAccessToOtherLnd.NewInvoice(1000, "", 60 * 60)
|
||||
const invoice = await T.externalAccessToOtherLnd.NewInvoice(1000, "", 60 * 60, { from: 'system', useProvider: false })
|
||||
const ctx = { app_id: bootstrappedUser.appId, user_id: bootstrappedUser.userId, app_user_id: bootstrappedUser.appUserIdentifier }
|
||||
const res = await bootstrapped.appUserManager.PayInvoice(ctx, { invoice: invoice.payRequest, amount: 0 })
|
||||
|
||||
const userBalance = await bootstrapped.appUserManager.GetUserInfo(ctx)
|
||||
T.expect(userBalance.balance).to.equal(986) // 2000 - (1000 + 6(x2) + 2)
|
||||
|
||||
const providerBalance = await bootstrapped.liquidProvider.CheckUserState()
|
||||
if (!providerBalance) {
|
||||
throw new Error("provider balance not found")
|
||||
}
|
||||
T.expect(providerBalance.balance).to.equal(992) // 2000 - (1000 + 6 +2)
|
||||
const providerBalance = await bootstrapped.liquidityProvider.GetLatestBalance()
|
||||
T.expect(providerBalance).to.equal(992) // 2000 - (1000 + 6 +2)
|
||||
T.d("testOutboundPaymentFromProvider done")
|
||||
}
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
import { LoadTestSettingsFromEnv } from "../services/main/settings.js"
|
||||
import { BitcoinCoreWrapper } from "./bitcoinCore.js"
|
||||
import LND from '../services/lnd/lnd.js'
|
||||
import { LiquidityProvider } from "../services/lnd/liquidityProvider.js"
|
||||
import { LiquidityProvider } from "../services/main/liquidityProvider.js"
|
||||
import { Utils } from "../services/helpers/utilsWrapper.js"
|
||||
|
||||
export const setupNetwork = async () => {
|
||||
const settings = LoadTestSettingsFromEnv()
|
||||
const core = new BitcoinCoreWrapper(settings)
|
||||
await core.InitAddress()
|
||||
await core.Mine(1)
|
||||
const alice = new LND(settings.lndSettings, new LiquidityProvider("", () => { }), () => { }, () => { }, () => { }, () => { })
|
||||
const bob = new LND({ ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }, new LiquidityProvider("", () => { }), () => { }, () => { }, () => { }, () => { })
|
||||
const setupUtils = new Utils(settings)
|
||||
const alice = new LND(settings.lndSettings, new LiquidityProvider("", setupUtils, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { })
|
||||
const bob = new LND({ ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }, new LiquidityProvider("", setupUtils, async () => { }), setupUtils, async () => { }, async () => { }, () => { }, () => { })
|
||||
await tryUntil<void>(async i => {
|
||||
const peers = await alice.ListPeers()
|
||||
if (peers.peers.length > 0) {
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ export const initBootstrappedInstance = async (T: TestBase) => {
|
|||
}
|
||||
const j = JSON.parse(data.content) as { requestId: string }
|
||||
console.log("sending new operation to provider")
|
||||
bootstrapped.liquidProvider.onEvent(j, T.app.publicKey)
|
||||
bootstrapped.liquidityProvider.onEvent(j, T.app.publicKey)
|
||||
})
|
||||
bootstrapped.liquidProvider.attachNostrSend(async (_, data, r) => {
|
||||
bootstrapped.liquidityProvider.attachNostrSend(async (_, data, r) => {
|
||||
const res = await handleSend(T, data)
|
||||
if (data.type === 'event') {
|
||||
throw new Error("unsupported event type")
|
||||
|
|
@ -34,12 +34,13 @@ export const initBootstrappedInstance = async (T: TestBase) => {
|
|||
if (!res) {
|
||||
return
|
||||
}
|
||||
bootstrapped.liquidProvider.onEvent(res, data.pub)
|
||||
bootstrapped.liquidityProvider.onEvent(res, data.pub)
|
||||
})
|
||||
bootstrapped.liquidProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey })
|
||||
bootstrapped.liquidityProvider.setNostrInfo({ clientId: liquidityProviderInfo.clientId, myPub: liquidityProviderInfo.publicKey })
|
||||
await new Promise<void>(res => {
|
||||
const interval = setInterval(() => {
|
||||
if (bootstrapped.liquidProvider.CanProviderHandle({ action: 'receive', amount: 2000 })) {
|
||||
const interval = setInterval(async () => {
|
||||
const canHandle = await bootstrapped.liquidityProvider.CanProviderHandle({ action: 'receive', amount: 2000 })
|
||||
if (canHandle) {
|
||||
clearInterval(interval)
|
||||
res()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default async (T: TestBase) => {
|
|||
const testSpamExternalPayment = async (T: TestBase) => {
|
||||
T.d("starting testSpamExternalPayment")
|
||||
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
|
||||
const invoices = await Promise.all(new Array(10).fill(0).map(() => T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry)))
|
||||
const invoices = await Promise.all(new Array(10).fill(0).map(() => T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry, { from: 'system', useProvider: false })))
|
||||
T.d("generated 10 500 sats invoices for external node")
|
||||
const res = await Promise.all(invoices.map(async (invoice, i) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export default async (T: TestBase) => {
|
|||
const testSpamExternalPayment = async (T: TestBase) => {
|
||||
T.d("starting testSpamExternalPayment")
|
||||
const application = await T.main.storage.applicationStorage.GetApplication(T.app.appId)
|
||||
const invoicesForExternal = await Promise.all(new Array(5).fill(0).map(() => T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry)))
|
||||
const invoicesForExternal = await Promise.all(new Array(5).fill(0).map(() => T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry, { from: 'system', useProvider: false })))
|
||||
const invoicesForUser2 = await Promise.all(new Array(5).fill(0).map(() => T.main.paymentManager.NewInvoice(T.user2.userId, { amountSats: 500, memo: "test" }, { linkedApplication: application, expiry: defaultInvoiceExpiry })))
|
||||
const invoices = invoicesForExternal.map(i => i.payRequest).concat(invoicesForUser2.map(i => i.invoice))
|
||||
T.d("generated 10 500 sats mixed invoices between external node and user 2")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js'
|
|||
import SanityChecker from '../services/main/sanityChecker.js'
|
||||
import LND from '../services/lnd/lnd.js'
|
||||
import { getLogger, resetDisabledLoggers } from '../services/helpers/logger.js'
|
||||
import { LiquidityProvider } from '../services/lnd/liquidityProvider.js'
|
||||
import { LiquidityProvider } from '../services/main/liquidityProvider.js'
|
||||
import { Utils } from '../services/helpers/utilsWrapper.js'
|
||||
chai.use(chaiString)
|
||||
export const expect = chai.expect
|
||||
export type Describe = (message: string, failure?: boolean) => void
|
||||
|
|
@ -45,16 +46,16 @@ export const SetupTest = async (d: Describe): Promise<TestBase> => {
|
|||
const user1 = { userId: u1.info.userId, appUserIdentifier: u1.identifier, appId: app.appId }
|
||||
const user2 = { userId: u2.info.userId, appUserIdentifier: u2.identifier, appId: app.appId }
|
||||
|
||||
|
||||
const externalAccessToMainLnd = new LND(settings.lndSettings, new LiquidityProvider("", () => { }), console.log, console.log, () => { }, () => { })
|
||||
const extermnalUtils = new Utils(settings)
|
||||
const externalAccessToMainLnd = new LND(settings.lndSettings, new LiquidityProvider("", extermnalUtils, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { })
|
||||
await externalAccessToMainLnd.Warmup()
|
||||
|
||||
const otherLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.otherNode }
|
||||
const externalAccessToOtherLnd = new LND(otherLndSetting, new LiquidityProvider("", () => { }), console.log, console.log, () => { }, () => { })
|
||||
const externalAccessToOtherLnd = new LND(otherLndSetting, new LiquidityProvider("", extermnalUtils, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { })
|
||||
await externalAccessToOtherLnd.Warmup()
|
||||
|
||||
const thirdLndSetting = { ...settings.lndSettings, mainNode: settings.lndSettings.thirdNode }
|
||||
const externalAccessToThirdLnd = new LND(thirdLndSetting, new LiquidityProvider("", () => { }), console.log, console.log, () => { }, () => { })
|
||||
const externalAccessToThirdLnd = new LND(thirdLndSetting, new LiquidityProvider("", extermnalUtils, async () => { }), extermnalUtils, async () => { }, async () => { }, () => { }, () => { })
|
||||
await externalAccessToThirdLnd.Warmup()
|
||||
|
||||
|
||||
|
|
@ -78,7 +79,7 @@ export const teardown = async (T: TestBase) => {
|
|||
export const safelySetUserBalance = async (T: TestBase, user: TestUserData, amount: number) => {
|
||||
const app = await T.main.storage.applicationStorage.GetApplication(user.appId)
|
||||
const invoice = await T.main.paymentManager.NewInvoice(user.userId, { amountSats: amount, memo: "test" }, { linkedApplication: app, expiry: defaultInvoiceExpiry })
|
||||
await T.externalAccessToOtherLnd.PayInvoice(invoice.invoice, 0, 100)
|
||||
await T.externalAccessToOtherLnd.PayInvoice(invoice.invoice, 0, 100, amount, { from: 'system', useProvider: false })
|
||||
const u = await T.main.storage.userStorage.GetUser(user.userId)
|
||||
expect(u.balance_sats).to.be.equal(amount)
|
||||
T.d(`user ${user.appUserIdentifier} balance is now ${amount}`)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default async (T: TestBase) => {
|
|||
|
||||
const testSuccessfulUserPaymentToExternalNode = async (T: TestBase) => {
|
||||
T.d("starting testSuccessfulUserPaymentToExternalNode")
|
||||
const invoice = await T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry)
|
||||
const invoice = await T.externalAccessToOtherLnd.NewInvoice(500, "test", defaultInvoiceExpiry, { from: 'system', useProvider: false })
|
||||
const payment = await T.main.appUserManager.PayInvoice({ app_id: T.user1.appId, user_id: T.user1.userId, app_user_id: T.user1.appUserIdentifier }, { invoice: invoice.payRequest, amount: 0 })
|
||||
T.d("paid 500 sats invoice from user1 to external node")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue