validate access rules

This commit is contained in:
boufni95 2024-09-20 20:08:03 +00:00
parent 9a1aff58d0
commit a2d502baa9
9 changed files with 161 additions and 42 deletions

View file

@ -800,6 +800,7 @@ The nostr server will send back a message response, and inside the body there wi
- __admin_token__: _string_ - __admin_token__: _string_
### FrequencyRule ### FrequencyRule
- __amount__: _number_
- __interval__: _[IntervalType](#IntervalType)_ - __interval__: _[IntervalType](#IntervalType)_
- __number_of_intervals__: _number_ - __number_of_intervals__: _number_
@ -855,7 +856,6 @@ The nostr server will send back a message response, and inside the body there wi
- __token__: _string_ - __token__: _string_
### LiveDebitRequest ### LiveDebitRequest
- __amount__: _number_
- __debit__: _[LiveDebitRequest_debit](#LiveDebitRequest_debit)_ - __debit__: _[LiveDebitRequest_debit](#LiveDebitRequest_debit)_
- __npub__: _string_ - __npub__: _string_

View file

@ -202,6 +202,7 @@ type EnrollAdminTokenRequest struct {
Admin_token string `json:"admin_token"` Admin_token string `json:"admin_token"`
} }
type FrequencyRule struct { type FrequencyRule struct {
Amount int64 `json:"amount"`
Interval IntervalType `json:"interval"` Interval IntervalType `json:"interval"`
Number_of_intervals int64 `json:"number_of_intervals"` Number_of_intervals int64 `json:"number_of_intervals"`
} }
@ -257,9 +258,8 @@ type LinkNPubThroughTokenRequest struct {
Token string `json:"token"` Token string `json:"token"`
} }
type LiveDebitRequest struct { type LiveDebitRequest struct {
Amount int64 `json:"amount"` Debit *LiveDebitRequest_debit `json:"debit"`
Debit *LiveDebitRequest_debit `json:"debit"` Npub string `json:"npub"`
Npub string `json:"npub"`
} }
type LiveUserOperation struct { type LiveUserOperation struct {
Operation *UserOperation `json:"operation"` Operation *UserOperation `json:"operation"`

View file

@ -1094,12 +1094,14 @@ export const EnrollAdminTokenRequestValidate = (o?: EnrollAdminTokenRequest, opt
} }
export type FrequencyRule = { export type FrequencyRule = {
amount: number
interval: IntervalType interval: IntervalType
number_of_intervals: number number_of_intervals: number
} }
export const FrequencyRuleOptionalFields: [] = [] export const FrequencyRuleOptionalFields: [] = []
export type FrequencyRuleOptions = OptionsBaseMessage & { export type FrequencyRuleOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: [] checkOptionalsAreSet?: []
amount_CustomCheck?: (v: number) => boolean
interval_CustomCheck?: (v: IntervalType) => boolean interval_CustomCheck?: (v: IntervalType) => boolean
number_of_intervals_CustomCheck?: (v: number) => boolean number_of_intervals_CustomCheck?: (v: number) => boolean
} }
@ -1107,6 +1109,9 @@ export const FrequencyRuleValidate = (o?: FrequencyRule, opts: FrequencyRuleOpti
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.amount !== 'number') return new Error(`${path}.amount: is not a number`)
if (opts.amount_CustomCheck && !opts.amount_CustomCheck(o.amount)) return new Error(`${path}.amount: custom check failed`)
if (!enumCheckIntervalType(o.interval)) return new Error(`${path}.interval: is not a valid IntervalType`) if (!enumCheckIntervalType(o.interval)) return new Error(`${path}.interval: is not a valid IntervalType`)
if (opts.interval_CustomCheck && !opts.interval_CustomCheck(o.interval)) return new Error(`${path}.interval: custom check failed`) if (opts.interval_CustomCheck && !opts.interval_CustomCheck(o.interval)) return new Error(`${path}.interval: custom check failed`)
@ -1419,14 +1424,12 @@ export const LinkNPubThroughTokenRequestValidate = (o?: LinkNPubThroughTokenRequ
} }
export type LiveDebitRequest = { export type LiveDebitRequest = {
amount: number
debit: LiveDebitRequest_debit debit: LiveDebitRequest_debit
npub: string npub: string
} }
export const LiveDebitRequestOptionalFields: [] = [] export const LiveDebitRequestOptionalFields: [] = []
export type LiveDebitRequestOptions = OptionsBaseMessage & { export type LiveDebitRequestOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: [] checkOptionalsAreSet?: []
amount_CustomCheck?: (v: number) => boolean
debit_Options?: LiveDebitRequest_debitOptions debit_Options?: LiveDebitRequest_debitOptions
npub_CustomCheck?: (v: string) => boolean npub_CustomCheck?: (v: string) => boolean
} }
@ -1434,9 +1437,6 @@ export const LiveDebitRequestValidate = (o?: LiveDebitRequest, opts: LiveDebitRe
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.amount !== 'number') return new Error(`${path}.amount: is not a number`)
if (opts.amount_CustomCheck && !opts.amount_CustomCheck(o.amount)) return new Error(`${path}.amount: custom check failed`)
const debitErr = LiveDebitRequest_debitValidate(o.debit, opts.debit_Options, `${path}.debit`) const debitErr = LiveDebitRequest_debitValidate(o.debit, opts.debit_Options, `${path}.debit`)
if (debitErr !== null) return debitErr if (debitErr !== null) return debitErr

View file

@ -512,6 +512,7 @@ enum IntervalType {
message FrequencyRule { message FrequencyRule {
int64 number_of_intervals = 1; int64 number_of_intervals = 1;
IntervalType interval = 2; IntervalType interval = 2;
int64 amount = 3;
} }
message DebitRule { message DebitRule {
@ -523,7 +524,6 @@ message DebitRule {
message LiveDebitRequest { message LiveDebitRequest {
string npub = 1; string npub = 1;
int64 amount = 2;
oneof debit { oneof debit {
string invoice = 3; string invoice = 3;
FrequencyRule frequency = 4; FrequencyRule frequency = 4;

View file

@ -5,6 +5,9 @@ import Storage from '../storage/index.js'
import LND from "../lnd/lnd.js" import LND from "../lnd/lnd.js"
import { ERROR, getLogger } from "../helpers/logger.js"; import { ERROR, getLogger } from "../helpers/logger.js";
import { DebitAccess, DebitAccessRules } from '../storage/entity/DebitAccess.js'; import { DebitAccess, DebitAccessRules } from '../storage/entity/DebitAccess.js';
import paymentManager from './paymentManager.js';
import { Application } from '../storage/entity/Application.js';
import { ApplicationUser } from '../storage/entity/ApplicationUser.js';
export const expirationRuleName = 'expiration' export const expirationRuleName = 'expiration'
export const frequencyRuleName = 'frequency' export const frequencyRuleName = 'frequency'
type RecurringDebitTimeUnit = 'day' | 'week' | 'month' type RecurringDebitTimeUnit = 'day' | 'week' | 'month'
@ -16,8 +19,78 @@ const unitToIntervalType = (unit: RecurringDebitTimeUnit) => {
case 'month': return Types.IntervalType.MONTH case 'month': return Types.IntervalType.MONTH
default: throw new Error("invalid unit") default: throw new Error("invalid unit")
} }
} }
const intervalTypeToUnit = (interval: Types.IntervalType): RecurringDebitTimeUnit => {
switch (interval) {
case Types.IntervalType.DAY: return 'day'
case Types.IntervalType.WEEK: return 'week'
case Types.IntervalType.MONTH: return 'month'
default: throw new Error("invalid interval")
}
}
const IntervalTypeToSeconds = (interval: Types.IntervalType) => {
switch (interval) {
case Types.IntervalType.DAY: return 24 * 60 * 60
case Types.IntervalType.WEEK: return 7 * 24 * 60 * 60
case Types.IntervalType.MONTH: return 30 * 24 * 60 * 60
default: throw new Error("invalid interval")
}
}
const debitRulesToDebitAccessRules = (rule: Types.DebitRule[]): DebitAccessRules | undefined => {
let rules: DebitAccessRules | undefined = undefined
rule.forEach(r => {
if (!rules) {
rules = {}
}
const { rule } = r
switch (rule.type) {
case Types.DebitRule_rule_type.EXPIRATION_RULE:
rules[expirationRuleName] = [rule.expiration_rule.expires_at_unix.toString()]
break
case Types.DebitRule_rule_type.FREQUENCY_RULE:
const intervals = rule.frequency_rule.number_of_intervals.toString()
const unit = intervalTypeToUnit(rule.frequency_rule.interval)
return { key: frequencyRuleName, val: [intervals, unit, rule.frequency_rule.amount.toString()] }
default:
throw new Error("invalid rule")
}
})
return rules
}
const debitAccessRulesToDebitRules = (rules: DebitAccessRules | null): Types.DebitRule[] => {
if (!rules) {
return []
}
return Object.entries(rules).map(([key, val]) => {
switch (key) {
case expirationRuleName:
return {
rule: {
type: Types.DebitRule_rule_type.EXPIRATION_RULE,
expiration_rule: {
expires_at_unix: +val[0]
}
}
}
case frequencyRuleName:
return {
rule: {
type: Types.DebitRule_rule_type.FREQUENCY_RULE,
frequency_rule: {
number_of_intervals: +val[0],
interval: unitToIntervalType(val[1] as RecurringDebitTimeUnit),
amount: +val[2]
}
}
}
default:
throw new Error("invalid rule")
}
})
}
export type NdebitData = { pointer?: string, amount_sats: number } & (RecurringDebit | { bolt11: string }) export type NdebitData = { pointer?: string, amount_sats: number } & (RecurringDebit | { bolt11: string })
export type NdebitSuccess = { res: 'ok' } export type NdebitSuccess = { res: 'ok' }
export type NdebitSuccessPayment = { res: 'ok', preimage: string } export type NdebitSuccessPayment = { res: 'ok', preimage: string }
@ -31,14 +104,15 @@ const nip68errs = {
6: "Invalid Request", 6: "Invalid Request",
} }
type HandleNdebitRes = { status: 'fail', debitRes: NdebitFailure } type HandleNdebitRes = { status: 'fail', debitRes: NdebitFailure }
| { status: 'invoicePaid', op: Types.UserOperation, appUserId: string, debitRes: NdebitSuccessPayment } | { status: 'invoicePaid', op: Types.UserOperation, app: Application, appUser: ApplicationUser, debitRes: NdebitSuccessPayment }
| { status: 'authRequired', liveDebitReq: Types.LiveDebitRequest, appUserId: string } | { status: 'authRequired', liveDebitReq: Types.LiveDebitRequest, app: Application, appUser: ApplicationUser }
| { status: 'authOk', debitRes: NdebitSuccess } | { status: 'authOk', debitRes: NdebitSuccess }
export class DebitManager { export class DebitManager {
applicationManager: ApplicationManager applicationManager: ApplicationManager
storage: Storage storage: Storage
lnd: LND lnd: LND
logger = getLogger({ component: 'DebitManager' }) logger = getLogger({ component: 'DebitManager' })
@ -49,12 +123,16 @@ export class DebitManager {
} }
AuthorizeDebit = async (ctx: Types.UserContext, req: Types.DebitAuthorizationRequest): Promise<Types.DebitAuthorization> => { AuthorizeDebit = async (ctx: Types.UserContext, req: Types.DebitAuthorizationRequest): Promise<Types.DebitAuthorization> => {
const access = await this.storage.debitStorage.AddDebitAccess(ctx.app_user_id, req.authorize_npub) const access = await this.storage.debitStorage.AddDebitAccess(ctx.app_user_id, {
authorize: true,
npub: req.authorize_npub,
rules: debitRulesToDebitAccessRules(req.rules)
})
return { return {
debit_id: access.serial_id.toString(), debit_id: access.serial_id.toString(),
npub: req.authorize_npub, npub: req.authorize_npub,
authorized: true, authorized: true,
rules: [] rules: req.rules
} }
} }
@ -64,7 +142,7 @@ export class DebitManager {
debit_id: access.serial_id.toString(), debit_id: access.serial_id.toString(),
authorized: access.authorized, authorized: access.authorized,
npub: access.npub, npub: access.npub,
rules: [] rules: debitAccessRulesToDebitRules(access.rules)
})) }))
return { debits } return { debits }
} }
@ -92,6 +170,8 @@ export class DebitManager {
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[1], code: 1 } } return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[1], code: 1 } }
} }
const appUserId = pointer const appUserId = pointer
const app = await this.storage.applicationStorage.GetApplication(appId)
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, appUserId)
const pointerFreq = pointerdata as RecurringDebit const pointerFreq = pointerdata as RecurringDebit
if (pointerFreq.frequency) { if (pointerFreq.frequency) {
if (!amount_sats) { if (!amount_sats) {
@ -100,14 +180,14 @@ export class DebitManager {
const debitAccess = await this.storage.debitStorage.GetDebitAccess(appUserId, requestorPub) const debitAccess = await this.storage.debitStorage.GetDebitAccess(appUserId, requestorPub)
if (!debitAccess) { if (!debitAccess) {
return { return {
status: 'authRequired', appUserId, liveDebitReq: { status: 'authRequired', app, appUser, liveDebitReq: {
amount: pointerdata.amount_sats,
npub: requestorPub, npub: requestorPub,
debit: { debit: {
type: Types.LiveDebitRequest_debit_type.FREQUENCY, type: Types.LiveDebitRequest_debit_type.FREQUENCY,
frequency: { frequency: {
interval: unitToIntervalType(pointerFreq.frequency.unit), interval: unitToIntervalType(pointerFreq.frequency.unit),
number_of_intervals: pointerFreq.frequency.number, number_of_intervals: pointerFreq.frequency.number,
amount: pointerdata.amount_sats,
} }
} }
} }
@ -132,8 +212,7 @@ export class DebitManager {
const authorization = await this.storage.debitStorage.GetDebitAccess(appUserId, requestorPub) const authorization = await this.storage.debitStorage.GetDebitAccess(appUserId, requestorPub)
if (!authorization) { if (!authorization) {
return { return {
status: 'authRequired', appUserId, liveDebitReq: { status: 'authRequired', app, appUser, liveDebitReq: {
amount: pointerdata.amount_sats,
npub: requestorPub, npub: requestorPub,
debit: { debit: {
type: Types.LiveDebitRequest_debit_type.INVOICE, type: Types.LiveDebitRequest_debit_type.INVOICE,
@ -145,33 +224,46 @@ export class DebitManager {
if (!authorization.authorized) { if (!authorization.authorized) {
return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[1], code: 1 } } return { status: 'fail', debitRes: { res: 'GFY', error: nip68errs[1], code: 1 } }
} }
await this.validateAccessRules(authorization) await this.validateAccessRules(authorization, app, appUser)
const payment = await this.applicationManager.PayAppUserInvoice(appId, { amount: 0, invoice: bolt11, user_identifier: appUserId }) const payment = await this.applicationManager.PayAppUserInvoice(appId, { amount: 0, invoice: bolt11, user_identifier: appUserId })
await this.storage.debitStorage.IncrementDebitAccess(appUserId, requestorPub, payment.amount_paid + payment.service_fee + payment.network_fee) await this.storage.debitStorage.IncrementDebitAccess(appUserId, requestorPub, payment.amount_paid + payment.service_fee + payment.network_fee)
const op = this.newPaymentOperation(payment, bolt11) const op = this.newPaymentOperation(payment, bolt11)
return { status: 'invoicePaid', op, appUserId, debitRes: { res: 'ok', preimage: payment.preimage } } return { status: 'invoicePaid', op, app, appUser, debitRes: { res: 'ok', preimage: payment.preimage } }
} }
validateAccessRules = async (access: DebitAccess): Promise<boolean> => { validateAccessRules = async (access: DebitAccess, app: Application, appUser: ApplicationUser): Promise<boolean> => {
const { rules } = access const { rules } = access
if (!rules) { if (!rules) {
return true return true
} }
return false if (rules[expirationRuleName]) {
// TODO: rules validation const [expiration] = rules[expirationRuleName]
/* if (rules[expirationRuleName]) { if (+expiration < Date.now()) {
await this.storage.debitStorage.RemoveDebitAccess(access.app_user_id, access.npub)
} return false
if (rules[frequencyRuleName]) { }
}
} */ if (rules[frequencyRuleName]) {
const [number, unit, max] = rules[frequencyRuleName]
const intervalType = unitToIntervalType(unit as RecurringDebitTimeUnit)
const seconds = IntervalTypeToSeconds(intervalType) * (+number)
const sinceUnix = Math.floor(Date.now() / 1000) * seconds
const payments = await this.storage.paymentStorage.GetUserDebitPayments(appUser.user.user_id, sinceUnix, access.npub)
let total = 0
for (const payment of payments) {
total += payment.paid_amount
}
if (total > +max) {
return false
}
}
return true
} }
newPaymentOperation = (payment: Types.PayInvoiceResponse, bolt11: string) => { newPaymentOperation = (payment: Types.PayInvoiceResponse, bolt11: string) => {
return { return {
amount: payment.amount_paid, amount: payment.amount_paid,
paidAtUnix: Date.now() / 1000, paidAtUnix: Math.floor(Date.now() / 1000),
inbound: false, inbound: false,
type: Types.UserOperationType.OUTGOING_INVOICE, type: Types.UserOperationType.OUTGOING_INVOICE,
identifier: bolt11, identifier: bolt11,

View file

@ -328,8 +328,7 @@ export default class {
this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } }) this.nostrSend({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
return return
} }
const app = await this.storage.applicationStorage.GetApplication(event.appId) const { appUser } = res
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, res.appUserId)
if (res.status === 'authRequired') { if (res.status === 'authRequired') {
const message: Types.LiveDebitRequest & { requestId: string, status: 'OK' } = { ...res.liveDebitReq, requestId: "GetLiveDebitRequests", status: 'OK' } const message: Types.LiveDebitRequest & { requestId: string, status: 'OK' } = { ...res.liveDebitReq, requestId: "GetLiveDebitRequests", status: 'OK' }
if (appUser.nostr_public_key) {// TODO - fix before support for http streams if (appUser.nostr_public_key) {// TODO - fix before support for http streams

View file

@ -2,6 +2,11 @@ import { DataSource, EntityManager } from "typeorm"
import UserStorage from './userStorage.js'; import UserStorage from './userStorage.js';
import TransactionsQueue from "./transactionsQueue.js"; import TransactionsQueue from "./transactionsQueue.js";
import { DebitAccess, DebitAccessRules } from "./entity/DebitAccess.js"; import { DebitAccess, DebitAccessRules } from "./entity/DebitAccess.js";
type AccessToAdd = {
npub: string
rules?: DebitAccessRules
authorize: boolean
}
export default class { export default class {
DB: DataSource | EntityManager DB: DataSource | EntityManager
txQueue: TransactionsQueue txQueue: TransactionsQueue
@ -10,11 +15,12 @@ export default class {
this.txQueue = txQueue this.txQueue = txQueue
} }
async AddDebitAccess(appUserId: string, pubToAuthorize: string, authorize = true, entityManager = this.DB) { async AddDebitAccess(appUserId: string, access: AccessToAdd, entityManager = this.DB) {
const entry = entityManager.getRepository(DebitAccess).create({ const entry = entityManager.getRepository(DebitAccess).create({
app_user_id: appUserId, app_user_id: appUserId,
npub: pubToAuthorize, npub: access.npub,
authorized: authorize, authorized: access.authorize,
rules: access.rules,
}) })
return this.txQueue.PushToQueue<DebitAccess>({ exec: async db => db.getRepository(DebitAccess).save(entry), dbTx: false }) return this.txQueue.PushToQueue<DebitAccess>({ exec: async db => db.getRepository(DebitAccess).save(entry), dbTx: false })
} }
@ -41,7 +47,7 @@ export default class {
async DenyDebitAccess(appUserId: string, pub: string) { async DenyDebitAccess(appUserId: string, pub: string) {
const access = await this.GetDebitAccess(appUserId, pub) const access = await this.GetDebitAccess(appUserId, pub)
if (!access) { if (!access) {
await this.AddDebitAccess(appUserId, pub, false) await this.AddDebitAccess(appUserId, { npub: pub, authorize: false })
} }
await this.UpdateDebitAccess(appUserId, pub, false) await this.UpdateDebitAccess(appUserId, pub, false)
} }

View file

@ -44,6 +44,9 @@ export class UserInvoicePayment {
}) })
paymentIndex: number paymentIndex: number
@Column({ nullable: true })
debit_to_pub: string
@CreateDateColumn() @CreateDateColumn()
created_at: Date created_at: Date

View file

@ -1,5 +1,5 @@
import crypto from 'crypto'; import crypto from 'crypto';
import { Between, DataSource, EntityManager, FindOperator, IsNull, LessThanOrEqual, MoreThan, MoreThanOrEqual } from "typeorm" import { Between, DataSource, EntityManager, FindOperator, IsNull, LessThanOrEqual, MoreThan, MoreThanOrEqual, Not } from "typeorm"
import { User } from './entity/User.js'; import { User } from './entity/User.js';
import { UserTransactionPayment } from './entity/UserTransactionPayment.js'; import { UserTransactionPayment } from './entity/UserTransactionPayment.js';
import { EphemeralKeyType, UserEphemeralKey } from './entity/UserEphemeralKey.js'; import { EphemeralKeyType, UserEphemeralKey } from './entity/UserEphemeralKey.js';
@ -154,9 +154,9 @@ export default class {
}) })
} }
async AddPendingExternalPayment(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, networkFee: number }, linkedApplication: Application, liquidityProvider: string | undefined,dbTx:DataSource|EntityManager): Promise<UserInvoicePayment> { async AddPendingExternalPayment(userId: string, invoice: string, amounts: { payAmount: number, serviceFee: number, networkFee: number }, linkedApplication: Application, liquidityProvider: string | undefined, dbTx: DataSource | EntityManager): Promise<UserInvoicePayment> {
const newPayment = dbTx.getRepository(UserInvoicePayment).create({ const newPayment = dbTx.getRepository(UserInvoicePayment).create({
user: await this.userStorage.GetUser(userId,dbTx), user: await this.userStorage.GetUser(userId, dbTx),
paid_amount: amounts.payAmount, paid_amount: amounts.payAmount,
invoice, invoice,
routing_fees: amounts.networkFee, routing_fees: amounts.networkFee,
@ -215,6 +215,25 @@ export default class {
}) })
} }
GetUserDebitPayments(userId: string, sinceUnix: number, debitToNpub: string, entityManager = this.DB): Promise<UserInvoicePayment[]> {
const pending = {
user: { user_id: userId },
debit_to_pub: debitToNpub,
paid_at_unix: 0,
}
const paid = {
user: { user_id: userId },
debit_to_pub: debitToNpub,
paid_at_unix: MoreThan(sinceUnix),
}
return entityManager.getRepository(UserInvoicePayment).find({
where: [pending, paid],
order: {
paid_at_unix: 'DESC'
}
})
}
async AddUserTransactionPayment(userId: string, address: string, txHash: string, txOutput: number, amount: number, chainFees: number, serviceFees: number, internal: boolean, height: number, linkedApplication: Application): Promise<UserTransactionPayment> { async AddUserTransactionPayment(userId: string, address: string, txHash: string, txOutput: number, amount: number, chainFees: number, serviceFees: number, internal: boolean, height: number, linkedApplication: Application): Promise<UserTransactionPayment> {
const newTx = this.DB.getRepository(UserTransactionPayment).create({ const newTx = this.DB.getRepository(UserTransactionPayment).create({
user: await this.userStorage.GetUser(userId), user: await this.userStorage.GetUser(userId),