list swaps
This commit is contained in:
parent
bf26e2ba83
commit
5dd03063ff
17 changed files with 291 additions and 12 deletions
|
|
@ -43,6 +43,7 @@ import { UserAccess1759426050669 } from './build/src/services/storage/migrations
|
|||
import { AddBlindToUserOffer1760000000000 } from './build/src/services/storage/migrations/1760000000000-add_blind_to_user_offer.js'
|
||||
import { ApplicationAvatarUrl1761000001000 } from './build/src/services/storage/migrations/1761000001000-application_avatar_url.js'
|
||||
import { AdminSettings1761683639419 } from './build/src/services/storage/migrations/1761683639419-admin_settings.js'
|
||||
import { TxSwap1762890527098 } from './build/src/services/storage/migrations/1762890527098-tx_swap.js'
|
||||
|
||||
export default new DataSource({
|
||||
type: "better-sqlite3",
|
||||
|
|
@ -51,11 +52,11 @@ export default new DataSource({
|
|||
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878,
|
||||
PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043,
|
||||
UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611,
|
||||
AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419],
|
||||
AppUserDevice1753285173175, UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098],
|
||||
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo,
|
||||
TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant, AppUserDevice, UserAccess, AdminSettings, TransactionSwap],
|
||||
// synchronize: true,
|
||||
})
|
||||
//npx typeorm migration:generate ./src/services/storage/migrations/tx_swap -d ./datasource.js
|
||||
//npx typeorm migration:generate ./src/services/storage/migrations/tx_swap_address -d ./datasource.js
|
||||
|
|
@ -243,6 +243,11 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- This methods has an __empty__ __request__ body
|
||||
- output: [LndChannels](#LndChannels)
|
||||
|
||||
- ListSwaps
|
||||
- auth type: __User__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [SwapsList](#SwapsList)
|
||||
|
||||
- LndGetInfo
|
||||
- auth type: __Admin__
|
||||
- input: [LndGetInfoRequest](#LndGetInfoRequest)
|
||||
|
|
@ -814,6 +819,13 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- This methods has an __empty__ __request__ body
|
||||
- output: [LndChannels](#LndChannels)
|
||||
|
||||
- ListSwaps
|
||||
- auth type: __User__
|
||||
- http method: __post__
|
||||
- http route: __/api/user/swap/list__
|
||||
- This methods has an __empty__ __request__ body
|
||||
- output: [SwapsList](#SwapsList)
|
||||
|
||||
- LndGetInfo
|
||||
- auth type: __Admin__
|
||||
- http method: __post__
|
||||
|
|
@ -1582,6 +1594,15 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __page__: _number_
|
||||
- __request_id__: _number_ *this field is optional
|
||||
|
||||
### SwapOperation
|
||||
- __address_paid__: _string_
|
||||
- __failure_reason__: _string_ *this field is optional
|
||||
- __operation_payment__: _[UserOperation](#UserOperation)_ *this field is optional
|
||||
- __swap_operation_id__: _string_
|
||||
|
||||
### SwapsList
|
||||
- __swaps__: ARRAY of: _[SwapOperation](#SwapOperation)_
|
||||
|
||||
### TransactionSwapQuote
|
||||
- __chain_fee_sats__: _number_
|
||||
- __invoice_amount_sats__: _number_
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ type Client struct {
|
|||
Health func() error
|
||||
LinkNPubThroughToken func(req LinkNPubThroughTokenRequest) error
|
||||
ListChannels func() (*LndChannels, error)
|
||||
ListSwaps func() (*SwapsList, error)
|
||||
LndGetInfo func(req LndGetInfoRequest) (*LndGetInfoResponse, error)
|
||||
NewAddress func(req NewAddressRequest) (*NewAddressResponse, error)
|
||||
NewInvoice func(req NewInvoiceRequest) (*NewInvoiceResponse, error)
|
||||
|
|
@ -1632,6 +1633,32 @@ func NewClient(params ClientParams) *Client {
|
|||
}
|
||||
return &res, nil
|
||||
},
|
||||
ListSwaps: func() (*SwapsList, error) {
|
||||
auth, err := params.RetrieveUserAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalRoute := "/api/user/swap/list"
|
||||
body := []byte{}
|
||||
resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := ResultError{}
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.Status == "ERROR" {
|
||||
return nil, fmt.Errorf(result.Reason)
|
||||
}
|
||||
res := SwapsList{}
|
||||
err = json.Unmarshal(resBody, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
},
|
||||
LndGetInfo: func(req LndGetInfoRequest) (*LndGetInfoResponse, error) {
|
||||
auth, err := params.RetrieveAdminAuth()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -655,6 +655,15 @@ type SingleMetricReq struct {
|
|||
Page int64 `json:"page"`
|
||||
Request_id int64 `json:"request_id"`
|
||||
}
|
||||
type SwapOperation struct {
|
||||
Address_paid string `json:"address_paid"`
|
||||
Failure_reason string `json:"failure_reason"`
|
||||
Operation_payment *UserOperation `json:"operation_payment"`
|
||||
Swap_operation_id string `json:"swap_operation_id"`
|
||||
}
|
||||
type SwapsList struct {
|
||||
Swaps []SwapOperation `json:"swaps"`
|
||||
}
|
||||
type TransactionSwapQuote struct {
|
||||
Chain_fee_sats int64 `json:"chain_fee_sats"`
|
||||
Invoice_amount_sats int64 `json:"invoice_amount_sats"`
|
||||
|
|
|
|||
|
|
@ -545,6 +545,16 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'ListSwaps':
|
||||
if (!methods.ListSwaps) {
|
||||
throw new Error('method ListSwaps not found' )
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.ListSwaps({...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 NewAddress not found' )
|
||||
|
|
@ -1594,6 +1604,25 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
|||
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 }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.ListSwaps) throw new Error('method: ListSwaps is not implemented')
|
||||
app.post('/api/user/swap/list', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'ListSwaps', batch: false, nostr: false, batchSize: 0}
|
||||
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
|
||||
let authCtx: Types.AuthContext = {}
|
||||
try {
|
||||
if (!methods.ListSwaps) throw new Error('method: ListSwaps is not implemented')
|
||||
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
|
||||
authCtx = authContext
|
||||
stats.guard = process.hrtime.bigint()
|
||||
stats.validate = stats.guard
|
||||
const query = req.query
|
||||
const params = req.params
|
||||
const response = await methods.ListSwaps({rpcName:'ListSwaps', ctx:authContext })
|
||||
stats.handle = process.hrtime.bigint()
|
||||
res.json({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 }
|
||||
})
|
||||
if (!opts.allowNotImplementedMethods && !methods.LndGetInfo) throw new Error('method: LndGetInfo is not implemented')
|
||||
app.post('/api/admin/lnd/getinfo', async (req, res) => {
|
||||
const info: Types.RequestInfo = { rpcName: 'LndGetInfo', batch: false, nostr: false, batchSize: 0}
|
||||
|
|
|
|||
|
|
@ -781,6 +781,20 @@ export default (params: ClientParams) => ({
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.SwapsList)> => {
|
||||
const auth = await params.retrieveUserAuth()
|
||||
if (auth === null) throw new Error('retrieveUserAuth() returned null')
|
||||
let finalRoute = '/api/user/swap/list'
|
||||
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.SwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
LndGetInfo: async (request: Types.LndGetInfoRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndGetInfoResponse)> => {
|
||||
const auth = await params.retrieveAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||
|
|
|
|||
|
|
@ -665,6 +665,20 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
|||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
ListSwaps: async (): Promise<ResultError | ({ status: 'OK' }& Types.SwapsList)> => {
|
||||
const auth = await params.retrieveNostrUserAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||
const nostrRequest: NostrRequest = {}
|
||||
const data = await send(params.pubDestination, {rpcName:'ListSwaps',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.SwapsListValidate(result)
|
||||
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||
}
|
||||
return { status: 'ERROR', reason: 'invalid response' }
|
||||
},
|
||||
LndGetInfo: async (request: Types.LndGetInfoRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndGetInfoResponse)> => {
|
||||
const auth = await params.retrieveNostrAdminAuth()
|
||||
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||
|
|
|
|||
|
|
@ -427,6 +427,16 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
|
||||
}
|
||||
break
|
||||
case 'ListSwaps':
|
||||
if (!methods.ListSwaps) {
|
||||
throw new Error('method not defined: ListSwaps')
|
||||
} else {
|
||||
opStats.validate = opStats.guard
|
||||
const res = await methods.ListSwaps({...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')
|
||||
|
|
@ -1109,6 +1119,19 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
|||
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 'ListSwaps':
|
||||
try {
|
||||
if (!methods.ListSwaps) throw new Error('method: ListSwaps 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.ListSwaps({rpcName:'ListSwaps', 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 'LndGetInfo':
|
||||
try {
|
||||
if (!methods.LndGetInfo) throw new Error('method: LndGetInfo is not implemented')
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ export type UserContext = {
|
|||
app_user_id: string
|
||||
user_id: string
|
||||
}
|
||||
export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeManage_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | EnrollMessagingToken_Input | GetDebitAuthorizations_Input | GetHttpCreds_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetManageAuthorizations_Input | GetPaymentState_Input | GetTransactionSwapQuote_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOfferInvoices_Input | GetUserOffers_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | ResetManage_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input
|
||||
export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeManage_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | EnrollMessagingToken_Output | GetDebitAuthorizations_Output | GetHttpCreds_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetManageAuthorizations_Output | GetPaymentState_Output | GetTransactionSwapQuote_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOfferInvoices_Output | GetUserOffers_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | ResetManage_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output
|
||||
export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeManage_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | EnrollMessagingToken_Input | GetDebitAuthorizations_Input | GetHttpCreds_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetManageAuthorizations_Input | GetPaymentState_Input | GetTransactionSwapQuote_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOfferInvoices_Input | GetUserOffers_Input | GetUserOperations_Input | ListSwaps_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | ResetManage_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input
|
||||
export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeManage_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | EnrollMessagingToken_Output | GetDebitAuthorizations_Output | GetHttpCreds_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetManageAuthorizations_Output | GetPaymentState_Output | GetTransactionSwapQuote_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOfferInvoices_Output | GetUserOffers_Output | GetUserOperations_Output | ListSwaps_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | ResetManage_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output
|
||||
export type AuthContext = AdminContext | AppContext | GuestContext | GuestWithPubContext | MetricsContext | UserContext
|
||||
|
||||
export type AddApp_Input = {rpcName:'AddApp', req: AddAppRequest}
|
||||
|
|
@ -238,6 +238,9 @@ export type LinkNPubThroughToken_Output = ResultError | { status: 'OK' }
|
|||
export type ListChannels_Input = {rpcName:'ListChannels'}
|
||||
export type ListChannels_Output = ResultError | ({ status: 'OK' } & LndChannels)
|
||||
|
||||
export type ListSwaps_Input = {rpcName:'ListSwaps'}
|
||||
export type ListSwaps_Output = ResultError | ({ status: 'OK' } & SwapsList)
|
||||
|
||||
export type LndGetInfo_Input = {rpcName:'LndGetInfo', req: LndGetInfoRequest}
|
||||
export type LndGetInfo_Output = ResultError | ({ status: 'OK' } & LndGetInfoResponse)
|
||||
|
||||
|
|
@ -385,6 +388,7 @@ export type ServerMethods = {
|
|||
Health?: (req: Health_Input & {ctx: GuestContext }) => Promise<void>
|
||||
LinkNPubThroughToken?: (req: LinkNPubThroughToken_Input & {ctx: GuestWithPubContext }) => Promise<void>
|
||||
ListChannels?: (req: ListChannels_Input & {ctx: AdminContext }) => Promise<LndChannels>
|
||||
ListSwaps?: (req: ListSwaps_Input & {ctx: UserContext }) => Promise<SwapsList>
|
||||
LndGetInfo?: (req: LndGetInfo_Input & {ctx: AdminContext }) => Promise<LndGetInfoResponse>
|
||||
NewAddress?: (req: NewAddress_Input & {ctx: UserContext }) => Promise<NewAddressResponse>
|
||||
NewInvoice?: (req: NewInvoice_Input & {ctx: UserContext }) => Promise<NewInvoiceResponse>
|
||||
|
|
@ -3845,6 +3849,66 @@ export const SingleMetricReqValidate = (o?: SingleMetricReq, opts: SingleMetricR
|
|||
return null
|
||||
}
|
||||
|
||||
export type SwapOperation = {
|
||||
address_paid: string
|
||||
failure_reason?: string
|
||||
operation_payment?: UserOperation
|
||||
swap_operation_id: string
|
||||
}
|
||||
export type SwapOperationOptionalField = 'failure_reason' | 'operation_payment'
|
||||
export const SwapOperationOptionalFields: SwapOperationOptionalField[] = ['failure_reason', 'operation_payment']
|
||||
export type SwapOperationOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: SwapOperationOptionalField[]
|
||||
address_paid_CustomCheck?: (v: string) => boolean
|
||||
failure_reason_CustomCheck?: (v?: string) => boolean
|
||||
operation_payment_Options?: UserOperationOptions
|
||||
swap_operation_id_CustomCheck?: (v: string) => boolean
|
||||
}
|
||||
export const SwapOperationValidate = (o?: SwapOperation, opts: SwapOperationOptions = {}, path: string = 'SwapOperation::root.'): Error | null => {
|
||||
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
|
||||
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
|
||||
|
||||
if (typeof o.address_paid !== 'string') return new Error(`${path}.address_paid: is not a string`)
|
||||
if (opts.address_paid_CustomCheck && !opts.address_paid_CustomCheck(o.address_paid)) return new Error(`${path}.address_paid: custom check failed`)
|
||||
|
||||
if ((o.failure_reason || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('failure_reason')) && typeof o.failure_reason !== 'string') return new Error(`${path}.failure_reason: is not a string`)
|
||||
if (opts.failure_reason_CustomCheck && !opts.failure_reason_CustomCheck(o.failure_reason)) return new Error(`${path}.failure_reason: custom check failed`)
|
||||
|
||||
if (typeof o.operation_payment === 'object' || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('operation_payment')) {
|
||||
const operation_paymentErr = UserOperationValidate(o.operation_payment, opts.operation_payment_Options, `${path}.operation_payment`)
|
||||
if (operation_paymentErr !== null) return operation_paymentErr
|
||||
}
|
||||
|
||||
|
||||
if (typeof o.swap_operation_id !== 'string') return new Error(`${path}.swap_operation_id: is not a string`)
|
||||
if (opts.swap_operation_id_CustomCheck && !opts.swap_operation_id_CustomCheck(o.swap_operation_id)) return new Error(`${path}.swap_operation_id: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type SwapsList = {
|
||||
swaps: SwapOperation[]
|
||||
}
|
||||
export const SwapsListOptionalFields: [] = []
|
||||
export type SwapsListOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
swaps_ItemOptions?: SwapOperationOptions
|
||||
swaps_CustomCheck?: (v: SwapOperation[]) => boolean
|
||||
}
|
||||
export const SwapsListValidate = (o?: SwapsList, opts: SwapsListOptions = {}, path: string = 'SwapsList::root.'): Error | null => {
|
||||
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
|
||||
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
|
||||
|
||||
if (!Array.isArray(o.swaps)) return new Error(`${path}.swaps: is not an array`)
|
||||
for (let index = 0; index < o.swaps.length; index++) {
|
||||
const swapsErr = SwapOperationValidate(o.swaps[index], opts.swaps_ItemOptions, `${path}.swaps[${index}]`)
|
||||
if (swapsErr !== null) return swapsErr
|
||||
}
|
||||
if (opts.swaps_CustomCheck && !opts.swaps_CustomCheck(o.swaps)) return new Error(`${path}.swaps: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type TransactionSwapQuote = {
|
||||
chain_fee_sats: number
|
||||
invoice_amount_sats: number
|
||||
|
|
|
|||
|
|
@ -503,6 +503,13 @@ service LightningPub {
|
|||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc ListSwaps(structs.Empty) returns (structs.SwapsList){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
option (http_route) = "/api/user/swap/list";
|
||||
option (nostr) = true;
|
||||
}
|
||||
|
||||
rpc NewInvoice(structs.NewInvoiceRequest) returns (structs.NewInvoiceResponse){
|
||||
option (auth_type) = "User";
|
||||
option (http_method) = "post";
|
||||
|
|
|
|||
|
|
@ -843,6 +843,18 @@ message TransactionSwapQuote {
|
|||
int64 chain_fee_sats = 5;
|
||||
int64 service_fee_sats = 7;
|
||||
}
|
||||
|
||||
message SwapOperation {
|
||||
string swap_operation_id = 1;
|
||||
optional UserOperation operation_payment = 2;
|
||||
optional string failure_reason = 3;
|
||||
string address_paid = 4;
|
||||
}
|
||||
|
||||
message SwapsList {
|
||||
repeated SwapOperation swaps = 1;
|
||||
}
|
||||
|
||||
message CumulativeFees {
|
||||
int64 networkFeeFixed = 2;
|
||||
int64 serviceFeeBps = 3;
|
||||
|
|
|
|||
|
|
@ -490,18 +490,18 @@ export default class {
|
|||
payment = await this.PayInvoice(ctx.user_id, { amount: 0, invoice: txSwap.invoice }, app, { swapOperationId: req.swap_operation_id })
|
||||
if (!swapResult.ok) {
|
||||
this.log("invoice payment successful, but swap failed")
|
||||
await this.storage.paymentStorage.FailTransactionSwap(req.swap_operation_id, swapResult.error)
|
||||
await this.storage.paymentStorage.FailTransactionSwap(req.swap_operation_id, req.address, swapResult.error)
|
||||
throw new Error(swapResult.error)
|
||||
}
|
||||
this.log("swap completed successfully")
|
||||
await this.storage.paymentStorage.FinalizeTransactionSwap(req.swap_operation_id, swapResult.txId)
|
||||
await this.storage.paymentStorage.FinalizeTransactionSwap(req.swap_operation_id, req.address, swapResult.txId)
|
||||
} catch (err: any) {
|
||||
if (swapResult.ok) {
|
||||
this.log("failed to pay swap invoice, but swap completed successfully", swapResult.txId)
|
||||
await this.storage.paymentStorage.FailTransactionSwap(req.swap_operation_id, err.message)
|
||||
await this.storage.paymentStorage.FailTransactionSwap(req.swap_operation_id, req.address, err.message)
|
||||
} else {
|
||||
this.log("failed to pay swap invoice and swap failed", swapResult.error)
|
||||
await this.storage.paymentStorage.FailTransactionSwap(req.swap_operation_id, swapResult.error)
|
||||
await this.storage.paymentStorage.FailTransactionSwap(req.swap_operation_id, req.address, swapResult.error)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
|
@ -545,6 +545,23 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
async ListSwaps(ctx: Types.UserContext): Promise<Types.SwapsList> {
|
||||
const swaps = await this.storage.paymentStorage.ListCompletedSwaps(ctx.app_user_id)
|
||||
return {
|
||||
swaps: swaps.map(s => {
|
||||
const p = s.payment
|
||||
const opId = `${Types.UserOperationType.OUTGOING_TX}-${p?.serial_id}`
|
||||
const op = p ? this.newInvoicePaymentOperation({ amount: p.paid_amount, confirmed: p.paid_at_unix !== 0, invoice: p.invoice, opId, networkFee: p.routing_fees, serviceFee: p.service_fees }) : undefined
|
||||
return {
|
||||
operation_payment: op,
|
||||
swap_operation_id: s.swap.swap_operation_id,
|
||||
address_paid: s.swap.address_paid,
|
||||
failure_reason: s.swap.failure_reason,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
balanceCheckUrl(k1: string): string {
|
||||
return `${this.settings.getSettings().serviceSettings.serviceUrl}/api/guest/lnurl_withdraw/info?k1=${k1}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
|||
if (err != null) throw new Error(err.message)
|
||||
return mainHandler.paymentManager.PayAddress(ctx, req)
|
||||
},
|
||||
ListSwaps: async ({ ctx }) => {
|
||||
return mainHandler.paymentManager.ListSwaps(ctx)
|
||||
},
|
||||
GetTransactionSwapQuote: async ({ ctx, req }) => {
|
||||
return mainHandler.paymentManager.GetTransactionSwapQuote(ctx, req)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ export class TransactionSwap {
|
|||
@Column({ default: "" })
|
||||
tx_id: string
|
||||
|
||||
@Column({ default: "" })
|
||||
address_paid: string
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TxSwapAddress1764779178945 implements MigrationInterface {
|
||||
name = 'TxSwapAddress1764779178945'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "temporary_transaction_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "lockup_address" varchar NOT NULL, "refund_public_key" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "preimage" varchar NOT NULL, "ephemeral_public_key" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "address_paid" varchar NOT NULL DEFAULT (''))`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_transaction_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at" FROM "transaction_swap"`);
|
||||
await queryRunner.query(`DROP TABLE "transaction_swap"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_transaction_swap" RENAME TO "transaction_swap"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "transaction_swap" RENAME TO "temporary_transaction_swap"`);
|
||||
await queryRunner.query(`CREATE TABLE "transaction_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "lockup_address" varchar NOT NULL, "refund_public_key" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "preimage" varchar NOT NULL, "ephemeral_public_key" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
|
||||
await queryRunner.query(`INSERT INTO "transaction_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "lockup_address", "refund_public_key", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "preimage", "ephemeral_public_key", "ephemeral_private_key", "used", "failure_reason", "tx_id", "created_at", "updated_at" FROM "temporary_transaction_swap"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_transaction_swap"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,13 +28,14 @@ import { AddBlindToUserOffer1760000000000 } from './1760000000000-add_blind_to_u
|
|||
import { ApplicationAvatarUrl1761000001000 } from './1761000001000-application_avatar_url.js'
|
||||
import { AdminSettings1761683639419 } from './1761683639419-admin_settings.js'
|
||||
import { TxSwap1762890527098 } from './1762890527098-tx_swap.js'
|
||||
import { TxSwapAddress1764779178945 } from './1764779178945-tx_swap_address.js'
|
||||
|
||||
|
||||
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189,
|
||||
TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264,
|
||||
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513,
|
||||
InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175,
|
||||
UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098]
|
||||
UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098, TxSwapAddress1764779178945]
|
||||
|
||||
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411]
|
||||
/* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import crypto from 'crypto';
|
||||
import { And, Between, Equal, FindOperator, IsNull, LessThan, LessThanOrEqual, MoreThan, MoreThanOrEqual } from "typeorm"
|
||||
import { And, Between, Equal, FindOperator, IsNull, LessThan, LessThanOrEqual, MoreThan, MoreThanOrEqual, Not } from "typeorm"
|
||||
import { User } from './entity/User.js';
|
||||
import { UserTransactionPayment } from './entity/UserTransactionPayment.js';
|
||||
import { EphemeralKeyType, UserEphemeralKey } from './entity/UserEphemeralKey.js';
|
||||
|
|
@ -470,17 +470,19 @@ export default class {
|
|||
return this.dbs.FindOne<TransactionSwap>('TransactionSwap', { where: { swap_operation_id: swapOperationId, used: false, app_user_id: appUserId } }, txId)
|
||||
}
|
||||
|
||||
async FinalizeTransactionSwap(swapOperationId: string, txId: string) {
|
||||
async FinalizeTransactionSwap(swapOperationId: string, address: string, txId: string) {
|
||||
return this.dbs.Update<TransactionSwap>('TransactionSwap', { swap_operation_id: swapOperationId }, {
|
||||
used: true,
|
||||
tx_id: txId,
|
||||
address_paid: address,
|
||||
})
|
||||
}
|
||||
|
||||
async FailTransactionSwap(swapOperationId: string, failureReason: string) {
|
||||
async FailTransactionSwap(swapOperationId: string, address: string, failureReason: string) {
|
||||
return this.dbs.Update<TransactionSwap>('TransactionSwap', { swap_operation_id: swapOperationId }, {
|
||||
used: true,
|
||||
failure_reason: failureReason,
|
||||
address_paid: address,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -491,6 +493,18 @@ export default class {
|
|||
async DeleteExpiredTransactionSwaps(currentHeight: number, txId?: string) {
|
||||
return this.dbs.Delete<TransactionSwap>('TransactionSwap', { timeout_block_height: LessThan(currentHeight) }, txId)
|
||||
}
|
||||
|
||||
async ListCompletedSwaps(appUserId: string, txId?: string) {
|
||||
const completed = await this.dbs.Find<TransactionSwap>('TransactionSwap', { where: { used: true, app_user_id: appUserId } }, txId)
|
||||
const payments = await this.dbs.Find<UserInvoicePayment>('UserInvoicePayment', { where: { swap_operation_id: Not(IsNull()) } }, txId)
|
||||
const paymentsMap = new Map<string, UserInvoicePayment>()
|
||||
payments.forEach(p => {
|
||||
paymentsMap.set(p.swap_operation_id, p)
|
||||
})
|
||||
return completed.map(c => ({
|
||||
swap: c, payment: paymentsMap.get(c.swap_operation_id)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const orFail = async <T>(resultPromise: Promise<T | null>) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue