more fixies
This commit is contained in:
parent
c5ea8c899d
commit
6ae03e520c
19 changed files with 2330 additions and 1880 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -66,10 +66,10 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.SetMockInvoiceAsPaid) throw new Error('method: SetMockInvoiceAsPaid is not implemented')
|
||||
app.post('/api/admin/lnd/mock/invoice/paid', async (req, res) => {
|
||||
app.post('/api/lnd/mock/invoice/paid', async (req, res) => {
|
||||
try {
|
||||
if (!methods.SetMockInvoiceAsPaid) throw new Error('method: SetMockInvoiceAsPaid is not implemented')
|
||||
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
|
||||
const authContext = await opts.GuestAuthGuard(req.headers['authorization'])
|
||||
const request = req.body
|
||||
const error = Types.SetMockInvoiceAsPaidRequestValidate(request)
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
|
||||
|
|
@ -93,6 +93,17 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
res.json({status: 'OK', ...response})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.GetApp) throw new Error('method: GetApp is not implemented')
|
||||
app.post('/api/app/get', async (req, res) => {
|
||||
try {
|
||||
if (!methods.GetApp) throw new Error('method: GetApp is not implemented')
|
||||
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
|
||||
const query = req.query
|
||||
const params = req.params
|
||||
const response = await methods.GetApp({ ...authContext, ...query, ...params })
|
||||
res.json({status: 'OK', ...response})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.AddAppUser) throw new Error('method: AddAppUser is not implemented')
|
||||
app.post('/api/app/user/add', async (req, res) => {
|
||||
try {
|
||||
|
|
@ -205,6 +216,34 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
res.json({status: 'OK', ...response})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.SetMockAppUserBalance) throw new Error('method: SetMockAppUserBalance is not implemented')
|
||||
app.post('/api/app/mock/user/blance/set', async (req, res) => {
|
||||
try {
|
||||
if (!methods.SetMockAppUserBalance) throw new Error('method: SetMockAppUserBalance is not implemented')
|
||||
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
|
||||
const request = req.body
|
||||
const error = Types.SetMockAppUserBalanceRequestValidate(request)
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
|
||||
const query = req.query
|
||||
const params = req.params
|
||||
await methods.SetMockAppUserBalance({ ...authContext, ...query, ...params }, request)
|
||||
res.json({status: 'OK'})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.SetMockAppBalance) throw new Error('method: SetMockAppBalance is not implemented')
|
||||
app.post('/api/app/mock/blance/set', async (req, res) => {
|
||||
try {
|
||||
if (!methods.SetMockAppBalance) throw new Error('method: SetMockAppBalance is not implemented')
|
||||
const authContext = await opts.AppAuthGuard(req.headers['authorization'])
|
||||
const request = req.body
|
||||
const error = Types.SetMockAppBalanceRequestValidate(request)
|
||||
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
|
||||
const query = req.query
|
||||
const params = req.params
|
||||
await methods.SetMockAppBalance({ ...authContext, ...query, ...params }, request)
|
||||
res.json({status: 'OK'})
|
||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.AddUser) throw new Error('method: AddUser is not implemented')
|
||||
app.post('/api/user/add', async (req, res) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -52,9 +52,9 @@ export default (params: ClientParams) => ({
|
|||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
SetMockInvoiceAsPaid: async (request: Types.SetMockInvoiceAsPaidRequest): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
let finalRoute = '/api/admin/lnd/mock/invoice/paid'
|
||||
const auth = await params.retrieveGuestAuth()
|
||||
if (auth === null) throw new Error('retrieveGuestAuth() returned null')
|
||||
let finalRoute = '/api/lnd/mock/invoice/paid'
|
||||
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
|
|
@ -76,6 +76,20 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
GetApp: async (): Promise<ResultError | ({ status: 'OK' }& Types.Application)> => {
|
||||
const auth = await params.retrieveAppAuth()
|
||||
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
||||
let finalRoute = '/api/app/get'
|
||||
const { data } = await axios.post(params.baseUrl + finalRoute, {}, { headers: { 'authorization': auth } })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
const result = data
|
||||
if(!params.checkResult) return { status: 'OK', ...result }
|
||||
const error = Types.ApplicationValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
AddAppUser: async (request: Types.AddAppUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.AppUser)> => {
|
||||
const auth = await params.retrieveAppAuth()
|
||||
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
||||
|
|
@ -182,6 +196,28 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
SetMockAppUserBalance: async (request: Types.SetMockAppUserBalanceRequest): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveAppAuth()
|
||||
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
||||
let finalRoute = '/api/app/mock/user/blance/set'
|
||||
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
SetMockAppBalance: async (request: Types.SetMockAppBalanceRequest): Promise<ResultError | ({ status: 'OK' })> => {
|
||||
const auth = await params.retrieveAppAuth()
|
||||
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
||||
let finalRoute = '/api/app/mock/blance/set'
|
||||
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
|
||||
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
|
||||
if (data.status === 'OK') {
|
||||
return data
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
AddUser: async (request: Types.AddUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.AddUserResponse)> => {
|
||||
const auth = await params.retrieveGuestAuth()
|
||||
if (auth === null) throw new Error('retrieveGuestAuth() returned null')
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -89,9 +89,9 @@ service LightningPub {
|
|||
};
|
||||
|
||||
rpc SetMockInvoiceAsPaid(structs.SetMockInvoiceAsPaidRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "Admin";
|
||||
option (auth_type) = "Guest";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/admin/lnd/mock/invoice/paid";
|
||||
option (http_route) = "/api/lnd/mock/invoice/paid";
|
||||
}
|
||||
|
||||
// <App>
|
||||
|
|
@ -102,6 +102,12 @@ service LightningPub {
|
|||
option (http_route) = "/api/admin/app/add";
|
||||
};
|
||||
|
||||
rpc GetApp(structs.Empty) returns (structs.Application) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/get";
|
||||
}
|
||||
|
||||
rpc AddAppUser(structs.AddAppUserRequest)returns (structs.AppUser) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
|
|
@ -149,6 +155,16 @@ service LightningPub {
|
|||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/user/lnurl/pay/info";
|
||||
}
|
||||
rpc SetMockAppUserBalance(structs.SetMockAppUserBalanceRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/mock/user/blance/set";
|
||||
}
|
||||
rpc SetMockAppBalance(structs.SetMockAppBalanceRequest) returns (structs.Empty) {
|
||||
option (auth_type) = "App";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/app/mock/blance/set";
|
||||
}
|
||||
|
||||
// </App>
|
||||
|
||||
|
|
|
|||
|
|
@ -29,11 +29,16 @@ message AddAppRequest {
|
|||
string name = 1;
|
||||
}
|
||||
|
||||
message AddAppResponse {
|
||||
message Application {
|
||||
string name = 1;
|
||||
string id = 2;
|
||||
string auth_token = 3;
|
||||
int64 balance = 3;
|
||||
}
|
||||
message AddAppResponse {
|
||||
Application app = 1;
|
||||
string auth_token = 2;
|
||||
}
|
||||
|
||||
|
||||
message AddAppUserRequest {
|
||||
string identifier = 1;
|
||||
|
|
@ -91,6 +96,15 @@ message GetAppUserLNURLInfoRequest {
|
|||
string user_identifier = 1;
|
||||
}
|
||||
|
||||
message SetMockAppUserBalanceRequest {
|
||||
string user_identifier = 1;
|
||||
int64 amount = 2;
|
||||
}
|
||||
|
||||
message SetMockAppBalanceRequest {
|
||||
int64 amount = 1;
|
||||
}
|
||||
|
||||
enum AddressType {
|
||||
WITNESS_PUBKEY_HASH = 0;
|
||||
NESTED_PUBKEY_HASH = 1;
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ export interface LightningHandler {
|
|||
|
||||
export default (settings: LndSettings, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb): LightningHandler => {
|
||||
if (settings.mockLnd) {
|
||||
console.log("registering mock lnd handler")
|
||||
return new MockLnd(settings, addressPaidCb, invoicePaidCb)
|
||||
} else {
|
||||
console.log("registering prod lnd handler")
|
||||
return new LND(settings, addressPaidCb, invoicePaidCb)
|
||||
}
|
||||
}
|
||||
|
|
@ -157,7 +157,6 @@ export default class {
|
|||
this.checkReady()
|
||||
const abortController = new AbortController()
|
||||
const req = PayInvoiceReq(invoice, amount, feeLimit)
|
||||
console.log(req)
|
||||
const stream = this.router.sendPaymentV2(req, { abort: abortController.signal })
|
||||
return new Promise((res, rej) => {
|
||||
stream.responses.onError(error => {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { SendCoinsReq } from './sendCoinsReq.js';
|
|||
import { LndSettings, AddressPaidCb, InvoicePaidCb, NodeInfo, Invoice, DecodedInvoice, PaidInvoice } from './settings.js';
|
||||
|
||||
export default class {
|
||||
invoicesAwaiting: Record<string /* invoice */, { value: number, memo: string, expiryUnix: number }>
|
||||
invoicesAwaiting: Record<string /* invoice */, { value: number, memo: string, expiryUnix: number }> = {}
|
||||
settings: LndSettings
|
||||
abortController = new AbortController()
|
||||
addressPaidCb: AddressPaidCb
|
||||
|
|
@ -49,12 +49,16 @@ export default class {
|
|||
}
|
||||
|
||||
async NewInvoice(value: number, memo: string, expiry: number): Promise<Invoice> {
|
||||
const mockInvoice = "lnbcrtmock" + crypto.randomBytes(32).toString('hex')
|
||||
const mockInvoice = "lnbcrtmockin" + crypto.randomBytes(32).toString('hex')
|
||||
this.invoicesAwaiting[mockInvoice] = { value, memo, expiryUnix: expiry + Date.now() / 1000 }
|
||||
return { payRequest: mockInvoice }
|
||||
}
|
||||
|
||||
async DecodeInvoice(paymentRequest: string): Promise<DecodedInvoice> {
|
||||
if (paymentRequest.startsWith('lnbcrtmockout')) {
|
||||
const amt = this.decodeOutboundInvoice(paymentRequest)
|
||||
return { numSatoshis: amt }
|
||||
}
|
||||
const i = this.invoicesAwaiting[paymentRequest]
|
||||
if (!i) {
|
||||
throw new Error("invoice not found")
|
||||
|
|
@ -70,15 +74,23 @@ export default class {
|
|||
return Math.max(0, Math.floor(amount * (1 - this.settings.feeRateLimit) - this.settings.feeFixedLimit))
|
||||
}
|
||||
|
||||
async PayInvoice(invoice: string, amount: number, feeLimit: number): Promise<PaidInvoice> {
|
||||
if (!invoice.startsWith('lnbcrtmock')) {
|
||||
decodeOutboundInvoice(invoice: string): number {
|
||||
if (!invoice.startsWith('lnbcrtmockout')) {
|
||||
throw new Error("invalid mock invoice provided for payment")
|
||||
}
|
||||
const amt = invoice.substring('lnbcrtmock'.length)
|
||||
const amt = invoice.substring('lnbcrtmockout'.length).split("__")[0]
|
||||
if (isNaN(+amt)) {
|
||||
throw new Error("invalid mock invoice provided for payment")
|
||||
}
|
||||
return { feeSat: 0, paymentPreimage: "all_good", valueSat: +amt || amount }
|
||||
return +amt
|
||||
}
|
||||
|
||||
async PayInvoice(invoice: string, amount: number, feeLimit: number): Promise<PaidInvoice> {
|
||||
console.log('payng', invoice)
|
||||
await new Promise(res => setTimeout(res, 200))
|
||||
const amt = this.decodeOutboundInvoice(invoice)
|
||||
console.log('paid', invoice)
|
||||
return { feeSat: 1, paymentPreimage: "all_good", valueSat: amt || amount }
|
||||
}
|
||||
|
||||
async EstimateChainFees(address: string, amount: number, targetConf: number): Promise<EstimateFeeResponse> {
|
||||
|
|
|
|||
|
|
@ -27,19 +27,45 @@ export default class {
|
|||
t = token.substring("Bearer ".length)
|
||||
}
|
||||
if (!t) throw new Error("no app token provided")
|
||||
return (jwt.verify(token, this.settings.jwtSecret) as { appId: string }).appId
|
||||
const decoded = jwt.verify(token, this.settings.jwtSecret) as { appId?: string }
|
||||
if (!decoded.appId) {
|
||||
throw new Error("the provided token is not an app token")
|
||||
}
|
||||
return decoded.appId
|
||||
}
|
||||
|
||||
async SetMockAppUserBalance(appId: string, req: Types.SetMockAppUserBalanceRequest) {
|
||||
const user = await this.storage.applicationStorage.GetOrCreateApplicationUser(appId, req.user_identifier, 0)
|
||||
await this.paymentManager.SetMockUserBalance(user.user.user_id, req.amount)
|
||||
}
|
||||
|
||||
async SetMockAppBalance(appId: string, req: Types.SetMockAppBalanceRequest) {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
await this.paymentManager.SetMockUserBalance(app.owner.user_id, req.amount)
|
||||
}
|
||||
|
||||
|
||||
async AddApp(req: Types.AddAppRequest): Promise<Types.AddAppResponse> {
|
||||
const app = await this.storage.applicationStorage.AddApplication(req.name)
|
||||
return {
|
||||
id: app.app_id,
|
||||
name: app.name,
|
||||
app: {
|
||||
id: app.app_id,
|
||||
name: app.name,
|
||||
balance: 0
|
||||
},
|
||||
auth_token: this.SignAppToken(app.app_id)
|
||||
}
|
||||
}
|
||||
|
||||
async GetApp(appId: string): Promise<Types.Application> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
return {
|
||||
name: app.name,
|
||||
id: app.app_id,
|
||||
balance: app.owner.balance_sats
|
||||
}
|
||||
}
|
||||
|
||||
async AddAppUser(appId: string, req: Types.AddAppUserRequest): Promise<Types.AppUser> {
|
||||
let u: ApplicationUser
|
||||
if (req.fail_if_exists) {
|
||||
|
|
@ -60,7 +86,7 @@ export default class {
|
|||
async AddAppInvoice(appId: string, req: Types.AddAppInvoiceRequest): Promise<Types.NewInvoiceResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const payer = await this.storage.applicationStorage.GetOrCreateApplicationUser(appId, req.payer_identifier, 0)
|
||||
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user }
|
||||
const opts: InboundOptionals = { callbackUrl: req.http_callback_url, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app }
|
||||
return this.paymentManager.NewInvoice(app.owner.user_id, req.invoice_req, opts)
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +103,7 @@ export default class {
|
|||
async GetAppUser(appId: string, req: Types.GetAppUserRequest): Promise<Types.AppUser> {
|
||||
const user = await this.storage.applicationStorage.GetApplicationUser(appId, req.user_identifier)
|
||||
const max = this.paymentManager.GetMaxPayableInvoice(user.user.balance_sats)
|
||||
console.log(max, user.user.balance_sats)
|
||||
return {
|
||||
max_withdrawable: max, identifier: req.user_identifier, info: {
|
||||
userId: user.user.user_id, balance: user.user.balance_sats
|
||||
|
|
@ -85,14 +112,16 @@ export default class {
|
|||
}
|
||||
|
||||
async PayAppUserInvoice(appId: string, req: Types.PayAppUserInvoiceRequest): Promise<Types.PayAppUserInvoiceResponse> {
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
const appUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.user_identifier)
|
||||
return this.paymentManager.PayInvoice(appUser.user.user_id, req)
|
||||
return this.paymentManager.PayInvoice(appUser.user.user_id, req, app)
|
||||
}
|
||||
|
||||
async SendAppUserToAppUserPayment(appId: string, req: Types.SendAppUserToAppUserPaymentRequest): Promise<void> {
|
||||
const fromUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.from_user_identifier)
|
||||
const toUser = await this.storage.applicationStorage.GetApplicationUser(appId, req.to_user_identifier)
|
||||
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, toUser.user.user_id, req.amount)
|
||||
const app = await this.storage.applicationStorage.GetApplication(appId)
|
||||
await this.paymentManager.SendUserToUserPayment(fromUser.user.user_id, toUser.user.user_id, req.amount, app)
|
||||
}
|
||||
|
||||
async SendAppUserToAppPayment(appId: string, req: Types.SendAppUserToAppPaymentRequest): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -73,11 +73,13 @@ export default class {
|
|||
const userInvoice = await this.storage.paymentStorage.GetInvoiceOwner(paymentRequest, tx)
|
||||
if (!userInvoice || userInvoice.paid_at_unix > 0) { return }
|
||||
const fee = this.paymentManager.getServiceFee(Types.UserOperationType.INCOMING_INVOICE, amount)
|
||||
const maybeApp = await this.storage.applicationStorage.IsApplicationUser(userInvoice.user.user_id)
|
||||
try {
|
||||
// This call will fail if the invoice is already registered
|
||||
await this.storage.paymentStorage.FlagInvoiceAsPaid(userInvoice, amount, fee, tx)
|
||||
await this.storage.userStorage.IncrementUserBalance(userInvoice.user.user_id, amount - fee, tx)
|
||||
if (userInvoice.linkedApplication) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, tx)
|
||||
}
|
||||
await this.triggerPaidCallback(userInvoice.callbackUrl)
|
||||
} catch {
|
||||
//TODO
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import * as Types from '../../../proto/autogenerated/ts/types.js'
|
|||
import { MainSettings } from './settings.js'
|
||||
import { InboundOptionals, defaultInvoiceExpiry } from '../storage/paymentStorage.js'
|
||||
import { LightningHandler } from '../lnd/index.js'
|
||||
import { Application } from '../storage/entity/Application.js'
|
||||
interface UserOperationInfo {
|
||||
serial_id: number
|
||||
paid_amount: number
|
||||
|
|
@ -39,9 +40,20 @@ export default class {
|
|||
}
|
||||
|
||||
async SetMockInvoiceAsPaid(req: Types.SetMockInvoiceAsPaidRequest) {
|
||||
if (!this.settings.lndSettings.mockLnd) {
|
||||
throw new Error("mock disabled, cannot set invoice as paid")
|
||||
}
|
||||
await this.lnd.SetMockInvoiceAsPaid(req.invoice, req.amount)
|
||||
}
|
||||
|
||||
async SetMockUserBalance(userId: string, balance: number) {
|
||||
if (!this.settings.lndSettings.mockLnd) {
|
||||
throw new Error("mock disabled, cannot set invoice as paid")
|
||||
}
|
||||
console.log("setting mock balance...")
|
||||
await this.storage.userStorage.UpdateUser(userId, { balance_sats: balance })
|
||||
}
|
||||
|
||||
async NewAddress(userId: string, req: Types.NewAddressRequest): Promise<Types.NewAddressResponse> {
|
||||
const res = await this.lnd.NewAddress(req.addressType)
|
||||
const userAddress = await this.storage.paymentStorage.AddUserAddress(userId, res.address)
|
||||
|
|
@ -60,8 +72,12 @@ export default class {
|
|||
}
|
||||
|
||||
async lockUserWithMinBalance(userId: string, minBalance: number) {
|
||||
console.log("locking", userId)
|
||||
return this.storage.StartTransaction(async tx => {
|
||||
const user = await this.storage.userStorage.GetUser(userId, tx)
|
||||
if (user.locked) {
|
||||
throw new Error("user is already withdrawing")
|
||||
}
|
||||
if (user.balance_sats < minBalance) {
|
||||
throw new Error("insufficient balance")
|
||||
}
|
||||
|
|
@ -80,7 +96,7 @@ export default class {
|
|||
amount: Number(decoded.numSatoshis)
|
||||
}
|
||||
}
|
||||
async PayInvoice(userId: string, req: Types.PayInvoiceRequest): Promise<Types.PayInvoiceResponse> {
|
||||
async PayInvoice(userId: string, req: Types.PayInvoiceRequest, linkedApplication?: Application): Promise<Types.PayInvoiceResponse> {
|
||||
const decoded = await this.lnd.DecodeInvoice(req.invoice)
|
||||
if (decoded.numSatoshis !== 0 && req.amount !== 0) {
|
||||
throw new Error("invoice has value, do not provide amount the the request")
|
||||
|
|
@ -92,11 +108,15 @@ export default class {
|
|||
const serviceFee = this.getServiceFee(Types.UserOperationType.OUTGOING_INVOICE, payAmount)
|
||||
const totalAmountToDecrement = payAmount + serviceFee
|
||||
|
||||
|
||||
const routingFeeLimit = this.lnd.GetFeeLimitAmount(payAmount)
|
||||
await this.lockUserWithMinBalance(userId, totalAmountToDecrement + routingFeeLimit)
|
||||
const payment = await this.lnd.PayInvoice(req.invoice, req.amount, routingFeeLimit)
|
||||
await this.storage.userStorage.DecrementUserBalance(userId, totalAmountToDecrement + Number(payment.feeSat))
|
||||
if (linkedApplication) {
|
||||
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, serviceFee)
|
||||
} else {
|
||||
//const appOwner = await this.storage.applicationStorage.IsApplicationOwner(userId)
|
||||
}
|
||||
await this.storage.userStorage.UnlockUser(userId)
|
||||
await this.storage.paymentStorage.AddUserInvoicePayment(userId, req.invoice, payAmount, Number(payment.feeSat), serviceFee)
|
||||
return {
|
||||
|
|
@ -241,7 +261,7 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number) {
|
||||
async SendUserToUserPayment(fromUserId: string, toUserId: string, amount: number, linkedApplication?: Application) {
|
||||
await this.storage.StartTransaction(async tx => {
|
||||
const fromUser = await this.storage.userStorage.GetUser(fromUserId, tx)
|
||||
const toUser = await this.storage.userStorage.GetUser(toUserId, tx)
|
||||
|
|
@ -253,6 +273,9 @@ export default class {
|
|||
await this.storage.userStorage.DecrementUserBalance(fromUser.user_id, amount, tx)
|
||||
await this.storage.userStorage.IncrementUserBalance(toUser.user_id, toIncrement, tx)
|
||||
await this.storage.paymentStorage.AddUserToUserPayment(fromUserId, toUserId, amount, fee)
|
||||
if (linkedApplication) {
|
||||
await this.storage.userStorage.IncrementUserBalance(linkedApplication.owner.user_id, fee)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,13 +15,18 @@ export default class {
|
|||
}
|
||||
|
||||
DecodeUserToken(token?: string): string {
|
||||
if (!token) throw new Error("empty user token provided")
|
||||
throw new Error("users methods temporarely disabled")
|
||||
/*if (!token) throw new Error("empty user token provided")
|
||||
let t = token
|
||||
if (token.startsWith("Bearer ")) {
|
||||
t = token.substring("Bearer ".length)
|
||||
}
|
||||
if (!t) throw new Error("no user token provided")
|
||||
return (jwt.verify(token, this.settings.jwtSecret) as { userId: string }).userId
|
||||
const decoded = jwt.verify(token, this.settings.jwtSecret) as { userId?: string }
|
||||
if (!decoded.userId) {
|
||||
throw new Error("the provided token is not an app token")
|
||||
}
|
||||
return decoded.userId*/
|
||||
}
|
||||
|
||||
async AddBasicUser(req: Types.AddUserRequest): Promise<Types.AddUserResponse> {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,9 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.AddApp(req)
|
||||
},
|
||||
GetApp: async (ctx) => {
|
||||
return mainHandler.applicationManager.GetApp(ctx.app_id)
|
||||
},
|
||||
AddAppUser: async (ctx, req) => {
|
||||
const err = Types.AddAppUserRequestValidate(req, {
|
||||
identifier_CustomCheck: id => id !== ''
|
||||
|
|
@ -176,6 +179,16 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.applicationManager.GetAppUserLNURLInfo(ctx.app_id, req)
|
||||
},
|
||||
SetMockAppUserBalance: async (ctx, req) => {
|
||||
const err = Types.SetMockAppUserBalanceRequestValidate(req, {
|
||||
user_identifier_CustomCheck: id => id !== ''
|
||||
})
|
||||
if (err != null) throw new Error(err.message)
|
||||
await mainHandler.applicationManager.SetMockAppUserBalance(ctx.app_id, req)
|
||||
},
|
||||
SetMockAppBalance: async (ctx, req) => {
|
||||
await mainHandler.applicationManager.SetMockAppBalance(ctx.app_id, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +66,6 @@ export default class {
|
|||
}
|
||||
|
||||
if (found.application.app_id !== appId) {
|
||||
console.log(found, appId)
|
||||
throw new Error("requested user does not belong to requestor application")
|
||||
}
|
||||
return found
|
||||
|
|
@ -75,4 +74,8 @@ export default class {
|
|||
async IsApplicationUser(userId: string, entityManager = this.DB): Promise<ApplicationUser | null> {
|
||||
return await entityManager.getRepository(ApplicationUser).findOne({ where: { user: { user_id: userId } } })
|
||||
}
|
||||
|
||||
async IsApplicationOwner(userId: string, entityManager = this.DB) {
|
||||
return entityManager.getRepository(Application).findOne({ where: { owner: { user_id: userId } } })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, Check, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"
|
||||
import { Product } from "./Product.js"
|
||||
import { User } from "./User.js"
|
||||
import { Application } from "./Application.js"
|
||||
|
||||
@Entity()
|
||||
export class UserReceivingInvoice {
|
||||
|
|
@ -37,6 +38,9 @@ export class UserReceivingInvoice {
|
|||
@ManyToOne(type => User, { eager: true })
|
||||
payer: User | null
|
||||
|
||||
@ManyToOne(type => Application, { eager: true })
|
||||
linkedApplication: Application | null
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { DataSource, EntityManager, MoreThan, MoreThanOrEqual } from "typeorm"
|
||||
import { DataSource, EntityManager, MoreThan, MoreThanOrEqual, TransactionAlreadyStartedError } from "typeorm"
|
||||
import crypto from 'crypto';
|
||||
import NewDB, { DbSettings, LoadDbSettingsFromEnv } from "./db.js"
|
||||
import { User } from "./entity/User.js"
|
||||
|
|
@ -29,6 +29,7 @@ export default class {
|
|||
applicationStorage: ApplicationStorage
|
||||
userStorage: UserStorage
|
||||
paymentStorage: PaymentStorage
|
||||
pendingTx: boolean
|
||||
constructor(settings: StorageSettings) {
|
||||
this.settings = settings
|
||||
}
|
||||
|
|
@ -40,6 +41,24 @@ export default class {
|
|||
this.paymentStorage = new PaymentStorage(this.DB, this.userStorage)
|
||||
}
|
||||
StartTransaction(exec: (entityManager: EntityManager) => Promise<void>) {
|
||||
return this.DB.transaction(exec)
|
||||
if (this.pendingTx) {
|
||||
throw new Error("cannot start transaction")
|
||||
}
|
||||
this.pendingTx = true
|
||||
console.log("starting tx")
|
||||
return this.DB.transaction(async tx => {
|
||||
try {
|
||||
await exec(tx)
|
||||
console.log("tx done")
|
||||
this.pendingTx = false
|
||||
} catch (err) {
|
||||
console.log("tx err")
|
||||
this.pendingTx = false
|
||||
throw err
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,8 @@ import UserStorage from './userStorage.js';
|
|||
import { AddressReceivingTransaction } from './entity/AddressReceivingTransaction.js';
|
||||
import { UserInvoicePayment } from './entity/UserInvoicePayment.js';
|
||||
import { UserToUserPayment } from './entity/UserToUserPayment.js';
|
||||
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User }
|
||||
import { Application } from './entity/Application.js';
|
||||
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application }
|
||||
export const defaultInvoiceExpiry = 60 * 60
|
||||
export default class {
|
||||
DB: DataSource | EntityManager
|
||||
|
|
@ -83,7 +84,8 @@ export default class {
|
|||
user: user,
|
||||
product: options.product,
|
||||
expires_at_unix: Math.floor(Date.now() / 1000) + options.expiry,
|
||||
payer: options.expectedPayer
|
||||
payer: options.expectedPayer,
|
||||
linkedApplication: options.linkedApplication
|
||||
})
|
||||
return entityManager.getRepository(UserReceivingInvoice).save(newUserInvoice)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ export default class {
|
|||
}
|
||||
}
|
||||
async UnlockUser(userId: string, entityManager = this.DB) {
|
||||
console.log("unlocking", userId)
|
||||
const res = await entityManager.getRepository(User).update({
|
||||
user_id: userId
|
||||
}, { locked: false })
|
||||
|
|
@ -102,4 +103,9 @@ export default class {
|
|||
throw new Error("unaffected balance decrement for " + userId) // TODO: fix logs doxing
|
||||
}
|
||||
}
|
||||
|
||||
async UpdateUser(userId: string, update: Partial<User>, entityManager = this.DB) {
|
||||
const user = await this.GetUser(userId, entityManager)
|
||||
await entityManager.getRepository(User).update(user.serial_id, update)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue