admin swaps
This commit is contained in:
parent
e9a8865192
commit
0a385188ae
17 changed files with 560 additions and 188 deletions
|
|
@ -93,6 +93,11 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- input: [MessagingToken](#MessagingToken)
|
- input: [MessagingToken](#MessagingToken)
|
||||||
- This methods has an __empty__ __response__ body
|
- This methods has an __empty__ __response__ body
|
||||||
|
|
||||||
|
- GetAdminTransactionSwapQuote
|
||||||
|
- auth type: __Admin__
|
||||||
|
- input: [TransactionSwapRequest](#TransactionSwapRequest)
|
||||||
|
- output: [TransactionSwapQuote](#TransactionSwapQuote)
|
||||||
|
|
||||||
- GetAppsMetrics
|
- GetAppsMetrics
|
||||||
- auth type: __Metrics__
|
- auth type: __Metrics__
|
||||||
- input: [AppsMetricsRequest](#AppsMetricsRequest)
|
- input: [AppsMetricsRequest](#AppsMetricsRequest)
|
||||||
|
|
@ -280,6 +285,11 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- input: [PayAddressRequest](#PayAddressRequest)
|
- input: [PayAddressRequest](#PayAddressRequest)
|
||||||
- output: [PayAddressResponse](#PayAddressResponse)
|
- output: [PayAddressResponse](#PayAddressResponse)
|
||||||
|
|
||||||
|
- PayAdminTransactionSwap
|
||||||
|
- auth type: __Admin__
|
||||||
|
- input: [TransactionSwapQuoteRequest](#TransactionSwapQuoteRequest)
|
||||||
|
- output: [AdminSwapResponse](#AdminSwapResponse)
|
||||||
|
|
||||||
- PayInvoice
|
- PayInvoice
|
||||||
- auth type: __User__
|
- auth type: __User__
|
||||||
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
- input: [PayInvoiceRequest](#PayInvoiceRequest)
|
||||||
|
|
@ -525,6 +535,13 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- input: [MessagingToken](#MessagingToken)
|
- input: [MessagingToken](#MessagingToken)
|
||||||
- This methods has an __empty__ __response__ body
|
- This methods has an __empty__ __response__ body
|
||||||
|
|
||||||
|
- GetAdminTransactionSwapQuote
|
||||||
|
- auth type: __Admin__
|
||||||
|
- http method: __post__
|
||||||
|
- http route: __/api/admin/swap/transaction/quote__
|
||||||
|
- input: [TransactionSwapRequest](#TransactionSwapRequest)
|
||||||
|
- output: [TransactionSwapQuote](#TransactionSwapQuote)
|
||||||
|
|
||||||
- GetApp
|
- GetApp
|
||||||
- auth type: __App__
|
- auth type: __App__
|
||||||
- http method: __post__
|
- http method: __post__
|
||||||
|
|
@ -870,6 +887,13 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- input: [PayAddressRequest](#PayAddressRequest)
|
- input: [PayAddressRequest](#PayAddressRequest)
|
||||||
- output: [PayAddressResponse](#PayAddressResponse)
|
- output: [PayAddressResponse](#PayAddressResponse)
|
||||||
|
|
||||||
|
- PayAdminTransactionSwap
|
||||||
|
- auth type: __Admin__
|
||||||
|
- http method: __post__
|
||||||
|
- http route: __/api/admin/swap/transaction/pay__
|
||||||
|
- input: [TransactionSwapQuoteRequest](#TransactionSwapQuoteRequest)
|
||||||
|
- output: [AdminSwapResponse](#AdminSwapResponse)
|
||||||
|
|
||||||
- PayAppUserInvoice
|
- PayAppUserInvoice
|
||||||
- auth type: __App__
|
- auth type: __App__
|
||||||
- http method: __post__
|
- http method: __post__
|
||||||
|
|
@ -1062,6 +1086,10 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- __name__: _string_
|
- __name__: _string_
|
||||||
- __price_sats__: _number_
|
- __price_sats__: _number_
|
||||||
|
|
||||||
|
### AdminSwapResponse
|
||||||
|
- __network_fee__: _number_
|
||||||
|
- __tx_id__: _string_
|
||||||
|
|
||||||
### AppMetrics
|
### AppMetrics
|
||||||
- __app__: _[Application](#Application)_
|
- __app__: _[Application](#Application)_
|
||||||
- __available__: _number_
|
- __available__: _number_
|
||||||
|
|
@ -1612,6 +1640,10 @@ The nostr server will send back a message response, and inside the body there wi
|
||||||
- __swap_operation_id__: _string_
|
- __swap_operation_id__: _string_
|
||||||
- __transaction_amount_sats__: _number_
|
- __transaction_amount_sats__: _number_
|
||||||
|
|
||||||
|
### TransactionSwapQuoteRequest
|
||||||
|
- __address__: _string_
|
||||||
|
- __swap_operation_id__: _string_
|
||||||
|
|
||||||
### TransactionSwapRequest
|
### TransactionSwapRequest
|
||||||
- __transaction_amount_sats__: _number_
|
- __transaction_amount_sats__: _number_
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ type Client struct {
|
||||||
EncryptionExchange func(req EncryptionExchangeRequest) error
|
EncryptionExchange func(req EncryptionExchangeRequest) error
|
||||||
EnrollAdminToken func(req EnrollAdminTokenRequest) error
|
EnrollAdminToken func(req EnrollAdminTokenRequest) error
|
||||||
EnrollMessagingToken func(req MessagingToken) error
|
EnrollMessagingToken func(req MessagingToken) error
|
||||||
|
GetAdminTransactionSwapQuote func(req TransactionSwapRequest) (*TransactionSwapQuote, error)
|
||||||
GetApp func() (*Application, error)
|
GetApp func() (*Application, error)
|
||||||
GetAppUser func(req GetAppUserRequest) (*AppUser, error)
|
GetAppUser func(req GetAppUserRequest) (*AppUser, error)
|
||||||
GetAppUserLNURLInfo func(req GetAppUserLNURLInfoRequest) (*LnurlPayInfoResponse, error)
|
GetAppUserLNURLInfo func(req GetAppUserLNURLInfoRequest) (*LnurlPayInfoResponse, error)
|
||||||
|
|
@ -121,6 +122,7 @@ type Client struct {
|
||||||
NewProductInvoice func(query NewProductInvoice_Query) (*NewInvoiceResponse, error)
|
NewProductInvoice func(query NewProductInvoice_Query) (*NewInvoiceResponse, error)
|
||||||
OpenChannel func(req OpenChannelRequest) (*OpenChannelResponse, error)
|
OpenChannel func(req OpenChannelRequest) (*OpenChannelResponse, error)
|
||||||
PayAddress func(req PayAddressRequest) (*PayAddressResponse, error)
|
PayAddress func(req PayAddressRequest) (*PayAddressResponse, error)
|
||||||
|
PayAdminTransactionSwap func(req TransactionSwapQuoteRequest) (*AdminSwapResponse, error)
|
||||||
PayAppUserInvoice func(req PayAppUserInvoiceRequest) (*PayInvoiceResponse, error)
|
PayAppUserInvoice func(req PayAppUserInvoiceRequest) (*PayInvoiceResponse, error)
|
||||||
PayInvoice func(req PayInvoiceRequest) (*PayInvoiceResponse, error)
|
PayInvoice func(req PayInvoiceRequest) (*PayInvoiceResponse, error)
|
||||||
PingSubProcesses func() error
|
PingSubProcesses func() error
|
||||||
|
|
@ -664,6 +666,35 @@ func NewClient(params ClientParams) *Client {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
GetAdminTransactionSwapQuote: func(req TransactionSwapRequest) (*TransactionSwapQuote, error) {
|
||||||
|
auth, err := params.RetrieveAdminAuth()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
finalRoute := "/api/admin/swap/transaction/quote"
|
||||||
|
body, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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 := TransactionSwapQuote{}
|
||||||
|
err = json.Unmarshal(resBody, &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &res, nil
|
||||||
|
},
|
||||||
GetApp: func() (*Application, error) {
|
GetApp: func() (*Application, error) {
|
||||||
auth, err := params.RetrieveAppAuth()
|
auth, err := params.RetrieveAppAuth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1834,6 +1865,35 @@ func NewClient(params ClientParams) *Client {
|
||||||
}
|
}
|
||||||
return &res, nil
|
return &res, nil
|
||||||
},
|
},
|
||||||
|
PayAdminTransactionSwap: func(req TransactionSwapQuoteRequest) (*AdminSwapResponse, error) {
|
||||||
|
auth, err := params.RetrieveAdminAuth()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
finalRoute := "/api/admin/swap/transaction/pay"
|
||||||
|
body, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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 := AdminSwapResponse{}
|
||||||
|
err = json.Unmarshal(resBody, &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &res, nil
|
||||||
|
},
|
||||||
PayAppUserInvoice: func(req PayAppUserInvoiceRequest) (*PayInvoiceResponse, error) {
|
PayAppUserInvoice: func(req PayAppUserInvoiceRequest) (*PayInvoiceResponse, error) {
|
||||||
auth, err := params.RetrieveAppAuth()
|
auth, err := params.RetrieveAppAuth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,10 @@ type AddProductRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Price_sats int64 `json:"price_sats"`
|
Price_sats int64 `json:"price_sats"`
|
||||||
}
|
}
|
||||||
|
type AdminSwapResponse struct {
|
||||||
|
Network_fee int64 `json:"network_fee"`
|
||||||
|
Tx_id string `json:"tx_id"`
|
||||||
|
}
|
||||||
type AppMetrics struct {
|
type AppMetrics struct {
|
||||||
App *Application `json:"app"`
|
App *Application `json:"app"`
|
||||||
Available int64 `json:"available"`
|
Available int64 `json:"available"`
|
||||||
|
|
@ -673,6 +677,10 @@ type TransactionSwapQuote struct {
|
||||||
Swap_operation_id string `json:"swap_operation_id"`
|
Swap_operation_id string `json:"swap_operation_id"`
|
||||||
Transaction_amount_sats int64 `json:"transaction_amount_sats"`
|
Transaction_amount_sats int64 `json:"transaction_amount_sats"`
|
||||||
}
|
}
|
||||||
|
type TransactionSwapQuoteRequest struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
Swap_operation_id string `json:"swap_operation_id"`
|
||||||
|
}
|
||||||
type TransactionSwapRequest struct {
|
type TransactionSwapRequest struct {
|
||||||
Transaction_amount_sats int64 `json:"transaction_amount_sats"`
|
Transaction_amount_sats int64 `json:"transaction_amount_sats"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -869,6 +869,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
||||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||||
})
|
})
|
||||||
|
if (!opts.allowNotImplementedMethods && !methods.GetAdminTransactionSwapQuote) throw new Error('method: GetAdminTransactionSwapQuote is not implemented')
|
||||||
|
app.post('/api/admin/swap/transaction/quote', async (req, res) => {
|
||||||
|
const info: Types.RequestInfo = { rpcName: 'GetAdminTransactionSwapQuote', 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.GetAdminTransactionSwapQuote) throw new Error('method: GetAdminTransactionSwapQuote is not implemented')
|
||||||
|
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
|
||||||
|
authCtx = authContext
|
||||||
|
stats.guard = process.hrtime.bigint()
|
||||||
|
const request = req.body
|
||||||
|
const error = Types.TransactionSwapRequestValidate(request)
|
||||||
|
stats.validate = process.hrtime.bigint()
|
||||||
|
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
|
||||||
|
const query = req.query
|
||||||
|
const params = req.params
|
||||||
|
const response = await methods.GetAdminTransactionSwapQuote({rpcName:'GetAdminTransactionSwapQuote', ctx:authContext , req: request})
|
||||||
|
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.GetApp) throw new Error('method: GetApp is not implemented')
|
if (!opts.allowNotImplementedMethods && !methods.GetApp) throw new Error('method: GetApp is not implemented')
|
||||||
app.post('/api/app/get', async (req, res) => {
|
app.post('/api/app/get', async (req, res) => {
|
||||||
const info: Types.RequestInfo = { rpcName: 'GetApp', batch: false, nostr: false, batchSize: 0}
|
const info: Types.RequestInfo = { rpcName: 'GetApp', batch: false, nostr: false, batchSize: 0}
|
||||||
|
|
@ -1752,6 +1774,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
|
||||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||||
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||||
})
|
})
|
||||||
|
if (!opts.allowNotImplementedMethods && !methods.PayAdminTransactionSwap) throw new Error('method: PayAdminTransactionSwap is not implemented')
|
||||||
|
app.post('/api/admin/swap/transaction/pay', async (req, res) => {
|
||||||
|
const info: Types.RequestInfo = { rpcName: 'PayAdminTransactionSwap', 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.PayAdminTransactionSwap) throw new Error('method: PayAdminTransactionSwap is not implemented')
|
||||||
|
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
|
||||||
|
authCtx = authContext
|
||||||
|
stats.guard = process.hrtime.bigint()
|
||||||
|
const request = req.body
|
||||||
|
const error = Types.TransactionSwapQuoteRequestValidate(request)
|
||||||
|
stats.validate = process.hrtime.bigint()
|
||||||
|
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
|
||||||
|
const query = req.query
|
||||||
|
const params = req.params
|
||||||
|
const response = await methods.PayAdminTransactionSwap({rpcName:'PayAdminTransactionSwap', ctx:authContext , req: request})
|
||||||
|
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.PayAppUserInvoice) throw new Error('method: PayAppUserInvoice is not implemented')
|
if (!opts.allowNotImplementedMethods && !methods.PayAppUserInvoice) throw new Error('method: PayAppUserInvoice is not implemented')
|
||||||
app.post('/api/app/invoice/pay', async (req, res) => {
|
app.post('/api/app/invoice/pay', async (req, res) => {
|
||||||
const info: Types.RequestInfo = { rpcName: 'PayAppUserInvoice', batch: false, nostr: false, batchSize: 0}
|
const info: Types.RequestInfo = { rpcName: 'PayAppUserInvoice', batch: false, nostr: false, batchSize: 0}
|
||||||
|
|
|
||||||
|
|
@ -273,6 +273,20 @@ export default (params: ClientParams) => ({
|
||||||
}
|
}
|
||||||
return { status: 'ERROR', reason: 'invalid response' }
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
},
|
},
|
||||||
|
GetAdminTransactionSwapQuote: async (request: Types.TransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.TransactionSwapQuote)> => {
|
||||||
|
const auth = await params.retrieveAdminAuth()
|
||||||
|
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||||
|
let finalRoute = '/api/admin/swap/transaction/quote'
|
||||||
|
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') {
|
||||||
|
const result = data
|
||||||
|
if(!params.checkResult) return { status: 'OK', ...result }
|
||||||
|
const error = Types.TransactionSwapQuoteValidate(result)
|
||||||
|
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||||
|
}
|
||||||
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
|
},
|
||||||
GetApp: async (): Promise<ResultError | ({ status: 'OK' }& Types.Application)> => {
|
GetApp: async (): Promise<ResultError | ({ status: 'OK' }& Types.Application)> => {
|
||||||
const auth = await params.retrieveAppAuth()
|
const auth = await params.retrieveAppAuth()
|
||||||
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
||||||
|
|
@ -881,6 +895,20 @@ export default (params: ClientParams) => ({
|
||||||
}
|
}
|
||||||
return { status: 'ERROR', reason: 'invalid response' }
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
},
|
},
|
||||||
|
PayAdminTransactionSwap: async (request: Types.TransactionSwapQuoteRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminSwapResponse)> => {
|
||||||
|
const auth = await params.retrieveAdminAuth()
|
||||||
|
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
|
||||||
|
let finalRoute = '/api/admin/swap/transaction/pay'
|
||||||
|
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') {
|
||||||
|
const result = data
|
||||||
|
if(!params.checkResult) return { status: 'OK', ...result }
|
||||||
|
const error = Types.AdminSwapResponseValidate(result)
|
||||||
|
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||||
|
}
|
||||||
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
|
},
|
||||||
PayAppUserInvoice: async (request: Types.PayAppUserInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayInvoiceResponse)> => {
|
PayAppUserInvoice: async (request: Types.PayAppUserInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayInvoiceResponse)> => {
|
||||||
const auth = await params.retrieveAppAuth()
|
const auth = await params.retrieveAppAuth()
|
||||||
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
if (auth === null) throw new Error('retrieveAppAuth() returned null')
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,21 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
||||||
}
|
}
|
||||||
return { status: 'ERROR', reason: 'invalid response' }
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
},
|
},
|
||||||
|
GetAdminTransactionSwapQuote: async (request: Types.TransactionSwapRequest): Promise<ResultError | ({ status: 'OK' }& Types.TransactionSwapQuote)> => {
|
||||||
|
const auth = await params.retrieveNostrAdminAuth()
|
||||||
|
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||||
|
const nostrRequest: NostrRequest = {}
|
||||||
|
nostrRequest.body = request
|
||||||
|
const data = await send(params.pubDestination, {rpcName:'GetAdminTransactionSwapQuote',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.TransactionSwapQuoteValidate(result)
|
||||||
|
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
|
||||||
|
}
|
||||||
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
|
},
|
||||||
GetAppsMetrics: async (request: Types.AppsMetricsRequest): Promise<ResultError | ({ status: 'OK' }& Types.AppsMetrics)> => {
|
GetAppsMetrics: async (request: Types.AppsMetricsRequest): Promise<ResultError | ({ status: 'OK' }& Types.AppsMetrics)> => {
|
||||||
const auth = await params.retrieveNostrMetricsAuth()
|
const auth = await params.retrieveNostrMetricsAuth()
|
||||||
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
|
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
|
||||||
|
|
@ -769,6 +784,21 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
|
||||||
}
|
}
|
||||||
return { status: 'ERROR', reason: 'invalid response' }
|
return { status: 'ERROR', reason: 'invalid response' }
|
||||||
},
|
},
|
||||||
|
PayAdminTransactionSwap: async (request: Types.TransactionSwapQuoteRequest): Promise<ResultError | ({ status: 'OK' }& Types.AdminSwapResponse)> => {
|
||||||
|
const auth = await params.retrieveNostrAdminAuth()
|
||||||
|
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
|
||||||
|
const nostrRequest: NostrRequest = {}
|
||||||
|
nostrRequest.body = request
|
||||||
|
const data = await send(params.pubDestination, {rpcName:'PayAdminTransactionSwap',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.AdminSwapResponseValidate(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)> => {
|
PayInvoice: async (request: Types.PayInvoiceRequest): Promise<ResultError | ({ status: 'OK' }& Types.PayInvoiceResponse)> => {
|
||||||
const auth = await params.retrieveNostrUserAuth()
|
const auth = await params.retrieveNostrUserAuth()
|
||||||
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
|
||||||
|
|
|
||||||
|
|
@ -687,6 +687,22 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
||||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||||
break
|
break
|
||||||
|
case 'GetAdminTransactionSwapQuote':
|
||||||
|
try {
|
||||||
|
if (!methods.GetAdminTransactionSwapQuote) throw new Error('method: GetAdminTransactionSwapQuote is not implemented')
|
||||||
|
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
|
||||||
|
stats.guard = process.hrtime.bigint()
|
||||||
|
authCtx = authContext
|
||||||
|
const request = req.body
|
||||||
|
const error = Types.TransactionSwapRequestValidate(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.GetAdminTransactionSwapQuote({rpcName:'GetAdminTransactionSwapQuote', 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 'GetAppsMetrics':
|
case 'GetAppsMetrics':
|
||||||
try {
|
try {
|
||||||
if (!methods.GetAppsMetrics) throw new Error('method: GetAppsMetrics is not implemented')
|
if (!methods.GetAppsMetrics) throw new Error('method: GetAppsMetrics is not implemented')
|
||||||
|
|
@ -1225,6 +1241,22 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
|
||||||
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
|
||||||
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
|
||||||
break
|
break
|
||||||
|
case 'PayAdminTransactionSwap':
|
||||||
|
try {
|
||||||
|
if (!methods.PayAdminTransactionSwap) throw new Error('method: PayAdminTransactionSwap is not implemented')
|
||||||
|
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
|
||||||
|
stats.guard = process.hrtime.bigint()
|
||||||
|
authCtx = authContext
|
||||||
|
const request = req.body
|
||||||
|
const error = Types.TransactionSwapQuoteRequestValidate(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.PayAdminTransactionSwap({rpcName:'PayAdminTransactionSwap', 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':
|
case 'PayInvoice':
|
||||||
try {
|
try {
|
||||||
if (!methods.PayInvoice) throw new Error('method: PayInvoice is not implemented')
|
if (!methods.PayInvoice) throw new Error('method: PayInvoice is not implemented')
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ export type RequestMetric = AuthContext & RequestInfo & RequestStats & { error?:
|
||||||
export type AdminContext = {
|
export type AdminContext = {
|
||||||
admin_id: string
|
admin_id: string
|
||||||
}
|
}
|
||||||
export type AdminMethodInputs = AddApp_Input | AddPeer_Input | AuthApp_Input | BanUser_Input | CloseChannel_Input | CreateOneTimeInviteLink_Input | GetInviteLinkState_Input | GetSeed_Input | ListChannels_Input | LndGetInfo_Input | OpenChannel_Input | UpdateChannelPolicy_Input
|
export type AdminMethodInputs = AddApp_Input | AddPeer_Input | AuthApp_Input | BanUser_Input | CloseChannel_Input | CreateOneTimeInviteLink_Input | GetAdminTransactionSwapQuote_Input | GetInviteLinkState_Input | GetSeed_Input | ListChannels_Input | LndGetInfo_Input | OpenChannel_Input | PayAdminTransactionSwap_Input | UpdateChannelPolicy_Input
|
||||||
export type AdminMethodOutputs = AddApp_Output | AddPeer_Output | AuthApp_Output | BanUser_Output | CloseChannel_Output | CreateOneTimeInviteLink_Output | GetInviteLinkState_Output | GetSeed_Output | ListChannels_Output | LndGetInfo_Output | OpenChannel_Output | UpdateChannelPolicy_Output
|
export type AdminMethodOutputs = AddApp_Output | AddPeer_Output | AuthApp_Output | BanUser_Output | CloseChannel_Output | CreateOneTimeInviteLink_Output | GetAdminTransactionSwapQuote_Output | GetInviteLinkState_Output | GetSeed_Output | ListChannels_Output | LndGetInfo_Output | OpenChannel_Output | PayAdminTransactionSwap_Output | UpdateChannelPolicy_Output
|
||||||
export type AppContext = {
|
export type AppContext = {
|
||||||
app_id: string
|
app_id: string
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +99,9 @@ export type EnrollAdminToken_Output = ResultError | { status: 'OK' }
|
||||||
export type EnrollMessagingToken_Input = {rpcName:'EnrollMessagingToken', req: MessagingToken}
|
export type EnrollMessagingToken_Input = {rpcName:'EnrollMessagingToken', req: MessagingToken}
|
||||||
export type EnrollMessagingToken_Output = ResultError | { status: 'OK' }
|
export type EnrollMessagingToken_Output = ResultError | { status: 'OK' }
|
||||||
|
|
||||||
|
export type GetAdminTransactionSwapQuote_Input = {rpcName:'GetAdminTransactionSwapQuote', req: TransactionSwapRequest}
|
||||||
|
export type GetAdminTransactionSwapQuote_Output = ResultError | ({ status: 'OK' } & TransactionSwapQuote)
|
||||||
|
|
||||||
export type GetApp_Input = {rpcName:'GetApp'}
|
export type GetApp_Input = {rpcName:'GetApp'}
|
||||||
export type GetApp_Output = ResultError | ({ status: 'OK' } & Application)
|
export type GetApp_Output = ResultError | ({ status: 'OK' } & Application)
|
||||||
|
|
||||||
|
|
@ -262,6 +265,9 @@ export type OpenChannel_Output = ResultError | ({ status: 'OK' } & OpenChannelRe
|
||||||
export type PayAddress_Input = {rpcName:'PayAddress', req: PayAddressRequest}
|
export type PayAddress_Input = {rpcName:'PayAddress', req: PayAddressRequest}
|
||||||
export type PayAddress_Output = ResultError | ({ status: 'OK' } & PayAddressResponse)
|
export type PayAddress_Output = ResultError | ({ status: 'OK' } & PayAddressResponse)
|
||||||
|
|
||||||
|
export type PayAdminTransactionSwap_Input = {rpcName:'PayAdminTransactionSwap', req: TransactionSwapQuoteRequest}
|
||||||
|
export type PayAdminTransactionSwap_Output = ResultError | ({ status: 'OK' } & AdminSwapResponse)
|
||||||
|
|
||||||
export type PayAppUserInvoice_Input = {rpcName:'PayAppUserInvoice', req: PayAppUserInvoiceRequest}
|
export type PayAppUserInvoice_Input = {rpcName:'PayAppUserInvoice', req: PayAppUserInvoiceRequest}
|
||||||
export type PayAppUserInvoice_Output = ResultError | ({ status: 'OK' } & PayInvoiceResponse)
|
export type PayAppUserInvoice_Output = ResultError | ({ status: 'OK' } & PayInvoiceResponse)
|
||||||
|
|
||||||
|
|
@ -348,6 +354,7 @@ export type ServerMethods = {
|
||||||
EncryptionExchange?: (req: EncryptionExchange_Input & {ctx: GuestContext }) => Promise<void>
|
EncryptionExchange?: (req: EncryptionExchange_Input & {ctx: GuestContext }) => Promise<void>
|
||||||
EnrollAdminToken?: (req: EnrollAdminToken_Input & {ctx: UserContext }) => Promise<void>
|
EnrollAdminToken?: (req: EnrollAdminToken_Input & {ctx: UserContext }) => Promise<void>
|
||||||
EnrollMessagingToken?: (req: EnrollMessagingToken_Input & {ctx: UserContext }) => Promise<void>
|
EnrollMessagingToken?: (req: EnrollMessagingToken_Input & {ctx: UserContext }) => Promise<void>
|
||||||
|
GetAdminTransactionSwapQuote?: (req: GetAdminTransactionSwapQuote_Input & {ctx: AdminContext }) => Promise<TransactionSwapQuote>
|
||||||
GetApp?: (req: GetApp_Input & {ctx: AppContext }) => Promise<Application>
|
GetApp?: (req: GetApp_Input & {ctx: AppContext }) => Promise<Application>
|
||||||
GetAppUser?: (req: GetAppUser_Input & {ctx: AppContext }) => Promise<AppUser>
|
GetAppUser?: (req: GetAppUser_Input & {ctx: AppContext }) => Promise<AppUser>
|
||||||
GetAppUserLNURLInfo?: (req: GetAppUserLNURLInfo_Input & {ctx: AppContext }) => Promise<LnurlPayInfoResponse>
|
GetAppUserLNURLInfo?: (req: GetAppUserLNURLInfo_Input & {ctx: AppContext }) => Promise<LnurlPayInfoResponse>
|
||||||
|
|
@ -395,6 +402,7 @@ export type ServerMethods = {
|
||||||
NewProductInvoice?: (req: NewProductInvoice_Input & {ctx: UserContext }) => Promise<NewInvoiceResponse>
|
NewProductInvoice?: (req: NewProductInvoice_Input & {ctx: UserContext }) => Promise<NewInvoiceResponse>
|
||||||
OpenChannel?: (req: OpenChannel_Input & {ctx: AdminContext }) => Promise<OpenChannelResponse>
|
OpenChannel?: (req: OpenChannel_Input & {ctx: AdminContext }) => Promise<OpenChannelResponse>
|
||||||
PayAddress?: (req: PayAddress_Input & {ctx: UserContext }) => Promise<PayAddressResponse>
|
PayAddress?: (req: PayAddress_Input & {ctx: UserContext }) => Promise<PayAddressResponse>
|
||||||
|
PayAdminTransactionSwap?: (req: PayAdminTransactionSwap_Input & {ctx: AdminContext }) => Promise<AdminSwapResponse>
|
||||||
PayAppUserInvoice?: (req: PayAppUserInvoice_Input & {ctx: AppContext }) => Promise<PayInvoiceResponse>
|
PayAppUserInvoice?: (req: PayAppUserInvoice_Input & {ctx: AppContext }) => Promise<PayInvoiceResponse>
|
||||||
PayInvoice?: (req: PayInvoice_Input & {ctx: UserContext }) => Promise<PayInvoiceResponse>
|
PayInvoice?: (req: PayInvoice_Input & {ctx: UserContext }) => Promise<PayInvoiceResponse>
|
||||||
PingSubProcesses?: (req: PingSubProcesses_Input & {ctx: MetricsContext }) => Promise<void>
|
PingSubProcesses?: (req: PingSubProcesses_Input & {ctx: MetricsContext }) => Promise<void>
|
||||||
|
|
@ -659,6 +667,29 @@ export const AddProductRequestValidate = (o?: AddProductRequest, opts: AddProduc
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AdminSwapResponse = {
|
||||||
|
network_fee: number
|
||||||
|
tx_id: string
|
||||||
|
}
|
||||||
|
export const AdminSwapResponseOptionalFields: [] = []
|
||||||
|
export type AdminSwapResponseOptions = OptionsBaseMessage & {
|
||||||
|
checkOptionalsAreSet?: []
|
||||||
|
network_fee_CustomCheck?: (v: number) => boolean
|
||||||
|
tx_id_CustomCheck?: (v: string) => boolean
|
||||||
|
}
|
||||||
|
export const AdminSwapResponseValidate = (o?: AdminSwapResponse, opts: AdminSwapResponseOptions = {}, path: string = 'AdminSwapResponse::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.network_fee !== 'number') return new Error(`${path}.network_fee: is not a number`)
|
||||||
|
if (opts.network_fee_CustomCheck && !opts.network_fee_CustomCheck(o.network_fee)) return new Error(`${path}.network_fee: custom check failed`)
|
||||||
|
|
||||||
|
if (typeof o.tx_id !== 'string') return new Error(`${path}.tx_id: is not a string`)
|
||||||
|
if (opts.tx_id_CustomCheck && !opts.tx_id_CustomCheck(o.tx_id)) return new Error(`${path}.tx_id: custom check failed`)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export type AppMetrics = {
|
export type AppMetrics = {
|
||||||
app: Application
|
app: Application
|
||||||
available: number
|
available: number
|
||||||
|
|
@ -3962,6 +3993,29 @@ export const TransactionSwapQuoteValidate = (o?: TransactionSwapQuote, opts: Tra
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TransactionSwapQuoteRequest = {
|
||||||
|
address: string
|
||||||
|
swap_operation_id: string
|
||||||
|
}
|
||||||
|
export const TransactionSwapQuoteRequestOptionalFields: [] = []
|
||||||
|
export type TransactionSwapQuoteRequestOptions = OptionsBaseMessage & {
|
||||||
|
checkOptionalsAreSet?: []
|
||||||
|
address_CustomCheck?: (v: string) => boolean
|
||||||
|
swap_operation_id_CustomCheck?: (v: string) => boolean
|
||||||
|
}
|
||||||
|
export const TransactionSwapQuoteRequestValidate = (o?: TransactionSwapQuoteRequest, opts: TransactionSwapQuoteRequestOptions = {}, path: string = 'TransactionSwapQuoteRequest::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 !== 'string') return new Error(`${path}.address: is not a string`)
|
||||||
|
if (opts.address_CustomCheck && !opts.address_CustomCheck(o.address)) return new Error(`${path}.address: custom check failed`)
|
||||||
|
|
||||||
|
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 TransactionSwapRequest = {
|
export type TransactionSwapRequest = {
|
||||||
transaction_amount_sats: number
|
transaction_amount_sats: number
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,20 @@ service LightningPub {
|
||||||
option (nostr) = true;
|
option (nostr) = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpc GetAdminTransactionSwapQuote(structs.TransactionSwapRequest) returns (structs.TransactionSwapQuote) {
|
||||||
|
option (auth_type) = "Admin";
|
||||||
|
option (http_method) = "post";
|
||||||
|
option (http_route) = "/api/admin/swap/transaction/quote";
|
||||||
|
option (nostr) = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc PayAdminTransactionSwap(structs.TransactionSwapQuoteRequest) returns (structs.AdminSwapResponse) {
|
||||||
|
option (auth_type) = "Admin";
|
||||||
|
option (http_method) = "post";
|
||||||
|
option (http_route) = "/api/admin/swap/transaction/pay";
|
||||||
|
option (nostr) = true;
|
||||||
|
}
|
||||||
|
|
||||||
rpc GetUsageMetrics(structs.LatestUsageMetricReq) returns (structs.UsageMetrics) {
|
rpc GetUsageMetrics(structs.LatestUsageMetricReq) returns (structs.UsageMetrics) {
|
||||||
option (auth_type) = "Metrics";
|
option (auth_type) = "Metrics";
|
||||||
option (http_method) = "post";
|
option (http_method) = "post";
|
||||||
|
|
|
||||||
|
|
@ -834,6 +834,11 @@ message TransactionSwapRequest {
|
||||||
int64 transaction_amount_sats = 2;
|
int64 transaction_amount_sats = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message TransactionSwapQuoteRequest {
|
||||||
|
string address = 1;
|
||||||
|
string swap_operation_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message TransactionSwapQuote {
|
message TransactionSwapQuote {
|
||||||
string swap_operation_id = 1;
|
string swap_operation_id = 1;
|
||||||
int64 invoice_amount_sats = 2;
|
int64 invoice_amount_sats = 2;
|
||||||
|
|
@ -844,6 +849,11 @@ message TransactionSwapQuote {
|
||||||
int64 service_fee_sats = 7;
|
int64 service_fee_sats = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message AdminSwapResponse {
|
||||||
|
string tx_id = 1;
|
||||||
|
int64 network_fee = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message SwapOperation {
|
message SwapOperation {
|
||||||
string swap_operation_id = 1;
|
string swap_operation_id = 1;
|
||||||
optional UserOperation operation_payment = 2;
|
optional UserOperation operation_payment = 2;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ import { getLogger, PubLogger, ERROR } from '../helpers/logger.js';
|
||||||
import SettingsManager from '../main/settingsManager.js';
|
import SettingsManager from '../main/settingsManager.js';
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js';
|
import * as Types from '../../../proto/autogenerated/ts/types.js';
|
||||||
import { BTCNetwork } from '../main/settings.js';
|
import { BTCNetwork } from '../main/settings.js';
|
||||||
|
import Storage from '../storage/index.js';
|
||||||
|
import LND from './lnd.js';
|
||||||
type InvoiceSwapResponse = { id: string, claimPublicKey: string, swapTree: string }
|
type InvoiceSwapResponse = { id: string, claimPublicKey: string, swapTree: string }
|
||||||
type InvoiceSwapInfo = { paymentHash: string, keys: ECPairInterface }
|
type InvoiceSwapInfo = { paymentHash: string, keys: ECPairInterface }
|
||||||
type InvoiceSwapData = { createdResponse: InvoiceSwapResponse, info: InvoiceSwapInfo }
|
type InvoiceSwapData = { createdResponse: InvoiceSwapResponse, info: InvoiceSwapInfo }
|
||||||
|
|
@ -47,9 +48,17 @@ export type TransactionSwapData = { createdResponse: TransactionSwapResponse, in
|
||||||
export class Swaps {
|
export class Swaps {
|
||||||
reverseSwaps: ReverseSwaps
|
reverseSwaps: ReverseSwaps
|
||||||
submarineSwaps: SubmarineSwaps
|
submarineSwaps: SubmarineSwaps
|
||||||
constructor(settings: SettingsManager) {
|
storage: Storage
|
||||||
|
lnd: LND
|
||||||
|
log = getLogger({ component: 'swaps' })
|
||||||
|
constructor(settings: SettingsManager, storage: Storage) {
|
||||||
this.reverseSwaps = new ReverseSwaps(settings)
|
this.reverseSwaps = new ReverseSwaps(settings)
|
||||||
this.submarineSwaps = new SubmarineSwaps(settings)
|
this.submarineSwaps = new SubmarineSwaps(settings)
|
||||||
|
this.storage = storage
|
||||||
|
}
|
||||||
|
|
||||||
|
SetLnd = (lnd: LND) => {
|
||||||
|
this.lnd = lnd
|
||||||
}
|
}
|
||||||
|
|
||||||
Stop = () => { }
|
Stop = () => { }
|
||||||
|
|
@ -58,6 +67,110 @@ export class Swaps {
|
||||||
const keys = ECPairFactory(ecc).fromPrivateKey(Buffer.from(privateKey, 'hex'))
|
const keys = ECPairFactory(ecc).fromPrivateKey(Buffer.from(privateKey, 'hex'))
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async GetTxSwapQuote(appUserId: string, amt: number, getServiceFee: (decodedAmt: number) => number): Promise<Types.TransactionSwapQuote> {
|
||||||
|
this.log("getting transaction swap quote")
|
||||||
|
const feesRes = await this.reverseSwaps.GetFees()
|
||||||
|
if (!feesRes.ok) {
|
||||||
|
throw new Error(feesRes.error)
|
||||||
|
}
|
||||||
|
const { claim, lockup } = feesRes.fees.minerFees
|
||||||
|
const minerFee = claim + lockup
|
||||||
|
const chainTotal = amt + minerFee
|
||||||
|
const res = await this.reverseSwaps.SwapTransaction(chainTotal)
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(res.error)
|
||||||
|
}
|
||||||
|
const decoded = await this.lnd.DecodeInvoice(res.createdResponse.invoice)
|
||||||
|
const swapFee = decoded.numSatoshis - chainTotal
|
||||||
|
const serviceFee = getServiceFee(decoded.numSatoshis)
|
||||||
|
const newSwap = await this.storage.paymentStorage.AddTransactionSwap({
|
||||||
|
app_user_id: appUserId,
|
||||||
|
swap_quote_id: res.createdResponse.id,
|
||||||
|
swap_tree: JSON.stringify(res.createdResponse.swapTree),
|
||||||
|
lockup_address: res.createdResponse.lockupAddress,
|
||||||
|
refund_public_key: res.createdResponse.refundPublicKey,
|
||||||
|
timeout_block_height: res.createdResponse.timeoutBlockHeight,
|
||||||
|
invoice: res.createdResponse.invoice,
|
||||||
|
invoice_amount: decoded.numSatoshis,
|
||||||
|
transaction_amount: chainTotal,
|
||||||
|
swap_fee_sats: swapFee,
|
||||||
|
chain_fee_sats: minerFee,
|
||||||
|
preimage: res.preimage,
|
||||||
|
ephemeral_private_key: res.privKey,
|
||||||
|
ephemeral_public_key: res.pubkey,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
swap_operation_id: newSwap.swap_operation_id,
|
||||||
|
swap_fee_sats: swapFee,
|
||||||
|
invoice_amount_sats: decoded.numSatoshis,
|
||||||
|
transaction_amount_sats: amt,
|
||||||
|
chain_fee_sats: minerFee,
|
||||||
|
service_fee_sats: serviceFee,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async PayAddrWithSwap(appUserId: string, swapOpId: string, address: string, payInvoice: (invoice: string, amt: number) => Promise<void>) {
|
||||||
|
this.log("paying address with swap", { appUserId, swapOpId, address })
|
||||||
|
if (!swapOpId) {
|
||||||
|
throw new Error("request a swap quote before paying an external address")
|
||||||
|
}
|
||||||
|
const txSwap = await this.storage.paymentStorage.GetTransactionSwap(swapOpId, appUserId)
|
||||||
|
if (!txSwap) {
|
||||||
|
throw new Error("swap quote not found")
|
||||||
|
}
|
||||||
|
const info = await this.lnd.GetInfo()
|
||||||
|
if (info.blockHeight >= txSwap.timeout_block_height) {
|
||||||
|
throw new Error("swap timeout")
|
||||||
|
}
|
||||||
|
const keys = this.GetKeys(txSwap.ephemeral_private_key)
|
||||||
|
const data: TransactionSwapData = {
|
||||||
|
createdResponse: {
|
||||||
|
id: txSwap.swap_quote_id,
|
||||||
|
invoice: txSwap.invoice,
|
||||||
|
lockupAddress: txSwap.lockup_address,
|
||||||
|
refundPublicKey: txSwap.refund_public_key,
|
||||||
|
swapTree: txSwap.swap_tree,
|
||||||
|
timeoutBlockHeight: txSwap.timeout_block_height,
|
||||||
|
onchainAmount: txSwap.transaction_amount,
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
destinationAddress: address,
|
||||||
|
keys,
|
||||||
|
chainFee: txSwap.chain_fee_sats,
|
||||||
|
preimage: Buffer.from(txSwap.preimage, 'hex'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the swap and the invoice payment are linked, swap will not start until the invoice payment is started, and will not complete once the invoice payment is completed
|
||||||
|
let swapResult = { ok: false, error: "swap never completed" } as { ok: true, txId: string } | { ok: false, error: string }
|
||||||
|
this.reverseSwaps.SubscribeToTransactionSwap(data, result => {
|
||||||
|
swapResult = result
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
await payInvoice(txSwap.invoice, txSwap.invoice_amount)
|
||||||
|
if (!swapResult.ok) {
|
||||||
|
this.log("invoice payment successful, but swap failed")
|
||||||
|
await this.storage.paymentStorage.FailTransactionSwap(swapOpId, address, swapResult.error)
|
||||||
|
throw new Error(swapResult.error)
|
||||||
|
}
|
||||||
|
this.log("swap completed successfully")
|
||||||
|
await this.storage.paymentStorage.FinalizeTransactionSwap(swapOpId, 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(swapOpId, address, err.message)
|
||||||
|
} else {
|
||||||
|
this.log("failed to pay swap invoice and swap failed", swapResult.error)
|
||||||
|
await this.storage.paymentStorage.FailTransactionSwap(swapOpId, address, swapResult.error)
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
const networkFeesTotal = txSwap.chain_fee_sats + txSwap.swap_fee_sats
|
||||||
|
return {
|
||||||
|
txId: swapResult.txId,
|
||||||
|
network_fee: networkFeesTotal
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SubmarineSwaps {
|
export class SubmarineSwaps {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import Storage from "../storage/index.js";
|
||||||
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
import * as Types from '../../../proto/autogenerated/ts/types.js'
|
||||||
import LND from "../lnd/lnd.js";
|
import LND from "../lnd/lnd.js";
|
||||||
import SettingsManager from "./settingsManager.js";
|
import SettingsManager from "./settingsManager.js";
|
||||||
|
import { Swaps } from "../lnd/swaps.js";
|
||||||
export class AdminManager {
|
export class AdminManager {
|
||||||
|
settings: SettingsManager
|
||||||
storage: Storage
|
storage: Storage
|
||||||
log = getLogger({ component: "adminManager" })
|
log = getLogger({ component: "adminManager" })
|
||||||
adminNpub = ""
|
adminNpub = ""
|
||||||
|
|
@ -17,10 +19,13 @@ export class AdminManager {
|
||||||
interval: NodeJS.Timer
|
interval: NodeJS.Timer
|
||||||
appNprofile: string
|
appNprofile: string
|
||||||
lnd: LND
|
lnd: LND
|
||||||
|
swaps: Swaps
|
||||||
nostrConnected: boolean = false
|
nostrConnected: boolean = false
|
||||||
private nostrReset: () => Promise<void> = async () => { this.log("nostr reset not initialized yet") }
|
private nostrReset: () => Promise<void> = async () => { this.log("nostr reset not initialized yet") }
|
||||||
constructor(settings: SettingsManager, storage: Storage) {
|
constructor(settings: SettingsManager, storage: Storage, swaps: Swaps) {
|
||||||
|
this.settings = settings
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
|
this.swaps = swaps
|
||||||
this.dataDir = settings.getStorageSettings().dataDir
|
this.dataDir = settings.getStorageSettings().dataDir
|
||||||
this.adminNpubPath = getDataPath(this.dataDir, 'admin.npub')
|
this.adminNpubPath = getDataPath(this.dataDir, 'admin.npub')
|
||||||
this.adminEnrollTokenPath = getDataPath(this.dataDir, 'admin.enroll')
|
this.adminEnrollTokenPath = getDataPath(this.dataDir, 'admin.enroll')
|
||||||
|
|
@ -45,6 +50,7 @@ export class AdminManager {
|
||||||
|
|
||||||
setLND = (lnd: LND) => {
|
setLND = (lnd: LND) => {
|
||||||
this.lnd = lnd
|
this.lnd = lnd
|
||||||
|
this.swaps.SetLnd(lnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
setNostrConnected = (connected: boolean) => {
|
setNostrConnected = (connected: boolean) => {
|
||||||
|
|
@ -253,6 +259,25 @@ export class AdminManager {
|
||||||
closing_txid: Buffer.from(res.txid).toString('hex')
|
closing_txid: Buffer.from(res.txid).toString('hex')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async GetAdminTransactionSwapQuote(req: Types.TransactionSwapRequest): Promise<Types.TransactionSwapQuote> {
|
||||||
|
return this.swaps.GetTxSwapQuote("admin", req.transaction_amount_sats, () => 0)
|
||||||
|
}
|
||||||
|
async PayAdminTransactionSwap(req: Types.TransactionSwapQuoteRequest): Promise<Types.AdminSwapResponse> {
|
||||||
|
const routingFloor = this.settings.getSettings().lndSettings.routingFeeFloor
|
||||||
|
const routingLimit = this.settings.getSettings().lndSettings.routingFeeLimitBps / 10000
|
||||||
|
|
||||||
|
|
||||||
|
const swap = await this.swaps.PayAddrWithSwap("admin", req.swap_operation_id, req.address, async (invoice, amt) => {
|
||||||
|
const r = Math.max(Math.ceil(routingLimit * amt), routingFloor)
|
||||||
|
const payment = await this.lnd.PayInvoice(invoice, 0, { routingFeeLimit: r, serviceFee: 0 }, amt, { useProvider: false, from: 'system' })
|
||||||
|
await this.storage.metricsStorage.AddRootOperation("invoice_payment", invoice, amt + payment.feeSat)
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
tx_id: swap.txId,
|
||||||
|
network_fee: swap.network_fee,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDataPath = (dataDir: string, dataPath: string) => {
|
const getDataPath = (dataDir: string, dataPath: string) => {
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ export default class {
|
||||||
this.liquidityManager = new LiquidityManager(this.settings, this.storage, this.utils, this.liquidityProvider, this.lnd, this.rugPullTracker)
|
this.liquidityManager = new LiquidityManager(this.settings, this.storage, this.utils, this.liquidityProvider, this.lnd, this.rugPullTracker)
|
||||||
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
this.metricsManager = new MetricsManager(this.storage, this.lnd)
|
||||||
|
|
||||||
this.paymentManager = new PaymentManager(this.storage, this.metricsManager, this.lnd, this.settings, this.liquidityManager, this.utils, this.addressPaidCb, this.invoicePaidCb)
|
this.paymentManager = new PaymentManager(this.storage, this.metricsManager, this.lnd, adminManager.swaps, this.settings, this.liquidityManager, this.utils, this.addressPaidCb, this.invoicePaidCb)
|
||||||
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
|
this.productManager = new ProductManager(this.storage, this.paymentManager, this.settings)
|
||||||
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager)
|
this.applicationManager = new ApplicationManager(this.storage, this.settings, this.paymentManager)
|
||||||
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
|
this.appUserManager = new AppUserManager(this.storage, this.settings, this.applicationManager)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { AdminManager } from "./adminManager.js"
|
||||||
import SettingsManager from "./settingsManager.js"
|
import SettingsManager from "./settingsManager.js"
|
||||||
import { LoadStorageSettingsFromEnv } from "../storage/index.js"
|
import { LoadStorageSettingsFromEnv } from "../storage/index.js"
|
||||||
import { NostrSender } from "../nostr/sender.js"
|
import { NostrSender } from "../nostr/sender.js"
|
||||||
|
import { Swaps } from "../lnd/swaps.js"
|
||||||
export type AppData = {
|
export type AppData = {
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
|
|
@ -32,7 +33,8 @@ export const initMainHandler = async (log: PubLogger, settingsManager: SettingsM
|
||||||
const utils = storageManager.utils
|
const utils = storageManager.utils
|
||||||
const unlocker = new Unlocker(settingsManager, storageManager)
|
const unlocker = new Unlocker(settingsManager, storageManager)
|
||||||
await unlocker.Unlock()
|
await unlocker.Unlock()
|
||||||
const adminManager = new AdminManager(settingsManager, storageManager)
|
const swaps = new Swaps(settingsManager, storageManager)
|
||||||
|
const adminManager = new AdminManager(settingsManager, storageManager, swaps)
|
||||||
let wizard: Wizard | null = null
|
let wizard: Wizard | null = null
|
||||||
if (settingsManager.getSettings().serviceSettings.wizard) {
|
if (settingsManager.getSettings().serviceSettings.wizard) {
|
||||||
wizard = new Wizard(settingsManager, storageManager, adminManager)
|
wizard = new Wizard(settingsManager, storageManager, adminManager)
|
||||||
|
|
|
||||||
|
|
@ -121,12 +121,11 @@ export class LiquidityProvider {
|
||||||
await this.invoicePaidCb(res.operation.identifier, res.operation.amount, 'provider')
|
await this.invoicePaidCb(res.operation.identifier, res.operation.amount, 'provider')
|
||||||
this.incrementProviderBalance(res.operation.amount)
|
this.incrementProviderBalance(res.operation.amount)
|
||||||
this.latestReceivedBalance = res.latest_balance
|
this.latestReceivedBalance = res.latest_balance
|
||||||
if (!res.operation.inbound && !res.operation.confirmed) {
|
|
||||||
delete this.pendingPaymentsAck[res.operation.identifier]
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this.log("error processing incoming invoice", err.message)
|
this.log("error processing incoming invoice", err.message)
|
||||||
}
|
}
|
||||||
|
} else if (res.operation.type === Types.UserOperationType.OUTGOING_INVOICE) {
|
||||||
|
delete this.pendingPaymentsAck[res.operation.identifier]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ export default class {
|
||||||
swaps: Swaps
|
swaps: Swaps
|
||||||
invoiceLock: InvoiceLock
|
invoiceLock: InvoiceLock
|
||||||
metrics: Metrics
|
metrics: Metrics
|
||||||
constructor(storage: Storage, metrics: Metrics, lnd: LND, settings: SettingsManager, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
constructor(storage: Storage, metrics: Metrics, lnd: LND, swaps: Swaps, settings: SettingsManager, liquidityManager: LiquidityManager, utils: Utils, addressPaidCb: AddressPaidCb, invoicePaidCb: InvoicePaidCb) {
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.metrics = metrics
|
this.metrics = metrics
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
|
|
@ -69,7 +69,7 @@ export default class {
|
||||||
this.liquidityManager = liquidityManager
|
this.liquidityManager = liquidityManager
|
||||||
this.utils = utils
|
this.utils = utils
|
||||||
this.watchDog = new Watchdog(settings, this.liquidityManager, this.lnd, this.storage, this.utils, this.liquidityManager.rugPullTracker)
|
this.watchDog = new Watchdog(settings, this.liquidityManager, this.lnd, this.storage, this.utils, this.liquidityManager.rugPullTracker)
|
||||||
this.swaps = new Swaps(settings)
|
this.swaps = swaps
|
||||||
this.addressPaidCb = addressPaidCb
|
this.addressPaidCb = addressPaidCb
|
||||||
this.invoicePaidCb = invoicePaidCb
|
this.invoicePaidCb = invoicePaidCb
|
||||||
this.invoiceLock = new InvoiceLock()
|
this.invoiceLock = new InvoiceLock()
|
||||||
|
|
@ -539,53 +539,15 @@ export default class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async GetTransactionSwapQuote(ctx: Types.UserContext, req: Types.TransactionSwapRequest): Promise<Types.TransactionSwapQuote> {
|
async GetTransactionSwapQuote(ctx: Types.UserContext, req: Types.TransactionSwapRequest): Promise<Types.TransactionSwapQuote> {
|
||||||
const feesRes = await this.swaps.reverseSwaps.GetFees()
|
|
||||||
if (!feesRes.ok) {
|
|
||||||
throw new Error(feesRes.error)
|
|
||||||
}
|
|
||||||
const { claim, lockup } = feesRes.fees.minerFees
|
|
||||||
const minerFee = claim + lockup
|
|
||||||
const chainTotal = req.transaction_amount_sats + minerFee
|
|
||||||
const res = await this.swaps.reverseSwaps.SwapTransaction(chainTotal)
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(res.error)
|
|
||||||
}
|
|
||||||
const decoded = await this.lnd.DecodeInvoice(res.createdResponse.invoice)
|
|
||||||
const swapFee = decoded.numSatoshis - chainTotal
|
|
||||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||||
|
return this.swaps.GetTxSwapQuote(ctx.app_user_id, req.transaction_amount_sats, decodedAmt => {
|
||||||
const isManagedUser = ctx.user_id !== app.owner.user_id
|
const isManagedUser = ctx.user_id !== app.owner.user_id
|
||||||
const serviceFee = this.getSendServiceFee(Types.UserOperationType.OUTGOING_INVOICE, decoded.numSatoshis, isManagedUser)
|
return this.getSendServiceFee(Types.UserOperationType.OUTGOING_INVOICE, decodedAmt, isManagedUser)
|
||||||
const newSwap = await this.storage.paymentStorage.AddTransactionSwap({
|
|
||||||
app_user_id: ctx.app_user_id,
|
|
||||||
swap_quote_id: res.createdResponse.id,
|
|
||||||
swap_tree: JSON.stringify(res.createdResponse.swapTree),
|
|
||||||
lockup_address: res.createdResponse.lockupAddress,
|
|
||||||
refund_public_key: res.createdResponse.refundPublicKey,
|
|
||||||
timeout_block_height: res.createdResponse.timeoutBlockHeight,
|
|
||||||
invoice: res.createdResponse.invoice,
|
|
||||||
invoice_amount: decoded.numSatoshis,
|
|
||||||
transaction_amount: chainTotal,
|
|
||||||
swap_fee_sats: swapFee,
|
|
||||||
chain_fee_sats: minerFee,
|
|
||||||
preimage: res.preimage,
|
|
||||||
ephemeral_private_key: res.privKey,
|
|
||||||
ephemeral_public_key: res.pubkey,
|
|
||||||
})
|
})
|
||||||
return {
|
|
||||||
swap_operation_id: newSwap.swap_operation_id,
|
|
||||||
swap_fee_sats: swapFee,
|
|
||||||
invoice_amount_sats: decoded.numSatoshis,
|
|
||||||
transaction_amount_sats: req.transaction_amount_sats,
|
|
||||||
chain_fee_sats: minerFee,
|
|
||||||
service_fee_sats: serviceFee,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async PayAddress(ctx: Types.UserContext, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
|
async PayAddress(ctx: Types.UserContext, req: Types.PayAddressRequest): Promise<Types.PayAddressResponse> {
|
||||||
await this.watchDog.PaymentRequested()
|
await this.watchDog.PaymentRequested()
|
||||||
|
|
@ -607,66 +569,18 @@ export default class {
|
||||||
throw new Error("request a swap quote before paying an external address")
|
throw new Error("request a swap quote before paying an external address")
|
||||||
}
|
}
|
||||||
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
const app = await this.storage.applicationStorage.GetApplication(ctx.app_id)
|
||||||
const txSwap = await this.storage.paymentStorage.GetTransactionSwap(req.swap_operation_id, ctx.app_user_id)
|
|
||||||
if (!txSwap) {
|
|
||||||
throw new Error("swap quote not found")
|
|
||||||
}
|
|
||||||
const info = await this.lnd.GetInfo()
|
|
||||||
if (info.blockHeight >= txSwap.timeout_block_height) {
|
|
||||||
throw new Error("swap timeout")
|
|
||||||
}
|
|
||||||
const keys = this.swaps.GetKeys(txSwap.ephemeral_private_key)
|
|
||||||
const data: TransactionSwapData = {
|
|
||||||
createdResponse: {
|
|
||||||
id: txSwap.swap_quote_id,
|
|
||||||
invoice: txSwap.invoice,
|
|
||||||
lockupAddress: txSwap.lockup_address,
|
|
||||||
refundPublicKey: txSwap.refund_public_key,
|
|
||||||
swapTree: txSwap.swap_tree,
|
|
||||||
timeoutBlockHeight: txSwap.timeout_block_height,
|
|
||||||
onchainAmount: txSwap.transaction_amount,
|
|
||||||
},
|
|
||||||
info: {
|
|
||||||
destinationAddress: req.address,
|
|
||||||
keys,
|
|
||||||
chainFee: txSwap.chain_fee_sats,
|
|
||||||
preimage: Buffer.from(txSwap.preimage, 'hex'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// the swap and the invoice payment are linked, swap will not start until the invoice payment is started, and will not complete once the invoice payment is completed
|
|
||||||
let swapResult = { ok: false, error: "swap never completed" } as { ok: true, txId: string } | { ok: false, error: string }
|
|
||||||
this.swaps.reverseSwaps.SubscribeToTransactionSwap(data, result => {
|
|
||||||
swapResult = result
|
|
||||||
})
|
|
||||||
let payment: Types.PayInvoiceResponse
|
let payment: Types.PayInvoiceResponse
|
||||||
try {
|
const swap = await this.swaps.PayAddrWithSwap(ctx.app_user_id, req.swap_operation_id, req.address, async (invoice) => {
|
||||||
payment = await this.PayInvoice(ctx.user_id, {
|
payment = await this.PayInvoice(ctx.user_id, {
|
||||||
amount: 0,
|
amount: 0,
|
||||||
invoice: txSwap.invoice
|
invoice: invoice
|
||||||
}, app, { swapOperationId: req.swap_operation_id })
|
}, 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, req.address, swapResult.error)
|
|
||||||
throw new Error(swapResult.error)
|
|
||||||
}
|
|
||||||
this.log("swap completed successfully")
|
|
||||||
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, 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, req.address, swapResult.error)
|
|
||||||
}
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
const networkFeesTotal = txSwap.chain_fee_sats + txSwap.swap_fee_sats // + payment.network_fee
|
|
||||||
return {
|
return {
|
||||||
txId: swapResult.txId,
|
txId: swap.txId,
|
||||||
network_fee: networkFeesTotal,
|
network_fee: swap.network_fee,
|
||||||
service_fee: payment.service_fee,
|
service_fee: payment!.service_fee,
|
||||||
operation_id: payment.operation_id,
|
operation_id: payment!.operation_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,13 @@ export default (mainHandler: Main): Types.ServerMethods => {
|
||||||
if (err != null) throw new Error(err.message)
|
if (err != null) throw new Error(err.message)
|
||||||
return mainHandler.adminManager.CloseChannel(req)
|
return mainHandler.adminManager.CloseChannel(req)
|
||||||
},
|
},
|
||||||
|
GetAdminTransactionSwapQuote: async ({ ctx, req }) => {
|
||||||
|
const err = Types.TransactionSwapRequestValidate(req, {
|
||||||
|
transaction_amount_sats_CustomCheck: amt => amt > 0
|
||||||
|
})
|
||||||
|
if (err != null) throw new Error(err.message)
|
||||||
|
return mainHandler.adminManager.GetAdminTransactionSwapQuote(req)
|
||||||
|
},
|
||||||
GetProvidersDisruption: async () => {
|
GetProvidersDisruption: async () => {
|
||||||
return mainHandler.metricsManager.GetProvidersDisruption()
|
return mainHandler.metricsManager.GetProvidersDisruption()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue